$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