# ========================================================= # MAX Windows Toolkit - 从仓库安装字体 # ========================================================= param( [switch]$Menu, [switch]$NoConfirm ) $Common = "C:\Setup\windows\lib\common.ps1" if (Test-Path $Common) { . $Common } else { function Write-Header { param([string]$Title) Clear-Host; Write-Host "==== $Title ====" -ForegroundColor Green } function Write-Section { param([string]$Text) Write-Host ""; Write-Host "-- $Text --" -ForegroundColor Magenta } function Write-OK { Write-Host " OK $($args[0])" -ForegroundColor Green } function Write-Warn { Write-Host " !! $($args[0])" -ForegroundColor Yellow } function Write-Err { Write-Host " XX $($args[0])" -ForegroundColor Red } function Write-Info { Write-Host " $($args[0])" -ForegroundColor White } function Write-Muted { Write-Host " $($args[0])" -ForegroundColor DarkGray } function Pause-Max { Write-Host ""; Read-Host "按 Enter 返回..." } function Write-Line { Write-Host "------------------------------" -ForegroundColor DarkGray } function Test-IsAdmin { try { $id = [Security.Principal.WindowsIdentity]::GetCurrent(); $p = New-Object Security.Principal.WindowsPrincipal($id); return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } catch { return $false } } function Show-AdminWarn { if (-not (Test-IsAdmin)) { Write-Warn "未以管理员身份运行,部分功能可能失败" } } } Show-AdminWarn # ───────────────────────────────────────── # 配置 # ───────────────────────────────────────── $RepoBase = "https://dl.unvmax.com/windows" $FontBaseUrl = "$RepoBase/fonts" $FontJsonUrl = "$FontBaseUrl/fonts.json" $LocalFontDir = "C:\Setup\fonts" $BackupDir = "C:\Setup\FontBackup" $LogDir = "C:\Setup\Logs" $WindowsFontsDir = Join-Path $env:WinDir "Fonts" $SupportedExtensions = @(".ttf", ".otf", ".ttc") New-Item -ItemType Directory -Path $LocalFontDir -Force | Out-Null New-Item -ItemType Directory -Path $BackupDir -Force | Out-Null New-Item -ItemType Directory -Path $LogDir -Force | Out-Null # Windows 核心字体,安装时跳过,避免覆盖系统字体导致乱码 $SystemFontFileBlockList = @( "arial.ttf", "arialbd.ttf", "arialbi.ttf", "ariali.ttf", "times.ttf", "timesbd.ttf", "timesbi.ttf", "timesi.ttf", "cour.ttf", "courbd.ttf", "courbi.ttf", "couri.ttf", "tahoma.ttf", "tahomabd.ttf", "verdana.ttf", "verdanab.ttf", "verdanai.ttf", "verdanaz.ttf", "segoeui.ttf", "segoeuib.ttf", "segoeuii.ttf", "segoeuil.ttf", "segoeuisl.ttf", "seguiemj.ttf", "seguisym.ttf", "simsun.ttc", "simhei.ttf", "simkai.ttf", "simfang.ttf", "msyh.ttc", "msyhbd.ttc", "msyhl.ttc", "mingliu.ttc", "gulim.ttc", "batang.ttc", "msgothic.ttc" ) # ───────────────────────────────────────── # 基础函数 # ───────────────────────────────────────── function Backup-FontRegistry { $Time = Get-Date -Format "yyyyMMdd-HHmmss" $CurrentBackupDir = Join-Path $BackupDir $Time New-Item -ItemType Directory -Path $CurrentBackupDir -Force | Out-Null Write-Info "正在备份字体注册表..." Write-Muted "备份目录: $CurrentBackupDir" try { reg.exe export "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" ` (Join-Path $CurrentBackupDir "HKLM-Fonts.reg") /y | Out-Null reg.exe export "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontSubstitutes" ` (Join-Path $CurrentBackupDir "HKLM-FontSubstitutes.reg") /y | Out-Null Write-OK "字体注册表备份完成" } catch { Write-Warn "字体注册表备份失败" Write-Muted $_.Exception.Message } } function Clear-FontCache { Write-Info "正在清理字体缓存..." try { Stop-Service FontCache -Force -ErrorAction SilentlyContinue Stop-Service FontCache3.0.0.0 -Force -ErrorAction SilentlyContinue Remove-Item "$env:WinDir\ServiceProfiles\LocalService\AppData\Local\FontCache*" ` -Force -ErrorAction SilentlyContinue Remove-Item "$env:LocalAppData\FontCache*" ` -Force -ErrorAction SilentlyContinue Start-Service FontCache -ErrorAction SilentlyContinue Write-OK "字体缓存已清理" } catch { Write-Warn "清理字体缓存失败" Write-Muted $_.Exception.Message } } function Test-IsBlockedSystemFont { param([string]$FileName) if ([string]::IsNullOrWhiteSpace($FileName)) { return $true } return ($SystemFontFileBlockList -contains $FileName.ToLower()) } function Test-FontAlreadyExistsInWindows { param([string]$FileName) $TargetPath = Join-Path $WindowsFontsDir $FileName if (Test-Path $TargetPath) { return $true } return $false } function Get-FontRegistryValueName { param([string]$FileName) $BaseName = [System.IO.Path]::GetFileNameWithoutExtension($FileName) $Ext = [System.IO.Path]::GetExtension($FileName).ToLower() switch ($Ext) { ".otf" { return "$BaseName (OpenType)" } ".ttc" { return "$BaseName (TrueType Collection)" } default { return "$BaseName (TrueType)" } } } function Get-RemoteFontListFromJson { Write-Info "尝试从 fonts.json 读取字体列表..." Write-Muted $FontJsonUrl try { $json = Invoke-RestMethod -Uri $FontJsonUrl -UseBasicParsing -TimeoutSec 30 -ErrorAction Stop $list = @() if ($json -is [array]) { $list = $json } elseif ($json.fonts) { $list = $json.fonts } $result = @() foreach ($item in $list) { if ($item -is [string]) { $fileName = $item } elseif ($item.file) { $fileName = $item.file } elseif ($item.name) { $fileName = $item.name } else { continue } $ext = [System.IO.Path]::GetExtension($fileName).ToLower() if ($SupportedExtensions -contains $ext) { $result += $fileName } } $result = $result | Sort-Object -Unique if ($result.Count -gt 0) { Write-OK "已从 fonts.json 加载字体列表: $($result.Count) 个" return @($result) } Write-Warn "fonts.json 存在但未列出有效字体文件" return @() } catch { Write-Warn "无法读取 fonts.json,将尝试目录列表" return @() } } function Get-RemoteFontListFromDirectory { Write-Info "尝试从目录列表解析字体文件..." Write-Muted $FontBaseUrl try { $html = Invoke-WebRequest -Uri $FontBaseUrl -UseBasicParsing -TimeoutSec 30 -ErrorAction Stop $content = $html.Content $matches = [regex]::Matches($content, 'href\s*=\s*["'']([^"'']+\.(ttf|otf|ttc))["'']', 'IgnoreCase') $result = @() foreach ($m in $matches) { $href = $m.Groups[1].Value $fileName = [System.IO.Path]::GetFileName([System.Uri]::UnescapeDataString($href)) $ext = [System.IO.Path]::GetExtension($fileName).ToLower() if ($SupportedExtensions -contains $ext) { $result += $fileName } } $result = $result | Sort-Object -Unique if ($result.Count -gt 0) { Write-OK "从目录列表解析到字体: $($result.Count) 个" return @($result) } Write-Warn "目录列表中未找到字体文件" return @() } catch { Write-Warn "解析目录列表失败" Write-Muted $_.Exception.Message return @() } } function Get-RemoteFontList { $list = Get-RemoteFontListFromJson if ($list -and $list.Count -gt 0) { return @($list) } $list = Get-RemoteFontListFromDirectory return @($list) } function Get-EncodedFontUrl { param([string]$FileName) return "$FontBaseUrl/$([System.Uri]::EscapeDataString($FileName))" } function Download-FontFile { param([string]$FileName) $url = Get-EncodedFontUrl -FileName $FileName $outFile = Join-Path $LocalFontDir $FileName try { Write-Info "正在下载: $FileName" Invoke-WebRequest ` -Uri $url ` -OutFile $outFile ` -UseBasicParsing ` -TimeoutSec 120 ` -ErrorAction Stop if ((Test-Path $outFile) -and ((Get-Item $outFile).Length -gt 0)) { $sizeKB = [Math]::Round((Get-Item $outFile).Length / 1KB, 2) Write-OK "下载完成: $FileName ($sizeKB KB)" return $true } Write-Err "下载文件为空: $FileName" return $false } catch { Write-Err "下载失败: $FileName" Write-Muted $_.Exception.Message return $false } } function Download-AllRepositoryFonts { param([switch]$NoConfirm) Write-Header "下载仓库全部字体" $fontList = Get-RemoteFontList if (-not $fontList -or $fontList.Count -eq 0) { Write-Err "未找到远程字体列表" Write-Warn "建议在 /windows/fonts/ 下创建 fonts.json" Write-Line Write-Info "fonts.json 示例:" Write-Muted '["FangZhengXiaoBiaoSong.ttf","FangZhengFangSong_GBK.ttf","KaiTi_GB2312.ttf"]' return } Write-Info "远程字体文件:" foreach ($f in $fontList) { Write-Muted "- $f" } Write-Line if (-not $NoConfirm) { $Confirm = Read-Host " 下载全部字体到 C:\Setup\fonts?输入 YES 确认" if ($Confirm -ne "YES") { Write-Warn "已取消" return } } else { Write-Info "自动模式:直接下载字体,无需确认" } $success = 0 $failed = 0 $skipped = 0 foreach ($font in $fontList) { if (Test-IsBlockedSystemFont -FileName $font) { Write-Muted "跳过系统保护字体: $font" $skipped++ continue } if (Download-FontFile -FileName $font) { $success++ } else { $failed++ } } Write-Line Write-OK "下载完成" Write-OK "已下载: $success" Write-Warn "已跳过: $skipped" Write-Err "失败 : $failed" } function Get-LocalFontFiles { if (-not (Test-Path $LocalFontDir)) { return @() } $files = Get-ChildItem -Path $LocalFontDir -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $SupportedExtensions -contains $_.Extension.ToLower() } return @($files) } function Install-FontToWindowsFonts { param([string]$FontPath) if (-not (Test-Path $FontPath)) { Write-Err "字体文件不存在: $FontPath" return $false } $fontFile = Get-Item $FontPath $fileName = $fontFile.Name $targetPath = Join-Path $WindowsFontsDir $fileName if (Test-IsBlockedSystemFont -FileName $fileName) { Write-Muted "跳过系统保护字体: $fileName" return $true } if (Test-FontAlreadyExistsInWindows -FileName $fileName) { Write-Muted "跳过已存在的字体: $fileName" return $true } try { Write-Info "正在安装字体: $fileName" Copy-Item ` -Path $fontFile.FullName ` -Destination $targetPath ` -Force:$false ` -ErrorAction Stop $regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" $regName = Get-FontRegistryValueName -FileName $fileName New-ItemProperty ` -Path $regPath ` -Name $regName ` -Value $fileName ` -PropertyType String ` -Force | Out-Null Write-OK "已安装: $fileName" return $true } catch { Write-Err "安装字体失败: $fileName" Write-Muted $_.Exception.Message return $false } } function Install-AllDownloadedFonts { param([switch]$NoConfirm) Write-Header "安装已下载的字体" if (-not (Test-IsAdmin)) { Write-Warn "请以管理员身份运行" return } $fonts = Get-LocalFontFiles if (-not $fonts -or $fonts.Count -eq 0) { Write-Warn "本地未找到字体文件: $LocalFontDir" Write-Warn "请先下载字体" return } Write-OK "本地字体文件数: $($fonts.Count)" Write-Line foreach ($font in $fonts) { Write-Muted "- $($font.Name)" } Write-Line Write-Info "安装规则:" Write-Muted "复制字体到 C:\Windows\Fonts" Write-Muted "在 HKLM Fonts 注册表中注册" Write-Muted "跳过已存在的字体" Write-Muted "跳过 Windows 核心系统字体" Write-Muted "安装前备份字体注册表" Write-Line if (-not $NoConfirm) { $Confirm = Read-Host " 安装全部已下载字体?输入 YES 确认" if ($Confirm -ne "YES") { Write-Warn "已取消" return } } else { Write-Info "自动模式:直接安装字体,无需确认" } Backup-FontRegistry $success = 0 $skipped = 0 $failed = 0 foreach ($font in $fonts) { if (Test-IsBlockedSystemFont -FileName $font.Name) { Write-Muted "跳过系统保护字体: $($font.Name)" $skipped++ continue } if (Test-FontAlreadyExistsInWindows -FileName $font.Name) { Write-Muted "跳过已存在字体: $($font.Name)" $skipped++ continue } if (Install-FontToWindowsFonts -FontPath $font.FullName) { $success++ } else { $failed++ } } Clear-FontCache Write-Line Write-OK "字体安装完成" Write-OK "已安装: $success" Write-Warn "已跳过: $skipped" Write-Err "失败 : $failed" Write-Warn "如果字体未立即显示,请重启 Windows" } function Download-And-InstallFonts { param([switch]$NoConfirm) Download-AllRepositoryFonts -NoConfirm:$NoConfirm Write-Line Install-AllDownloadedFonts -NoConfirm:$NoConfirm } function Open-LocalFontFolder { New-Item -ItemType Directory -Path $LocalFontDir -Force | Out-Null Start-Process explorer.exe $LocalFontDir } function Open-WindowsFontFolder { Start-Process explorer.exe $WindowsFontsDir } function Open-WindowsFontSettings { try { Start-Process "ms-settings:fonts" } catch { Start-Process "control.exe" "fonts" } } function Show-RestoreDefaultFontSettingsTip { Write-Header "恢复默认字体设置" Write-Warn "如果安装字体后 Windows 文本出现乱码,请尝试:" Write-Info "" Write-Info "控制面板 -> 外观和个性化 -> 字体 -> 字体设置" Write-Info "点击:恢复默认字体设置" Write-Info "" Write-Warn "同时检查:" Write-Info "控制面板 -> 区域 -> 管理 -> 更改系统区域设置" Write-Info "推荐:中文(简体,中国)" Write-Warn "不要勾选:Beta UTF-8 提供全球语言支持" } # ───────────────────────────────────────── # 默认操作(无 -Menu 参数直接执行) # ───────────────────────────────────────── if (-not $Menu) { Download-And-InstallFonts -NoConfirm exit 0 } # ───────────────────────────────────────── # 主菜单 # ───────────────────────────────────────── :fontLoop while ($true) { Write-Header "从仓库安装字体" Write-Info "可用操作:" Write-Info "1. 从仓库下载全部字体" Write-Info "2. 安装已下载字体到系统" Write-Info "3. 下载并安装字体" Write-Info "4. 打开本地字体目录" Write-Info "5. 打开系统字体目录" Write-Info "6. 打开系统字体设置" Write-Info "7. 清理字体缓存" Write-Info "8. 备份字体注册表" Write-Info "9. 恢复默认字体设置提示" Write-Info "0. 退出" Write-Line $Choice = Read-Host " 请选择" switch ($Choice) { "1" { Download-AllRepositoryFonts Pause-Max } "2" { Install-AllDownloadedFonts Pause-Max } "3" { Download-And-InstallFonts -NoConfirm:$NoConfirm Pause-Max } "4" { Open-LocalFontFolder Pause-Max } "5" { Open-WindowsFontFolder Pause-Max } "6" { Open-WindowsFontSettings Pause-Max } "7" { Clear-FontCache Pause-Max } "8" { Backup-FontRegistry Pause-Max } "9" { Show-RestoreDefaultFontSettingsTip Pause-Max } "0" { break :fontLoop } default { Write-Err "无效选项" Pause-Max } } }