- Quick Note
- What It Does
- Prerequisites
- Usage
- Project Structure
- Customization
- Technical Notes
- Acknowledgements
- Builds Windows Server 2025 template ("golden image"); from download to final export as
.vhdx. - Whole process is automated.
- Not hardened; intended for lab/test Hyper-V use. But can be customized to provide hardening.
- Uses Packer, works on Hyper-V, connects to VM via SSH (SSH communicator is used, not WinRM).
- Provisioned
svc-deployadmin\service account during first boot; to be used with pub\priv key authentication via SSH. - Provisioned
admin-deployadmin account, using Windows Credential Manager. Allows PowerShell Direct connection to the VM, even if the VM has no network connectivity, provided Integration Services are enabled.
-
Windows 10/11/Server with Hyper-V enabled.
-
Packer 1.8+ on PATH.
-
PowerShell 5.1+ (PS 7 recommended).
-
Sufficient disk space (~30 GB per template + ISOs).
-
(optional) SSH client (Should be provided with Windows feature OpenSSH)
- In case you want to connect directly to VM using ssh
-
By default, the normal/original ISO is used (no_prompt ISO is not generated unless explicitly enabled). To allow automated boot from original ISO:
- Secure Boot must be disabled for the VM.
- No other Hyper-V connection to the VM must be open during build (close all VMConnect/console windows).
- Enhanced Session must be disabled.
-
For PowerShell Direct (after template is build, to manage new VM without network or IP):
- VM must be running and have Hyper-V integration services enabled.
- Requires PowerShell module
TUN.CredentialManager, to install, run:Install-Module -Name TUN.CredentialManager
Optional: If you want to use the no_prompt ISO method (to skip the "Press any key to boot..." prompt):
- Enable
-Use_No_Prompt_Iso $true. - Windows ADK (for
efisys_noPrompt.bin) - script expects ADK atC:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\(override via-NoPromptBootFile).
Works for Windows Server 2025 Evaluation. Expected SHA256 related to download link is defined in files\windows\windows_image_catalog.json
By default, the process uses the normal/original ISO for installation. No no_prompt ISO is generated unless you explicitly enable it.
If you want to use the no_prompt ISO method (to skip the "Press any key to boot..." prompt):
- Enable
-Use_No_Prompt_Iso $true. - Windows ADK (for
efisys_noPrompt.bin) is required.
When using the normal/original ISO (default):
- Secure Boot must be disabled for the VM
- No other Hyper-V connection to the VM is open (close all VMConnect/console windows)
- Enhanced Session is disabled These requirements are necessary to allow automated boot from the original ISO.
3) Builds a secondary (provisioning) ISO with autounattend.xml, unattend.xml, bootstrap and first-boot scripts.
Autounattend selection: build-specific autounattend.xml (Standard\Datacenter, Core\GUI) is copied as autounattend.xml to secondary_iso.iso.
Provisioning payload: bootstrap.ps1, unattend.xml, scripts, settings, and sshd config are copied to a temp directory, then whole directory is packed into secondary_iso.iso.
4) Runs Packer (Hyper-V provider, Gen2, UEFI) using primary + secondary ISOs to create a ready-to-use template.
Installation flow is driven by the autounattend from step 3; customization lives in bootstrap.ps1.
If original ISO from step 2 is used: Packer enters boot_command into Hyper-V VM connection windows.
If no_prompt ISO from step 2 is used, no boot_command or manual intervention is needed.
autounattend.xml calls bootstrap.ps1. bootstrap.ps1:
- Installs Scoop (package manager, can be used for automated provisioning on finalized VM)
- PowerShell 7 (pwsh)
- CLI tools (
microtext editor,farfile manager,batcat with colours) for more convenient interactive SSH sessions. - Runs UI/CLI tweaks (don’t start Server Manager, custom prompt, etc.).
- Runs
Enable-Connector-SSH_packer.ps1to install/configure sshd for Packer and later access. - Copies
unattend.xmltoC:\Windows\System32\Sysprep\,unattend.xmldefines first-boot behavior.
After SSH connection is established, Packer connects and runs a script to prepare the template for sysprep (currently just a dummy script). Packer also creates the admin-deploy account in the VM, injecting a password securely from Windows Credential Manager.
Sysprep generalizes the image (referencing unattend.xml), then Packer exports the template as .vhdx.
From template, new VMs are created using differential disks (parent/child VHDX).
Offline SSH key injection: Mount the differential disk, inject SSH public key to C:\bootstrap\svc-deploy\authorized_keys, which FirstBoot.ps1 copies to svc-deploy user's .ssh\authorized_keys during first boot (no WinRM/credentials needed pre-boot).
See usage_examples\Create-VMwithSshKey.ps1 for complete workflow example.
Then, when VM is first started:
Unattend.xml orchestrates first boot: Runs FirstBoot.ps1 which creates svc-deploy admin account with random password (logged), sets up SSH authorized_keys structure for offline injection, and finalizes SSH configuration.
PowerShell Direct connection
And\or you or automation tool like Terraform can connect to VM using admin-deploy account.
Clone or otherwise download repo.
Navigate to <repo_root>\build_template.
From here, run one of the scripts:
- build_WINDOWS_SERVER_2025_EVAL_DATACENTER_CORE.ps1
- build_WINDOWS_SERVER_2025_EVAL_DATACENTER_GUI.ps1
- build_WINDOWS_SERVER_2025_EVAL_STANDARD_CORE.ps1
- build_WINDOWS_SERVER_2025_EVAL_STANDARD_GUI.ps1
Scripts cover all possible combinations of editions and experiences for Windows Server 2025 Evaluation ISO (Standard\Datacenter, Core\GUI).
Options are \ reflect what you'll see if you mount .iso and enumerate images:
dism /Get-WimInfo /WimFile:"$drive:\sources\install.wim"You can switch Windows editions — from Standard → Datacenter or Evaluation → Paid after you build VM.
The scripts are just "wrappers" to call ...\pipelines\Build-WindowsTemplate.ps1. There is no need to call the pipeline directly with parameters, as all implemented options are covered. Run Get-Help .\Build-WindowsTemplate.ps1 -Detailed or check script itself for details.
By default, the build uses the normal/original ISO (no_prompt ISO is not generated).
If you want to use the no_prompt ISO method (to skip the "Press any key to boot..." prompt), set -Use_No_Prompt_Iso $true.
Here is explanation of some parameters, in case you need to run them more than once or adjust:
Build-WindowsTemplate.ps1 `
-IsoId "WINDOWS_SERVER_2025_EVAL" ` # One of possible ids from .json catalog file
-ImageOption "Windows Server 2025 Datacenter Evaluation" ` # one of possible images stored in .wim file on .iso
-OverwriteDownloadedIso $false `
-CompareChecksums $true ` # validates download, makes sense to change to $false after first download
-Use_No_Prompt_Iso $false ` # $false by default => uses original .iso (no no_prompt ISO generated)
-OverwriteNoPromptIso $false ` # only needed if Use_No_Prompt_Iso is $true
-NoPromptBootFile = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\efisys_noPrompt.bin"
# Only needed if Use_No_Prompt_Iso is $true; defaults to where Windows ADK is expected to be installedAfter build is done, check output for generated artifacts, namely output\packer\windows_server_2025_..your...build\ for .vhdx. Can be used as template now.
You (and automation) can connect into newly build machines by two ways:
This is convenient if the new VM build from the template have no IP address or no network set up.
Uses account admin-deploy with pre-generated password stored in Credential Manager.
You can run e.g. Invoke-Command, from Terraform, using existing credentials:
resource "null_resource" "set_vm_ip" {
provisioner "local-exec" {
command =
powershell -NoProfile -Command "Invoke-Command -VMName 'test_VM_2' -Credential (Get-StoredCredential -Target 'packer_admin-deploy') -ScriptBlock {
# Your PowerShell code here, e.g.:
Get-NetIPAddress
}"
}
}Use Enter-PSSession for interactive session; credentials for the admin-deploy account are securely retrieved from Windows Credential Manager, so you never need to pass them as plain text or via "pop-up" credential prompt.
Enter-PSSession -VMName <name of your VM> -Credential (Get-StoredCredential -Target "packer_admin-deploy") Use as parent disk for new differential, inject there <drive>\\bootstrap\svc-deploy\authorised_keys (with content of $env:USERPROFILE\.ssh\ed25519_svc-deploy_hyperv_vm.pub) to be used by svc-deploy, set new VM to boot from new disk.
This can be done by script or via Terraform (example using null_resource with local-exec provisioner):
resource "null_resource" "inject_ssh_key" {
provisioner "local-exec" {
command = "powershell -NoProfile -Command & '${path.module}/Mount-DiskAndInjectSSHKey.ps1' -VhdPath '${hyperv_vhd.my_vhd.path}' -PublicKeyPath '$${env:USERPROFILE}/.ssh/ed25519_svc-deploy_hyperv_vm.pub'"
}
}Example script which mount new vhdx, injects and build machine - usage_examples\Create-VMwithSshKey.ps1
You can test if it works via
ssh -i $env:USERPROFILE\.ssh\ed25519_svc-deploy_hyperv_vm svc-deploy@<IP of new VM>build_template\pipelines\Build-WindowsTemplate.ps1— Main entrypoint orchestrating the entire build process.build_template\helpers\— Helper scripts:Download-WindowsIso.ps1— Downloads and validates ISO checksumsNew-NoPromptIso.ps1— Injectsefisys_noPrompt.bininto ISONew-IsoFile.ps1- Creates new ISOInvoke-PackerBuild.ps1— Launches Packer with validated variablesEnsure-SshKeyPair.ps1— Generates SSH keys for Packer communicatorNew-LocalStoreCredentials.ps1— Createsadmin-deployand store it in Credential Store
top_dir— Determines top directory of repo
files\windows\windows_image_catalog.json— Single source of truth: ISO URLs, SHA256, editions, autounattend mappings.files\windows\autounattend\— Unattended install XMLs (per edition/SKU). Build-specific files selected by catalog.files\windows\bootstrap.ps1— Orchestrates provisioning steps inside the VM (installs scoop, pwsh, configures SSH, tweaks UI).files\windows\scripts\— Individual provisioning scripts:Enable-Connector-SSH_packer.ps1— Installs and configures OpenSSH for PackerFirstBoot.ps1— Createssvc-deployaccount, sets up SSH keys (runs post-sysprep)packerRun_Prepare-ForSysprep.ps1— Pre-sysprep cleanup
packer\windows\— Packer HCL definitions and.auto.pkrvars.hclvariable overrides.
iso\original\— Downloaded Microsoft ISOs. See iso/original/original_iso.md.iso\no_prompt\— Generated no-prompt variants withefisys_noPrompt.bin. See iso/no_prompt/No_Prompt_iso.md.output\<build_name>\secondary_iso\— Staging directory for secondary ISO contents.output\packer\<template_name>\— Final templates (VHDX + Hyper-V VM configs).
- Add/modify autounattend XMLs and reference them in
windows_image_catalog.json. - Extend provisioning by adding scripts to
files\windows\scripts\and referencing them inbootstrap.ps1. - Define what happens during finalised VM first start, call custom scripts from
files\windows\scripts\FirstBoot.ps1 - Adjust Packer variables in
packer\windows\*.auto.pkrvars.hcl(CPU, RAM, switch).
By default, the normal/original ISO method is used. If it doesn't work in your environment, use the no_prompt ISO method (-Use_No_Prompt_Iso $true).
See Prerequisites for requirements.
Updates aren't provided during instalation \ build. Nor I did experienced any updates interferece with installation process so far. Finalized template reflects the update level at ISO time of release.
Possible ways to install updates:
- Inject updates into custom ISO before build: Add updates MSU offline into Windows images WIM
- Install updates before sysprep (by running script to update OS from
bootstrap.ps1, before sysprep, easier to implement.)
- None implemented. (Can be done by running script to update OS from
bootstrap.ps1, before sysprep)
- Primary ISO (Windows installation media) boots first via Hyper-V DVD drive.
- Secondary ISO (provisioning media) is attached as second DVD drive, available after Windows PE boots.
efisys_noPrompt.binavoids "Press any key to boot…" prompt (more reliable than Packerboot_commandwhich requires enhanced session disabled and precise timing).
Packer warns about a null hash for the primary DVD (no_prompt ISO variant), which is expected.
SHA256 validation confirms that the downloaded file matches the one published online.
But, the no_prompt ISO is generated from the original Windows ISO; New-IsoFile.ps1 retrieves the content of the original downloaded ISO and creates a new one with a different boot file. Many factors affect the SHA256 of the resulting ISO file, so the no_prompt ISO hash cannot be compared to any predetermined value.
The original Windows ISO undergoes SHA256 validation at the start of the build process (see What It Does, Step 1). To skip this validation after the first download, use -CompareChecksums $false:
Build-WindowsTemplate.ps1 -IsoId "WINDOWS_SERVER_2025_EVAL" -ImageOption "Windows Server 2025 Datacenter Evaluation" -CompareChecksums $falseIf the SHA256 of the no_prompt ISO were deterministic, it could verify whether New-IsoFile.ps1 created a valid ISO file with an exact SHA256. If necessary, oscdimg.exe from the Windows ADK can create bootable ISO files and validate them by means other than SHA256 comparison. DISM tools could also help.
Anyway, so far, I have not experienced any issues with no_prompt iso, so validation is probably not necessary.
- Allows connection to VM (post build, VM made from template) even if it have no assigned IP address, which is often the case. I choosed this method as it is enough for home labs and test environments and still much better than using plain text password.
- Can be used manually or with Terraform or other automation.
- No password in plain text or prompt asking for credentials:
- Password is pre-generated by script and stored in Windows native Credential Manager.
- User
admin-deployis created by Packer and credentials are passed. - Requires module
TUN.CredentialManager
During build:
Enable-Connector-SSH_packer.ps1installs sshd, configures firewall, sets authorized_keys forpackeruser- Packer connects via
$env:USERPROFILE\.ssh\ed25519_packer_hyperv_vmkey
Post-deployment (manually, script, or Terraform local-exec):
-
Mount differential disk offline, inject pub key to
C:\bootstrap\svc-deploy\authorized_keys -
On first boot:
FirstBoot.ps1createssvc-deployadmin account, copies key toC:\Users\svc-deploy\.ssh\authorized_keys -
Test connection:
ssh -i $env:USERPROFILE\.ssh\ed25519_svc-deploy_hyperv_vm svc-deploy@<VM_IP> -
svc-deploycreated with 35-character random password (not logged nor writen to console) + pub/priv key authentication. -
svc-deployis intended for Terraform, Ansible, scripts, and other automation.
You can upgrade Windows editions post-build:
- Standard → Datacenter but not vice versa.
- Evaluation → Paid: Provide valid license key after deployment.
Commands to upgrade edition (powershell):
# List available editions for upgrade
DISM /Online /Get-TargetEditions
# Upgrade to target edition (e.g., ServerDatacenter)
DISM /Online /Set-Edition:ServerDatacenter /ProductKey:XXXXX-XXXXX-XXXXX-XXXXX-XXXXX /AcceptEulaYou can check remainding days by running (cmd):
cscript //Nologo C:\Windows\System32\slmgr.vbs /dli
cscript //Nologo C:\Windows\System32\slmgr.vbs /xprEvaluation period starts when VM is build.
New-IsoFile.ps1 - Slight edit of script by TheDotSource