feat: add integration test infra and bootstrapper updates
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# Copy this file to .env (same directory) and fill in your local values.
|
||||
# `.env` itself is gitignored. Load it before running `packer build` with:
|
||||
#
|
||||
# . .\load-env.ps1
|
||||
#
|
||||
# All recognized variables map to Packer inputs via the PKR_VAR_<name>
|
||||
# convention, so every name below is also a valid -var flag.
|
||||
|
||||
# Required. Absolute path to the Windows 11 installer ISO on this host.
|
||||
PKR_VAR_iso_path=C:/path/to/Win11_25H2_English_x64_v2.iso
|
||||
|
||||
# Optional. SHA-256 of the ISO. Compute with:
|
||||
# Get-FileHash <iso> -Algorithm SHA256
|
||||
# Leave as "none" to skip validation (not recommended for shared boxes).
|
||||
PKR_VAR_iso_checksum=none
|
||||
|
||||
# Optional overrides. Defaults are in windows-11.pkr.hcl. The build VM is
|
||||
# throwaway and intentionally claims most of the host (24 vCPU, 32 GB RAM);
|
||||
# the runtime integration VM uses a smaller footprint set in
|
||||
# test/integration/Vagrantfile.
|
||||
# PKR_VAR_cpus=24
|
||||
# PKR_VAR_memory=32768
|
||||
# PKR_VAR_disk_size=81920
|
||||
# PKR_VAR_switch_name=Default Switch
|
||||
# PKR_VAR_vm_name=bootstrap-win11
|
||||
# PKR_VAR_box_output=bootstrap-win11.box
|
||||
@@ -0,0 +1,167 @@
|
||||
# Vagrant box build (Packer + Hyper-V + Windows 11 25H2)
|
||||
|
||||
Builds a `bootstrap-win11.box` Vagrant box from a stock Windows 11 25H2 ISO,
|
||||
ready to use with `test/integration/Vagrantfile`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
On the host (must be Windows with Hyper-V):
|
||||
|
||||
- Hyper-V role enabled; current user in **Hyper-V Administrators**.
|
||||
- [HashiCorp Packer](https://developer.hashicorp.com/packer/install) 1.15+ in
|
||||
`PATH`. HashiCorp does not publish a winget manifest, so use the helper:
|
||||
|
||||
```powershell
|
||||
# from an elevated PowerShell
|
||||
cd test\integration\packer
|
||||
.\install-packer.ps1 # installs the pinned default version
|
||||
.\install-packer.ps1 -Version 1.14.0 # or pick a specific release
|
||||
```
|
||||
|
||||
The script downloads
|
||||
`https://releases.hashicorp.com/packer/<Version>/packer_<Version>_windows_amd64.zip`,
|
||||
extracts `packer.exe` to `C:\Program Files\Packer\`, and adds that directory
|
||||
to the machine-scoped `PATH`. It is idempotent — re-running with the same
|
||||
version just re-asserts the `PATH` entry. See the `param()` block in
|
||||
`install-packer.ps1` for available flags.
|
||||
|
||||
By default the same script also installs **Windows ADK Deployment Tools**
|
||||
to get `oscdimg.exe`, which Packer's `hyperv-iso` builder needs to build the
|
||||
unattended-install secondary ISO from `cd_files`. Only the
|
||||
`OptionId.DeploymentTools` feature is selected, so the install is small
|
||||
(~70 MB) and unattended. Pass `-SkipOscdimg` to opt out (e.g. if you already
|
||||
have `xorriso`/`mkisofs` on `PATH`). After install you may need to open a
|
||||
fresh shell — or run
|
||||
`$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine')` — for
|
||||
`oscdimg.exe` to resolve in the current session.
|
||||
- [HashiCorp Vagrant](https://developer.hashicorp.com/vagrant/install) 2.4+ in
|
||||
`PATH` (used for the `vagrant` post-processor and, later, the integration
|
||||
test itself).
|
||||
- A Windows 11 client ISO somewhere on disk. Its path is read from `.env`
|
||||
(see below) and is **not** hardcoded in the Packer config.
|
||||
- Roughly 90 GB free disk and ~60 minutes of patience for a first build.
|
||||
- An elevated PowerShell session for the build (`packer build` shells out to
|
||||
Hyper-V management APIs that require admin).
|
||||
|
||||
## Configure the ISO path via `.env`
|
||||
|
||||
The Packer config has no default ISO path. Copy the template, fill it in, and
|
||||
load it into the current shell before building:
|
||||
|
||||
```powershell
|
||||
cd test\integration\packer
|
||||
Copy-Item .env.example .env
|
||||
# edit .env so PKR_VAR_iso_path points at your Windows 11 ISO
|
||||
. .\load-env.ps1
|
||||
```
|
||||
|
||||
`load-env.ps1` reads `.env` line-by-line and exports each `KEY=VALUE` into the
|
||||
process environment. `.env` is gitignored at the repo root, so your local path
|
||||
never gets committed.
|
||||
|
||||
Packer picks the values up automatically through its `PKR_VAR_<name>` env-var
|
||||
convention; nothing about the `packer build` invocation changes.
|
||||
|
||||
## One-time setup
|
||||
|
||||
```powershell
|
||||
packer init .\windows-11.pkr.hcl
|
||||
```
|
||||
|
||||
That downloads the `hyperv` and `vagrant` Packer plugins.
|
||||
|
||||
## Compute and pin the ISO checksum (recommended)
|
||||
|
||||
```powershell
|
||||
Get-FileHash $env:PKR_VAR_iso_path -Algorithm SHA256
|
||||
```
|
||||
|
||||
Set `PKR_VAR_iso_checksum=sha256:<digest>` in `.env` and re-run `load-env.ps1`.
|
||||
Leaving it as `none` skips validation; fine for local builds but loses the "my
|
||||
ISO has not been swapped" guarantee.
|
||||
|
||||
## Build
|
||||
|
||||
```powershell
|
||||
packer build .\windows-11.pkr.hcl
|
||||
```
|
||||
|
||||
Roughly what happens:
|
||||
|
||||
1. Packer creates a Gen 2 Hyper-V VM (Secure Boot + TPM + 24 vCPU + 32 GB RAM
|
||||
+ 80 GB dynamic VHDX) attached to `Default Switch`. The build VM is
|
||||
throwaway and intentionally claims most of the host for speed; the
|
||||
runtime integration VM uses smaller defaults set in
|
||||
`test/integration/Vagrantfile` (8 vCPU / 16 GB).
|
||||
2. It mounts the Windows ISO and a tiny `PROVISION` ISO containing
|
||||
`cd/autounattend.xml` and `cd/scripts/oobe-enable-winrm.ps1`.
|
||||
3. Windows Setup performs an unattended install (Pro edition, generic
|
||||
activation key) and creates user `vagrant`:`vagrant`.
|
||||
4. FirstLogonCommands enable WinRM (HTTP, basic auth) and set
|
||||
`LocalAccountTokenFilterPolicy=1` so remote admin tokens are not filtered.
|
||||
5. Packer connects over WinRM and runs the provisioners in `scripts/`:
|
||||
- `configure-os.ps1` — UAC token policy, DiagTrack off, NTP resync.
|
||||
- `disable-windows-updates.ps1` — fully disables WU and friends.
|
||||
- `disable-defender-cloud.ps1` — disables MAPS / cloud lookups.
|
||||
- `install-vagrant-key.ps1` — drops the vagrant insecure public key.
|
||||
- `compact.ps1` — cleans caches, zeros free space.
|
||||
6. Packer shuts the guest down cleanly and the `vagrant` post-processor
|
||||
packages the VHDX into `bootstrap-win11.box`.
|
||||
|
||||
## Register the resulting box with Vagrant
|
||||
|
||||
```powershell
|
||||
vagrant box add bootstrap-win11 .\bootstrap-win11.box
|
||||
```
|
||||
|
||||
Then in `test/integration/Vagrantfile` set:
|
||||
|
||||
```powershell
|
||||
$env:BOOTSTRAP_BOX = "bootstrap-win11"
|
||||
go run ..\..\test\integration
|
||||
```
|
||||
|
||||
(or just edit the default in the Vagrantfile to `bootstrap-win11`).
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
test/integration/packer/
|
||||
├── README.md ← this file
|
||||
├── .env.example ← template; copy to .env (gitignored)
|
||||
├── load-env.ps1 ← dot-source to export PKR_VAR_* into shell
|
||||
├── install-packer.ps1 ← elevated installer for packer.exe
|
||||
├── windows-11.pkr.hcl ← Packer HCL2 build definition
|
||||
├── vagrantfile-template.rb ← baked into the output box
|
||||
├── cd/
|
||||
│ ├── autounattend.xml ← Windows Setup answer file
|
||||
│ └── scripts/
|
||||
│ └── oobe-enable-winrm.ps1
|
||||
└── scripts/ ← provisioners run after WinRM is up
|
||||
├── configure-os.ps1
|
||||
├── disable-windows-updates.ps1
|
||||
├── disable-defender-cloud.ps1
|
||||
├── install-vagrant-key.ps1
|
||||
└── compact.ps1
|
||||
```
|
||||
|
||||
## Notes and gotchas
|
||||
|
||||
- **Run elevated.** `packer build` opens Hyper-V management APIs; non-elevated
|
||||
shells fail with confusing "access denied" messages midway through.
|
||||
- **First build is slow.** Windows Setup + updates trimming + cipher /w on a
|
||||
60 GB volume can take 45–90 min. Subsequent rebuilds reuse the parent VHDX
|
||||
via linked clone and are much faster.
|
||||
- **License compliance.** The box uses the public KMS client setup key
|
||||
(W269N-WFGWX-YVC9B-4J6C9-T83GX) to pick the Pro edition during install. The
|
||||
resulting VM is not activated; for short-lived integration runs the eval
|
||||
period is more than sufficient, but redistribute the box only under your
|
||||
own licensing terms.
|
||||
- **25H2 OOBE.** The autounattend sets `BypassNRO=1` during `specialize`
|
||||
because 24H2/25H2 removed the `BypassNRO.cmd` helper. If a future Windows
|
||||
update changes the OOBE flow again, the symptom will be a hung
|
||||
`vagrant up` waiting for WinRM; check by opening Hyper-V Manager and
|
||||
looking for an OOBE screen on the VM console.
|
||||
- **Secure Boot.** Enabled (`MicrosoftWindows` template). Switch off in the
|
||||
Packer source if you ever need to install unsigned kernel-mode drivers in
|
||||
the box.
|
||||
@@ -0,0 +1,200 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Unattend file for Windows 11 25H2 (Pro), Hyper-V Gen 2, UEFI + Secure Boot.
|
||||
|
||||
Goals:
|
||||
- Fully unattended install on the only VHDX presented by the VM.
|
||||
- Create local user `vagrant` (password `vagrant`) in Administrators.
|
||||
- Skip every OOBE wizard page (including the network-required screen).
|
||||
- Bring WinRM online with basic auth and unencrypted transport so Packer
|
||||
and Vagrant can talk to the guest.
|
||||
|
||||
The BypassNRO trick is set in `specialize` rather than relying on the
|
||||
removed BypassNRO.cmd script, so this works on 24H2/25H2.
|
||||
-->
|
||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||
|
||||
<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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<SetupUILanguage>
|
||||
<UILanguage>en-US</UILanguage>
|
||||
</SetupUILanguage>
|
||||
<InputLocale>0409:00000409</InputLocale>
|
||||
<SystemLocale>en-US</SystemLocale>
|
||||
<UILanguage>en-US</UILanguage>
|
||||
<UserLocale>en-US</UserLocale>
|
||||
</component>
|
||||
|
||||
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<DiskConfiguration>
|
||||
<Disk wcm:action="add">
|
||||
<DiskID>0</DiskID>
|
||||
<WillWipeDisk>true</WillWipeDisk>
|
||||
<CreatePartitions>
|
||||
<CreatePartition wcm:action="add">
|
||||
<Order>1</Order>
|
||||
<Type>EFI</Type>
|
||||
<Size>300</Size>
|
||||
</CreatePartition>
|
||||
<CreatePartition wcm:action="add">
|
||||
<Order>2</Order>
|
||||
<Type>MSR</Type>
|
||||
<Size>128</Size>
|
||||
</CreatePartition>
|
||||
<CreatePartition wcm:action="add">
|
||||
<Order>3</Order>
|
||||
<Type>Primary</Type>
|
||||
<Extend>true</Extend>
|
||||
</CreatePartition>
|
||||
</CreatePartitions>
|
||||
<ModifyPartitions>
|
||||
<ModifyPartition wcm:action="add">
|
||||
<Order>1</Order>
|
||||
<PartitionID>1</PartitionID>
|
||||
<Format>FAT32</Format>
|
||||
<Label>System</Label>
|
||||
</ModifyPartition>
|
||||
<ModifyPartition wcm:action="add">
|
||||
<Order>2</Order>
|
||||
<PartitionID>2</PartitionID>
|
||||
</ModifyPartition>
|
||||
<ModifyPartition wcm:action="add">
|
||||
<Order>3</Order>
|
||||
<PartitionID>3</PartitionID>
|
||||
<Format>NTFS</Format>
|
||||
<Label>Windows</Label>
|
||||
<Letter>C</Letter>
|
||||
</ModifyPartition>
|
||||
</ModifyPartitions>
|
||||
</Disk>
|
||||
</DiskConfiguration>
|
||||
|
||||
<ImageInstall>
|
||||
<OSImage>
|
||||
<InstallTo>
|
||||
<DiskID>0</DiskID>
|
||||
<PartitionID>3</PartitionID>
|
||||
</InstallTo>
|
||||
<InstallFrom>
|
||||
<MetaData wcm:action="add">
|
||||
<Key>/IMAGE/NAME</Key>
|
||||
<Value>Windows 11 Pro</Value>
|
||||
</MetaData>
|
||||
</InstallFrom>
|
||||
</OSImage>
|
||||
</ImageInstall>
|
||||
|
||||
<UserData>
|
||||
<!-- KMS client setup key for Windows 11 Pro; lets the installer pick
|
||||
the right edition without prompting. Replaced at activation. -->
|
||||
<ProductKey>
|
||||
<Key>W269N-WFGWX-YVC9B-4J6C9-T83GX</Key>
|
||||
<WillShowUI>OnError</WillShowUI>
|
||||
</ProductKey>
|
||||
<AcceptEula>true</AcceptEula>
|
||||
<FullName>vagrant</FullName>
|
||||
<Organization>vagrant</Organization>
|
||||
</UserData>
|
||||
</component>
|
||||
</settings>
|
||||
|
||||
<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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ComputerName>bootstrap-win11</ComputerName>
|
||||
<TimeZone>UTC</TimeZone>
|
||||
</component>
|
||||
|
||||
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<RunSynchronous>
|
||||
<!-- BypassNRO: allows OOBE without a network/Microsoft account.
|
||||
24H2/25H2 removed the BypassNRO.cmd helper, so we set the
|
||||
registry key directly during specialize. -->
|
||||
<RunSynchronousCommand wcm:action="add">
|
||||
<Order>1</Order>
|
||||
<Path>reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE /v BypassNRO /t REG_DWORD /d 1 /f</Path>
|
||||
</RunSynchronousCommand>
|
||||
<!-- Allow WinRM remote-elevated tokens (#3 mitigation). -->
|
||||
<RunSynchronousCommand wcm:action="add">
|
||||
<Order>2</Order>
|
||||
<Path>reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f</Path>
|
||||
</RunSynchronousCommand>
|
||||
</RunSynchronous>
|
||||
</component>
|
||||
</settings>
|
||||
|
||||
<settings pass="oobeSystem">
|
||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<OOBE>
|
||||
<HideEULAPage>true</HideEULAPage>
|
||||
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
||||
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
||||
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
||||
<NetworkLocation>Work</NetworkLocation>
|
||||
<ProtectYourPC>3</ProtectYourPC>
|
||||
<SkipMachineOOBE>true</SkipMachineOOBE>
|
||||
<SkipUserOOBE>true</SkipUserOOBE>
|
||||
</OOBE>
|
||||
|
||||
<UserAccounts>
|
||||
<LocalAccounts>
|
||||
<LocalAccount wcm:action="add">
|
||||
<Name>vagrant</Name>
|
||||
<Group>Administrators</Group>
|
||||
<DisplayName>vagrant</DisplayName>
|
||||
<Description>Vagrant Box Build</Description>
|
||||
<Password>
|
||||
<Value>vagrant</Value>
|
||||
<PlainText>true</PlainText>
|
||||
</Password>
|
||||
</LocalAccount>
|
||||
</LocalAccounts>
|
||||
</UserAccounts>
|
||||
|
||||
<AutoLogon>
|
||||
<Enabled>true</Enabled>
|
||||
<Username>vagrant</Username>
|
||||
<LogonCount>3</LogonCount>
|
||||
<Password>
|
||||
<Value>vagrant</Value>
|
||||
<PlainText>true</PlainText>
|
||||
</Password>
|
||||
</AutoLogon>
|
||||
|
||||
<FirstLogonCommands>
|
||||
<!-- Allow PowerShell scripts unsigned (machine scope). -->
|
||||
<SynchronousCommand wcm:action="add">
|
||||
<Order>1</Order>
|
||||
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy Bypass -Force"</CommandLine>
|
||||
<Description>Set ExecutionPolicy Bypass</Description>
|
||||
</SynchronousCommand>
|
||||
<!-- Enable WinRM inline so we don't depend on the PROVISION ISO
|
||||
still being mounted at first logon (Windows Setup sometimes
|
||||
detaches secondary DVDs after install). Mirrors the contents
|
||||
of scripts/oobe-enable-winrm.ps1 (now unused) and writes a
|
||||
transcript to C:\Windows\Temp\firstlogon.log for debugging. -->
|
||||
<SynchronousCommand wcm:action="add">
|
||||
<Order>2</Order>
|
||||
<CommandLine>cmd.exe /c winrm quickconfig -force -q > C:\Windows\Temp\firstlogon.log 2>&1</CommandLine>
|
||||
<Description>WinRM quickconfig</Description>
|
||||
</SynchronousCommand>
|
||||
<SynchronousCommand wcm:action="add">
|
||||
<Order>3</Order>
|
||||
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "Set-NetConnectionProfile -NetworkCategory Private -ErrorAction SilentlyContinue; Set-Item WSMan:\localhost\Service\Auth\Basic $true; Set-Item WSMan:\localhost\Service\AllowUnencrypted $true; Set-Item WSMan:\localhost\Service\MaxConcurrentOperationsPerUser 12000; Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1024" >> C:\Windows\Temp\firstlogon.log 2>&1</CommandLine>
|
||||
<Description>Configure WinRM auth and limits</Description>
|
||||
</SynchronousCommand>
|
||||
<SynchronousCommand wcm:action="add">
|
||||
<Order>4</Order>
|
||||
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "New-NetFirewallRule -Name WinRM-HTTP -DisplayName 'WinRM HTTP' -Protocol TCP -LocalPort 5985 -Action Allow -Profile Any -ErrorAction SilentlyContinue" >> C:\Windows\Temp\firstlogon.log 2>&1</CommandLine>
|
||||
<Description>Open firewall for WinRM HTTP</Description>
|
||||
</SynchronousCommand>
|
||||
<SynchronousCommand wcm:action="add">
|
||||
<Order>5</Order>
|
||||
<CommandLine>cmd.exe /c sc config WinRM start= auto && net stop WinRM && net start WinRM >> C:\Windows\Temp\firstlogon.log 2>&1</CommandLine>
|
||||
<Description>Restart WinRM service</Description>
|
||||
</SynchronousCommand>
|
||||
</FirstLogonCommands>
|
||||
|
||||
<TimeZone>UTC</TimeZone>
|
||||
</component>
|
||||
</settings>
|
||||
</unattend>
|
||||
@@ -0,0 +1,206 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Installs HashiCorp Packer on Windows by downloading the release zip,
|
||||
extracting it to Program Files, and adding that directory to the system
|
||||
PATH.
|
||||
|
||||
.DESCRIPTION
|
||||
Winget does not publish HashiCorp Packer, so this helper does the install
|
||||
by hand. It is idempotent: if the requested version is already installed
|
||||
at the target location it just re-asserts the PATH entry and exits.
|
||||
|
||||
Requires an elevated PowerShell session (writes under Program Files and
|
||||
modifies the machine-scoped PATH environment variable).
|
||||
|
||||
.PARAMETER Version
|
||||
Packer release to install (e.g. "1.15.3"). The download URL is computed
|
||||
as https://releases.hashicorp.com/packer/<Version>/packer_<Version>_windows_amd64.zip.
|
||||
|
||||
.PARAMETER InstallRoot
|
||||
Directory under which packer.exe is placed. Defaults to
|
||||
"$env:ProgramFiles\Packer". This directory is what gets added to the
|
||||
system PATH.
|
||||
|
||||
.PARAMETER SkipOscdimg
|
||||
Skip installing the Windows ADK Deployment Tools (which provide
|
||||
oscdimg.exe). Packer's hyperv-iso builder needs an ISO-creation tool
|
||||
(xorriso, mkisofs, hdiutil, or oscdimg) on PATH when using cd_files,
|
||||
so by default this script also installs oscdimg via the Windows ADK
|
||||
bootstrapper with only the DeploymentTools feature selected (~70 MB
|
||||
on disk).
|
||||
|
||||
.PARAMETER AdkSetupUrl
|
||||
Override the URL used to download the Windows ADK setup bootstrapper.
|
||||
Defaults to Microsoft's stable fwlink for the Windows 11 ADK.
|
||||
|
||||
.EXAMPLE
|
||||
# Install the default version into "C:\Program Files\Packer".
|
||||
.\install-packer.ps1
|
||||
|
||||
.EXAMPLE
|
||||
# Install a specific version.
|
||||
.\install-packer.ps1 -Version 1.14.0
|
||||
|
||||
.EXAMPLE
|
||||
# Install Packer only; skip the Windows ADK Deployment Tools install.
|
||||
.\install-packer.ps1 -SkipOscdimg
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string] $Version = '1.15.3',
|
||||
[string] $InstallRoot = (Join-Path $env:ProgramFiles 'Packer'),
|
||||
[switch] $SkipOscdimg,
|
||||
[string] $AdkSetupUrl = 'https://go.microsoft.com/fwlink/?linkid=2289980'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Assert-Elevated {
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw "install-packer.ps1 must be run from an elevated PowerShell session (writes to Program Files and the machine PATH)."
|
||||
}
|
||||
}
|
||||
|
||||
function Get-InstalledPackerVersion {
|
||||
param([string] $Root)
|
||||
$exe = Join-Path $Root 'packer.exe'
|
||||
if (-not (Test-Path -LiteralPath $exe)) { return $null }
|
||||
try {
|
||||
$output = & $exe version 2>$null
|
||||
} catch {
|
||||
return $null
|
||||
}
|
||||
foreach ($line in $output) {
|
||||
if ($line -match 'Packer\s+v?([0-9]+\.[0-9]+\.[0-9]+)') {
|
||||
return $Matches[1]
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Add-ToMachinePath {
|
||||
param([string] $Directory)
|
||||
$current = [Environment]::GetEnvironmentVariable('Path', 'Machine')
|
||||
$parts = @()
|
||||
if ($current) {
|
||||
$parts = $current.Split(';') | Where-Object { $_ -ne '' }
|
||||
}
|
||||
if ($parts -contains $Directory) {
|
||||
Write-Host "system PATH already contains $Directory"
|
||||
return
|
||||
}
|
||||
$newPath = ($parts + $Directory) -join ';'
|
||||
[Environment]::SetEnvironmentVariable('Path', $newPath, 'Machine')
|
||||
# Make the change visible in the current session as well so the caller
|
||||
# can immediately run `packer` without opening a new shell.
|
||||
if (-not (($env:Path -split ';') -contains $Directory)) {
|
||||
$env:Path = "$env:Path;$Directory"
|
||||
}
|
||||
Write-Host "added $Directory to the system PATH (machine scope)."
|
||||
}
|
||||
|
||||
function Get-OscdimgPath {
|
||||
$candidates = @(
|
||||
(Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe'),
|
||||
(Join-Path ${env:ProgramFiles} 'Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe')
|
||||
)
|
||||
foreach ($c in $candidates) {
|
||||
if ($c -and (Test-Path -LiteralPath $c)) { return $c }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Install-Oscdimg {
|
||||
param([string] $SetupUrl)
|
||||
|
||||
$existing = Get-OscdimgPath
|
||||
if ($existing) {
|
||||
Write-Host "oscdimg.exe already present at $existing"
|
||||
Add-ToMachinePath -Directory (Split-Path -Parent $existing)
|
||||
return
|
||||
}
|
||||
|
||||
$tmp = Join-Path ([IO.Path]::GetTempPath()) ("install-adk-" + [Guid]::NewGuid())
|
||||
$setup = Join-Path $tmp 'adksetup.exe'
|
||||
New-Item -ItemType Directory -Path $tmp -Force | Out-Null
|
||||
try {
|
||||
Write-Host "downloading Windows ADK bootstrapper from $SetupUrl"
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
||||
Invoke-WebRequest -UseBasicParsing -Uri $SetupUrl -OutFile $setup
|
||||
|
||||
Write-Host "installing Windows ADK Deployment Tools (this may take several minutes)"
|
||||
# /features OptionId.DeploymentTools => oscdimg only (~70 MB)
|
||||
# /quiet /norestart /ceip off => unattended, no telemetry prompt
|
||||
$args = @('/features','OptionId.DeploymentTools','/quiet','/norestart','/ceip','off')
|
||||
$proc = Start-Process -FilePath $setup -ArgumentList $args -Wait -PassThru
|
||||
if ($proc.ExitCode -ne 0) {
|
||||
throw "Windows ADK setup exited with code $($proc.ExitCode)."
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$installed = Get-OscdimgPath
|
||||
if (-not $installed) {
|
||||
throw "Windows ADK setup completed but oscdimg.exe was not found in the expected location."
|
||||
}
|
||||
Write-Host "installed oscdimg.exe at $installed"
|
||||
Add-ToMachinePath -Directory (Split-Path -Parent $installed)
|
||||
}
|
||||
|
||||
Assert-Elevated
|
||||
|
||||
# Idempotency: if the target version is already installed, just make sure the
|
||||
# PATH entry is in place and return.
|
||||
$existing = Get-InstalledPackerVersion -Root $InstallRoot
|
||||
if ($existing -eq $Version) {
|
||||
Write-Host "packer $Version already installed at $InstallRoot."
|
||||
Add-ToMachinePath -Directory $InstallRoot
|
||||
if (-not $SkipOscdimg) { Install-Oscdimg -SetupUrl $AdkSetupUrl }
|
||||
return
|
||||
}
|
||||
if ($existing) {
|
||||
Write-Host "replacing packer $existing at $InstallRoot with $Version."
|
||||
}
|
||||
|
||||
$url = "https://releases.hashicorp.com/packer/$Version/packer_${Version}_windows_amd64.zip"
|
||||
$tmpRoot = Join-Path ([IO.Path]::GetTempPath()) ("install-packer-" + [Guid]::NewGuid())
|
||||
$zipPath = Join-Path $tmpRoot "packer_${Version}.zip"
|
||||
|
||||
New-Item -ItemType Directory -Path $tmpRoot -Force | Out-Null
|
||||
try {
|
||||
Write-Host "downloading $url"
|
||||
# TLS 1.2 is required to talk to releases.hashicorp.com on stock
|
||||
# Windows 10/Server 2019 PowerShell sessions.
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
||||
Invoke-WebRequest -UseBasicParsing -Uri $url -OutFile $zipPath
|
||||
|
||||
Write-Host "extracting to $tmpRoot"
|
||||
Expand-Archive -LiteralPath $zipPath -DestinationPath $tmpRoot -Force
|
||||
|
||||
$srcExe = Join-Path $tmpRoot 'packer.exe'
|
||||
if (-not (Test-Path -LiteralPath $srcExe)) {
|
||||
throw "packer.exe not found inside $zipPath after extraction."
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null
|
||||
Copy-Item -LiteralPath $srcExe -Destination (Join-Path $InstallRoot 'packer.exe') -Force
|
||||
Write-Host "installed packer.exe to $InstallRoot"
|
||||
|
||||
Add-ToMachinePath -Directory $InstallRoot
|
||||
|
||||
$installed = Get-InstalledPackerVersion -Root $InstallRoot
|
||||
if ($installed -ne $Version) {
|
||||
Write-Warning "post-install version check returned '$installed' (expected $Version)."
|
||||
} else {
|
||||
Write-Host "verified: packer $installed available at $(Join-Path $InstallRoot 'packer.exe')."
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $tmpRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if (-not $SkipOscdimg) { Install-Oscdimg -SetupUrl $AdkSetupUrl }
|
||||
@@ -0,0 +1,34 @@
|
||||
# Loads variables from .env (in this directory) into the current PowerShell
|
||||
# session as environment variables. Dot-source it so the variables stick:
|
||||
#
|
||||
# . .\load-env.ps1
|
||||
#
|
||||
# Format:
|
||||
# - One KEY=VALUE per line.
|
||||
# - Lines starting with '#' and blank lines are ignored.
|
||||
# - Values are taken verbatim (no shell quoting / interpolation). Surround
|
||||
# with quotes only if you actually want quotes in the value.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$envFile = Join-Path $PSScriptRoot '.env'
|
||||
if (-not (Test-Path -LiteralPath $envFile)) {
|
||||
throw ".env not found at $envFile. Copy .env.example to .env and fill in PKR_VAR_iso_path."
|
||||
}
|
||||
|
||||
$loaded = 0
|
||||
foreach ($line in Get-Content -LiteralPath $envFile) {
|
||||
$trimmed = $line.Trim()
|
||||
if ($trimmed -eq '' -or $trimmed.StartsWith('#')) { continue }
|
||||
$idx = $trimmed.IndexOf('=')
|
||||
if ($idx -lt 1) {
|
||||
Write-Warning "skipping malformed line: $line"
|
||||
continue
|
||||
}
|
||||
$name = $trimmed.Substring(0, $idx).Trim()
|
||||
$value = $trimmed.Substring($idx + 1)
|
||||
[Environment]::SetEnvironmentVariable($name, $value, 'Process')
|
||||
$loaded++
|
||||
}
|
||||
|
||||
Write-Host "loaded $loaded variable(s) from $envFile into the current session."
|
||||
@@ -0,0 +1,78 @@
|
||||
Warning: A checksum of 'none' was specified. Since ISO files are so big,
|
||||
a checksum is highly recommended.
|
||||
|
||||
on .\windows-11.pkr.hcl line 83:
|
||||
(source code not available)
|
||||
|
||||
|
||||
hyperv-iso.win11: output will be in this color.
|
||||
|
||||
==> hyperv-iso.win11: Creating build directory...
|
||||
==> hyperv-iso.win11: Retrieving ISO
|
||||
==> hyperv-iso.win11: Trying C:/Users/jasonross/Downloads/Win11_25H2_English_x64_v2(1).iso
|
||||
==> hyperv-iso.win11: Trying file://C:/Users/jasonross/Downloads/Win11_25H2_English_x64_v2%281%29.iso
|
||||
==> hyperv-iso.win11: file://C:/Users/jasonross/Downloads/Win11_25H2_English_x64_v2%281%29.iso => C:/Users/jasonross/Downloads/Win11_25H2_English_x64_v2(1).iso
|
||||
==> hyperv-iso.win11: Creating switch 'Default Switch' if required...
|
||||
==> hyperv-iso.win11: switch 'Default Switch' already exists. Will not delete on cleanup...
|
||||
==> hyperv-iso.win11: Creating virtual machine...
|
||||
==> hyperv-iso.win11: Enabling Integration Service...
|
||||
==> hyperv-iso.win11: Setting boot drive to os dvd drive C:/Users/jasonross/Downloads/Win11_25H2_English_x64_v2(1).iso ...
|
||||
==> hyperv-iso.win11: Mounting os dvd drive C:/Users/jasonross/Downloads/Win11_25H2_English_x64_v2(1).iso ...
|
||||
==> hyperv-iso.win11: Skipping mounting Integration Services Setup Disk...
|
||||
==> hyperv-iso.win11: Creating CD disk...
|
||||
==> hyperv-iso.win11: OSCDIMG 2.56 CD-ROM and DVD-ROM Premastering Utility
|
||||
==> hyperv-iso.win11: Copyright (C) Microsoft, 1993-2012. All rights reserved.
|
||||
==> hyperv-iso.win11: Licensed only for producing Microsoft authorized content.
|
||||
==> hyperv-iso.win11: Scanning source tree
|
||||
==> hyperv-iso.win11: Scanning source tree complete (2 files in 1 directories)
|
||||
==> hyperv-iso.win11: Computing directory information complete
|
||||
==> hyperv-iso.win11: Image file is 61440 bytes (before optimization)
|
||||
==> hyperv-iso.win11: Writing 2 files in 1 directories to C:\Users\jasonross\AppData\Local\Temp\packer2666332811.iso
|
||||
==> hyperv-iso.win11: Storage optimization saved 0 files, 0 bytes (0% of image)
|
||||
==> hyperv-iso.win11: After optimization, image file is 61440 bytes
|
||||
==> hyperv-iso.win11: Done.
|
||||
==> hyperv-iso.win11: 100% complete
|
||||
==> hyperv-iso.win11: Done copying paths from CD_dirs
|
||||
==> hyperv-iso.win11: Mounting secondary DVD images...
|
||||
==> hyperv-iso.win11: Mounting secondary dvd drive C:\Users\jasonross\AppData\Local\Temp\packer2666332811.iso ...
|
||||
==> hyperv-iso.win11: Configuring vlan...
|
||||
==> hyperv-iso.win11: Determine Host IP for HyperV machine...
|
||||
==> hyperv-iso.win11: Host IP for the HyperV machine: 172.25.16.1
|
||||
==> hyperv-iso.win11: Attempting to connect with vmconnect...
|
||||
==> hyperv-iso.win11: Starting the virtual machine...
|
||||
==> hyperv-iso.win11: Waiting 1s for boot...
|
||||
==> hyperv-iso.win11: Typing the boot command...
|
||||
==> hyperv-iso.win11: Waiting for WinRM to become available...
|
||||
==> hyperv-iso.win11: WinRM connected.
|
||||
==> hyperv-iso.win11: Connected to WinRM!
|
||||
==> hyperv-iso.win11: Provisioning with Powershell...
|
||||
==> hyperv-iso.win11: Provisioning with powershell script: ./scripts/configure-os.ps1
|
||||
==> hyperv-iso.win11: Script exited with non-zero exit status: 2147943462. Allowed exit codes are: [0]
|
||||
==> hyperv-iso.win11: Step "StepProvision" failed, aborting...
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepConnect"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepTypeBootCommand"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepRun"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepSetFirstBootDevice"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepSetBootOrder"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepConfigureVlan"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepMountSecondaryDvdImages"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepCreateCD"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepMountGuestAdditions"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepMountFloppydrive"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepMountDvdDrive"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepEnableIntegrationService"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepCreateVM"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepCreateSwitch"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepHTTPServer"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepCreateFloppy"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepDownload"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepOutputDir"
|
||||
==> hyperv-iso.win11: aborted: skipping cleanup of step "StepCreateBuildDir"
|
||||
Build 'hyperv-iso.win11' errored after 16 minutes 59 seconds: Script exited with non-zero exit status: 2147943462. Allowed exit codes are: [0]
|
||||
|
||||
==> Wait completed after 16 minutes 59 seconds
|
||||
|
||||
==> Some builds didn't complete successfully and had errors:
|
||||
--> hyperv-iso.win11: Script exited with non-zero exit status: 2147943462. Allowed exit codes are: [0]
|
||||
|
||||
==> Builds finished but no artifacts were created.
|
||||
@@ -0,0 +1 @@
|
||||
51192
|
||||
@@ -0,0 +1,25 @@
|
||||
# Zero out free space so the resulting .box file is small.
|
||||
#
|
||||
# Runs last, just before Packer initiates shutdown. Cleans caches and the WU
|
||||
# component store, which directly shrinks the on-disk footprint of the VHDX.
|
||||
# We intentionally do not call `cipher /w:C:\` here: it costs 15–30 minutes
|
||||
# and its only payoff is better tarball compression of unused space, which
|
||||
# only matters when shipping the box. Re-add it before publishing publicly.
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
# Clean Windows Update download cache and component store.
|
||||
Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -Path "$env:WINDIR\SoftwareDistribution\Download\*" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
& dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase /Quiet
|
||||
|
||||
# Empty Recycle Bin and temp dirs.
|
||||
Get-ChildItem -Path "$env:TEMP" -Force -ErrorAction SilentlyContinue |
|
||||
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Clear-RecycleBin -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Light defrag pass to consolidate free extents. Quick on an SSD-backed VHDX.
|
||||
& defrag C: /U /V
|
||||
|
||||
# Drop hibernation file (Windows 11 has it on by default).
|
||||
& powercfg /h off
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generic OS tweaks applied after WinRM is reachable.
|
||||
#
|
||||
# Idempotent; safe to rerun.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Make sure the remote-elevated token policy survives any unattended changes.
|
||||
$policy = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
|
||||
New-ItemProperty -Path $policy -Name 'LocalAccountTokenFilterPolicy' `
|
||||
-PropertyType DWord -Value 1 -Force | Out-Null
|
||||
|
||||
# Disable telemetry "Connected User Experiences" service to avoid background
|
||||
# network noise that competes with the bootstrapper.
|
||||
$svc = Get-Service -Name DiagTrack -ErrorAction SilentlyContinue
|
||||
if ($svc) {
|
||||
Set-Service -Name DiagTrack -StartupType Disabled
|
||||
Stop-Service -Name DiagTrack -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Disable SmartScreen network checks (slows winget installs in the lab).
|
||||
$ss = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System'
|
||||
New-Item -Path $ss -Force | Out-Null
|
||||
New-ItemProperty -Path $ss -Name 'EnableSmartScreen' -PropertyType DWord -Value 0 -Force | Out-Null
|
||||
|
||||
# Force NTP resync; Hyper-V time-sync after sysprep can drift, which breaks
|
||||
# TLS handshakes against winget / GitHub.
|
||||
Start-Service -Name w32time -ErrorAction SilentlyContinue
|
||||
& w32tm /resync /force 2>&1 | Out-Null
|
||||
@@ -0,0 +1,12 @@
|
||||
# Turn off Defender cloud-delivered protection and sample submission.
|
||||
#
|
||||
# Cloud lookups add tens of seconds per winget install on a cold box and have
|
||||
# no value in an ephemeral integration VM.
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
Set-MpPreference -MAPSReporting Disabled -ErrorAction SilentlyContinue
|
||||
Set-MpPreference -SubmitSamplesConsent NeverSend -ErrorAction SilentlyContinue
|
||||
Set-MpPreference -DisableBlockAtFirstSeen $true -ErrorAction SilentlyContinue
|
||||
Set-MpPreference -DisableIOAVProtection $true -ErrorAction SilentlyContinue
|
||||
Set-MpPreference -ScanAvgCPULoadFactor 5 -ErrorAction SilentlyContinue
|
||||
@@ -0,0 +1,26 @@
|
||||
# Disable Windows Update on the gold image so it doesn't fight with the
|
||||
# bootstrapper's own WindowsUpdateCommand step at integration-test time.
|
||||
#
|
||||
# This is the precise mitigation for the 6-hour CI hang investigation: by
|
||||
# the time the bootstrapper runs, no other process is holding the WU agent.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$au = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU'
|
||||
New-Item -Path $au -Force | Out-Null
|
||||
New-ItemProperty -Path $au -Name 'NoAutoUpdate' -PropertyType DWord -Value 1 -Force | Out-Null
|
||||
New-ItemProperty -Path $au -Name 'AUOptions' -PropertyType DWord -Value 1 -Force | Out-Null
|
||||
New-ItemProperty -Path $au -Name 'ScheduledInstallDay' -PropertyType DWord -Value 0 -Force | Out-Null
|
||||
New-ItemProperty -Path $au -Name 'NoAutoRebootWithLoggedOnUsers' -PropertyType DWord -Value 1 -Force | Out-Null
|
||||
|
||||
$wu = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
|
||||
New-Item -Path $wu -Force | Out-Null
|
||||
New-ItemProperty -Path $wu -Name 'DoNotConnectToWindowsUpdateInternetLocations' -PropertyType DWord -Value 1 -Force | Out-Null
|
||||
|
||||
foreach ($svc in 'wuauserv','UsoSvc','WaaSMedicSvc') {
|
||||
$s = Get-Service -Name $svc -ErrorAction SilentlyContinue
|
||||
if ($s) {
|
||||
Set-Service -Name $svc -StartupType Disabled -ErrorAction SilentlyContinue
|
||||
Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# Install the Vagrant insecure SSH key marker so `vagrant ssh-config` and
|
||||
# tooling that expects the Vagrant-flavoured user account is happy. WinRM is
|
||||
# the actual communicator, but several Vagrant plugins probe for this layout.
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
$sshDir = "$env:USERPROFILE\.ssh"
|
||||
New-Item -ItemType Directory -Path $sshDir -Force | Out-Null
|
||||
|
||||
$keyUrl = 'https://raw.githubusercontent.com/hashicorp/vagrant/main/keys/vagrant.pub'
|
||||
try {
|
||||
Invoke-WebRequest -UseBasicParsing -Uri $keyUrl -OutFile "$sshDir\authorized_keys"
|
||||
} catch {
|
||||
Write-Host "warning: could not fetch vagrant insecure public key: $($_.Exception.Message)"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Vagrantfile fragment baked into the output .box file by the
|
||||
# `packer-post-processor-vagrant` plugin. This is what every `vagrant up`
|
||||
# of a derived box inherits before our top-level Vagrantfile is applied.
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.communicator = "winrm"
|
||||
config.vm.guest = :windows
|
||||
config.winrm.username = "vagrant"
|
||||
config.winrm.password = "vagrant"
|
||||
config.winrm.transport = :plaintext
|
||||
config.winrm.basic_auth_only = true
|
||||
config.vm.boot_timeout = 1800
|
||||
|
||||
config.vm.provider "hyperv" do |hv|
|
||||
hv.enable_virtualization_extensions = false
|
||||
hv.linked_clone = true
|
||||
hv.vm_integration_services = {
|
||||
guest_service_interface: true,
|
||||
heartbeat: true,
|
||||
shutdown: true,
|
||||
time_synchronization: true,
|
||||
vss: true,
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,157 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
hyperv = {
|
||||
version = ">= 1.1.4"
|
||||
source = "github.com/hashicorp/hyperv"
|
||||
}
|
||||
vagrant = {
|
||||
version = ">= 1.1.5"
|
||||
source = "github.com/hashicorp/vagrant"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Inputs ───────────────────────────────────────────────────────────────────
|
||||
|
||||
variable "iso_path" {
|
||||
type = string
|
||||
description = "Absolute path to the Windows 11 installer ISO. Set via PKR_VAR_iso_path (load .env first; see README)."
|
||||
}
|
||||
|
||||
variable "iso_checksum" {
|
||||
type = string
|
||||
description = "Checksum of the ISO (e.g. 'sha256:<hex>'); 'none' skips validation. Set via PKR_VAR_iso_checksum."
|
||||
default = "none"
|
||||
}
|
||||
|
||||
variable "vm_name" {
|
||||
type = string
|
||||
default = "bootstrap-win11"
|
||||
}
|
||||
|
||||
variable "output_directory" {
|
||||
type = string
|
||||
default = "output-bootstrap-win11"
|
||||
}
|
||||
|
||||
variable "box_output" {
|
||||
type = string
|
||||
default = "bootstrap-win11.box"
|
||||
}
|
||||
|
||||
variable "cpus" {
|
||||
type = number
|
||||
default = 24
|
||||
description = "vCPUs allocated to the build VM. The Packer guest is throwaway and gets the full host budget for speed; the runtime integration VM keeps a smaller footprint in test/integration/Vagrantfile."
|
||||
}
|
||||
|
||||
variable "memory" {
|
||||
type = number
|
||||
default = 32768
|
||||
description = "Build-VM memory (MB). Throwaway VM, full host budget."
|
||||
}
|
||||
|
||||
variable "disk_size" {
|
||||
type = number
|
||||
default = 81920
|
||||
description = "Guest disk size in MB."
|
||||
}
|
||||
|
||||
variable "switch_name" {
|
||||
type = string
|
||||
default = "Default Switch"
|
||||
}
|
||||
|
||||
variable "winrm_username" {
|
||||
type = string
|
||||
default = "vagrant"
|
||||
}
|
||||
|
||||
variable "winrm_password" {
|
||||
type = string
|
||||
default = "vagrant"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# ─── Source ───────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# Hyper-V Gen 2 + Secure Boot is required for modern Windows 11. The
|
||||
# unattend payload lives on a small ISO that Packer builds on the fly from
|
||||
# `cd_files` and attaches as a secondary drive; Windows Setup auto-discovers
|
||||
# `autounattend.xml` on any attached media at the root.
|
||||
|
||||
source "hyperv-iso" "win11" {
|
||||
vm_name = var.vm_name
|
||||
iso_url = var.iso_path
|
||||
iso_checksum = var.iso_checksum
|
||||
|
||||
cpus = var.cpus
|
||||
memory = var.memory
|
||||
disk_size = var.disk_size
|
||||
|
||||
generation = 2
|
||||
enable_secure_boot = true
|
||||
secure_boot_template = "MicrosoftWindows"
|
||||
enable_tpm = true
|
||||
enable_dynamic_memory = false
|
||||
enable_virtualization_extensions = false
|
||||
guest_additions_mode = "disable"
|
||||
switch_name = var.switch_name
|
||||
|
||||
output_directory = var.output_directory
|
||||
|
||||
communicator = "winrm"
|
||||
winrm_username = var.winrm_username
|
||||
winrm_password = var.winrm_password
|
||||
winrm_timeout = "2h"
|
||||
winrm_use_ssl = false
|
||||
winrm_insecure = true
|
||||
|
||||
# Gen 2 UEFI shows "Press any key to boot from CD or DVD..." for ~5s.
|
||||
# Default boot_wait is 10s (already past the prompt) and default
|
||||
# boot_command is empty, so without these the firmware falls through to
|
||||
# PXE and Windows Setup never starts. Hammer ENTER early to catch it.
|
||||
boot_wait = "1s"
|
||||
boot_command = ["<enter><wait><enter><wait><enter>"]
|
||||
|
||||
shutdown_command = "shutdown /s /t 10 /f /d p:4:1 /c \"Packer Shutdown\""
|
||||
shutdown_timeout = "30m"
|
||||
|
||||
# Build a tiny "PROVISION" ISO containing the unattend file. The
|
||||
# FirstLogonCommands in autounattend.xml enable WinRM inline (no helper
|
||||
# script needed) since Windows Setup may detach secondary DVDs after
|
||||
# install, leaving the PROVISION volume unreachable at first logon.
|
||||
cd_files = [
|
||||
"./cd/autounattend.xml",
|
||||
]
|
||||
cd_label = "PROVISION"
|
||||
}
|
||||
|
||||
# ─── Build ────────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# Provisioners run *after* WinRM is reachable, which is after OOBE has finished
|
||||
# and the autounattend's FirstLogonCommands have brought WinRM online. Each
|
||||
# script is small and single-purpose; see comments in each .ps1.
|
||||
|
||||
build {
|
||||
sources = ["source.hyperv-iso.win11"]
|
||||
|
||||
provisioner "powershell" {
|
||||
elevated_user = var.winrm_username
|
||||
elevated_password = var.winrm_password
|
||||
scripts = [
|
||||
"./scripts/configure-os.ps1",
|
||||
"./scripts/disable-windows-updates.ps1",
|
||||
"./scripts/disable-defender-cloud.ps1",
|
||||
"./scripts/install-vagrant-key.ps1",
|
||||
"./scripts/compact.ps1",
|
||||
]
|
||||
}
|
||||
|
||||
post-processor "vagrant" {
|
||||
output = var.box_output
|
||||
keep_input_artifact = false
|
||||
provider_override = "hyperv"
|
||||
vagrantfile_template = "./vagrantfile-template.rb"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user