From c4d42aa54cffcef04c6ce7a62dd9c28d2568a535 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 21 Sep 2026 21:12:41 -0700 Subject: [PATCH] separated church and state --- .gitignore | 1 + build-isos.sh | 53 ++++++ oobe-desktop.ps1 | 153 +++++++++++++++ oobe-rm-ai.ps1 | 63 ------- oobe-ltsc.ps1 => oobe-vms.ps1 | 9 +- unattend/desktop-PROMPT-DISK.xml | 311 +++++++++++++++++++++++++++++++ unattend/vms-WIPE-DISK.xml | 38 ++-- 7 files changed, 548 insertions(+), 80 deletions(-) create mode 100644 .gitignore create mode 100755 build-isos.sh create mode 100644 oobe-desktop.ps1 delete mode 100644 oobe-rm-ai.ps1 rename oobe-ltsc.ps1 => oobe-vms.ps1 (97%) create mode 100644 unattend/desktop-PROMPT-DISK.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/build-isos.sh b/build-isos.sh new file mode 100755 index 0000000..04943fd --- /dev/null +++ b/build-isos.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Builds the answer-file ISOs into build/: +# virtio-vms.iso latest virtio-win drivers + unattend/vms-WIPE-DISK.xml (as autounattend.xml) + oobe-vms.ps1 +# desktop.iso unattend/desktop-PROMPT-DISK.xml (as autounattend.xml) + oobe-desktop.ps1 +# The virtio-win ISO is only downloaded again when a new release is published. +# Requires: curl, xorriso +set -euo pipefail +cd "$(dirname "$0")" + +VIRTIO_LATEST=https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/virtio-win.iso +BUILD=build +STAGE=$BUILD/stage +# Joliet names so Windows sees oobe-*.ps1 and autounattend.xml unmangled (volume labels max 16 chars) +JOLIET=(-joliet on -compliance joliet_long_names) + +# Root of the ISO as Windows reads it (Joliet, not Rock Ridge) +show_root() { + echo "== $1" + xorriso -report_about WARNING -read_fs norock -indev "$1" -lsl / 2>/dev/null | grep -E -i 'autounattend|oobe-|guest-tools' | awk '{print " ", $NF}' +} + +mkdir -p "$BUILD" + +# Resolve "latest" to the versioned file; Fedora's redirects drop to plain http, so force https +VIRTIO_URL=$(curl -fsSIL -o /dev/null -w '%{url_effective}' "$VIRTIO_LATEST" | sed 's#^http://#https://#') +VIRTIO_ISO=$BUILD/$(basename "$VIRTIO_URL") +if [[ -f $VIRTIO_ISO ]]; then + echo "Using cached $VIRTIO_ISO" +else + echo "Downloading $VIRTIO_URL" + curl -fL --retry 3 -o "$VIRTIO_ISO.part" "$VIRTIO_URL" + mv "$VIRTIO_ISO.part" "$VIRTIO_ISO" +fi + +# a. VMs: virtio-win + VM answer file + oobe-vms.ps1 +# Files are added to a copy of the original image rather than extracting and repacking it: +# virtio-win stores identical drivers once, and repacking would nearly double its size. +rm -f "$BUILD/virtio-vms.iso" +xorriso -report_about WARNING -indev "$VIRTIO_ISO" -outdev "$BUILD/virtio-vms.iso" "${JOLIET[@]}" -volid VIRTIO_VMS \ + -map unattend/vms-WIPE-DISK.xml /autounattend.xml \ + -map oobe-vms.ps1 /oobe-vms.ps1 \ + -commit + +# b. Desktops: desktop answer file + oobe-desktop.ps1, no virtio +rm -rf "$STAGE" "$BUILD/desktop.iso" +mkdir -p "$STAGE" +cp unattend/desktop-PROMPT-DISK.xml "$STAGE/autounattend.xml" +cp oobe-desktop.ps1 "$STAGE/" +xorriso -report_about WARNING -outdev "$BUILD/desktop.iso" "${JOLIET[@]}" -volid OOBE_DESKTOP -map "$STAGE" / -commit +rm -rf "$STAGE" +show_root "$BUILD/virtio-vms.iso" +show_root "$BUILD/desktop.iso" +ls -lh "$BUILD"/*.iso diff --git a/oobe-desktop.ps1 b/oobe-desktop.ps1 new file mode 100644 index 0000000..9d76589 --- /dev/null +++ b/oobe-desktop.ps1 @@ -0,0 +1,153 @@ +# OOBE for Windows LTSC desktops and laptops. Runs fully unattended (no prompts). +# Pulled by autounattend.xml at first logon, or run by hand: +# irm https://url.isworking.fyi/oobe-desktop | iex +# Log: C:\ProgramData\OOBE\oobe-desktop.log + +# Check if running with elevated privileges +if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Host "Please run this script as an administrator." + Exit 1 +} + +$MeshAgentUrl = "https://rmm.iamchrisama.com/meshagents?id=4&meshid=zxl@U2zM95zh9ZNqah@9mUEjCJ3ptGOE6s5cjsGniacVU1fjRXtVKCKlKJN4aJQW&installflags=0" + +$ErrorActionPreference = 'Continue' +# The progress bar makes Invoke-WebRequest extremely slow on Windows PowerShell 5.1 +$ProgressPreference = 'SilentlyContinue' +# Older LTSC builds don't enable TLS 1.2 by default, which breaks GitHub downloads +[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + +$WorkDir = Join-Path $env:ProgramData 'OOBE' +New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null +Start-Transcript -Path (Join-Path $WorkDir 'oobe-desktop.log') -Append | Out-Null + +function Save-Download($Uri, $OutFile) { + for ($Attempt = 1; $Attempt -le 3; $Attempt++) { + try { + Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing + return $true + } catch { + Write-Warning "Download attempt $Attempt of $Uri failed: $_" + Start-Sleep -Seconds 10 + } + } + return $false +} + +# Wait for internet access, the network may not be up yet during first logon +function Wait-Network { + $MeshHost = ([uri]$MeshAgentUrl).Host + $Deadline = (Get-Date).AddMinutes(5) + while ((Get-Date) -lt $Deadline) { + $Client = New-Object System.Net.Sockets.TcpClient + try { if ($Client.ConnectAsync($MeshHost, 443).Wait(5000)) { return } } catch {} finally { $Client.Close() } + Write-Output "Waiting for network..." + Start-Sleep -Seconds 5 + } + Write-Warning "No network after 5 minutes, continuing anyway." +} + +# Balanced plan: dim the display after 5 minutes, sleep after 30 minutes. +# Turning the display off stays at the plan default (10 minutes plugged in). +$Balanced = '381b4222-f694-41f0-9685-ff5bb260df2e' +powercfg /setactive $Balanced + +# "Dim display after" (seconds). Only has a visible effect on screens Windows can +# control the brightness of (laptops, all-in-ones); external monitors ignore it. +$DisplaySubgroup = '7516b95f-f776-4464-8c53-06167f40cc99' +$DimTimeout = '17aaa29b-8b43-4b94-aafe-35f64daaf1ee' +powercfg /setacvalueindex $Balanced $DisplaySubgroup $DimTimeout 300 +powercfg /setdcvalueindex $Balanced $DisplaySubgroup $DimTimeout 300 + +powercfg /change standby-timeout-ac 30 +powercfg /change standby-timeout-dc 30 +powercfg /setactive $Balanced + +# Treat the network as Private and suppress the "allow this PC to be discoverable" prompt +Wait-Network +New-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff' -Force | Out-Null +Get-NetConnectionProfile | Where-Object NetworkCategory -ne 'DomainAuthenticated' | Set-NetConnectionProfile -NetworkCategory Private + +# Enable ping (ICMP Echo) requests +if (-not (Get-NetFirewallRule -Name 'OOBE-ICMPv4-In' -ErrorAction SilentlyContinue)) { + New-NetFirewallRule -Name 'OOBE-ICMPv4-In' -DisplayName "Allow ICMP Echo Request" -Protocol ICMPv4 -IcmpType 8 -Enabled True | Out-Null +} + +# Allow Remote Desktop (firewall groups are referenced by resource ID so this works on non-English installs) +Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0 +Enable-NetFirewallRule -Group '@FirewallAPI.dll,-28752' + +# Enable c$ (AutoShareWks must be 1; 0 disables the admin shares) +reg add "HKLM\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters" /f /v AutoShareWks /t REG_DWORD /d 1 +reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v "LocalAccountTokenFilterPolicy" /t REG_DWORD /d 1 /f +Enable-NetFirewallRule -Group '@FirewallAPI.dll,-28502' + +# Disable UAC prompt +Set-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows\CurrentVersion\policies\system' -Name "ConsentPromptBehaviorAdmin" -Value 0 + +# Dark Mode and no mouse acceleration, for the current user and the default profile (future users) +function Set-UserPreferences($Hive) { + reg add "$Hive\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" /v AppsUseLightTheme /t REG_DWORD /d 0 /f | Out-Null + reg add "$Hive\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" /v SystemUsesLightTheme /t REG_DWORD /d 0 /f | Out-Null + # "Enhance pointer precision" off + reg add "$Hive\Control Panel\Mouse" /v MouseSpeed /t REG_SZ /d 0 /f | Out-Null + reg add "$Hive\Control Panel\Mouse" /v MouseThreshold1 /t REG_SZ /d 0 /f | Out-Null + reg add "$Hive\Control Panel\Mouse" /v MouseThreshold2 /t REG_SZ /d 0 /f | Out-Null +} +Set-UserPreferences 'HKCU' +$DefaultProfile = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList').Default +reg load 'HKU\OOBEDefault' "$DefaultProfile\NTUSER.DAT" | Out-Null +if ($LASTEXITCODE -eq 0) { + Set-UserPreferences 'HKU\OOBEDefault' + [gc]::Collect() + reg unload 'HKU\OOBEDefault' | Out-Null +} + +# Install MeshCentral agent silently as a service +if (Get-Service -Name 'Mesh Agent' -ErrorAction SilentlyContinue) { + Write-Output "Mesh Agent is already installed." +} else { + $MeshInstaller = Join-Path $WorkDir 'meshagent.exe' + if (Save-Download $MeshAgentUrl $MeshInstaller) { + # -fullinstall installs and starts the service without showing the install dialog + Start-Process -FilePath $MeshInstaller -ArgumentList '-fullinstall' -WindowStyle Hidden -Wait + } + if (-not (Get-Service -Name 'Mesh Agent' -ErrorAction SilentlyContinue)) { + Write-Warning "Mesh Agent service was not found after install." + } +} + +# Install OpenSSH +foreach ($Capability in 'OpenSSH.Client~~~~0.0.1.0', 'OpenSSH.Server~~~~0.0.1.0') { + if ((Get-WindowsCapability -Online -Name $Capability).State -ne 'Installed') { + Add-WindowsCapability -Online -Name $Capability | Out-Null + } +} + +Set-Service -Name sshd -StartupType 'Automatic' +Start-Service sshd + +# Use PowerShell instead of cmd.exe for SSH sessions +if (Test-Path 'HKLM:\SOFTWARE\OpenSSH') { + New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force | Out-Null +} + +if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) { + Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..." + New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 +} else { + Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists." +} + +# Install Winget (LTSC ships without the Store / App Installer) +# Run in a child process: the installer script calls exit, which would end this script too +$WingetInstaller = Join-Path $WorkDir 'winget-install.ps1' +if (Save-Download 'https://github.com/asheroto/winget-install/releases/latest/download/winget-install.ps1' $WingetInstaller) { + & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $WingetInstaller -Force +} + +# Restart to finish OpenSSH and driver installation. +# Delayed so the script can exit cleanly and the log is flushed before Windows goes down. +Write-Output "OOBE complete, restarting in 10 seconds." +Stop-Transcript | Out-Null +shutdown.exe /r /t 10 /d p:4:1 /c "OOBE complete, restarting to finish installation." diff --git a/oobe-rm-ai.ps1 b/oobe-rm-ai.ps1 deleted file mode 100644 index 6123dc2..0000000 --- a/oobe-rm-ai.ps1 +++ /dev/null @@ -1,63 +0,0 @@ -# Check if running with elevated privileges -if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { - Write-Host "Please run this script as an administrator." - Exit -} - -# Enable ping (ICMP Echo) requests -New-NetFirewallRule -DisplayName "Allow ICMP Echo Request" -Protocol ICMPv4 -IcmpType 8 -Enabled True - -# Turn on Dark Mode -New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize -Name AppsUseLightTheme -Value 0 -PropertyType DWORD -Force - -# Disable Mouse Acceleration -Set-ItemProperty -Path "HKCU:\Control Panel\Mouse" -Name MouseSensitivity -Value 0 - -# Allow Remote Desktop -Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0 -Enable-NetFirewallRule -DisplayGroup "Remote Desktop" - -# Enable c$ -reg add "HKLM\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters" /f /v AutoShareWks /t REG_DWORD /d 0 -reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v "LocalAccountTokenFilterPolicy" /t REG_DWORD /d 1 /f - -# Disable UAC prompt -Set-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows\CurrentVersion\policies\system' -Name "ConsentPromptBehaviorAdmin" -Value 0 - -# Download and run application -$DownloadPath = "$env:TEMP\app.exe" -Invoke-WebRequest -Uri "https://rmm.iamchrisama.com/meshagents?id=4&meshid=zxl@U2zM95zh9ZNqah@9mUEjCJ3ptGOE6s5cjsGniacVU1fjRXtVKCKlKJN4aJQW&installflags=0" -OutFile $DownloadPath -Start-Process -FilePath $DownloadPath -Wait - -# Remove Winows AI -& ([scriptblock]::Create((irm "https://raw.githubusercontent.com/zoicware/RemoveWindowsAI/main/RemoveWindowsAi.ps1"))) -nonInteractive -AllOptions - -# Run commands in new PowerShell instance -# Start-Process powershell.exe -ArgumentList "-NoProfile -Command {irm https://massgrave.dev/get | iex}" -# Start-Process powershell.exe -ArgumentList "-NoProfile -Command {irm https://christitus.com/win | iex}" - -# Disable sleep and enable high performance mode, enable hibernation, and display black after 30 minutes -powercfg -change -standby-timeout-ac 0 -powercfg -setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c -powercfg /hibernate on -powercfg -change -monitor-timeout-ac 30 - -# Install Winget -#Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -irm https://github.com/asheroto/winget-install/releases/latest/download/winget-install.ps1 | iex - -# Install OpenSSH -Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*' -Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0 -Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 - -Start-Service sshd - -Set-Service -Name sshd -StartupType 'Automatic' - -if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) { - Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..." - New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 -} else { - Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists." -} diff --git a/oobe-ltsc.ps1 b/oobe-vms.ps1 similarity index 97% rename from oobe-ltsc.ps1 rename to oobe-vms.ps1 index cf4ce1d..7d2ca66 100644 --- a/oobe-ltsc.ps1 +++ b/oobe-vms.ps1 @@ -1,6 +1,7 @@ -# OOBE for Windows LTSC virtual machines. Runs fully unattended (no prompts), e.g. from autounattend.xml: -# powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Windows\Setup\Scripts\oobe-ltsc.ps1 -# Log: C:\ProgramData\OOBE\oobe-ltsc.log +# OOBE for Windows LTSC virtual machines (Unraid / KVM with VirtIO). Runs fully unattended (no prompts). +# Pulled by autounattend.xml at first logon, or run by hand: +# irm https://url.isworking.fyi/oobe-vms | iex +# Log: C:\ProgramData\OOBE\oobe-vms.log # Check if running with elevated privileges if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { @@ -18,7 +19,7 @@ $ProgressPreference = 'SilentlyContinue' $WorkDir = Join-Path $env:ProgramData 'OOBE' New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null -Start-Transcript -Path (Join-Path $WorkDir 'oobe-ltsc.log') -Append | Out-Null +Start-Transcript -Path (Join-Path $WorkDir 'oobe-vms.log') -Append | Out-Null function Save-Download($Uri, $OutFile) { for ($Attempt = 1; $Attempt -le 3; $Attempt++) { diff --git a/unattend/desktop-PROMPT-DISK.xml b/unattend/desktop-PROMPT-DISK.xml new file mode 100644 index 0000000..5b38494 --- /dev/null +++ b/unattend/desktop-PROMPT-DISK.xml @@ -0,0 +1,311 @@ + + + + + + + + + + en-US + + 0409:00000409 + en-US + en-US + en-US + + + + + + + D:\viostor\w10\amd64 + + + E:\viostor\w10\amd64 + + + F:\viostor\w10\amd64 + + + + D:\vioscsi\w10\amd64 + + + E:\vioscsi\w10\amd64 + + + F:\vioscsi\w10\amd64 + + + + D:\NetKVM\w10\amd64 + + + E:\NetKVM\w10\amd64 + + + F:\NetKVM\w10\amd64 + + + + + + + + + + + + + /IMAGE/INDEX + 1 + + + OnError + + + + + + + M7XTQ-FN8P6-TTKYV-9D4CC-J462D + OnError + + true + IT + Contoso + + + + + + + + + + + * + + Pacific Standard Time + IT + Contoso + + + + true + + + + + false + + + + 1 + 2 + + + + + + + + + 0409:00000409 + en-US + en-US + en-US + + + + + + true + true + true + true + true + Work + 3 + + + + + + mdwelcome + true</PlainText> + </AdministratorPassword> + + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Name>labadmin</Name> + <DisplayName>Lab Admin</DisplayName> + <Description>Local administrator for automated builds</Description> + <Group>Administrators</Group> + <Password> + <Value>mdwelcome</Value> + <PlainText>true</PlainText> + </Password> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + + <!-- Autologon ONCE, just long enough for the FirstLogonCommands. After + oobe-desktop.ps1 restarts the PC it lands on the sign-in screen. --> + <AutoLogon> + <Enabled>true</Enabled> + <Username>labadmin</Username> + <LogonCount>1</LogonCount> + <Password> + <Value>mdwelcome</Value> + <PlainText>true</PlainText> + </Password> + </AutoLogon> + + <DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet> + + <!-- =============================================================== + FIRST LOGON COMMANDS - run once, elevated, as labadmin. + =============================================================== --> + <FirstLogonCommands> + + <SynchronousCommand wcm:action="add"> + <Order>1</Order> + <Description>Show file extensions</Description> + <CommandLine>reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine> + <RequiresUserInput>false</RequiresUserInput> + </SynchronousCommand> + + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <Description>Allow signed PowerShell scripts</Description> + <CommandLine>powershell -NoProfile -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force"</CommandLine> + <RequiresUserInput>false</RequiresUserInput> + </SynchronousCommand> + + <!-- VMs only: if a virtio-win disc is attached, install the guest + tools (NetKVM is needed before anything can be downloaded). On + physical PCs no drive has the installer and this does nothing. Scans every drive for + the installer rather than assuming a letter, since the VirtIO + disc moves around post-install. --> + <SynchronousCommand wcm:action="add"> + <Order>3</Order> + <Description>Install VirtIO guest tools</Description> + <CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-Volume | Where-Object DriveLetter | ForEach-Object { $exe = $_.DriveLetter + ':\virtio-win-guest-tools.exe'; if (Test-Path $exe) { Start-Process $exe -ArgumentList '/install','/quiet','/norestart' -Wait } }"</CommandLine> + <RequiresUserInput>false</RequiresUserInput> + </SynchronousCommand> + + <!-- Provisioning: pulls oobe-desktop.ps1 from the repo so the PC always + gets the current version, retrying for ~2.5 minutes while the + network comes up. If that fails, falls back to the copy of + oobe-desktop.ps1 baked into the root of the ISO. The script installs + the MeshCentral agent, applies the power/RDP/SSH settings and + restarts the PC itself, so this must stay the LAST command. + Log: C:\ProgramData\OOBE\oobe-desktop.log --> + <SynchronousCommand wcm:action="add"> + <Order>4</Order> + <Description>Run oobe-desktop.ps1 (url.isworking.fyi, else ISO copy)</Description> + <CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $s = $null; for ($i = 1; $i -le 10; $i++) { try { $s = Invoke-RestMethod 'https://url.isworking.fyi/oobe-desktop'; break } catch { Start-Sleep -Seconds 15 } }; if (-not $s) { $f = Get-PSDrive -PSProvider FileSystem | ForEach-Object { Join-Path $_.Root 'oobe-desktop.ps1' } | Where-Object { Test-Path $_ } | Select-Object -First 1; if ($f) { $s = Get-Content -Raw $f } }; if ($s) { Invoke-Expression $s } else { Write-Warning 'oobe-desktop.ps1 not found online or on any drive'; Start-Sleep -Seconds 60 }"</CommandLine> + <RequiresUserInput>false</RequiresUserInput> + </SynchronousCommand> + + </FirstLogonCommands> + </component> + </settings> + +</unattend> + diff --git a/unattend/vms-WIPE-DISK.xml b/unattend/vms-WIPE-DISK.xml index fb2522f..0bf4b55 100644 --- a/unattend/vms-WIPE-DISK.xml +++ b/unattend/vms-WIPE-DISK.xml @@ -5,12 +5,12 @@ Target: Unraid / KVM virtual machine, UEFI firmware, GPT disk Storage: VirtIO (or VirtIO-SCSI) vdisk - drivers injected into WinPE Behavior: Wipes Disk 0, creates ESP/MSR/Windows, installs, autologons, - then runs oobe-ltsc.ps1 (VirtIO guest tools, MeshCentral, settings). + installs the VirtIO guest tools, then downloads and runs + oobe-vms.ps1 from https://url.isworking.fyi/oobe-vms. - PLACE AT: the ROOT of the merged VirtIO ISO, renamed to autounattend.xml, - alongside virtio-win-guest-tools.exe AND oobe-ltsc.ps1. The ISO - needs Joliet names so oobe-ltsc.ps1 isn't mangled to 8.3. - Attach that ISO in the Unraid VM's + BUILD: ./build-isos.sh produces build/virtio-vms.iso with this file as + autounattend.xml plus oobe-vms.ps1 (offline fallback) at the + root of the latest virtio-win ISO. Attach that ISO in the Unraid VM's "VirtIO Drivers ISO" slot; leave "OS Install ISO" on the LTSC image. >>> DESTRUCTIVE: WillWipeDisk erases Disk 0 with no prompt. <<< @@ -325,16 +325,28 @@ <RequiresUserInput>false</RequiresUserInput> </SynchronousCommand> - <!-- Provisioning: oobe-ltsc.ps1 sits at the root of the merged ISO - next to this file. Scans every drive for it rather than assuming - a letter, since the VirtIO disc moves around post-install. - The script installs the VirtIO guest tools, MeshCentral agent, - power/RDP/SSH settings, then restarts the VM itself, so this - must stay the LAST command. Log: C:\ProgramData\OOBE\oobe-ltsc.log --> + <!-- NetKVM has to be in place before anything can be downloaded, so + the guest tools go on from the ISO first. Scans every drive for + the installer rather than assuming a letter, since the VirtIO + disc moves around post-install. --> <SynchronousCommand wcm:action="add"> <Order>3</Order> - <Description>Run oobe-ltsc.ps1 provisioning script</Description> - <CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "$s = Get-PSDrive -PSProvider FileSystem | ForEach-Object { Join-Path $_.Root 'oobe-ltsc.ps1' } | Where-Object { Test-Path $_ } | Select-Object -First 1; if ($s) { &amp; $s } else { Write-Warning 'oobe-ltsc.ps1 not found on any drive'; Start-Sleep -Seconds 60 }"</CommandLine> + <Description>Install VirtIO guest tools</Description> + <CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-Volume | Where-Object DriveLetter | ForEach-Object { $exe = $_.DriveLetter + ':\virtio-win-guest-tools.exe'; if (Test-Path $exe) { Start-Process $exe -ArgumentList '/install','/quiet','/norestart' -Wait } }"</CommandLine> + <RequiresUserInput>false</RequiresUserInput> + </SynchronousCommand> + + <!-- Provisioning: pulls oobe-vms.ps1 from the repo so the VM always + gets the current version, retrying for ~2.5 minutes while the + network comes up. If that fails, falls back to the copy of + oobe-vms.ps1 baked into the root of the ISO. The script installs + the MeshCentral agent, applies the power/RDP/SSH settings and + restarts the VM itself, so this must stay the LAST command. + Log: C:\ProgramData\OOBE\oobe-vms.log --> + <SynchronousCommand wcm:action="add"> + <Order>4</Order> + <Description>Run oobe-vms.ps1 (url.isworking.fyi, else ISO copy)</Description> + <CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $s = $null; for ($i = 1; $i -le 10; $i++) { try { $s = Invoke-RestMethod 'https://url.isworking.fyi/oobe-vms'; break } catch { Start-Sleep -Seconds 15 } }; if (-not $s) { $f = Get-PSDrive -PSProvider FileSystem | ForEach-Object { Join-Path $_.Root 'oobe-vms.ps1' } | Where-Object { Test-Path $_ } | Select-Object -First 1; if ($f) { $s = Get-Content -Raw $f } }; if ($s) { Invoke-Expression $s } else { Write-Warning 'oobe-vms.ps1 not found online or on any drive'; Start-Sleep -Seconds 60 }"</CommandLine> <RequiresUserInput>false</RequiresUserInput> </SynchronousCommand>