254 lines
8.9 KiB
PowerShell
254 lines
8.9 KiB
PowerShell
$ErrorActionPreference = "Stop"
|
|
|
|
$Root = "C:\UniDesk\personal-wechat"
|
|
$WechatRoot = Join-Path $Root "wechat-3.9.12.51"
|
|
$ExtractRoot = Join-Path $Root "wechat-3.9.12.51-extracted"
|
|
$VersionRoot = Join-Path $ExtractRoot "[3.9.12.51]"
|
|
$DataRoot = Join-Path $Root "data\profile"
|
|
$WcfRoot = Join-Path $Root "wcf\v39.5.2"
|
|
$PythonSite = Join-Path $WcfRoot "python-site"
|
|
$StateRoot = Join-Path $Root "wcf-state"
|
|
$DownloadRoot = Join-Path $Root "downloads"
|
|
$PrepareResult = Join-Path $StateRoot "prepare-result.json"
|
|
$ProgressLog = Join-Path $StateRoot "prepare-progress.log"
|
|
$Python = "C:\ProgramData\miniconda3\python.exe"
|
|
$PipIndex = "http://mirrors.aliyun.com/pypi/simple/"
|
|
$ReleaseBase = "https://github.com/lich0821/WeChatFerry/releases/download/v39.5.2"
|
|
$RegistryPath = "HKCU:\Software\Tencent\WeChat"
|
|
|
|
New-Item -ItemType Directory -Force $WechatRoot,$ExtractRoot,$VersionRoot,$DataRoot,$WcfRoot,$PythonSite,$StateRoot,$DownloadRoot | Out-Null
|
|
$StartedAt = (Get-Date).ToUniversalTime().ToString("o")
|
|
|
|
function Write-PrepareProgress {
|
|
param([string]$Message)
|
|
Add-Content -Encoding utf8 -Path $ProgressLog -Value "$((Get-Date).ToUniversalTime().ToString("o")) $Message"
|
|
}
|
|
|
|
trap {
|
|
$message = $_.Exception.Message
|
|
Write-PrepareProgress "error $message"
|
|
$failed = [ordered]@{
|
|
ok = $false
|
|
startedAt = $StartedAt
|
|
finishedAt = (Get-Date).ToUniversalTime().ToString("o")
|
|
root = $Root
|
|
stateRoot = $StateRoot
|
|
python = $Python
|
|
error = $message
|
|
valuesPrinted = $false
|
|
}
|
|
$failed | ConvertTo-Json -Depth 8 | Set-Content -Encoding utf8 $PrepareResult
|
|
exit 1
|
|
}
|
|
|
|
function Download-IfMissing {
|
|
param([string]$Url, [string]$Path)
|
|
if (Test-Path -LiteralPath $Path) { return }
|
|
Write-PrepareProgress "download-start $Url"
|
|
$tmp = "$Path.part"
|
|
Invoke-WebRequest -Uri $Url -OutFile $tmp -UseBasicParsing
|
|
Move-Item -Force $tmp $Path
|
|
Write-PrepareProgress "download-done $Path"
|
|
}
|
|
|
|
function Get-SevenZipPath {
|
|
$candidates = @(
|
|
"C:\Program Files\7-Zip\7z.exe",
|
|
"C:\Program Files (x86)\7-Zip\7z.exe",
|
|
"7z.exe"
|
|
)
|
|
foreach ($candidate in $candidates) {
|
|
$cmd = Get-Command $candidate -ErrorAction SilentlyContinue
|
|
if ($cmd) { return $cmd.Source }
|
|
if (Test-Path -LiteralPath $candidate) { return $candidate }
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Find-DedicatedWeChatExe {
|
|
$candidates = @(
|
|
(Join-Path $VersionRoot "WeChat.exe"),
|
|
(Join-Path $ExtractRoot "WeChat.exe"),
|
|
(Join-Path $WechatRoot "WeChat.exe")
|
|
)
|
|
foreach ($candidate in $candidates) {
|
|
if (!(Test-Path -LiteralPath $candidate)) { continue }
|
|
$dir = Split-Path -Parent $candidate
|
|
if (Test-Path -LiteralPath (Join-Path $dir "WeChatWin.dll")) { return $candidate }
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Ensure-WeChatExtracted {
|
|
param([string]$Installer)
|
|
$wechatExe = Find-DedicatedWeChatExe
|
|
if ($wechatExe) { return [ordered]@{ mode = "dedicated-existing"; wechatExe = $wechatExe } }
|
|
|
|
$sevenZip = Get-SevenZipPath
|
|
if (!$sevenZip) { throw "7-Zip is required to extract the WeChat installer without administrator elevation" }
|
|
$extractLog = Join-Path $StateRoot "7z-extract.log"
|
|
Write-PrepareProgress "wechat-extract-start $Installer"
|
|
& $sevenZip x -y "-o$ExtractRoot" $Installer > $extractLog 2>&1
|
|
if ($LASTEXITCODE -ne 0) { throw "7-Zip extract failed; see $extractLog" }
|
|
$wechatExe = Find-DedicatedWeChatExe
|
|
if (!$wechatExe) { throw "Extracted installer did not produce dedicated WeChat.exe with WeChatWin.dll" }
|
|
Write-PrepareProgress "wechat-extract-done $wechatExe"
|
|
return [ordered]@{ mode = "nsis-extracted-hkcu-registry-shim"; wechatExe = $wechatExe; extractLog = $extractLog }
|
|
}
|
|
|
|
function Ensure-IsolatedProfile {
|
|
$appData = Join-Path $DataRoot "AppData\Roaming"
|
|
$localAppData = Join-Path $DataRoot "AppData\Local"
|
|
$documents = Join-Path $DataRoot "Documents"
|
|
$temp = Join-Path $localAppData "Temp"
|
|
New-Item -ItemType Directory -Force $appData,$localAppData,$documents,$temp | Out-Null
|
|
return [ordered]@{
|
|
dataRoot = $DataRoot
|
|
appData = $appData
|
|
localAppData = $localAppData
|
|
documents = $documents
|
|
temp = $temp
|
|
}
|
|
}
|
|
|
|
function Ensure-WeChatRegistryShim {
|
|
param([string]$WechatExe)
|
|
$installPath = Split-Path -Parent $WechatExe
|
|
New-Item -Path $RegistryPath -Force | Out-Null
|
|
Set-ItemProperty -Path $RegistryPath -Name InstallPath -Value ($installPath + "\")
|
|
Set-ItemProperty -Path $RegistryPath -Name Version -Value 0x03090c33 -Type DWord
|
|
return $installPath + "\"
|
|
}
|
|
|
|
function Install-Wcferry {
|
|
if (!(Test-Path -LiteralPath $Python)) { throw "Expected Python not found: $Python" }
|
|
$pipOut = Join-Path $StateRoot "prepare-pip.stdout.log"
|
|
$pipErr = Join-Path $StateRoot "prepare-pip.stderr.log"
|
|
Write-PrepareProgress "pip-install-start"
|
|
& $Python -m pip install --upgrade --target $PythonSite --trusted-host mirrors.aliyun.com --index-url $PipIndex --timeout 120 --retries 8 "wcferry==39.5.2.0" > $pipOut 2> $pipErr
|
|
if ($LASTEXITCODE -ne 0) { throw "pip install wcferry failed; see $pipErr" }
|
|
Write-PrepareProgress "pip-install-done"
|
|
|
|
$probePy = Join-Path $StateRoot "prepare-probe.py"
|
|
@'
|
|
import importlib.metadata
|
|
import json
|
|
import os
|
|
import wcferry
|
|
|
|
payload = {
|
|
"ok": True,
|
|
"wcferryVersion": importlib.metadata.version("wcferry"),
|
|
"packageRoot": os.path.abspath(os.path.dirname(wcferry.__file__)),
|
|
}
|
|
print(json.dumps(payload, ensure_ascii=False))
|
|
'@ | Set-Content -Encoding utf8 $probePy
|
|
$oldPythonPath = $env:PYTHONPATH
|
|
try {
|
|
$env:PYTHONPATH = if ($oldPythonPath) { "$PythonSite;$oldPythonPath" } else { $PythonSite }
|
|
$pyProbe = (& $Python $probePy) | ConvertFrom-Json
|
|
} finally {
|
|
$env:PYTHONPATH = $oldPythonPath
|
|
}
|
|
return [ordered]@{
|
|
ok = [bool]$pyProbe.ok
|
|
version = $pyProbe.wcferryVersion
|
|
packageRoot = $pyProbe.packageRoot
|
|
stdout = $pipOut
|
|
stderr = $pipErr
|
|
}
|
|
}
|
|
|
|
function Sync-WcfDlls {
|
|
param([string]$PackageRoot)
|
|
$dlls = @()
|
|
foreach ($name in @("sdk.dll","spy.dll","spy_debug.dll")) {
|
|
$src = Join-Path $WcfRoot $name
|
|
$dst = Join-Path $PackageRoot $name
|
|
if (!(Test-Path -LiteralPath $src)) { throw "Missing WCF release asset: $src" }
|
|
if (!(Test-Path -LiteralPath $dst)) { throw "Missing wcferry package dll target: $dst" }
|
|
$backup = "$dst.unidesk-bak"
|
|
if (!(Test-Path -LiteralPath $backup)) {
|
|
Copy-Item -LiteralPath $dst -Destination $backup -Force
|
|
}
|
|
Copy-Item -LiteralPath $src -Destination $dst -Force
|
|
$srcItem = Get-Item -LiteralPath $src
|
|
$dstItem = Get-Item -LiteralPath $dst
|
|
$dlls += [ordered]@{
|
|
name = $name
|
|
sourceBytes = $srcItem.Length
|
|
targetBytes = $dstItem.Length
|
|
targetVersion = $dstItem.VersionInfo.FileVersion
|
|
backup = $backup
|
|
}
|
|
}
|
|
return $dlls
|
|
}
|
|
|
|
function Ensure-Firewall {
|
|
try {
|
|
if (-not (Get-NetFirewallRule -DisplayName "UniDesk Personal WeChat WCF 10086" -ErrorAction SilentlyContinue)) {
|
|
New-NetFirewallRule `
|
|
-DisplayName "UniDesk Personal WeChat WCF 10086" `
|
|
-Direction Inbound `
|
|
-Action Allow `
|
|
-Protocol TCP `
|
|
-LocalPort 10086,10087 `
|
|
-RemoteAddress 172.26.0.0/16,10.42.0.0/16,127.0.0.1 `
|
|
-Profile Any `
|
|
-ErrorAction Stop | Out-Null
|
|
return [ordered]@{ ok = $true; applied = $true }
|
|
}
|
|
return [ordered]@{ ok = $true; applied = $false; alreadyPresent = $true }
|
|
} catch {
|
|
Write-PrepareProgress "firewall-skip $($_.Exception.Message)"
|
|
return [ordered]@{ ok = $false; skipped = $true; reason = $_.Exception.Message }
|
|
}
|
|
}
|
|
|
|
Write-PrepareProgress "prepare-start"
|
|
$installer = Join-Path $DownloadRoot "WeChatSetup-3.9.12.51.exe"
|
|
Download-IfMissing "$ReleaseBase/WeChatSetup-3.9.12.51.exe" $installer
|
|
Download-IfMissing "$ReleaseBase/sdk.dll" (Join-Path $WcfRoot "sdk.dll")
|
|
Download-IfMissing "$ReleaseBase/spy.dll" (Join-Path $WcfRoot "spy.dll")
|
|
Download-IfMissing "$ReleaseBase/spy_debug.dll" (Join-Path $WcfRoot "spy_debug.dll")
|
|
|
|
Copy-Item -Force "$PSScriptRoot\wcf_host.py" (Join-Path $WcfRoot "wcf_host.py")
|
|
|
|
$wechat = Ensure-WeChatExtracted $installer
|
|
$profile = Ensure-IsolatedProfile
|
|
$installPath = Ensure-WeChatRegistryShim $wechat.wechatExe
|
|
$wcferry = Install-Wcferry
|
|
$dlls = Sync-WcfDlls $wcferry.packageRoot
|
|
$firewall = Ensure-Firewall
|
|
|
|
$summary = [ordered]@{
|
|
ok = [bool]($wechat.wechatExe -and $wcferry.ok)
|
|
startedAt = $StartedAt
|
|
finishedAt = (Get-Date).ToUniversalTime().ToString("o")
|
|
root = $Root
|
|
mode = $wechat.mode
|
|
wechatInstaller = $installer
|
|
wechatRoot = $WechatRoot
|
|
extractRoot = $ExtractRoot
|
|
versionRoot = $VersionRoot
|
|
wechatExe = $wechat.wechatExe
|
|
installPath = $installPath
|
|
registryKey = $RegistryPath
|
|
requiredVersion = "3.9.12.51"
|
|
profile = $profile
|
|
wcfRoot = $WcfRoot
|
|
pythonSite = $PythonSite
|
|
stateRoot = $StateRoot
|
|
python = $Python
|
|
wcferryVersion = $wcferry.version
|
|
wcferryPackageRoot = $wcferry.packageRoot
|
|
dllOverride = $dlls
|
|
firewall = $firewall
|
|
valuesPrinted = $false
|
|
next = "Run start.ps1 and scan the isolated WeChat login QR."
|
|
}
|
|
Write-PrepareProgress "prepare-done ok=$($summary.ok)"
|
|
$summary | ConvertTo-Json -Depth 10 | Set-Content -Encoding utf8 $PrepareResult
|
|
$summary | ConvertTo-Json -Depth 10
|