diff --git a/lib/images/windows/templates/generalize.nix b/lib/images/windows/templates/generalize.nix index ed37374..5db8285 100644 --- a/lib/images/windows/templates/generalize.nix +++ b/lib/images/windows/templates/generalize.nix @@ -85,6 +85,22 @@ in staticDnsList = lib.optionalString (staticIP != null) (lib.concatMapStringsSep "," (s: "'${s}'") staticIP.dns); + # Its own file rather than inline in post-oobe.cmd: the command is long, and + # cmd's handling of quotes and pipes inside it is a needless hazard. + # + # Two things this has to get right. The adapter may not be up yet when + # FirstLogonCommands runs, so it is waited for rather than assumed. And the + # interface arrives DHCP-managed -- assigning an address without turning DHCP + # off first does not stick, which is how a VM meant to be at a fixed address + # ended up holding a lease instead. + staticIPScript = pkgs.writeText "vmix-static-ip.cmd" '' + @echo off + powershell -NoProfile -ExecutionPolicy Bypass -Command "$a = $null; for ($n = 0; $n -lt 30; $n++) { $a = Get-NetAdapter -Physical | Where-Object Status -eq 'Up' | Sort-Object ifIndex | Select-Object -First 1; if ($a) { break }; Start-Sleep -Seconds 2 }; if (-not $a) { Write-Output 'vmix: no adapter came up'; exit 1 }; $i = $a.ifIndex; Set-NetIPInterface -InterfaceIndex $i -Dhcp Disabled -ErrorAction SilentlyContinue; Remove-NetIPAddress -InterfaceIndex $i -AddressFamily IPv4 -Confirm:$false -ErrorAction SilentlyContinue; Remove-NetRoute -InterfaceIndex $i -AddressFamily IPv4 -Confirm:$false -ErrorAction SilentlyContinue; New-NetIPAddress -InterfaceIndex $i -IPAddress '${staticIP.address}' -PrefixLength ${toString staticIP.prefixLength} -DefaultGateway '${staticIP.gateway}' -ErrorAction Stop | Out-Null; Set-DnsClientServerAddress -InterfaceIndex $i -ServerAddresses ${staticDnsList}; Write-Output ('vmix: set ' + '${staticIP.address}' + ' on ifIndex ' + $i)" > C:\Windows\Temp\vmix-static-ip.log 2>&1 + :: Answer pings. Windows blocks ICMP by default, which makes a box with a + :: fixed address look dead to everything that checks it the obvious way. + powershell -NoProfile -Command "New-NetFirewallRule -DisplayName 'ICMPv4 Echo' -Protocol ICMPv4 -IcmpType 8 -Direction Inbound -Action Allow -Profile Any -Enabled True | Out-Null" > nul 2>&1 + ''; + dataDriveLetter = if dataDisk != null then (dataDisk.driveLetter or "D") else "D"; dataLabel = if dataDisk != null then (dataDisk.label or "data") else "data"; @@ -223,7 +239,7 @@ in :: This VM's only NIC sits on a macvtap bridged to the host's LAN, so its :: address is a LAN address that nothing hands out -- the guest asserts it. :: Clearing first makes the command idempotent across re-runs. - powershell -NoProfile -Command "$a = Get-NetAdapter -Physical | Sort-Object ifIndex | Select-Object -First 1; Remove-NetIPAddress -InterfaceIndex $a.ifIndex -AddressFamily IPv4 -Confirm:$false -ErrorAction SilentlyContinue; Remove-NetRoute -InterfaceIndex $a.ifIndex -AddressFamily IPv4 -Confirm:$false -ErrorAction SilentlyContinue; New-NetIPAddress -InterfaceIndex $a.ifIndex -IPAddress '${staticIP.address}' -PrefixLength ${toString staticIP.prefixLength} -DefaultGateway '${staticIP.gateway}' | Out-Null; Set-DnsClientServerAddress -InterfaceIndex $a.ifIndex -ServerAddresses ${staticDnsList}" + call C:\vmix-static-ip.cmd ''} ${lib.optionalString (writeFilter != null) '' @@ -337,7 +353,8 @@ in { { source = postOobeScript; dest = "/post-oobe.cmd"; } { source = masScript; dest = "/MAS_AIO.cmd"; } ] ++ lib.optional (dataDisk != null) { source = initDataDiskScript; dest = "/vmix-init-data-disk.cmd"; } - ++ lib.optional (writeFilter != null) { source = uwfConfigScript; dest = "/vmix-uwf-config.cmd"; }; + ++ lib.optional (writeFilter != null) { source = uwfConfigScript; dest = "/vmix-uwf-config.cmd"; } + ++ lib.optional (staticIP != null) { source = staticIPScript; dest = "/vmix-static-ip.cmd"; }; # delayOobeRun: sysprep + shutdown — OOBE runs on real hardware # generalize: sysprep + reboot into OOBE in the same QEMU session auditScript = ''