feat(template): trim build-VM footprint (services, features, Edge, apps)
An ephemeral headless build VM carries a lot of dead weight. Split the trim by cost so heavy servicing never runs under the live explorer shell (which crash-loops during CBS churn at first logon): post-install.win.ps1 (light, runs under explorer at first logon): - disable waste services: audio, camera, CDP/sync, push (Wpn), telemetry (DiagTrack/InventorySvc), search index (WSearch), DPS/WDI, MSDTC, TrkWks, Themes, MapsBroker, RetailDemo, PcaSvc, WSAIFabricSvc; BITS -> on-demand - disable per-user service templates (CDPUserSvc/OneSyncSvc/WpnUserService/...) so no per-session clone spawns - remove in-box apps: FeedbackHub/GetHelp/Getstarted/OfficeHub (appx + provisioned), Snip & Sketch, Paint, RDP client (mstsc) — all best-effort prepare-win (heavy, runs under cmd shell, before the final reboot so the following DISM cleanup compacts the freed component store): - Uninstall-WindowsFeature WindowsAdminCenterSetup, XPS-Viewer, Wireless-Networking, Windows-Defender (-NoRestart) - Remove-WindowsCapability AzureArcSetup (payload; auto-start already off) - uninstall Edge + EdgeWebView2 and disable their leftover updater services - gated by --skip-trim Server Manager auto-launch was already disabled (task + reg + GPO policy); its exe is part of the OS and cannot be uninstalled. Defender was already runtime-off; removing the feature drops the AV stack entirely.
This commit is contained in:
@@ -523,6 +523,8 @@ def _run_with_heartbeat(
|
||||
@click.option("--skip-windows-update", is_flag=True, help="Pass -SkipWindowsUpdate to toolchain script.")
|
||||
@click.option("--skip-tier2", is_flag=True, help="Pass -SkipTier2 to toolchain script.")
|
||||
@click.option("--skip-cleanup", is_flag=True, help="Pass -SkipCleanup to toolchain script.")
|
||||
@click.option("--skip-trim", is_flag=True,
|
||||
help="Skip removing unused features/roles/Edge before the final reboot.")
|
||||
@click.option("--winrm-timeout", default=300, show_default=True, help="Seconds to wait for WinRM.")
|
||||
@click.option("--auth", default="basic", show_default=True,
|
||||
type=click.Choice(["basic", "ntlm", "negotiate"]),
|
||||
@@ -558,6 +560,7 @@ def template_prepare_win(
|
||||
skip_windows_update: bool,
|
||||
skip_tier2: bool,
|
||||
skip_cleanup: bool,
|
||||
skip_trim: bool,
|
||||
winrm_timeout: int,
|
||||
auth: str,
|
||||
store_credential: bool,
|
||||
@@ -832,6 +835,61 @@ Register-ScheduledTask -TaskName 'CI-StaticIp' `
|
||||
f"[prepare-win] Validation script error:\n{val_result.stderr}", err=True
|
||||
)
|
||||
|
||||
# Heavy trim BEFORE disk cleanup: remove roles/capabilities/Edge that a
|
||||
# build VM never uses. This is CBS/installer-heavy, so it runs here under
|
||||
# the cmd shell (never under explorer at first logon — that is what the
|
||||
# post-install script deliberately leaves out). -NoRestart defers the
|
||||
# single reboot to the final one below; the DISM cleanup that follows
|
||||
# then compacts the component store the removals leave behind.
|
||||
if not skip_trim:
|
||||
click.echo("[prepare-win] Trimming unused features / Edge (heavy) ...")
|
||||
trim_invoke = (
|
||||
"$ErrorActionPreference = 'Continue'\n"
|
||||
# Server roles/optional features no build needs.
|
||||
"foreach ($f in 'WindowsAdminCenterSetup','XPS-Viewer',"
|
||||
"'Wireless-Networking','Windows-Defender') {\n"
|
||||
" $wf = Get-WindowsFeature -Name $f -ErrorAction SilentlyContinue\n"
|
||||
" if ($wf -and $wf.Installed) {\n"
|
||||
" Uninstall-WindowsFeature -Name $f -NoRestart "
|
||||
"-ErrorAction SilentlyContinue | Out-Null\n"
|
||||
" \"trimmed feature: $f\"\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
# Azure Arc Setup capability (the auto-start was already disabled
|
||||
# in post-install; this removes the payload entirely).
|
||||
"Get-WindowsCapability -Online -ErrorAction SilentlyContinue |\n"
|
||||
" Where-Object { $_.Name -like 'AzureArcSetup*' -and "
|
||||
"$_.State -eq 'Installed' } |\n"
|
||||
" ForEach-Object { Remove-WindowsCapability -Online -Name $_.Name "
|
||||
"-ErrorAction SilentlyContinue | Out-Null; \"trimmed capability: $($_.Name)\" }\n"
|
||||
# Edge + EdgeWebView2 (and their background updater services/tasks).
|
||||
"foreach ($e in @(\n"
|
||||
" @{ Glob='C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\*\\Installer\\setup.exe';"
|
||||
" Args=@('--uninstall','--msedge','--channel=stable','--system-level','--force-uninstall') },\n"
|
||||
" @{ Glob='C:\\Program Files (x86)\\Microsoft\\EdgeWebView\\Application\\*\\Installer\\setup.exe';"
|
||||
" Args=@('--uninstall','--msedgewebview','--system-level','--force-uninstall') }\n"
|
||||
")) {\n"
|
||||
" $setup = Get-ChildItem -Path $e.Glob -ErrorAction SilentlyContinue |\n"
|
||||
" Sort-Object FullName -Descending | Select-Object -First 1\n"
|
||||
" if ($setup) {\n"
|
||||
" & $setup.FullName @($e.Args) 2>&1 | Out-Null\n"
|
||||
" \"trimmed: $($setup.FullName)\"\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
# Edge auto-update services left behind if the uninstall is partial.
|
||||
"foreach ($s in 'edgeupdate','edgeupdatem','MicrosoftEdgeElevationService') {\n"
|
||||
" if (Get-Service -Name $s -ErrorAction SilentlyContinue) {\n"
|
||||
" sc.exe stop $s 2>&1 | Out-Null; sc.exe config $s start= disabled 2>&1 | Out-Null\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
'"CI_EXITCODE:0"\n'
|
||||
)
|
||||
trim_result = _run_with_heartbeat(transport, trim_invoke, check=False)
|
||||
display = _strip_exit_marker(trim_result.stdout).strip()
|
||||
if display:
|
||||
click.echo(display)
|
||||
click.echo("[prepare-win] Feature / Edge trim complete.")
|
||||
|
||||
# Disk cleanup BEFORE the final reboot. Heavy servicing ops (DISM
|
||||
# StartComponentCleanup, WU finalization) destabilise the running shell
|
||||
# session — explorer crash-loops until the next reboot — so all such work
|
||||
|
||||
@@ -258,6 +258,51 @@ reg add 'HKLM\SOFTWARE\Microsoft\AzureArc' /v OnboardingState /t REG_DWORD /d 4
|
||||
reg delete 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' /v AzureArcSetup /f 2>&1 | Out-Null
|
||||
L 'Azure Arc auto-start disabled'
|
||||
|
||||
# ── Trim waste services (light: no CBS, safe under explorer) ──────────────
|
||||
# An ephemeral headless build VM needs no audio/camera/sync/push/telemetry/
|
||||
# search-index/DTC/link-tracking. All reversible via Set-Service. Heavy feature
|
||||
# and Edge removal is done in prepare-win (under the cmd shell) to avoid
|
||||
# destabilising the live explorer shell at first logon.
|
||||
foreach ($svc in @(
|
||||
'Audiosrv','AudioEndpointBuilder','WpnService','camsvc','CDPSvc','DPS',
|
||||
'TrkWks','MSDTC','WSAIFabricSvc','InventorySvc','DiagTrack','Themes','WSearch',
|
||||
'MapsBroker','RetailDemo','PcaSvc','WdiServiceHost','WdiSystemHost'
|
||||
)) {
|
||||
if (Get-Service -Name $svc -ErrorAction SilentlyContinue) {
|
||||
sc.exe stop $svc 2>&1 | Out-Null
|
||||
sc.exe config $svc start= disabled 2>&1 | Out-Null
|
||||
}
|
||||
}
|
||||
# Per-user template services (the WpnUserService_xxxx / CDPUserSvc_xxxx clones
|
||||
# spawn from these templates) — disable the template so no per-session clone starts.
|
||||
foreach ($base in @(
|
||||
'CDPUserSvc','OneSyncSvc','WpnUserService','cbdhsvc',
|
||||
'PimIndexMaintenanceSvc','UnistoreSvc','UserDataSvc','MessagingService'
|
||||
)) {
|
||||
$k = "HKLM\SYSTEM\CurrentControlSet\Services\$base"
|
||||
reg query $k 1>$null 2>$null
|
||||
if ($LASTEXITCODE -eq 0) { reg add $k /v Start /t REG_DWORD /d 4 /f 2>&1 | Out-Null }
|
||||
}
|
||||
# BITS is needed on demand by installers/WU; just stop it auto-running.
|
||||
sc.exe config BITS start= demand 2>&1 | Out-Null
|
||||
L 'Waste services disabled (audio/camera/sync/push/telemetry/search/DTC); BITS on-demand'
|
||||
|
||||
# ── Strip in-box apps that the build VM never uses (light, per-app stubs) ──
|
||||
# Server 2025 Desktop may or may not ship these; each is best-effort.
|
||||
foreach ($pkgLike in 'Microsoft.WindowsFeedbackHub','Microsoft.GetHelp','Microsoft.Getstarted','Microsoft.MicrosoftOfficeHub') {
|
||||
Get-AppxPackage -AllUsers | Where-Object { $_.Name -like "$pkgLike*" } |
|
||||
Remove-AppxPackage -AllUsers -ErrorAction SilentlyContinue
|
||||
Get-AppxProvisionedPackage -Online |
|
||||
Where-Object { $_.DisplayName -like $pkgLike } |
|
||||
ForEach-Object { Remove-AppxProvisionedPackage -Online -PackageName $_.PackageName -ErrorAction SilentlyContinue } | Out-Null
|
||||
}
|
||||
# Optional-feature "apps" exposing a /uninstall stub (Snip & Sketch, Paint, RDP client).
|
||||
foreach ($app in 'SnippingTool','mspaint','mstsc') {
|
||||
$exe = Join-Path $env:WINDIR "System32\$app.exe"
|
||||
if (Test-Path $exe) { & $exe /uninstall 2>&1 | Out-Null }
|
||||
}
|
||||
L 'In-box apps trimmed (FeedbackHub/GetHelp/Getstarted/OfficeHub, Snip/Paint/mstsc)'
|
||||
|
||||
# ── Explorer UX: show file extensions + open to This PC ──────────────────
|
||||
reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' /v HideFileExt /t REG_DWORD /d 0 /f | Out-Null
|
||||
reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' /v LaunchTo /t REG_DWORD /d 1 /f | Out-Null
|
||||
|
||||
Reference in New Issue
Block a user