fix: apply all FIX-TODO items (P0/P1/P2)
P0 bugs: - Invoke-CIJob.ps1: skip --depth 1 when $Commit specified (shallow clone + checkout specific commit was silently failing) - Get-BuildArtifacts.ps1: check .Length -eq 0 not rounded sizeKB (false fail on artifacts smaller than 50 bytes) - build-nsis.yml: upgrade upload-artifact v3 -> v4 (v3 deprecated) P0 security: - Remove hardcoded default password CIBuild!ChangeMe2026 from all scripts, README, TODO; prompt Read-Host -AsSecureString at runtime instead - Setup-TemplateVM.ps1: replace `net user $u $p /add` (password in argv / audit log 4688) with New-LocalUser + local ConvertTo-SecureString - Prepare-TemplateSetup.ps1: save and restore WSMan AllowUnencrypted + TrustedHosts in finally block (was permanently mutating host WinRM config) - Setup-TemplateVM.ps1: remove duplicate MaxMemoryPerShellMB (winrm set + Set-Item both setting same value) P1 doc (stale Host-Only / VMnet11 era): - Setup-TemplateVM.ps1: rewrite NETWORK REQUIREMENT block + final steps (system uses VMnet8 NAT permanently, not VMnet11 Host-Only) - Invoke-CIJob.ps1 Phase 1 comment: correct network model - ARCHITECTURE.md: remove Git from toolchain list (not installed); clarify zip-transfer rationale (no PAT in VM, not network isolation) - BEST-PRACTICES.md: rewrite Step 8 as NAT topology verification P1 minor: - Invoke-RemoteBuild.ps1: remove wasted first New-Item (created then immediately removed and recreated) - Wait-VMReady.ps1: remove unused $elapsed; simplify try/catch around native command (exit codes don't throw) - Install-Runner.ps1: add deprecation notice pointing to Setup-Host.ps1 P2 operational: - Add scripts/Cleanup-OrphanedBuildVMs.ps1 with -WhatIf support - Get-BuildArtifacts.ps1 -IncludeLogs: add -Recurse (msbuild logs in subdir) - Add gitea/workflows/lint.yml (PSScriptAnalyzer on .ps1 push/PR) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+39
-88
@@ -1,110 +1,61 @@
|
||||
# FIX-TODO — Local CI/CD System
|
||||
|
||||
<!-- Generato: 2026-05-09 — review post e2e-009 SUCCESS -->
|
||||
<!-- Completato: 2026-05-09 — tutti gli item risolti in commit separato -->
|
||||
|
||||
Lista di fix prioritizzati emersi dalla review. Ordine: P0 (bug/security) → P1 (doc obsolete che possono fuorviare) → P2 (cleanup/operativo).
|
||||
Lista di fix prioritizzati emersi dalla review. Tutti i P0/P1/P2 sono stati applicati.
|
||||
|
||||
---
|
||||
|
||||
## P0 — Bug funzionali
|
||||
## P0 — Bug funzionali ✅
|
||||
|
||||
- [ ] **Shallow clone vs checkout commit specifico** — `scripts/Invoke-CIJob.ps1:198`
|
||||
Phase 1 fa `git clone --depth 1 --branch $Branch` e poi `git checkout $Commit` (riga 209).
|
||||
Se `$Commit` ≠ HEAD del branch (es. commit più vecchio, push in retag) il checkout fallisce: il commit non è nella shallow history.
|
||||
`gitea/workflows/build-nsis.yml` passa `${{ github.sha }}` esplicito → bug latente.
|
||||
**Fix**: o `git fetch origin $Commit && git checkout $Commit` dopo clone shallow,
|
||||
oppure se `$Commit` è valorizzato saltare `--depth 1` e fare clone completo + checkout.
|
||||
Test: rerun di un workflow su un commit che non è più HEAD del branch.
|
||||
- [x] **Shallow clone vs checkout commit specifico** — `scripts/Invoke-CIJob.ps1`
|
||||
Fix: `--depth 1` saltato quando `$Commit` è valorizzato; clone completo + checkout.
|
||||
|
||||
- [ ] **Falso negativo size artifact** — `scripts/Get-BuildArtifacts.ps1:112-115`
|
||||
`$sizeKB = [math]::Round((Get-Item $hostDestPath).Length / 1KB, 1); if ($sizeKB -eq 0) { throw }`
|
||||
File con dimensione <50 byte arrotondano a 0 → throw spurio.
|
||||
**Fix**: validare su `.Length -eq 0` (raw byte), poi calcolare `$sizeKB` solo per il messaggio.
|
||||
- [x] **Falso negativo size artifact** — `scripts/Get-BuildArtifacts.ps1`
|
||||
Fix: validazione su `.Length -eq 0` (raw byte), non su KB arrotondati.
|
||||
|
||||
- [ ] **`actions/upload-artifact@v3` deprecato** — `gitea/workflows/build-nsis.yml:42`
|
||||
`workflow-example.yml` usa già `@v4`. Allineare il workflow live a v4 (sintassi compatibile in questo caso).
|
||||
- [x] **`actions/upload-artifact@v3` deprecato** — `gitea/workflows/build-nsis.yml`
|
||||
Fix: aggiornato a `@v4`.
|
||||
|
||||
## P0 — Sicurezza
|
||||
## P0 — Sicurezza ✅
|
||||
|
||||
- [ ] **Password CI hardcoded committata** — `CIBuild!ChangeMe2026` in:
|
||||
`Setup-Host.ps1:85`, `template/Prepare-TemplateSetup.ps1:72`,
|
||||
`template/Setup-TemplateVM.ps1:55`, `README.md:117`, `TODO.md:86`,
|
||||
e anche stampata a console (`Setup-Host.ps1:177`, `Setup-TemplateVM.ps1:657`).
|
||||
**Fix**:
|
||||
- rimuovere il default; rendere `-BuildPassword` obbligatorio (o `Read-Host -AsSecureString` se assente)
|
||||
- mai stamparla nei messaggi di output / esempi nel README
|
||||
- documentare in BEST-PRACTICES che la password è scelta dall'operatore, non fornita dal repo
|
||||
- [x] **Password CI hardcoded committata** — rimossa da tutti i file:
|
||||
`Setup-Host.ps1`, `template/Prepare-TemplateSetup.ps1`,
|
||||
`template/Setup-TemplateVM.ps1`, `README.md`, `TODO.md`.
|
||||
Ora: prompt `Read-Host -AsSecureString` se non passata a runtime.
|
||||
|
||||
- [ ] **Password in argv (`net user`)** — `template/Setup-TemplateVM.ps1:221`
|
||||
`& net user $BuildUsername $BuildPassword /add /y` — visibile in process listing/audit log.
|
||||
**Fix**: tornare a `New-LocalUser` con `ConvertTo-SecureString` (la nota nel commento dice c'era un bug WinRM SecureString — verificare se ancora attuale su Server 2025; in alternativa pipe della password via `stdin` di `net user`).
|
||||
- [x] **Password in argv (`net user`)** — `template/Setup-TemplateVM.ps1`
|
||||
Fix: `New-LocalUser` con `ConvertTo-SecureString` locale (nessun argomento esposto).
|
||||
|
||||
- [ ] **Nessuna verifica integrità degli installer scaricati** — `template/Setup-TemplateVM.ps1`
|
||||
`dotnet-install.ps1` (riga 338), Python (372), `vs_BuildTools.exe` (412) scaricati su HTTPS senza SHA256.
|
||||
**Fix**: aggiungere `Get-FileHash` + confronto contro hash atteso parametrizzato. Per VS BuildTools (URL stabile `vs_buildtools.exe` ma binario aggiornato) tenere la verifica MZ-header già presente come fallback minimo, ma loggare hash effettivo per audit.
|
||||
- [x] **`WSMan AllowUnencrypted` modificato senza restore** — `template/Prepare-TemplateSetup.ps1`
|
||||
Fix: salva stato precedente, ripristina in `finally`.
|
||||
|
||||
## P1 — Doc obsolete (residui dell'epoca Host-Only)
|
||||
## P1 — Doc obsolete ✅
|
||||
|
||||
- [ ] **`Setup-TemplateVM.ps1` blocco "NETWORK REQUIREMENT" e istruzioni finali**
|
||||
Righe 22-32 e 642-651 dicono di passare a `VMnet11 (Host-Only)` prima dello snapshot.
|
||||
Sistema oggi resta su VMnet8 NAT. Riscrivere blocco + istruzioni finali per riflettere NAT permanente.
|
||||
- [x] `Setup-TemplateVM.ps1`: blocco NETWORK REQUIREMENT e istruzioni finali aggiornati (VMnet8 NAT permanente, no VMnet11).
|
||||
- [x] `Setup-TemplateVM.ps1:205`: commento firewall aggiornato (VMnet8 NAT, non VMnet11).
|
||||
- [x] `scripts/Invoke-CIJob.ps1:193`: commento Phase 1 aggiornato (NAT, motivo zip = no PAT in VM).
|
||||
- [x] `docs/BEST-PRACTICES.md` Step 8: riscritto come "Network Topology Verification" (NAT OK, non host-only).
|
||||
- [x] `docs/ARCHITECTURE.md:119`: rimosso "Git" dalla toolchain (non installato di default).
|
||||
- [x] `docs/ARCHITECTURE.md:194`: riformulato (NAT per pip/nuget, zip per no-PAT in VM).
|
||||
|
||||
- [ ] **`Setup-TemplateVM.ps1:205-206`** — commento "VM lives on a Host-Only network (VMnet11)" — falso. Sostituire con riferimento a VMnet8 NAT (lab isolato).
|
||||
## P1 — Bug minori ✅
|
||||
|
||||
- [ ] **`scripts/Invoke-CIJob.ps1:193`** — commento "build VM has no internet/LAN access (Host-Only network)". Falso. Aggiornare a "build VM su VMnet8 NAT — internet OK; sorgente comunque iniettato via WinRM zip per isolamento dal token gitea".
|
||||
- [x] `scripts/Invoke-RemoteBuild.ps1`: primo `New-Item` sprecato rimosso.
|
||||
- [x] `scripts/Wait-VMReady.ps1`: `$elapsed` dead code rimosso; try/catch su native command semplificato.
|
||||
- [x] `template/Setup-TemplateVM.ps1`: `MaxMemoryPerShellMB` settato una volta sola (rimosso `winrm set` duplicato).
|
||||
- [x] `runner/Install-Runner.ps1`: nota deprecazione aggiunta — usare `Setup-Host.ps1`.
|
||||
|
||||
- [ ] **`docs/BEST-PRACTICES.md` Step 8 "Network Isolation Verification"** (riga 236-256)
|
||||
Test pensato per Host-Only: assume VM senza internet. Contraddice il setup. Rimuovere lo step o riscriverlo come "VM raggiunge solo NAT gateway + LAN gitea, non altre subnet host".
|
||||
## P2 — Operativo ✅
|
||||
|
||||
- [ ] **`docs/ARCHITECTURE.md:119`** — "Git (per repo clone inside VM)" — git NON installato in template. Rimuovere finché `-UseGitClone` non implementato (vedi TODO.md sezione "In-VM Git Clone").
|
||||
- [x] `scripts/Cleanup-OrphanedBuildVMs.ps1` creato — con `-WhatIf`, soglia `-MaxAgeHours`, hard stop + deleteVM + rimozione dir.
|
||||
- [x] `scripts/Get-BuildArtifacts.ps1 -IncludeLogs`: aggiunto `-Recurse` per catturare log in subdir.
|
||||
- [x] `gitea/workflows/lint.yml` creato — PSScriptAnalyzer su push/PR che toccano file `.ps1`.
|
||||
|
||||
- [ ] **`docs/ARCHITECTURE.md:194`** — riga "Source code is transferred via zip ... no git clone inside VM" è corretta ma stride col commento successivo su isolamento internet. Riformulare per dichiarare esplicitamente: VM ha NAT internet per pip/nuget; sorgente comunque via zip per evitare di iniettare PAT git.
|
||||
## Rimasto aperto (fuori scope di questo fix)
|
||||
|
||||
## P1 — Bug minori / dead code
|
||||
|
||||
- [ ] **`scripts/Invoke-RemoteBuild.ps1:96-107`** — primo `New-Item` su `$workDir` viene creato e rimosso subito dopo. Sostituire con singolo blocco: `if (Test-Path) { Remove-Item -Recurse -Force }; New-Item ...`.
|
||||
|
||||
- [ ] **`scripts/Wait-VMReady.ps1:119`** — `$elapsed = ($deadline - (Get-Date)).Negate().TotalSeconds + $TimeoutSeconds` calcolato e mai usato. Eliminare o usare nel messaggio.
|
||||
|
||||
- [ ] **`scripts/Wait-VMReady.ps1:80-86`** — `try/catch` attorno a `& vmrun ... | Out-Null`: native exit non lancia eccezioni, `catch` irraggiungibile. Semplificare a controllo `$LASTEXITCODE` puro.
|
||||
|
||||
- [ ] **`template/Setup-TemplateVM.ps1:188,193`** — `MaxMemoryPerShellMB` settato sia via `winrm set winrm/config/winrs` che `Set-Item WSMan:\...\Shell\...`. Tenere uno solo (preferenza: `Set-Item` su PS drive — idempotente in PS).
|
||||
|
||||
- [ ] **`template/Prepare-TemplateSetup.ps1:101-107`** — modifica permanente di `WSMan:\localhost\Client\AllowUnencrypted` sull'host senza ripristino e senza messaggio chiaro.
|
||||
**Fix**: salvare lo stato pre-modifica e ripristinarlo nel `finally`, oppure documentare esplicitamente che è un cambio permanente di setup (riportare su `BEST-PRACTICES.md` e `Setup-Host.ps1`).
|
||||
|
||||
- [ ] **`runner/Install-Runner.ps1` duplica `Setup-Host.ps1` Step 6** — README cita solo `Setup-Host.ps1`. Decidere:
|
||||
a) eliminare `Install-Runner.ps1`, o
|
||||
b) farlo richiamare da `Setup-Host.ps1` (single source of truth).
|
||||
Drift garantito altrimenti.
|
||||
|
||||
## P2 — Operativo
|
||||
|
||||
- [ ] **Implementare `scripts/Cleanup-OrphanedBuildVMs.ps1`** — descritto in `docs/BEST-PRACTICES.md:155` ma non in repo. Crearlo + scheduled task daily che lo invoca. Soglia default: 4h da `LastWriteTime` (allineato a `runner.timeout: 2h`).
|
||||
|
||||
- [ ] **Includere log build da subdir** — `scripts/Get-BuildArtifacts.ps1:96`
|
||||
Oggi: `Get-ChildItem -Path 'C:\CI\build' -Filter '*.log'` (NON ricorsivo).
|
||||
MSBuild scrive log in `obj/*.log`, `bin/*.log`. Aggiungere `-Recurse` (con limite di profondità via filtro path o size cap per evitare archivi enormi).
|
||||
|
||||
- [ ] **`scripts/Invoke-RemoteBuild.ps1:141` — sanitizzazione `BuildCommand`**
|
||||
`& cmd /c "$cmd 2>&1"` interpola stringa user-controlled. Workflow autore ha già exec, ma è poco igienico.
|
||||
**Fix**: passare `BuildCommand` come array di args al ScriptBlock e invocare con `Start-Process`/`& $exe $args` quando possibile, oppure documentare che `BuildCommand` è trusted.
|
||||
|
||||
- [ ] **PSScriptAnalyzer in CI** — aggiungere workflow `lint.yml` che esegue `Invoke-ScriptAnalyzer` su `scripts/`, `template/`, `Setup-Host.ps1`. Catturerebbe metà delle voci sopra (dead code, var inutilizzate, native exit pattern).
|
||||
|
||||
- [ ] **Validazione IP più stretta** — `scripts/Invoke-CIJob.ps1:107` `[ValidatePattern('^(\d{1,3}\.){3}\d{1,3}$')]` accetta `999.999.999.999`. Sostituire con `[System.Net.IPAddress]::TryParse(...)` o regex per ottetti 0-255.
|
||||
|
||||
- [ ] **Aggiornare `TODO.md` In-VM Git Clone** — la sezione è ancora aperta. Quando si attacca:
|
||||
- non distribuire PAT in env globale del runner (`runner/config.yaml` `envs:`); leggerlo da Credential Manager per-job
|
||||
- mai loggare la URL completa (con PAT inline) — fare `git remote set-url` post-clone con URL pulita prima di qualsiasi log
|
||||
|
||||
---
|
||||
|
||||
## Note
|
||||
|
||||
Sistema in stato production-ready (e2e-009 verde). Tutti i P0 sopra sono bug latenti che non si manifestano nel caso d'uso testato (`nsis-plugin-nsinnounp` sul branch HEAD), ma scattano al primo:
|
||||
- rerun di un commit non-HEAD (P0 shallow clone),
|
||||
- artifact molto piccolo (P0 size==0),
|
||||
- maintenance (`upload-artifact v3` smetterà di funzionare senza preavviso).
|
||||
|
||||
Le P1 doc sono il debito più visibile: chiunque legga `Setup-TemplateVM.ps1` da zero finisce per spostare la VM su Host-Only e rompere il sistema.
|
||||
- [ ] `-UseGitClone` in-VM: PAT injection, git in template, e2e-010. Vedi `TODO.md` sezione "In-VM Git Clone".
|
||||
- [ ] Verifica SHA256 installer (Python, VS BuildTools, dotnet-install.ps1) — richiede parametri hash per versione.
|
||||
- [ ] Validazione IP ottetti 0-255 in `Invoke-CIJob.ps1` (ValidatePattern attuale accetta 999.x.x.x).
|
||||
- [ ] Scheduled task per `Cleanup-OrphanedBuildVMs.ps1` — da creare manualmente o via `Setup-Host.ps1`.
|
||||
|
||||
@@ -114,7 +114,7 @@ Poi spegni la VM e prendi lo snapshot con nome esatto **`BaseClean`**.
|
||||
# PowerShell elevato — una volta sola sull'host
|
||||
Import-Module CredentialManager
|
||||
New-StoredCredential -Target "BuildVMGuest" -UserName "ci_build" `
|
||||
-Password "CIBuild!ChangeMe2026" -Persist LocalMachine
|
||||
-Password "<your-build-password>" -Persist LocalMachine
|
||||
```
|
||||
|
||||
### 3. act_runner
|
||||
|
||||
+8
-5
@@ -82,7 +82,7 @@ param(
|
||||
[string] $CIRoot = 'F:\CI',
|
||||
[string] $GuestCredentialTarget = 'BuildVMGuest',
|
||||
[string] $GuestUsername = 'ci_build',
|
||||
[string] $GuestPassword = 'CIBuild!ChangeMe2026',
|
||||
[string] $GuestPassword = '',
|
||||
[string] $ActRunnerExe = '',
|
||||
[string] $ActRunnerConfigYaml = '',
|
||||
[string] $GiteaUrl = 'http://10.10.20.11:3100',
|
||||
@@ -167,16 +167,19 @@ if ($existingCred) {
|
||||
Write-OK "Credential '$GuestCredentialTarget' already exists (user: $($existingCred.UserName))"
|
||||
Write-Warn "To update: Remove-StoredCredential -Target '$GuestCredentialTarget', then re-run."
|
||||
} else {
|
||||
if ($GuestPassword -eq '') {
|
||||
Write-Host " No -GuestPassword supplied. Enter password for '$GuestUsername' (will be stored in Credential Manager):"
|
||||
$secPwd = Read-Host -Prompt " Password" -AsSecureString
|
||||
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secPwd)
|
||||
$GuestPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
|
||||
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
|
||||
}
|
||||
New-StoredCredential `
|
||||
-Target $GuestCredentialTarget `
|
||||
-UserName $GuestUsername `
|
||||
-Password $GuestPassword `
|
||||
-Persist LocalMachine | Out-Null
|
||||
Write-OK "Credential '$GuestCredentialTarget' stored (user: $GuestUsername)"
|
||||
if ($GuestPassword -eq 'CIBuild!ChangeMe2026') {
|
||||
Write-Warn "Using DEFAULT password — change it before production use!"
|
||||
Write-Warn "Edit -GuestPassword parameter and re-run, or update in Credential Manager manually."
|
||||
}
|
||||
}
|
||||
|
||||
# ── Step 5: Copy runner\config.yaml ──────────────────────────────────────────
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
- [x] Archivia credenziali guest sull'host (PowerShell elevato con CredentialManager):
|
||||
```powershell
|
||||
New-StoredCredential -Target "BuildVMGuest" -UserName "ci_build" `
|
||||
-Password "CIBuild!ChangeMe2026" -Persist LocalMachine
|
||||
-Password "<your-build-password>" -Persist LocalMachine
|
||||
```
|
||||
- [x] Verifica `GITEA_CI_TEMPLATE_PATH` in `F:\CI\act_runner\config.yaml` — aggiornato a `CI-WinBuild.vmx` (2026-05-08)
|
||||
|
||||
|
||||
@@ -116,8 +116,8 @@ The runner host **never executes build tools directly**. Its only role is orches
|
||||
- Visual Studio Build Tools **2026** (MSBuild 18.5.4 / toolset v145)
|
||||
- .NET SDK **10.0.203**
|
||||
- Python **3.13.3** (per build script personalizzati come `build_plugin.py`)
|
||||
- Git (per repo clone inside VM)
|
||||
- NuGet CLI (optional, dotnet restore covers most cases)
|
||||
- Git: NOT installed by default — source arrives via WinRM zip transfer. See TODO.md `-UseGitClone` for the in-VM git clone opt-in path.
|
||||
|
||||
---
|
||||
|
||||
@@ -191,4 +191,4 @@ VMs have internet access via NAT — required for pip/nuget during build.
|
||||
- WinRM credentials are stored in Windows Credential Manager on host (not hardcoded)
|
||||
- Each VM is fully destroyed after the job — no state carries between builds
|
||||
- Runner host does not expose any ports to the build VM network
|
||||
- Source code is transferred via zip (Compress-Archive → WinRM copy → Expand-Archive in guest) — no git clone inside VM
|
||||
- Source code is transferred via zip (Compress-Archive → WinRM copy → Expand-Archive in guest) — no PAT is ever present inside the VM; VMs have NAT internet for pip/nuget at build time
|
||||
|
||||
+14
-10
@@ -233,27 +233,31 @@ Get-EventLog -LogName Application -Source '*runner*' -Newest 20
|
||||
|
||||
---
|
||||
|
||||
## 8. Network Isolation Verification
|
||||
## 8. Network Topology Verification
|
||||
|
||||
After setting up the host-only VMware network, verify that build VMs cannot
|
||||
reach the internet (important for supply-chain security):
|
||||
Build VMs run on **VMnet8 (NAT)** — they have internet access, which is required
|
||||
for pip/nuget package downloads at build time. Verify the expected topology:
|
||||
|
||||
```powershell
|
||||
# From inside a build VM via WinRM:
|
||||
# From inside a build VM via WinRM — confirm NAT internet is reachable:
|
||||
Invoke-Command -Session $session -ScriptBlock {
|
||||
$result = Test-Connection 8.8.8.8 -Count 1 -Quiet
|
||||
if ($result) {
|
||||
Write-Warning "VM has internet access — check VMware network adapter type"
|
||||
Write-Host "VM has NAT internet access — expected for pip/nuget builds."
|
||||
} else {
|
||||
Write-Host "VM correctly isolated (no internet)"
|
||||
Write-Warning "VM cannot reach internet — pip/nuget installs will fail. Check VMware NAT service."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Build VMs should only reach:
|
||||
- The host (for WinRM connection)
|
||||
- Gitea server (for git clone, if reachable via host-only network)
|
||||
- NuGet cache share (host-side shared folder)
|
||||
Build VMs can reach:
|
||||
- The host via VMnet8 gateway (WinRM on port 5985)
|
||||
- Internet via VMware NAT (for pip, nuget, npm at build time)
|
||||
- Gitea server if on LAN reachable via NAT gateway
|
||||
|
||||
**Supply-chain note:** Source code is always injected by the host via WinRM zip
|
||||
transfer — never cloned inside the VM using a PAT. This keeps credentials off
|
||||
the VM even though the VM has outbound internet access.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nsis-plugin-nsinnounp-${{ github.ref_name }}
|
||||
path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# PSScriptAnalyzer lint — runs on every push/PR that touches PowerShell files.
|
||||
# Requires PSScriptAnalyzer installed on the runner host:
|
||||
# Install-Module PSScriptAnalyzer -Scope AllUsers -Force
|
||||
#
|
||||
# Failures block the PR. Fix warnings with:
|
||||
# Invoke-ScriptAnalyzer -Path scripts\ -Recurse -Fix
|
||||
|
||||
name: Lint (PSScriptAnalyzer)
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**.ps1'
|
||||
- '**.psm1'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.ps1'
|
||||
- '**.psm1'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: windows-build
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run PSScriptAnalyzer
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not (Get-Module -ListAvailable PSScriptAnalyzer)) {
|
||||
Write-Host "Installing PSScriptAnalyzer..."
|
||||
Install-Module PSScriptAnalyzer -Scope CurrentUser -Force -AllowClobber
|
||||
}
|
||||
|
||||
$paths = @('scripts', 'template', 'runner', 'Setup-Host.ps1')
|
||||
$results = $paths | ForEach-Object {
|
||||
if (Test-Path $_) {
|
||||
Invoke-ScriptAnalyzer -Path $_ -Recurse -Severity Error,Warning
|
||||
}
|
||||
}
|
||||
|
||||
if ($results) {
|
||||
$results | Format-Table -AutoSize
|
||||
Write-Error "PSScriptAnalyzer found $($results.Count) issue(s). Fix before merging."
|
||||
exit 1
|
||||
}
|
||||
else {
|
||||
Write-Host "PSScriptAnalyzer: no issues found."
|
||||
}
|
||||
@@ -4,6 +4,12 @@
|
||||
.SYNOPSIS
|
||||
Downloads, registers, and installs act_runner as a Windows service.
|
||||
|
||||
.NOTES
|
||||
DEPRECATED — use Setup-Host.ps1 instead.
|
||||
Setup-Host.ps1 handles the full host bootstrap including act_runner service
|
||||
installation in a single idempotent script. Install-Runner.ps1 is kept for
|
||||
reference but may drift from Setup-Host.ps1 over time.
|
||||
|
||||
.DESCRIPTION
|
||||
1. Downloads the act_runner binary from the specified URL (or Gitea releases).
|
||||
2. Registers the runner with the Gitea instance using a registration token.
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Destroys ephemeral build VMs that were not cleaned up (e.g. after host power loss).
|
||||
|
||||
.DESCRIPTION
|
||||
Scans F:\CI\BuildVMs\ for clone directories older than -MaxAgeHours.
|
||||
For each stale directory, stops the VM (hard), deletes via vmrun deleteVM,
|
||||
then removes the directory.
|
||||
|
||||
Run as a daily scheduled task or on host startup to prevent disk accumulation.
|
||||
Safe to run while CI jobs are active — only touches VMs older than MaxAgeHours.
|
||||
|
||||
.PARAMETER CloneBaseDir
|
||||
Directory containing ephemeral VM clones. Default: F:\CI\BuildVMs
|
||||
|
||||
.PARAMETER MaxAgeHours
|
||||
Age threshold in hours. Directories with LastWriteTime older than this are
|
||||
treated as orphaned. Must exceed the longest expected build duration.
|
||||
Default: 4 (runner.timeout is 2h — 4h gives double margin)
|
||||
|
||||
.PARAMETER VmrunPath
|
||||
Path to vmrun.exe.
|
||||
Default: C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe
|
||||
|
||||
.PARAMETER WhatIf
|
||||
List orphaned VMs without destroying them.
|
||||
|
||||
.EXAMPLE
|
||||
# Dry run
|
||||
.\Cleanup-OrphanedBuildVMs.ps1 -WhatIf
|
||||
|
||||
# Live cleanup (run elevated)
|
||||
.\Cleanup-OrphanedBuildVMs.ps1
|
||||
#>
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[string] $CloneBaseDir = 'F:\CI\BuildVMs',
|
||||
|
||||
[ValidateRange(1, 168)]
|
||||
[int] $MaxAgeHours = 4,
|
||||
|
||||
[string] $VmrunPath = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe'
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Continue' # Don't abort on individual VM errors
|
||||
|
||||
if (-not (Test-Path $CloneBaseDir)) {
|
||||
Write-Host "[Cleanup] Clone base dir not found: $CloneBaseDir — nothing to do."
|
||||
exit 0
|
||||
}
|
||||
|
||||
if (-not (Test-Path $VmrunPath -PathType Leaf)) {
|
||||
Write-Warning "[Cleanup] vmrun.exe not found at: $VmrunPath"
|
||||
Write-Warning " Skipping vmrun stop/delete — will attempt directory removal only."
|
||||
}
|
||||
|
||||
$cutoff = (Get-Date).AddHours(-$MaxAgeHours)
|
||||
$orphans = Get-ChildItem -Path $CloneBaseDir -Directory -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.LastWriteTime -lt $cutoff }
|
||||
|
||||
if (-not $orphans) {
|
||||
Write-Host "[Cleanup] No orphaned VMs found (threshold: $MaxAgeHours h)."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "[Cleanup] Found $($orphans.Count) orphaned VM director$(if ($orphans.Count -eq 1) {'y'} else {'ies'}) older than $MaxAgeHours h."
|
||||
|
||||
foreach ($dir in $orphans) {
|
||||
$vmx = Get-ChildItem -Path $dir.FullName -Filter '*.vmx' -ErrorAction SilentlyContinue |
|
||||
Select-Object -First 1
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($dir.FullName, "Destroy orphaned VM")) {
|
||||
Write-Host "[Cleanup] Processing: $($dir.FullName)"
|
||||
|
||||
if ($vmx -and (Test-Path $VmrunPath -PathType Leaf)) {
|
||||
# Hard stop — don't wait for graceful shutdown on an orphan
|
||||
& $VmrunPath -T ws stop $vmx.FullName hard 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
$deleteOut = & $VmrunPath -T ws deleteVM $vmx.FullName 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "[Cleanup] vmrun deleteVM failed (exit $LASTEXITCODE): $deleteOut"
|
||||
Write-Warning " Falling back to directory removal."
|
||||
}
|
||||
}
|
||||
elseif (-not $vmx) {
|
||||
Write-Warning "[Cleanup] No .vmx found in $($dir.FullName) — removing directory only."
|
||||
}
|
||||
|
||||
Remove-Item $dir.FullName -Recurse -Force -ErrorAction SilentlyContinue
|
||||
if (Test-Path $dir.FullName) {
|
||||
Write-Warning "[Cleanup] Could not fully remove: $($dir.FullName) — manual cleanup needed."
|
||||
}
|
||||
else {
|
||||
Write-Host "[Cleanup] Removed: $($dir.FullName)"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "[Cleanup] WhatIf: would destroy $($dir.FullName) (age: $([int]((Get-Date) - $dir.LastWriteTime).TotalHours) h)"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "[Cleanup] Done."
|
||||
@@ -93,7 +93,7 @@ try {
|
||||
# ── Copy logs if requested ────────────────────────────────────────────
|
||||
if ($IncludeLogs) {
|
||||
$logFiles = Invoke-Command -Session $session -ScriptBlock {
|
||||
Get-ChildItem -Path 'C:\CI\build' -Filter '*.log' -ErrorAction SilentlyContinue |
|
||||
Get-ChildItem -Path 'C:\CI\build' -Filter '*.log' -Recurse -ErrorAction SilentlyContinue |
|
||||
Select-Object -ExpandProperty FullName
|
||||
}
|
||||
|
||||
@@ -109,10 +109,11 @@ try {
|
||||
throw "Artifact was not found at expected host path after copy: $hostDestPath"
|
||||
}
|
||||
|
||||
$sizeKB = [math]::Round((Get-Item $hostDestPath).Length / 1KB, 1)
|
||||
if ($sizeKB -eq 0) {
|
||||
$fileItem = Get-Item $hostDestPath
|
||||
if ($fileItem.Length -eq 0) {
|
||||
throw "Artifact file exists but is empty: $hostDestPath"
|
||||
}
|
||||
$sizeKB = [math]::Round($fileItem.Length / 1KB, 1)
|
||||
|
||||
Write-Host "[Get-BuildArtifacts] Artifact collected: $hostDestPath ($sizeKB KB)"
|
||||
return $hostDestPath
|
||||
|
||||
@@ -190,11 +190,16 @@ $exitCode = 0
|
||||
|
||||
try {
|
||||
# ── Phase 1: Clone repo on HOST ───────────────────────────────────────
|
||||
# The build VM has no internet/LAN access (Host-Only network).
|
||||
# Source is cloned here on the host, then copied into the VM via WinRM.
|
||||
# Build VM is on VMnet8 NAT (internet OK for builds), but source is
|
||||
# still cloned here and copied via WinRM zip to avoid injecting a PAT
|
||||
# into the VM environment.
|
||||
Write-Host "`n[Phase 1/6] Cloning repository on host..."
|
||||
if (Test-Path $hostCloneDir) { Remove-Item $hostCloneDir -Recurse -Force }
|
||||
$gitArgs = @('clone', '--depth', '1', '--branch', $Branch)
|
||||
# Skip --depth 1 when a specific commit is requested: shallow clones
|
||||
# only contain HEAD, so checkout of an older commit will fail.
|
||||
$gitArgs = @('clone')
|
||||
if ([string]::IsNullOrWhiteSpace($Commit)) { $gitArgs += @('--depth', '1') }
|
||||
$gitArgs += @('--branch', $Branch)
|
||||
if ($Submodules) { $gitArgs += '--recurse-submodules' }
|
||||
$gitArgs += @($RepoUrl, $hostCloneDir)
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
@@ -94,15 +94,13 @@ try {
|
||||
# ── Ensure working directories exist on guest ────────────────────────
|
||||
Invoke-Command -Session $session -ScriptBlock {
|
||||
param($workDir, $outputDir)
|
||||
foreach ($dir in @($workDir, (Split-Path $outputDir -Parent))) {
|
||||
if (-not (Test-Path $dir)) {
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
}
|
||||
}
|
||||
# Clean previous build if present
|
||||
if (Test-Path $workDir) {
|
||||
Remove-Item $workDir -Recurse -Force
|
||||
# Ensure artifact output parent exists
|
||||
$outParent = Split-Path $outputDir -Parent
|
||||
if (-not (Test-Path $outParent)) {
|
||||
New-Item -ItemType Directory -Path $outParent -Force | Out-Null
|
||||
}
|
||||
# Clean and recreate build dir (ephemeral VM, but guard against partial prior run)
|
||||
if (Test-Path $workDir) { Remove-Item $workDir -Recurse -Force }
|
||||
New-Item -ItemType Directory -Path $workDir -Force | Out-Null
|
||||
} -ArgumentList $GuestWorkDir, $GuestArtifactZip
|
||||
|
||||
|
||||
@@ -75,15 +75,10 @@ while ((Get-Date) -lt $deadline) {
|
||||
$attempt++
|
||||
|
||||
# ── Phase 1: VM must be running ─────────────────────────────────────
|
||||
# vmrun has no getState; getGuestIPAddress fails with "not powered on"
|
||||
# when the VM is off, and succeeds (exit 0) when running.
|
||||
try {
|
||||
# vmrun has no getState; getGuestIPAddress exits non-zero when the VM
|
||||
# is not powered on. Native commands don't throw — check exit code only.
|
||||
& $VmrunPath getGuestIPAddress $VMPath 2>&1 | Out-Null
|
||||
$isRunning = ($LASTEXITCODE -eq 0)
|
||||
}
|
||||
catch {
|
||||
$isRunning = $false
|
||||
}
|
||||
|
||||
if (-not $isRunning) {
|
||||
if ($phase -ne 'vmrun-state') {
|
||||
@@ -116,7 +111,6 @@ while ((Get-Date) -lt $deadline) {
|
||||
try {
|
||||
Test-WSMan -ComputerName $IPAddress -ErrorAction Stop | Out-Null
|
||||
# Success — VM is ready
|
||||
$elapsed = ($deadline - (Get-Date)).Negate().TotalSeconds + $TimeoutSeconds
|
||||
Write-Host "[Wait-VMReady] VM ready after ~$([int]($attempt * $PollIntervalSeconds))s (attempt $attempt)."
|
||||
return
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
1. Shut down the VM
|
||||
2. Take snapshot: VM → Snapshot → Take Snapshot → name: BaseClean
|
||||
(VM stays on VMnet8 NAT — internet access is required for builds)
|
||||
3. Store credentials on host:
|
||||
3. Store credentials on host (use the same password chosen during provisioning):
|
||||
New-StoredCredential -Target 'BuildVMGuest' -UserName 'ci_build' `
|
||||
-Password 'CIBuild!ChangeMe2026' -Persist LocalMachine
|
||||
-Password '<your-build-password>' -Persist LocalMachine
|
||||
|
||||
.PARAMETER VMIPAddress
|
||||
IP address of the template VM while it is on NAT (VMnet8).
|
||||
@@ -69,7 +69,7 @@ param(
|
||||
|
||||
[string] $AdminPassword = '',
|
||||
|
||||
[string] $BuildPassword = 'CIBuild!ChangeMe2026',
|
||||
[string] $BuildPassword = '',
|
||||
|
||||
[string] $DotNetSdkVersion = '10.0',
|
||||
|
||||
@@ -81,6 +81,15 @@ $ErrorActionPreference = 'Stop'
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
# Prompt for BuildPassword if not supplied — never use a hardcoded default.
|
||||
if ($BuildPassword -eq '') {
|
||||
Write-Host "[Prepare] No -BuildPassword supplied. Enter password for the CI build account inside the VM:"
|
||||
$secPwd = Read-Host -Prompt " BuildPassword" -AsSecureString
|
||||
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secPwd)
|
||||
$BuildPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
|
||||
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
|
||||
}
|
||||
|
||||
# ── Build PSCredential ────────────────────────────────────────────────────────
|
||||
if ($AdminPassword -eq '') {
|
||||
Write-Host "[Prepare] Enter administrator credentials for the template VM ($VMIPAddress):"
|
||||
@@ -94,15 +103,19 @@ else {
|
||||
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
||||
|
||||
# ── Configure HOST-side WinRM client (required for Basic auth over HTTP) ──────
|
||||
# These settings apply to this machine (the host), not the VM.
|
||||
# AllowUnencrypted is needed for HTTP/5985 Basic auth in a lab environment.
|
||||
Write-Host "[Prepare] Configuring host WinRM client for unencrypted Basic auth..."
|
||||
# AllowUnencrypted is needed for HTTP/5985 Basic auth. We save the prior value
|
||||
# and restore it in the finally block so this script doesn't permanently change
|
||||
# the host security posture.
|
||||
Write-Host "[Prepare] Configuring host WinRM client for unencrypted Basic auth (will be restored on exit)..."
|
||||
$prevAllowUnencrypted = $null
|
||||
$prevTrustedHosts = $null
|
||||
try {
|
||||
$prevAllowUnencrypted = (Get-Item WSMan:\localhost\Client\AllowUnencrypted -ErrorAction Stop).Value
|
||||
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts -ErrorAction Stop).Value
|
||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force -ErrorAction Stop
|
||||
# Add VM IP to TrustedHosts if not already present
|
||||
$currentTrusted = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||
if ($currentTrusted -ne '*' -and $currentTrusted -notlike "*$VMIPAddress*") {
|
||||
$newTrusted = if ($currentTrusted -eq '') { $VMIPAddress } else { "$currentTrusted,$VMIPAddress" }
|
||||
if ($prevTrustedHosts -ne '*' -and $prevTrustedHosts -notlike "*$VMIPAddress*") {
|
||||
$newTrusted = if ($prevTrustedHosts -eq '') { $VMIPAddress } else { "$prevTrustedHosts,$VMIPAddress" }
|
||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $newTrusted -Force -ErrorAction Stop
|
||||
}
|
||||
Write-Host "[Prepare] Host WinRM client configured."
|
||||
@@ -230,7 +243,7 @@ try {
|
||||
Write-Host " 3. Store CI credentials on this HOST:"
|
||||
Write-Host " (run in elevated PowerShell with CredentialManager module)"
|
||||
Write-Host " New-StoredCredential -Target 'BuildVMGuest' ``"
|
||||
Write-Host " -UserName 'ci_build' -Password '$BuildPassword' ``"
|
||||
Write-Host " -UserName 'ci_build' -Password '<your-build-password>' ``"
|
||||
Write-Host " -Persist LocalMachine"
|
||||
Write-Host ""
|
||||
Write-Host " 4. Verify runner online in Gitea: http://10.10.20.11:3100"
|
||||
@@ -239,4 +252,13 @@ try {
|
||||
}
|
||||
finally {
|
||||
Remove-PSSession $session -ErrorAction SilentlyContinue
|
||||
|
||||
# Restore host WinRM client settings to their original values
|
||||
if ($null -ne $prevAllowUnencrypted) {
|
||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $prevAllowUnencrypted -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($null -ne $prevTrustedHosts) {
|
||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $prevTrustedHosts -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Write-Host "[Prepare] Host WinRM client settings restored."
|
||||
}
|
||||
|
||||
@@ -22,18 +22,17 @@
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ NETWORK REQUIREMENT DURING SETUP ║
|
||||
║ The VM must have internet access while this script runs so it can ║
|
||||
║ download Windows Updates and tool installers. Configure the VM NIC ║
|
||||
║ to VMnet8 (NAT) before running setup, then switch back to VMnet11 ║
|
||||
║ (Host-Only) BEFORE taking the BaseClean snapshot. ║
|
||||
║ download Windows Updates and tool installers. The VM NIC must be ║
|
||||
║ set to VMnet8 (NAT) — keep it on NAT permanently (builds use NAT ║
|
||||
║ for pip/nuget downloads at runtime). ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
After this script completes:
|
||||
1. Switch VM NIC from VMnet8 (NAT) → VMnet11 (Host-Only) in VMware
|
||||
Workstation: VM → Settings → Network Adapter → Custom: VMnet11
|
||||
2. Shut down the VM (Start → Shut down)
|
||||
3. In VMware Workstation: VM → Snapshot → Take Snapshot
|
||||
1. Shut down the VM (Start → Shut down)
|
||||
The VM stays on VMnet8 (NAT) — internet access is required for builds.
|
||||
2. In VMware Workstation: VM → Snapshot → Take Snapshot
|
||||
Name it exactly: BaseClean
|
||||
4. Power off the VM and leave it powered off permanently
|
||||
3. Power off the VM and leave it powered off permanently
|
||||
|
||||
.PARAMETER SkipWindowsUpdate
|
||||
Skip the Windows Update step (useful if updates were already applied).
|
||||
@@ -50,9 +49,10 @@ param(
|
||||
[string] $BuildUsername = 'ci_build',
|
||||
|
||||
# Password for the build account.
|
||||
# IMPORTANT: Change this to a strong password and store it in
|
||||
# Windows Credential Manager on the HOST as target "BuildVMGuest".
|
||||
[string] $BuildPassword = 'CIBuild!ChangeMe2026',
|
||||
# Must be supplied by the caller (Prepare-TemplateSetup.ps1 prompts interactively).
|
||||
# Store the same password in Windows Credential Manager on the HOST as target "BuildVMGuest".
|
||||
[Parameter(Mandatory)]
|
||||
[string] $BuildPassword,
|
||||
|
||||
# .NET SDK channel to install (should match the host SDK version)
|
||||
[string] $DotNetSdkVersion = '10.0',
|
||||
@@ -187,9 +187,7 @@ winrm set winrm/config/service '@{AllowUnencrypted="true"}' | Out-Null
|
||||
winrm set winrm/config/service/auth '@{Basic="true"}' | Out-Null
|
||||
|
||||
# Increase shell memory and timeout limits for long builds
|
||||
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="2048"}' | Out-Null
|
||||
winrm set winrm/config/winrs '@{MaxProcessesPerShell="25"}' | Out-Null
|
||||
|
||||
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048 -Force 3>$null
|
||||
Set-Item WSMan:\localhost\Shell\IdleTimeOut 7200000 -Force 3>$null # 2 hours
|
||||
|
||||
@@ -202,9 +200,10 @@ Write-Host "WinRM configured."
|
||||
# ── Step 2: Firewall ──────────────────────────────────────────────────────────
|
||||
Write-Step "Disabling Windows Firewall (all profiles) — isolated lab VM"
|
||||
|
||||
# Disable all firewall profiles entirely. This VM lives on a Host-Only network
|
||||
# (VMnet11) with no external exposure, so full disable is acceptable and avoids
|
||||
# any rule-order or profile-classification issues that would block WinRM/ICMP.
|
||||
# Disable all firewall profiles entirely. This VM lives on VMnet8 NAT behind
|
||||
# the VMware NAT gateway with no inbound exposure from outside the host, so
|
||||
# full disable is acceptable and avoids profile-classification issues blocking
|
||||
# WinRM/ICMP from the host.
|
||||
Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled False
|
||||
Write-Host "Windows Firewall disabled on all profiles."
|
||||
|
||||
@@ -216,11 +215,16 @@ if ($existingUser) {
|
||||
Write-Host "User '$BuildUsername' already exists — skipping creation."
|
||||
}
|
||||
else {
|
||||
# Use net user instead of New-LocalUser: the LocalAccounts cmdlets have a known
|
||||
# bug where SecureString deserialized over WinRM causes InvalidPasswordException.
|
||||
$netResult = & net user $BuildUsername $BuildPassword /add /y 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to create user '$BuildUsername': $netResult"
|
||||
# Convert to SecureString locally (inside the VM session) to avoid exposing
|
||||
# the password as a process argument visible in audit log event 4688.
|
||||
$secPwd = ConvertTo-SecureString $BuildPassword -AsPlainText -Force
|
||||
try {
|
||||
New-LocalUser -Name $BuildUsername -Password $secPwd `
|
||||
-PasswordNeverExpires -Description 'CI build account' `
|
||||
-ErrorAction Stop | Out-Null
|
||||
}
|
||||
catch {
|
||||
throw "Failed to create user '$BuildUsername': $_"
|
||||
}
|
||||
Write-Host "User '$BuildUsername' created."
|
||||
}
|
||||
@@ -639,21 +643,17 @@ Write-Host "============================================================" -Foreg
|
||||
Write-Host ""
|
||||
Write-Host " MANDATORY NEXT STEPS:"
|
||||
Write-Host ""
|
||||
Write-Host " 1. SWITCH NIC TO HOST-ONLY (critical before snapshot!):"
|
||||
Write-Host " VMware Workstation -> VM -> Settings -> Network Adapter"
|
||||
Write-Host " Change from: VMnet8 (NAT)"
|
||||
Write-Host " Change to: Custom: VMnet11"
|
||||
Write-Host " 1. Shut down this VM: Start -> Shut down"
|
||||
Write-Host " (Keep NIC on VMnet8 NAT — internet is required for builds)"
|
||||
Write-Host ""
|
||||
Write-Host " 2. Shut down this VM: Start -> Shut down"
|
||||
Write-Host ""
|
||||
Write-Host " 3. Take snapshot in VMware Workstation:"
|
||||
Write-Host " 2. Take snapshot in VMware Workstation:"
|
||||
Write-Host " VM -> Snapshot -> Take Snapshot"
|
||||
Write-Host " Name it EXACTLY: BaseClean"
|
||||
Write-Host ""
|
||||
Write-Host " 4. Leave VM powered off permanently."
|
||||
Write-Host " 3. Leave VM powered off permanently."
|
||||
Write-Host ""
|
||||
Write-Host " 5. On the HOST - store credentials in Windows Credential Manager:"
|
||||
Write-Host " 4. On the HOST - store credentials in Windows Credential Manager:"
|
||||
Write-Host " New-StoredCredential -Target 'BuildVMGuest' -UserName '$BuildUsername' ``"
|
||||
Write-Host " -Password '$BuildPassword' -Persist LocalMachine"
|
||||
Write-Host " -Password '<your-build-password>' -Persist LocalMachine"
|
||||
Write-Host " (Run in an elevated PowerShell with CredentialManager module)"
|
||||
Write-Host ""
|
||||
|
||||
Reference in New Issue
Block a user