<# 云舟 QQ助手 · Windows 一键安装脚本 https://wpn.yfmk68.com/install.ps1 用法(推荐,在 PowerShell 中执行): irm https://wpn.yfmk68.com/install.ps1 | iex 带参数执行: & ([scriptblock]::Create((irm 'https://wpn.yfmk68.com/install.ps1'))) -Silent & ([scriptblock]::Create((irm 'https://wpn.yfmk68.com/install.ps1'))) -Uninstall 参数: -Silent 静默模式:自动补齐 QQ 时不等待交互 -Uninstall 卸载(移除程序、快捷方式与注册表项) -NoLaunch 安装后不自动启动 安全约定: · 只从 wpn.yfmk68.com 与腾讯官方域名 (qq.com / gtimg.cn) 下载 · 下载后使用 sha256 校验;服务端未提供时明确提示并跳过 · 不执行任何未校验的远程代码;幂等,可重复运行 #> [CmdletBinding()] param( [switch]$Uninstall, [switch]$Silent, [switch]$NoLaunch ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'Continue' if ($PSVersionTable.PSVersion.Major -lt 5) { throw '需要 PowerShell 5.1 或更高版本。' } $Site = 'https://wpn.yfmk68.com' $Api = "$Site/api/public" $QqConfig = 'https://im.qq.com/proxy/domain/cdn-go.cn/qq-web/im.qq.com_new/latest/rainbow/pcConfig.json' $AppName = '云舟QQ助手' $InstallDir = Join-Path $env:LOCALAPPDATA 'Programs\云舟QQ助手' $Tmp = $null function Write-Step([string]$m){ Write-Host ''; Write-Host "▶ $m" -ForegroundColor Cyan } function Write-Ok([string]$m){ Write-Host "✔ $m" -ForegroundColor Green } function Write-Warn([string]$m){ Write-Host "▲ $m" -ForegroundColor Yellow } function Write-Err([string]$m){ Write-Host "✖ $m" -ForegroundColor Red } function Test-AllowedUrl([string]$url){ if ($url -match '^https://wpn\.yfmk68\.com/') { return $true } if ($url -match '^https://qqdl\.gtimg\.cn/') { return $true } if ($url -match '^https://im\.qq\.com/') { return $true } if ($url -match '^https://dldir1(v6)?\.qq\.com/') { return $true } return $false } function Get-RemoteFile([string]$url,[string]$dest){ if (-not (Test-AllowedUrl $url)) { throw "拒绝下载非白名单地址: $url" } Write-Host " 下载: $url" $ok = $false try { $uri = [System.Uri]$url $wc = New-Object System.Net.WebClient $wc.Headers.Add('User-Agent','wpn-installer/1.0') $wc.DownloadFile($uri, $dest) $ok = $true } catch { Write-Warn ("WebClient 下载失败: " + $_.Exception.Message) } if (-not $ok) { $curl = Get-Command curl.exe -ErrorAction SilentlyContinue if ($curl) { $esc = [System.Uri]::EscapeUriString($url) & curl.exe -fL --connect-timeout 20 --max-time 1800 --retry 3 --retry-delay 2 -o $dest $esc if ($LASTEXITCODE -eq 0) { $ok = $true } } } if (-not $ok -or -not (Test-Path $dest)) { throw "下载失败: $url" } } function Get-Sha256([string]$path){ return (Get-FileHash -Algorithm SHA256 -Path $path).Hash.ToLower() } function Get-Platform { $arch = $env:PROCESSOR_ARCHITECTURE if ($env:PROCESSOR_ARCHITEW6432) { $arch = $env:PROCESSOR_ARCHITEW6432 } $rt = '' try { $rt = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() } catch { } if ($arch -match 'ARM64' -or $rt -match 'Arm64') { return 'win-arm64' } return 'win-x64' } function Get-Release([string]$platform){ try { $j = Invoke-RestMethod -Uri "$Api/version?platform=$platform" -UseBasicParsing -TimeoutSec 30 } catch { return $null } if (-not $j -or -not $j.ok -or -not $j.data -or -not $j.data.url) { return $null } return $j.data } function Test-QQ { $pf86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)') $paths = @( (Join-Path $env:ProgramFiles 'Tencent\QQNT\QQ.exe'), (Join-Path $env:LOCALAPPDATA 'Programs\Tencent\QQNT\QQ.exe'), (Join-Path $env:ProgramFiles 'Tencent\QQ\Bin\QQ.exe') ) if ($pf86) { $paths += (Join-Path $pf86 'Tencent\QQNT\QQ.exe') } foreach ($p in $paths) { if ($p -and (Test-Path $p)) { return $true } } $regs = @( 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\QQ', 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\QQ', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ' ) foreach ($r in $regs) { if (Test-Path $r) { return $true } } $roots = @( 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall', 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall' ) foreach ($root in $roots) { $items = Get-ChildItem $root -ErrorAction SilentlyContinue foreach ($it in $items) { $dn = (Get-ItemProperty $it.PSPath -ErrorAction SilentlyContinue).DisplayName if ($dn -and ($dn -eq 'QQ' -or $dn -like 'QQ *' -or $dn -like '腾讯QQ*')) { return $true } } } return $false } function Ensure-QQ([string]$platform){ if (Test-QQ) { Write-Ok '已检测到 QQ。'; return $true } Write-Warn '未检测到 QQ,正在获取腾讯官方安装包...' $cfg = $null try { $cfg = Invoke-RestMethod -Uri $QqConfig -UseBasicParsing -TimeoutSec 30 } catch { } $url = $null if ($cfg -and $cfg.Windows) { if ($platform -eq 'win-arm64' -and $cfg.Windows.ntDownloadARMUrl) { $url = $cfg.Windows.ntDownloadARMUrl } elseif ($cfg.Windows.ntDownloadX64Url) { $url = $cfg.Windows.ntDownloadX64Url } elseif ($cfg.Windows.ntDownloadUrl) { $url = $cfg.Windows.ntDownloadUrl } } if (-not $url) { Write-Warn '无法获取官方 QQ 下载地址,请手动安装: https://im.qq.com/pcqq/' return $false } $setup = Join-Path $Tmp 'QQ-setup.exe' Get-RemoteFile $url $setup Write-Host ' 启动 QQ 安装程序...' if ($Silent) { Start-Process -FilePath $setup -ArgumentList '/S' -Wait } else { Start-Process -FilePath $setup -Wait } if (Test-QQ) { Write-Ok 'QQ 安装完成。'; return $true } Write-Warn 'QQ 似乎尚未安装完成,可稍后手动安装,脚本将继续。' return $false } function New-Shortcut([string]$dir,[string]$target,[string]$workdir){ if (-not $dir) { return } try { $ws = New-Object -ComObject WScript.Shell $lnk = $ws.CreateShortcut((Join-Path $dir ($AppName + '.lnk'))) $lnk.TargetPath = $target $lnk.WorkingDirectory = $workdir $lnk.IconLocation = $target $lnk.Save() } catch { Write-Warn "创建快捷方式失败: $dir" } } # ── 卸载 ────────────────────────────────────────────── if ($Uninstall) { Write-Step "卸载 $AppName" if (Test-Path $InstallDir) { Remove-Item -Recurse -Force $InstallDir -ErrorAction SilentlyContinue } Remove-Item -Force (Join-Path ([Environment]::GetFolderPath('Programs')) ($AppName + '.lnk')) -ErrorAction SilentlyContinue Remove-Item -Force (Join-Path ([Environment]::GetFolderPath('Desktop')) ($AppName + '.lnk')) -ErrorAction SilentlyContinue Remove-Item -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\YunzhouQQAssistant' -Recurse -Force -ErrorAction SilentlyContinue Write-Ok '已卸载完成。' return } # ── 安装 ────────────────────────────────────────────── Write-Host '' Write-Host ' 云舟 QQ助手 · Windows 一键安装' -ForegroundColor Cyan Write-Host '' $platform = Get-Platform Write-Ok "系统架构: $platform" $release = Get-Release $platform if (-not $release -and $platform -eq 'win-arm64') { Write-Warn 'Windows ARM64 专用包暂不可用,回退到 x64 版(ARM 设备可兼容运行)。' $platform = 'win-x64' $release = Get-Release $platform } if (-not $release) { throw "未找到可用的 Windows 安装包。请稍后重试,或访问 $Site 手动下载。" } Write-Ok "最新版本: v$($release.version)" $Tmp = Join-Path $env:TEMP ('wpn-install-' + [Guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Force -Path $Tmp | Out-Null try { Write-Step '1/5 下载安装包' $zip = Join-Path $Tmp 'app.zip' Get-RemoteFile $release.url $zip Write-Step '2/5 校验安装包完整性(sha256)' if ($release.sha256) { $actual = Get-Sha256 $zip if ($actual -ne $release.sha256.ToLower()) { throw "sha256 校验失败。期望 $($release.sha256),实际 $actual" } Write-Ok "sha256 校验通过: $actual" } else { Write-Warn '服务端未提供 sha256,已跳过校验。' } Write-Step '3/5 补齐缺失组件(QQ)' Ensure-QQ $platform | Out-Null Write-Step "4/5 安装到 $InstallDir" $extract = Join-Path $Tmp 'extract' Expand-Archive -Path $zip -DestinationPath $extract -Force $entries = @(Get-ChildItem $extract) if ($entries.Count -eq 1 -and $entries[0].PSIsContainer) { $src = $entries[0].FullName } else { $src = $extract } if (Test-Path $InstallDir) { Remove-Item -Recurse -Force $InstallDir } New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null Copy-Item -Path (Join-Path $src '*') -Destination $InstallDir -Recurse -Force $exe = Get-ChildItem $InstallDir -Recurse -Filter '*.exe' -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '云舟|Yunzhou' } | Select-Object -First 1 if (-not $exe) { $exe = Get-ChildItem $InstallDir -Recurse -Filter '*.exe' -ErrorAction SilentlyContinue | Where-Object { $_.Name -notmatch 'unins|update|crash' } | Select-Object -First 1 } if (-not $exe) { throw '安装目录中未找到可执行文件。' } Write-Ok "已安装: $($exe.FullName)" New-Shortcut ([Environment]::GetFolderPath('Programs')) $exe.FullName $exe.DirectoryName New-Shortcut ([Environment]::GetFolderPath('Desktop')) $exe.FullName $exe.DirectoryName Write-Ok '已创建开始菜单与桌面快捷方式。' $unins = Join-Path $InstallDir 'Uninstall-云舟QQ助手.cmd' $uninsText = @' @echo off powershell -NoProfile -ExecutionPolicy Bypass -Command "& ([scriptblock]::Create((Invoke-RestMethod 'https://wpn.yfmk68.com/install.ps1'))) -Uninstall" '@ Set-Content -Path $unins -Value $uninsText -Encoding OEM $key = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\YunzhouQQAssistant' New-Item -Path $key -Force | Out-Null Set-ItemProperty -Path $key -Name DisplayName -Value $AppName Set-ItemProperty -Path $key -Name DisplayVersion -Value ([string]$release.version) Set-ItemProperty -Path $key -Name Publisher -Value '云舟' Set-ItemProperty -Path $key -Name InstallLocation -Value $InstallDir Set-ItemProperty -Path $key -Name UninstallString -Value ('"' + $unins + '"') Write-Ok '已注册卸载入口(设置 -> 应用)。' Write-Step '5/5 启动' if (-not $NoLaunch) { Start-Process -FilePath $exe.FullName Write-Ok '已启动。' } else { Write-Warn '已按 -NoLaunch 跳过自动启动。' } Write-Host '' Write-Host '安装完成 🎉' -ForegroundColor Green Write-Host '首次使用请在客户端内扫码登录 QQ。' -ForegroundColor Gray } catch { Write-Host '' Write-Err $_.Exception.Message Write-Host '下一步: 请检查网络后重试,或访问 https://wpn.yfmk68.com/dl/ 手动下载。' -ForegroundColor Yellow throw } finally { if ($Tmp -and (Test-Path $Tmp)) { Remove-Item -Recurse -Force $Tmp -ErrorAction SilentlyContinue } }