separated church and state

master
Chris Nutter 2026-09-21 21:12:41 -07:00
parent b9a8ded252
commit c4d42aa54c
7 changed files with 548 additions and 80 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

53
build-isos.sh Executable file
View File

@ -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

153
oobe-desktop.ps1 Normal file
View File

@ -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."

View File

@ -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."
}

View File

@ -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++) {

View File

@ -0,0 +1,311 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
============================================================================
autounattend.xml - Windows 10 Enterprise LTSC 2021 (21H2), x64
Target: Physical desktops/laptops AND virtual machines, UEFI or BIOS
Storage: Any. VirtIO drivers load automatically if a virtio-win ISO is
attached; otherwise use "Load driver" on the disk screen.
Behavior: STOPS at "Where do you want to install Windows?" so you pick
(and delete/format) partitions yourself - nothing is wiped
without you. Then installs, autologons ONCE, installs VirtIO
guest tools if a virtio-win disc is present, and downloads and
runs oobe-desktop.ps1 from https://url.isworking.fyi/oobe-desktop.
BUILD: ./build-isos.sh produces build/desktop.iso with this file as
autounattend.xml plus oobe-desktop.ps1 (offline fallback).
VM: attach it as a second CD next to the LTSC ISO.
PC: copy autounattend.xml and oobe-desktop.ps1 to the root of
the LTSC install USB (or a second USB stick).
Credentials baked in below: labadmin / mdwelcome (stored in CLEAR TEXT)
Search for "EDIT" to find every value you probably want to change.
============================================================================
-->
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<!-- =======================================================================
PASS 1: windowsPE
======================================================================= -->
<settings pass="windowsPE">
<component name="Microsoft-Windows-International-Core-WinPE"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<SetupUILanguage>
<UILanguage>en-US</UILanguage>
</SetupUILanguage>
<InputLocale>0409:00000409</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>
<!-- ===============================================================
VIRTIO DRIVER INJECTION - the load-bearing part.
WinPE has no viostor/vioscsi driver, so without this the vdisk is
invisible on the disk screen. On physical PCs these paths don't
exist and are skipped; the disk screen's Load Driver button is
still available for anything else (RAID/VMD/NVMe controllers).
Drive letters in WinPE are not deterministic: the LTSC disc and the
VirtIO disc land on D:/E: in either order, so both are listed, plus
F: as insurance. Setup logs a warning for paths that don't exist
and continues, so the extra entries cost nothing.
viostor = VirtIO block bus (Unraid vdisk bus: virtio)
vioscsi = VirtIO SCSI bus (Unraid vdisk bus: virtio-scsi)
NetKVM = network, so WinPE has connectivity if you ever need it
=============================================================== -->
<component name="Microsoft-Windows-PnpCustomizationsWinPE"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
<Path>D:\viostor\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
<Path>E:\viostor\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
<Path>F:\viostor\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="4">
<Path>D:\vioscsi\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="5">
<Path>E:\vioscsi\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="6">
<Path>F:\vioscsi\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="7">
<Path>D:\NetKVM\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="8">
<Path>E:\NetKVM\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="9">
<Path>F:\NetKVM\w10\amd64</Path>
</PathAndCredentials>
</DriverPaths>
</component>
<component name="Microsoft-Windows-Setup"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<!-- No DiskConfiguration and no InstallTo on purpose: Setup prompts
for the target disk/partition, which is the only screen shown. -->
<ImageInstall>
<OSImage>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/INDEX</Key>
<Value>1</Value>
</MetaData>
</InstallFrom>
<WillShowUI>OnError</WillShowUI>
</OSImage>
</ImageInstall>
<UserData>
<!-- EDIT: generic KMS client key for Enterprise LTSC 2021. Gets Setup
past the prompt; does NOT activate. N edition is
92NFX-8DJQP-P6BBQ-THF9C-7CG2H. -->
<ProductKey>
<Key>M7XTQ-FN8P6-TTKYV-9D4CC-J462D</Key>
<WillShowUI>OnError</WillShowUI>
</ProductKey>
<AcceptEula>true</AcceptEula>
<FullName>IT</FullName>
<Organization>Contoso</Organization>
</UserData>
</component>
</settings>
<!-- =======================================================================
PASS 2: specialize
======================================================================= -->
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<!-- "*" = random name. A literal name must NOT match any local account. -->
<ComputerName>*</ComputerName>
<!-- EDIT: `tzutil /l` lists every valid value. -->
<TimeZone>Pacific Standard Time</TimeZone>
<RegisteredOwner>IT</RegisteredOwner>
<RegisteredOrganization>Contoso</RegisteredOrganization>
</component>
<component name="Microsoft-Windows-Security-SPP-UX"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<SkipAutoActivation>true</SkipAutoActivation>
</component>
<!-- RDP on (NLA required) so the machine is reachable remotely. -->
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<fDenyTSConnections>false</fDenyTSConnections>
</component>
<component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<UserAuthentication>1</UserAuthentication>
<SecurityLayer>2</SecurityLayer>
</component>
</settings>
<!-- =======================================================================
PASS 3: oobeSystem
======================================================================= -->
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<InputLocale>0409:00000409</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>
<component name="Microsoft-Windows-Shell-Setup"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<HideLocalAccountScreen>true</HideLocalAccountScreen>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>3</ProtectYourPC>
</OOBE>
<!-- ===============================================================
ACCOUNTS - password is mdwelcome, in clear text, three times.
Treat this file and the merged ISO as secrets.
=============================================================== -->
<UserAccounts>
<AdministratorPassword>
<Value>mdwelcome</Value>
<PlainText>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>

View File

@ -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>