# ============================================================================= # NextGuard SSE Agent — Windows Installer(W1,Win10/11) # ============================================================================= # 同 macOS install.sh 同一流程,對應平台差異: # - 免 admin 預設(per-user 安裝)— 企業環境最常見嘅限制 # - Python 用官方 python.org installer(有 admin)或 NuGet/WinGet; # 首選:偵測現有 Python 3.12;冇 → 引導下載 embeddable 版(免 admin) # - 依賴全部 --only-binary :all:(X221 教訓:寧願响亮失敗唔好 source # build — Windows 冇編譯工具鏈嘅機係常態) # - CA 信任:certutil -user -addstore Root(免 admin,首次彈 Windows # 安全警告 — 用戶必須撳「是」,呢個係 Microsoft 設計冇得繞) # - 自啟:Scheduled Task(ONLOGON,目前用戶)— 唔用 SYSTEM service # (代理要行用戶 session 先見到用戶代理設定/證書庫,見 service.py) # - 系統代理:Registry HKCU Internet Settings + wininet refresh # # 用法(喺目標 Windows 機,PowerShell): # A) 自助註冊(推薦 — 同 macOS 一樣 email 收 6 位碼,唔使問 IT 攞 key): # powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1 # B) 企業預派 key: # powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1 ` # -TenantId -ApiKey -Email # ============================================================================= [CmdletBinding()] param( # 自助註冊(推薦 — 同 macOS 一樣 email → 6 位碼):兩個都唔填就會互動問 [string]$TenantId = "", [string]$ApiKey = "", [string]$Email = "", [string]$PortalBase = "https://portal.next-guard.com" ) $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" # Invoke-WebRequest 冇進度條先快 # X309:安全紅線 —— 代理一掛上,任何中途失敗都必須自動回滾代理。 # 用戶實錘(RoscoeCheung):install 死喺 ⑧b 之後留低指住死引擎嘅代理 = # 部機即時斷網。trap 喺 exit 時觸發;回滾用純 PS(venv/引擎可能就係 # 故障源,唔可以靠佢哋)。 $script:ProxyEngaged = $false trap { if ($script:ProxyEngaged) { Write-Host "[FAIL] 安裝中途失敗 — 自動回滾系統代理(唔留死代理黑洞)" -ForegroundColor Red $rp = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" Set-ItemProperty $rp ProxyEnable 0 -ErrorAction SilentlyContinue Remove-ItemProperty $rp ProxyServer -ErrorAction SilentlyContinue Remove-ItemProperty $rp AutoConfigURL -ErrorAction SilentlyContinue & netsh winhttp reset proxy 2>$null | Out-Null $script:ProxyEngaged = $false } Write-Host "[FAIL] $($_.Exception.Message)" -ForegroundColor Red exit 1 } function Info($m) { Write-Host "[INFO] $m" -ForegroundColor Cyan } function Ok($m) { Write-Host "[OK] $m" -ForegroundColor Green } function Warn($m) { Write-Host "[WARN] $m" -ForegroundColor Yellow } function Die($m) { Write-Host "[FAIL] $m" -ForegroundColor Red; exit 1 } $InstallDir = Join-Path $env:LOCALAPPDATA "NextGuard" $VenvDir = Join-Path $InstallDir "venv" $LogFile = Join-Path $env:TEMP "nextguard_install.log" # review 輪:上一次次裝失败重跑會撞「transcript 已經行緊」— try 包住 try { Start-Transcript -Path $LogFile -Append | Out-Null } catch { } Info "NextGuard SSE Agent Installer (Windows) — tenant $TenantId" Info "Install dir: $InstallDir(per-user,免 admin)" # ── ① Python 3.12 偵測 ────────────────────────────────────────────────────── $Python = $null foreach ($cand in @( (Join-Path $InstallDir "python3\python.exe"), "python.exe", "python3.exe", "$env:LOCALAPPDATA\Programs\Python\Python312\python.exe" )) { try { $v = & $cand --version 2>$null if ($v -match "Python 3\.1[2-9]") { $Python = (Resolve-Path $cand).Path; break } } catch { } } if (-not $Python) { Warn "搵唔到 Python 3.12+ — 下載官方 embeddable package(免 admin)…" $pyZip = Join-Path $env:TEMP "python-embed.zip" $pyUrl = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-embed-amd64.zip" try { Invoke-WebRequest -Uri $pyUrl -OutFile $pyZip -UseBasicParsing $pyDir = Join-Path $InstallDir "python3" Expand-Archive -Path $pyZip -DestinationPath $pyDir -Force $Python = Join-Path $pyDir "python.exe" # embeddable 版要開 site-packages(改 python312._pth)+ 裝 pip $pth = Get-ChildItem $pyDir -Filter "python3*._pth" | Select-Object -First 1 if ($pth) { (Get-Content $pth.FullName) -replace '#\s*import site', 'import site' | Set-Content $pth.FullName } $getPip = Join-Path $env:TEMP "get-pip.py" Invoke-WebRequest -Uri "https://bootstrap.pypa.io/get-pip.py" ` -OutFile $getPip -UseBasicParsing & $Python $getPip --quiet | Out-Null } catch { Die "Python 下載/安裝失敗:$_ — 請先手動裝 Python 3.12(python.org)" } } Ok "Python: $Python" # ── ② 檔案佈局 ────────────────────────────────────────────────────────────── New-Item -ItemType Directory -Force -Path $InstallDir, (Join-Path $InstallDir "engine"), (Join-Path $InstallDir "platform_ng"), (Join-Path $InstallDir "logs") | Out-Null # ── ③ 下載 agent 檔案(同 macOS 一條分發鏈)────────────────────────────────── Info "Downloading agent files from $PortalBase ..." function Fetch($url, $dst, $required = $true) { # X311:Fetch 一律直連繞過系統代理 —— ⑦ 之後系統代理指住本機引擎, # 引擎未起嗰陣經代理 fetch = 自己攞自己嘅檔攞唔到(真機實錘: # ⑧c tray 下載 Unable to connect → Die → 安裝死)。portal 係本地 # 資源,冇理由行代理。 $prevProxy = [System.Net.WebRequest]::DefaultWebProxy [System.Net.WebRequest]::DefaultWebProxy = [System.Net.GlobalProxySelection]::GetEmptyWebProxy() try { Invoke-WebRequest -Uri $url -OutFile $dst -UseBasicParsing return $true } catch { if ($required) { Die "下載失敗 $url — $_" } Warn "optional download failed: $url" return $false } finally { [System.Net.WebRequest]::DefaultWebProxy = $prevProxy } } Fetch "$PortalBase/agent/sse_engine_local.py" (Join-Path $InstallDir "sse_engine_local.py") Fetch "$PortalBase/agent/posture_agent.py" (Join-Path $InstallDir "posture_agent.py") $false foreach ($f in @("dlp_core.py","semantic_layer.py","ner_layer.py","dlp_policy.py", "injection_layer.py","toxicity_layer.py","modeling_piguard.py", "genai_channels.py","domain_registry.py","face_layer.py")) { Fetch "$PortalBase/agent/engine/$f" (Join-Path $InstallDir "engine\$f") $false | Out-Null } foreach ($f in @("__init__.py","paths.py","proxy_config.py","ca_trust.py", "service.py","notify.py","tamper.py")) { Fetch "$PortalBase/agent/platform_ng/$f" (Join-Path $InstallDir "platform_ng\$f") } foreach ($f in @("ng_paths.py","canary_probe.py","ng_crypto.py", "proto_redact.py","doc_redact.py","watermark_overlay.py", "dlp_offline_ruleset.py","dlp-rules.json")) { Fetch "$PortalBase/agent/$f" (Join-Path $InstallDir $f) $false | Out-Null } Ok "Agent files installed" # ── ③b 裝置自助註冊(X50 對等 — email OTP → 自動派裝置 key;企業預派 # TenantId/ApiKey 嘅話成段跳過)。同 macOS install.sh 行同一對 API: # POST /portal/agent/enroll/start {email} → 寄 6 位碼(10 分鐘有效) # POST /portal/agent/enroll/verify {email, code, device_name} → tenant_id # + device_key + device_cert(X133 裝置證書 → certs/device.crt) if (-not $TenantId -or -not $ApiKey) { if (-not [Environment]::UserInteractive) { Die "自助註冊要互動輸入 email/註冊碼 — 請用互動 PowerShell 視窗跑,或者用 -TenantId/-ApiKey 預派" } Info "── 裝置自助註冊(Self-enrollment — 同 macOS 一樣 email 收 6 位碼)──" $enrollEmail = $Email while (-not $enrollEmail) { $enrollEmail = (Read-Host "公司 Email(註冊碼會寄去你信箱,10 分鐘有效)").Trim().ToLower() if ($enrollEmail -notmatch '^[^@\s]+@[^@\s]+\.[^@\s]+$') { Warn "Email 格式唔啱,再試"; $enrollEmail = $null } } function _EnrollErr($ex) { try { return (($ex.ErrorDetails.Message | ConvertFrom-Json).detail) } catch { return "$ex" } } try { Invoke-RestMethod -Uri "$PortalBase/portal/agent/enroll/start" -Method Post ` -ContentType "application/json" -TimeoutSec 20 ` -Body (@{ email = $enrollEmail; platform = "windows" } | ConvertTo-Json) | Out-Null } catch { Die "發送註冊碼失敗:$(_EnrollErr $_) — 確認 email 係租戶註冊成員" } Ok "註冊碼已寄出(email 必須係租戶註冊成員)。請查收郵件。" $deviceName = $env:COMPUTERNAME $resp = $null for ($a = 1; $a -le 5; $a++) { $code = (Read-Host "6 位註冊碼(第 $a/5 次)").Trim() try { $resp = Invoke-RestMethod -Uri "$PortalBase/portal/agent/enroll/verify" ` -Method Post -ContentType "application/json" -TimeoutSec 20 ` -Body (@{ email = $enrollEmail; code = $code; device_name = $deviceName; platform = "windows" } | ConvertTo-Json) break } catch { $err = _EnrollErr $_ Warn "驗證失敗:$err" if ("$err" -match "Too many|expired") { break } } } if (-not $resp) { Die "自助註冊失敗 — 請重新執行安裝或聯絡 IT admin" } $TenantId = "$($resp.tenant_id)" $ApiKey = "$($resp.device_key)" if (-not $TenantId -or -not $ApiKey) { Die "註冊回應缺 tenant_id/device_key" } # X133:裝置證書(PKI 裝置身份)落碟 — 同 macOS certs/device.crt 一致 if ($resp.device_cert) { $CertDir = Join-Path $InstallDir "certs" New-Item -ItemType Directory -Force -Path $CertDir | Out-Null [System.IO.File]::WriteAllText((Join-Path $CertDir "device.crt"), "$($resp.device_cert)", (New-Object System.Text.UTF8Encoding $false)) } $Email = $enrollEmail Ok "裝置已註冊:tenant $TenantId · $enrollEmail" } # ── ④ config.json(tenant 綁定 — 同 macOS install.sh 逐字對齊, # 已核對真實 config.json 格式,唔係靠估)─────────────────────────────── Info "Writing config.json (tenant binding)..." $gatewayUrl = "http://proxy.next-guard.com:3000" $CaDir = Join-Path $InstallDir "certs" $configLines = @" { "GATEWAY_URL": "$gatewayUrl", "TENANT_ID": "$TenantId", "API_KEY": "$ApiKey", "USER_EMAIL": "$Email", "portal_url": "$PortalBase", "device_token": "$ApiKey", "PROXY_PORT": 8443, "HTTP_PORT": 8080, "CA_CERT_PATH": "$($CaDir -replace '\\','/')/ca.crt", "CA_KEY_PATH": "$($CaDir -replace '\\','/')/ca.key", "LOG_LEVEL": "INFO", "MAX_INSPECT_BYTES": 16777216 } "@ # 寫檔 **必須冇 BOM**:PS 5.1 嘅 Set-Content -Encoding UTF8 會加 BOM, # Python json.load 見 BOM 即 parse 錯 — agent 裝好即刻 config 爆炸。 # .NET WriteAllText + UTF8Encoding($false) 兩版 PowerShell 行為一致。 [System.IO.File]::WriteAllText( (Join-Path $InstallDir "config.json"), $configLines, (New-Object System.Text.UTF8Encoding $false)) Ok "Config written (UTF-8 no BOM)" # ── ④b Lite branding(X353 — 同 macOS install.sh 寫 branding.txt 一款)────── # 引擎 _lite_brand_bootstrap 同 tray-go 讀 %APPDATA%\NextGuard\branding.txt # 顯示產品名。PortalBase 係 Lite(sslip.io host)先寫——Full 版唔寫, # tray 顯示預設「NextGuard SSE」。 if ($PortalBase -match "sslip\.io") { $BrandDir = Join-Path $env:APPDATA "NextGuard" New-Item -ItemType Directory -Force -Path $BrandDir | Out-Null $BrandFile = Join-Path $BrandDir "branding.txt" if (-not (Test-Path $BrandFile)) { [System.IO.File]::WriteAllText( $BrandFile, "NextGuard SSE Lite (Local-First)`n", (New-Object System.Text.UTF8Encoding $false)) Info "Lite branding written: $BrandFile" } } # ── ⑤ venv + 依賴(全部 --only-binary :all:)──────────────────────────────── Info "Creating Python virtual environment..." # X328:embeddable python 冇 venv 模組(VM + Roscoe PC 真機實錘: # "No module named venv" → 安裝死喺呢度)。重用 python3 嗰條路都會中 # (二次安裝 reuse 候選跳過 get-pip 段)。venv 失敗 → pip 裝 virtualenv # 頂上;兩邊都建唔到先准死。 # X328c:PS 5.1 真機再實錘 — `& python -m venv 2>$null` 喺 # $ErrorActionPreference="Stop" 之下依然會將 native stderr 升級成 # TerminatingError(2>$null 擋唔住 EAP=Stop)→ 成個安裝 trap 死。 # 所以每個可能噴 stderr 嘅 native call 都用 try/catch 包住(EAP=Stop # 嘅錯誤 try/catch 一定捉到)。 try { & $Python -m venv $VenvDir 2>$null } catch { } if (-not (Test-Path (Join-Path $VenvDir "Scripts\python.exe"))) { Info "python -m venv 唔得(embeddable python 冇 venv 模組)— 裝 virtualenv 頂上" try { & $Python -m pip install --quiet virtualenv --no-cache-dir 2>$null } catch { } try { & $Python -m virtualenv $VenvDir 2>$null } catch { } if (-not (Test-Path (Join-Path $VenvDir "Scripts\python.exe"))) { Die "venv/virtualenv 都建唔到 — 請手動裝 Python 3.12(python.org 官方安裝器)" } } $Pip = Join-Path $VenvDir "Scripts\pip.exe" # X328d:PS 5.1 EAP=Stop + native stderr = 地雷陣(venv/pip/certutil 任何一個 # 噴 stderr 都會 TerminatingError 殺死成個安裝 — 2>$null 擋唔住,VM 三次 # 真機實錘)。由呢度起(全部係 native call + 已有 $LASTEXITCODE 顯式檢查) # 轉 EAP=Continue;Die 係顯式呼叫唔受影響。 $ErrorActionPreference = "Continue" & $Pip install --quiet --upgrade pip --no-cache-dir 2>$null # X221:cryptography pin — 同 macOS 一致(48.0.1 有 win_amd64 wheel) & $Pip install --quiet --only-binary ":all:" aiohttp "cryptography==48.0.1" --no-cache-dir ` | Out-Null if ($LASTEXITCODE -ne 0) { Die "aiohttp/cryptography 安裝失敗(見 $LogFile)" } # X220:PDF redact 執行依賴(win_amd64 wheel 有) & $Pip install --quiet --no-cache-dir --no-deps "pikepdf==10.5.1" packaging deprecated ` | Out-Null # X250:on-device PDF 提取 & $Pip install --quiet --only-binary ":all:" pymupdf pypdf --no-cache-dir | Out-Null # X245:on-device OCR(onnxruntime 有 win_amd64 wheel;~150MB) # X320:rapidocr 喺離線 wheels 冇貨(150MB)— 呢一步清 PIP_NO_INDEX # 行 PyPI 即場下(best-effort;失敗都係 Warn 唔准死) Remove-Item Env:PIP_NO_INDEX -ErrorAction SilentlyContinue Remove-Item Env:PIP_FIND_LINKS -ErrorAction SilentlyContinue & $Pip install --quiet --only-binary ":all:" rapidocr_onnxruntime --no-cache-dir ` | Out-Null if ($LASTEXITCODE -ne 0) { Warn "rapidocr 裝唔到 — on-device OCR 暫缺(規則照行)" } Ok "Dependencies installed (aiohttp, cryptography, pikepdf/pymupdf/pypdf, rapidocr best-effort)" # ── ⑥ 裝置 CA 生成 + 信任(放 certs/ 子目錄 — 同 macOS 一致)──────────── Info "Generating device-local CA certificate..." New-Item -ItemType Directory -Force -Path $CaDir | Out-Null $CaCrt = Join-Path $CaDir "ca.crt" $CaKey = Join-Path $CaDir "ca.key" $caPy = @" from cryptography import x509 from cryptography.x509.oid import NameOID from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec import datetime key = ec.generate_private_key(ec.SECP256R1()) now = datetime.datetime.now(datetime.timezone.utc) cert = (x509.CertificateBuilder() .subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, 'NextGuard Device CA')])) .issuer_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, 'NextGuard Device CA')])) .public_key(key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now) .not_valid_after(now + datetime.timedelta(days=3650)) .sign(key, hashes.SHA256())) open(r'$($CaCrt -replace '\\','/')', 'wb').write(cert.public_bytes(serialization.Encoding.PEM)) open(r'$($CaKey -replace '\\','/')', 'wb').write(key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption())) "@ $VenvPython = Join-Path $VenvDir "Scripts\python.exe" & $VenvPython -c $caPy if (-not (Test-Path $CaCrt)) { Die "CA 生成失敗" } Ok "CA generated" Info "Trusting CA in CurrentUser Root store..." # X330:非互動環境(session 0 / 排程任務裝 — VM 實錘)certutil 嘅 # 安全警告彈窗永遠冇人撳「是」→ CA 靜態唔信任 → TLS 攔截 degraded。 # Import-Certificate 唔會彈窗,非互動時直接用佢;互動環境照舊行 # certutil(Microsoft 官方確認路徑)。 if ([Environment]::UserInteractive) { Info "⚠️ Windows 會彈出一次安全警告 — 請撳『是 / Yes』完成信任(Microsoft 嘅設計)" & certutil -user -addstore Root $CaCrt | Out-Null if ($LASTEXITCODE -ne 0) { Warn "certutil 信任失敗 — TLS 攔截會行 degraded 模式(上網唔受影響,DLP 暫停)" } else { Ok "Trusted in CurrentUser Root (Firefox 要用自家 NSS store — v1 限制,見文檔)" } } else { Info "非互動環境 — 用 Import-Certificate(唔彈窗)" try { Import-Certificate -FilePath $CaCrt -CertStoreLocation "Cert:\CurrentUser\Root" | Out-Null Ok "Trusted in CurrentUser Root (headless, X330)" } catch { Warn "CA 信任失敗 ($_) — TLS 攔截會行 degraded 模式" } } # ── ⑦ 系統代理(Registry HKCU + wininet refresh)───────────────────────────── Info "Setting system proxy (HKCU Internet Settings)..." $proxyPy = @" import sys sys.path.insert(0, r'$($InstallDir -replace '\\','/')') from platform_ng import proxy_config proxy_config.set_system_proxy(True, '127.0.0.1', 8443, 8080) print('engaged:', proxy_config.system_proxy_engaged('127.0.0.1', 8443, 8080)) "@ & $VenvPython -c $proxyPy $script:ProxyEngaged = $true # X309:由呢刻起,失敗 = 自動回滾 Ok "System proxy ON (127.0.0.1:8443)" # ── ⑧ 註冊登入自啟(Scheduled Task,用戶級)────────────────────────────────── Info "Registering logon autostart (Scheduled Task)..." # X331:Scheduled Task 用 pythonw(無 console window)— Task Scheduler 拉起 # python.exe 會開一個即時被收回嘅 console,X321 lastwill console handler 將 # 回收當 shutdown → 引擎 graceful exit rc=0 冇 log 靜默死(VM 實錘 # LastResult=0 但 PY-DEAD)。pythonw 冇 console → 唔收 close event → 引擎 # 長駐;log 全部落 proxy.log 檔案。pythonw 唔存在先回落 python.exe。 $VenvPythonW = Join-Path $VenvDir "Scripts\pythonw.exe" $EngineExe = if (Test-Path $VenvPythonW) { $VenvPythonW } else { $VenvPython } # ── ③c 隧道 daemon(X375 v0.2):vpnd_win + wireguard-go + wintun ───────── # 同 macOS vpnd 共享契約(vpn\request.json / vpnd_status.json,tray 同一 # 解析碼)。隧道成層都要 admin(wintun adapter / route / advfirewall) # —— daemon 註冊做 ONLOGON /rl highest;免 admin 裝機跳過自啟,引擎照行 # proxy-only degraded(同 macOS 冇 root 冇 tunnel 一致)。 $VpnDir = Join-Path $InstallDir "vpn" New-Item -ItemType Directory -Force $VpnDir | Out-Null Fetch "$PortalBase/agent/vpnd_win.py" (Join-Path $InstallDir "vpnd_win.py") $false | Out-Null foreach ($b in @("wireguard.exe", "wintun.dll")) { $dst = Join-Path $VpnDir $b Fetch "$PortalBase/agent/bin/$b" $dst | Out-Null # X271b 端對端驗證同款:sha256 sidecar 對唔上 = 腐敗/攔截,唔准落地 try { $got = (Get-FileHash $dst -Algorithm SHA256).Hash.ToLower() $want = (Invoke-WebRequest -Uri "$PortalBase/agent/bin/$b.sha256" -UseBasicParsing).Content.Trim().ToLower() if ($got -ne $want) { Remove-Item $dst -Force -ErrorAction SilentlyContinue; Die "$b sha256 mismatch — removed" } } catch { Die "$b hash verify failed: $_" } } $vpndCmd = '"' + $VenvPython + '" "' + (Join-Path $InstallDir "vpnd_win.py") + '"' & schtasks /create /tn "NextGuard Tunnel" /sc onlogon /tr $vpndCmd /f /rl highest 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { Warn "隧道 daemon 需要 admin — 未註冊自啟(引擎照行 proxy-only;要隧道請以管理員重跑 install.ps1)" } else { Ok "Tunnel daemon registered (highest privileges)" } $taskCmd = '"' + $EngineExe + '" "' + (Join-Path $InstallDir "sse_engine_local.py") + '"' & schtasks /create /tn "NextGuard Agent" /sc onlogon /tr $taskCmd /f /rl limited | Out-Null if ($LASTEXITCODE -ne 0) { # X310:有機(企業 GPO)拒 /rl limited — 冇 flag 再試 & schtasks /create /tn "NextGuard Agent" /sc onlogon /tr $taskCmd /f | Out-Null } if ($LASTEXITCODE -ne 0) { # 後備 2:ScheduledTasks module(繞過 schtasks ACL 差異) try { $action = New-ScheduledTaskAction -Execute $VenvPython ` -Argument (Join-Path $InstallDir "sse_engine_local.py") -WorkingDirectory $InstallDir $trigger = New-ScheduledTaskTrigger -AtLogOn Register-ScheduledTask -TaskName "NextGuard Agent" -Action $action ` -Trigger $trigger -Force -ErrorAction Stop | Out-Null $global:LASTEXITCODE = 0 } catch { } } if ($LASTEXITCODE -ne 0) { Warn "autostart 註冊失敗 — 引擎照起,但重開機後要手動啟動(或者撳桌面修復掣)" } else { Ok "Autostart registered" } # X329b:Scheduled Tasks 預設 DisallowStartIfOnBatteries=True — VM/無電池 # 機器(ACPI 回報異常)同 schtasks /run 都會被呢個條件靜默拒跑(VM 實錘: # LastResult 267011「task has not yet run」,引擎永遠起唔到)。註冊後 # 即刻剷走電池條件(schtasks CLI 冇呢個 flag,要用 PS API)。 try { foreach ($_tn in @("NextGuard Agent", "NextGuard Tray", "NextGuard Tunnel")) { $_t = Get-ScheduledTask -TaskName $_tn -ErrorAction Stop $_t.Settings.DisallowStartIfOnBatteries = $false $_t.Settings.StopIfGoingOnBatteries = $false $_t | Set-ScheduledTask | Out-Null } Ok "Battery conditions cleared (X329b)" } catch { Warn "battery-condition clear failed (non-fatal): $_" } # X334:GPO 擋 schtasks(Roscoe PC 2026-09-27 實錘 Access denied + 用戶冇 # admin 密碼)嘅企業機 — Startup 資料夾兜底(純用戶級,零權限要求)。 # 本地 Start-NextGuard.bat(pythonw 引擎 X331 + Go tray)+ shell:startup # shortcut,每次登入自動行。就算 GPO 連 startup 都封,雙擊 Restart bat # 依然係自救路(X326)。 try { $startBat = Join-Path $InstallDir "Start-NextGuard.bat" $pyw = Join-Path $VenvDir "Scripts\pythonw.exe" if (-not (Test-Path $pyw)) { $pyw = $VenvPython } $trayExe = Join-Path $InstallDir "tray\NextGuardTray.exe" @" @echo off REM NextGuard session start (per-user, no admin) - X334 start "" /min "$pyw" "$(Join-Path $InstallDir 'sse_engine_local.py')" if exist "$trayExe" start "" "$trayExe" "@ | Set-Content -Path $startBat -Encoding ascii # X342:VBS supervisor 做主啟動(窗口無、wscript 系統信任 parent — # 企業 EDR 按 spawn 來源殺進程嘅機(Roscoe PC 實錘:bat/Start-Process # 生出嚟嘅引擎秒死,安裝器生出嚟嘅活 2.5 小時)。supervisor 引擎一死 # 即自動重起,退出碼寫 logs\launcher.log — 之後 Collect zip 一定有料。 $vbs = Join-Path $InstallDir "Start-NextGuard.vbs" @' Option Explicit Dim sh, fso, ng, code, f Set sh = CreateObject("WScript.Shell") Set fso = CreateObject("Scripting.FileSystemObject") ng = sh.ExpandEnvironmentStrings("%LOCALAPPDATA%") & "\NextGuard" sh.CurrentDirectory = ng Do code = sh.Run("""" & ng & "\venv\Scripts\python.exe"" """ & ng & "\sse_engine_local.py"" >> """ & ng & "\logs\launcher.log"" 2>&1", 0, True) Set f = fso.OpenTextFile(ng & "\logs\launcher.log", 8, True) f.WriteLine "[supervisor] engine exited code=" & code & " at " & Now & " - restarting in 5s" f.Close WScript.Sleep 5000 Loop '@ | Set-Content -Path $vbs -Encoding ascii $startupDir = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\Startup" New-Item -ItemType Directory -Force -Path $startupDir | Out-Null $ws = New-Object -ComObject WScript.Shell $sc = $ws.CreateShortcut((Join-Path $startupDir "NextGuard.lnk")) $sc.TargetPath = "wscript.exe" $sc.Arguments = '"' + $vbs + '"' $sc.WorkingDirectory = $InstallDir $sc.WindowStyle = 7 $sc.Save() # X343:裝完即起 supervisor — 開機捷徑淨係下次登入先郁;唔等, # 而家即刻行(wscript 無窗口)。之後引擎/ tray 死咗都會 5 秒內復活。 try { Start-Process "wscript.exe" -ArgumentList '"'$vbs'"' } catch { Warn "supervisor start failed (non-fatal): $_" } # 後備:Start-NextGuard.bat 照留(EDR 放行 bat 嘅環境照用得到) Ok "Startup-folder autostart registered (X342 VBS supervisor, no admin)" } catch { Warn "startup-folder autostart failed (non-fatal): $_" } # ── ⑧b 企業防篡改(W2):安裝目錄 ACL 鎖(owner 保留寫入 — self-update/logs 唔鎮死) # + 任務檔鎖(要 admin;免 admin 安裝會 Warn 跳過 — v1 限制)────────────────── Info "Applying tamper protection (W2)..." $hardenPy = @" import sys sys.path.insert(0, r'$($InstallDir -replace '\\','/')') from platform_ng import tamper, service ok1 = tamper.lockdown_dir(r'$InstallDir') ok2 = service.harden_service() print('DIR_LOCK=%s TASK_LOCK=%s' % (ok1, ok2)) "@ try { $ErrorActionPreference = "Continue" # X309:子進程非零唔准殺 script $hv = & $VenvPython -c $hardenPy 2>&1 | Select-Object -Last 1 $ErrorActionPreference = "Stop" Info "$hv" if ("$hv" -match "TASK_LOCK=False") { Warn "任務檔鎖要 admin — 企業部署請用提權安裝;目錄鎖已生效" } # X346b:tamper 鎖(icacls /inheritance:r)後,runtime 子目錄明確 # 俾返 owner FULL — 某啲環境(800A0046 Permission denied 實錘)下喺 # 安裝樹入面建新檔會被拒,引擎/tray/supervisor 全部靜默死。 foreach ($sub in @("logs", "vpn", "tray", "certs")) { $sd = Join-Path $InstallDir $sub if (Test-Path $sd) { & icacls $sd /inheritance:e "/grant:r" "$env:USERNAME:(OI)(CI)F" 2>$null | Out-Null } } } catch { Warn "防篡改步驟失敗(best-effort,唔阻塞):$_" } # ── ⑧c Windows 托盤(W6 — Go 版 NextGuardTray.exe 優先,PS1 NotifyIcon 後備)── # 用戶反映 PowerShell tray 表現差(powershell.exe 長駐食百幾 MB、啟動慢)— # tray 改用 Go 編譯 binary(純 syscall 冇 cgo,單 binary ~7MB),功能對標: # 狀態圖示 / 暫停恢復 / 氣球通知 / 開日誌。EXE 冇發布時自動回落 PS1 版。 Info "Installing Windows tray (NotifyIcon)..." $TrayDir = Join-Path $InstallDir "tray" New-Item -ItemType Directory -Force -Path $TrayDir | Out-Null $trayExe = Join-Path $TrayDir "NextGuardTray.exe" $gotExe = Fetch "$PortalBase/agent/NextGuardTray.exe" $trayExe $false if ($gotExe -and (Test-Path $trayExe)) { $trayCmd = '"' + $trayExe + '"' & schtasks /create /tn "NextGuard Tray" /sc onlogon /tr $trayCmd /f /rl limited | Out-Null if ($LASTEXITCODE -ne 0) { # X313:GPO deny schtasks 嘅機(真機實錘 Access denied ×2)— # 冇 /rl 重試再 module 後備;全部都死都直接起 tray(watchdog # 同 clipboard DLP 都喺 tray 度 — 佢冇行係功能損失唔可以靜默) & schtasks /create /tn "NextGuard Tray" /sc onlogon /tr $trayCmd /f | Out-Null } if ($LASTEXITCODE -ne 0) { try { $ta = New-ScheduledTaskAction -Execute $trayExe -WorkingDirectory (Split-Path $trayExe) $tt = New-ScheduledTaskTrigger -AtLogOn Register-ScheduledTask -TaskName "NextGuard Tray" -Action $ta -Trigger $tt -Force -ErrorAction Stop | Out-Null $global:LASTEXITCODE = 0 } catch { } } if ($LASTEXITCODE -ne 0) { Warn "tray autostart 全部後備失敗 — 直接起 tray(重開機後要手動開 tray)" } else { Ok "Tray registered (Go)" } Start-Process $trayExe -WindowStyle Hidden } else { Warn "NextGuardTray.exe 未發布 — 回落 PowerShell tray" Fetch "$PortalBase/agent/tray/NextGuardTray.ps1" (Join-Path $TrayDir "NextGuardTray.ps1") $false | Out-Null $trayCmd = 'powershell -NoProfile -WindowStyle Hidden -File "' + (Join-Path $TrayDir "NextGuardTray.ps1") + '"' & schtasks /create /tn "NextGuard Tray" /sc onlogon /tr $trayCmd /f /rl limited | Out-Null if ($LASTEXITCODE -ne 0) { Warn "tray 註冊失敗 — 功能唔受影響,淨係冇托盤圖示" } else { Ok "Tray registered (PowerShell fallback)" Start-Process powershell -ArgumentList "-NoProfile","-WindowStyle","Hidden","-File",(Join-Path $TrayDir "NextGuardTray.ps1") -WindowStyle Hidden } } # ── ⑧d W7:卸載器 + 網絡修復落地裝置(用戶實錘:agent 失常搞到上唔到 # 網嗰陣,唔可以依賴上網先攞到 uninstaller —— 必須離線喺本機就緒, # 桌面 + Start Menu 一鍵可撳)─────────────────────────────────────────────── Info "Installing uninstaller + network repair shortcuts..." $gotUn = Fetch "$PortalBase/agent/installer/uninstall.ps1" (Join-Path $InstallDir "uninstall.ps1") $false $gotRn = Fetch "$PortalBase/agent/installer/repair-network.ps1" (Join-Path $InstallDir "repair-network.ps1") $false if (-not ($gotUn -and $gotRn)) { Warn "卸載/修復 script 下載唔到 — 今次略過捷徑(保護功能唔受影響)" } if ($gotUn -and $gotRn) { try { $Wsh = New-Object -ComObject WScript.Shell foreach ($base in @([Environment]::GetFolderPath("Desktop"), [Environment]::GetFolderPath("StartMenu"))) { $lnk = $Wsh.CreateShortcut((Join-Path $base "卸載 NextGuard.lnk")) $lnk.TargetPath = "powershell.exe" $lnk.Arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$(Join-Path $InstallDir 'uninstall.ps1')`"" $lnk.IconLocation = "$env:SystemRoot\System32\shell32.dll,31" $lnk.Description = "完全移除 NextGuard Agent(還原網絡設定)" $lnk.Save() $lnk2 = $Wsh.CreateShortcut((Join-Path $base "修復 NextGuard 網絡.lnk")) $lnk2.TargetPath = "powershell.exe" $lnk2.Arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$(Join-Path $InstallDir 'repair-network.ps1')`"" $lnk2.IconLocation = "$env:SystemRoot\System32\shell32.dll,238" $lnk2.Description = "上唔到網嗰陣撳呢個 — 停保護 + 還原代理/路由/firewall" $lnk2.Save() } Ok "Shortcuts created (Desktop + Start Menu)" } catch { Warn "shortcut 建立失敗:$_ — uninstall.ps1 / repair-network.ps1 已喺 $InstallDir" } } # ── ⑧e X322:屏幕水印 overlay ────────────────────────────────────────────── Fetch "$PortalBase/agent/NextGuardWatermark.exe" (Join-Path $InstallDir "NextGuardWatermark.exe") $false | Out-Null # ── ⑨ 即刻啟動 + 驗證 ──────────────────────────────────────────────────────── Info "Starting agent..." & schtasks /run /tn "NextGuard Agent" | Out-Null if ($LASTEXITCODE -ne 0) { Warn "schtasks /run 失敗 — 直接起進程做後備" Start-Process $VenvPython -ArgumentList (Join-Path $InstallDir "sse_engine_local.py") -WorkingDirectory $InstallDir } Start-Sleep -Seconds 10 $listening = Get-NetTCPConnection -LocalPort 8443 -State Listen -ErrorAction SilentlyContinue if (-not $listening) { Start-Sleep -Seconds 10 $listening = Get-NetTCPConnection -LocalPort 8443 -State Listen -ErrorAction SilentlyContinue } if ($listening) { Ok "Agent is listening on 127.0.0.1:8443" } else { # X332c:引擎即時起唔到唔再算安裝失敗 — 排程任務(X329b 電池條件已剷 + # X331 pythonw)會喺下次登入自動拉起;interactive task 喺無人值守/ # 鎖屏 session 裝機係常見 VM 實錘。回滾代理保網絡(X309 trap 唔郁, # 呢度自己回滾),俾清晰自救路,exit 0(全部件已裝好)。 Warn "Agent 未即時啟動(session 未就緒)— 已回滾系統代理保證上網" $rp = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" Set-ItemProperty $rp ProxyEnable 0 -ErrorAction SilentlyContinue Remove-ItemProperty $rp ProxyServer -ErrorAction SilentlyContinue Remove-ItemProperty $rp AutoConfigURL -ErrorAction SilentlyContinue & netsh winhttp reset proxy 2>$null | Out-Null Warn "下次登入會自動啟動;或者即刻雙擊 Restart-NextGuard.bat 拉起" } Write-Host "" Write-Host "╔══════════════════════════════════════════════════════╗" -ForegroundColor Blue Write-Host "║ ✅ NextGuard Agent 安裝完成(Windows) ║" -ForegroundColor Green Write-Host "║ 驗證:瀏覽器開 kimi.ai 貼段測試 PII,應見遮罩/攔截 ║" -ForegroundColor Blue Write-Host "║ 日誌:$InstallDir\logs\proxy.log" -ForegroundColor Blue Write-Host "╚══════════════════════════════════════════════════════╝" -ForegroundColor Blue Stop-Transcript | Out-Null