Home

Awesome

This builds Windows 11/2022/2025 base Vagrant boxes using Packer and Hyper-V/libvirt/QEMU/Proxmox VE/VMware vSphere.

Usage

Install a supported hypervisor (e.g. libvirt), packer 1.10+ and vagrant. If you are using Windows and Chocolatey, you can install the tools (you still need to install Hyper-V) from an administrative PowerShell session with:

choco install -y packer vagrant msys2

# configure the msys2 launcher to let the shell inherit the PATH.
$msys2BasePath = 'C:\tools\msys64'
$msys2ConfigPath = "$msys2BasePath\msys2.ini"
[IO.File]::WriteAllText(
    $msys2ConfigPath,
    ([IO.File]::ReadAllText($msys2ConfigPath) `
        -replace '#?(MSYS2_PATH_TYPE=).+','$1inherit')
)

# define a function for easing the execution of bash scripts.
$bashPath = "$msys2BasePath\usr\bin\bash.exe"
function Bash($script) {
    $eap = $ErrorActionPreference
    $ErrorActionPreference = 'Continue'
    try {
        # we also redirect the stderr to stdout because PowerShell
        # oddly interleaves them.
        # see https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin
        echo 'exec 2>&1;set -eu;export PATH="/usr/bin:$PATH";export HOME=$USERPROFILE;' $script | &$bashPath
        if ($LASTEXITCODE) {
            throw "bash execution failed with exit code $LASTEXITCODE"
        }
    } finally {
        $ErrorActionPreference = $eap
    }
}

Bash 'pacman --noconfirm -Sy make zip unzip tar p7zip dos2unix xorriso'

Open a bash shell by starting C:\tools\msys64\mingw64.exe and execute the remaining commands inside it.

To build the base box based on the Windows Server 2022 Evaluation ISO run:

make build-windows-2022-libvirt

If you want to use your own ISO, you need to manually run the packer command, e.g.:

# NB when the windows product key does not match the windows version and edition
#    inside the iso file, the windows setup will fail with the error message:
#       No images are available.
#    inside the windows setup, press shit+f10 to open a command prompt, then
#    verify the available images with:
#       dism -get-imageinfo -imagefile:d:\sources\install.wim
# see https://learn.microsoft.com/en-us/windows-server/get-started/kms-client-activation-keys
PKR_VAR_iso_url='windows-2022.iso' \
PKR_VAR_iso_checksum='none' \
PKR_VAR_windows_product_key='VDYBN-27WPP-V4HQT-9VMD4-VMK7H' \
  make build-windows-2022-libvirt

NB if the build fails with something like Post-processor failed: write /tmp/packer073329394/packer-windows-2022-amd64-libvirt-1505050546-disk001.vmdk: no space left on device you need to increase your temporary partition size or change its location as described in the packer TMPDIR/TMP environment variable documentation.

NB if you are having trouble building the base box due to floppy drive removal errors try adding, as a workaround, "post_shutdown_delay": "30s", to the windows-2022.pkr.hcl file.

NB the packer logs are saved inside a *-packer.log file (e.g. windows-2022-amd64-libvirt-packer.log).

You can then add the base box to your local vagrant installation with:

vagrant box add -f windows-2022-amd64 windows-2022-amd64-libvirt.box

And test this base box by launching an example Vagrant environment:

cd example
vagrant plugin install vagrant-windows-sysprep
vagrant up --no-destroy-on-error --provider=libvirt
vagrant ssh
exit
vagrant destroy -f
cd ..

NB if you are having trouble running the example with the vagrant libvirt provider check the libvirt logs in the host (e.g. sudo tail -f /var/log/libvirt/qemu/example_default.log) and in the guest (inside C:\Windows\Temp).

Then test with a more complete example:

git clone https://github.com/rgl/customize-windows-vagrant
cd customize-windows-vagrant
vagrant up --no-destroy-on-error --provider=libvirt
vagrant ssh
exit
vagrant destroy -f
cd ..

List this repository dependencies (and which have newer versions):

export GITHUB_COM_TOKEN='YOUR_GITHUB_PERSONAL_TOKEN'
./renovate.sh

libvirt

Build the base box for the vagrant-libvirt provider with:

make build-windows-2022-libvirt

If you want to access the UI run:

spicy --uri 'spice+unix:///tmp/packer-windows-2022-amd64-libvirt-spice.socket'

NB the packer template file defines qemuargs (which overrides the default packer qemu arguments), if you modify it, verify if you also need include the default packer qemu arguments (see builder/qemu/step_run.go or start packer without qemuargs defined to see how it starts qemu).

Proxmox VE usage

Install Proxmox VE.

NB This assumes Proxmox VE was installed alike rgl/proxmox-ve.

Set your Proxmox VE details:

cat >secrets-proxmox.sh <<EOF
export PROXMOX_URL='https://192.168.1.21:8006/api2/json'
export PROXMOX_USERNAME='root@pam'
export PROXMOX_PASSWORD='vagrant'
export PROXMOX_NODE='pve'
EOF
source secrets-proxmox.sh

Create the template:

make build-windows-2022-proxmox

NB There is no way to use the created template with vagrant (the vagrant-proxmox plugin is no longer compatible with recent vagrant versions). Instead, use packer (e.g. like in this repository) or terraform (e.g. see rgl/terraform-proxmox-windows-example).

Hyper-V usage

Install Hyper-V.

Make sure your user is in the Hyper-V Administrators group or you run with Administrative privileges.

Hyper-V automatically creates the Default Switch VM Switch and the vEthernet (Default Switch) network adapter/interface. It provides DHCP, DNS forwarding, and NAT internet access. But it cannot be configured, and it changes the assigned IP addresses at every boot; this makes it unusable for me. Instead you should run your own DHCP service and NAT virtual network.

Create the Vagrant vSwitch and NAT network in a PowerShell with Administrative privileges:

$name = 'Vagrant'
$ipAddress = '192.168.192.1'
$ipAddressPrefix = '24'

# create the vSwitch.
$vmSwitch = New-VMSwitch -SwitchName $name -SwitchType Internal

# reconfigure the vSwitch IP configuration to use a known IP and network and disable IPv6.
$netAdapterName = "vEthernet ($name)"
$netAdapter = Get-NetAdapter -Name $netAdapterName
$netAdapter | Disable-NetAdapterBinding -ComponentID ms_tcpip6
$netAdapter | Remove-NetIPAddress -Confirm:$false
$netAdapter | New-NetIPAddress -IPAddress $ipAddress -PrefixLength $ipAddressPrefix

# create the NAT network.
New-NetNat -Name $name -InternalIPInterfaceAddressPrefix "$ipAddress/$ipAddressPrefix"

Then, install and start the WinDHCP DHCP service.

Make sure the Virtual Switch (its vEthernet network adapter) is excluded from the Windows Firewall protected network connections by executing the following commands in a bash shell with Administrative privileges:

PowerShell -Command 'Get-NetFirewallProfile | Select-Object -Property Name,DisabledInterfaceAliases'
PowerShell -Command 'Set-NetFirewallProfile -DisabledInterfaceAliases (Get-NetAdapter -name "vEthernet*" | Where-Object {$_.ifIndex}).InterfaceAlias'

Create the base image in a bash shell with Administrative privileges:

cat >secrets.sh <<'EOF'
# set this value when you need to set the VM Switch Name.
export HYPERV_SWITCH_NAME='Vagrant'

# set this value when you need to set the VM VLAN ID.
#export HYPERV_VLAN_ID=''

# set the credentials that the guest will use
# to connect to this host smb share.
# NB you should create a new local user named _vagrant_share
#    and use that one here instead of your user credentials.
# NB it would be nice for this user to have its credentials
#    automatically rotated, if you implement that feature,
#    let me known!
export VAGRANT_SMB_USERNAME='_vagrant_share'
export VAGRANT_SMB_PASSWORD=''

# remove the virtual switch from the windows firewall.
# NB execute if the VM fails to obtain an IP address from DHCP.
PowerShell -Command 'Set-NetFirewallProfile -DisabledInterfaceAliases (Get-NetAdapter -name "vEthernet*" | Where-Object {$_.ifIndex}).InterfaceAlias'
EOF
source secrets.sh
time make build-windows-2022-hyperv

Try the example guest:

NB You will need Administrative privileges to create the SMB share.

cd example
# grant $VAGRANT_SMB_USERNAME full permissions to the
# current directory.
# NB you must first install the Carbon PowerShell module
#    with choco install -y carbon.
# TODO set VM screen resolution.
PowerShell -Command 'Import-Module Carbon; Grant-Permission . $env:VAGRANT_SMB_USERNAME FullControl'
vagrant up --no-destroy-on-error --provider=hyperv
vagrant ssh
exit
vagrant destroy -f

VMware vSphere

Download the Windows Evaluation ISO (you can find the full iso URL in the windows-2022-vsphere.pkr.hcl file) and place it inside the datastore as defined by the vsphere_iso_url user variable that is inside the packer template.

Download the VMware Tools VMware-tools-windows-<SAME_VERSION_AS_IN_PACKER_TEMPLATE>.iso file into the datastore defined by the vsphere_tools_iso_url user variable that is inside the packer template.

Download govc and place it inside your /usr/local/bin directory.

Install the vsphere vagrant plugin, set your vSphere details, and test the connection to vSphere:

sudo apt-get install build-essential patch ruby-dev zlib1g-dev liblzma-dev
vagrant plugin install vagrant-vsphere
vagrant plugin install vagrant-windows-sysprep
cat >secrets.sh <<'EOF'
export GOVC_INSECURE='1'
export GOVC_HOST='vsphere.local'
export GOVC_URL="https://$GOVC_HOST/sdk"
export GOVC_USERNAME='administrator@vsphere.local'
export GOVC_PASSWORD='password'
export GOVC_DATACENTER='Datacenter'
export GOVC_CLUSTER='Cluster'
export GOVC_DATASTORE='Datastore'
export VSPHERE_ESXI_HOST='esxi.local'
export VSPHERE_TEMPLATE_FOLDER='test/templates'
# NB the VSPHERE_TEMPLATE_NAME last segment MUST match the
#    builders.vm_name property inside the packer template.
export VSPHERE_TEMPLATE_NAME="$VSPHERE_TEMPLATE_FOLDER/windows-2022-amd64-vsphere"
export VSPHERE_TEMPLATE_IPATH="//$GOVC_DATACENTER/vm/$VSPHERE_TEMPLATE_NAME"
export VSPHERE_VM_FOLDER='test'
export VSPHERE_VM_NAME='windows-2022-vagrant-example'
export VSPHERE_VLAN='packer'
# set the credentials that the guest will use
# to connect to this host smb share.
# NB you should create a new local user named _vagrant_share
#    and use that one here instead of your user credentials.
# NB it would be nice for this user to have its credentials
#    automatically rotated, if you implement that feature,
#    let me known!
export VAGRANT_SMB_USERNAME='_vagrant_share'
export VAGRANT_SMB_PASSWORD=''
EOF
source secrets.sh
# see https://github.com/vmware/govmomi/blob/master/govc/USAGE.md
govc version
govc about
govc datacenter.info # list datacenters
govc find # find all managed objects

Build the base box with:

make build-windows-2022-vsphere

Try the example guest:

source secrets.sh
cd example
# check if you are using the expected template.
echo "$VSPHERE_TEMPLATE_NAME"
# start the vm.
vagrant up --no-destroy-on-error --provider=vsphere
# using ssh, open a remote shell session.
vagrant ssh
# exit the remove shell session.
exit
# run a command (thru the vmware tools daemon service instead of ssh).
export GOVC_GUEST_LOGIN='vagrant:vagrant'
VSPHERE_VM_IPATH="//$GOVC_DATACENTER/vm/$VSPHERE_VM_FOLDER/$VSPHERE_VM_NAME"
govc guest.run -vm.ipath "$VSPHERE_VM_IPATH" whoami /all
# destroy the vm.
vagrant destroy -f

Non-Administrator account

The above example uses the administrator account, but you can use a less privileged account like in the following example.

Example

First, review the glossary:

<dl> <dt>Privilege</dt> <dd>The ability to perform a specific action or read a specific property.</dd> <dt>Role</dt> <dd>A collection of privileges. Roles provide a way to aggregate all the individual privileges that are required to perform a higher-level task.</dd> <dt>Permission</dt> <dd>Consists of a user or group and an assigned role for an inventory object.</dd> </dl>

Then follow the next steps to create an example configuration.

In the vSphere Single Sign-On (SSO) configuration page create a Vagrants group and add your non-administrator user to it.

In the vSphere Access Control page create a Vagrant role with the privileges:

In vSphere configure the following Inventory Objects permissions:

Inventory ObjectRolePrincipal (User or Group)Propagate
DatacenterVagrantVSPHERE.LOCAL\Vagrantsyes
testAdministratorVSPHERE.LOCAL\Vagrantsyes

NB test is a folder that will store the virtual machines launched by vagrant.

For more information see the vSphere Virtual Machine Administration/Required Privileges for Common Tasks document in the vSphere Virtual Machine Administration manual.

SSH access

You can connect to this machine through SSH to run a remote command, e.g.:

ssh -p 2222 vagrant@localhost "whoami /all"

NB the exact SSH address and port can be obtained with vagrant ssh-config.

NB we cannot use the vagrant SMB shared folder type when using the winssh communicator; it will fail to mount the shared folder with the error:

cmdkey /add:192.168.1.xxx /user:xxx /pass:"*****"
CMDKEY: Credentials cannot be saved from this logon session.

NB this is a Windows design restriction that prevents remote network logon sessions from accessing certain parts of the system.

NB this is why the default vagrant box communicator is winrm.

PowerShell Remoting over SSH

You can connect to this machine through PowerShell Remoting over SSH. In a Linux (or Windows) PowerShell 7 session execute, e.g.:

Enter-PSSession -HostName vagrant@localhost:2222
$PSVersionTable
whoami /all
exit

WinRM access

You can connect to this machine through WinRM to run a remote command. In a Windows Command Prompt session execute, e.g.:

winrs -r:localhost:55985 -u:vagrant -p:vagrant "whoami /all"

NB the exact local WinRM port should be displayed by vagrant, in this case:

==> default: Forwarding ports...
    default: 5985 (guest) => 55985 (host) (adapter 1)

PowerShell Remoting over WinRM

You can connect to this machine through PowerShell Remoting over WinRM. In a Windows PowerShell 7 session execute, e.g.:

# Configure this machine WinRM client to trust all remote servers.
# NB Since this local client machine is not in the AD nor its using HTTPS to
#    access the server, we must configure it to trust the server, or in this
#    case, trust all servers.
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force

# Open a session and execute commands remotely.
# NB To open a PowerShell 5 session, remove the -ConfigurationName argument.
Enter-PSSession -ConfigurationName PowerShell.7 -ComputerName localhost -Port 55985 -Credential vagrant
Get-PSSessionConfiguration  # show the available configurations.
$PSVersionTable             # show the powershell version.
whoami /all                 # show the user permissions.
exit                        # exit the session.

WinRM and UAC (aka LUA)

This base image uses WinRM. WinRM poses several limitations on remote administration, those were worked around by disabling User Account Control (UAC) (aka Limited User Account (LUA)) in autounattend.xml and UAC remote restrictions in provision-winrm.ps1.

If needed, you can later enable them with:

Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Name EnableLUA -Value 1
Set-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Policies\System' -Name EnableLUA -Value 1
Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Name LocalAccountTokenFilterPolicy
Restart-Computer

Or disable them with:

Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Name EnableLUA -Value 0
Set-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Policies\System' -Name EnableLUA -Value 0
New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Name LocalAccountTokenFilterPolicy -Value 1 -Force
Restart-Computer

Windows Unattended Installation

When Windows boots from the installation media its Setup application loads the e:\autounattend.xml file. It contains all the answers needed to automatically install Windows without any human intervention. For more information on how this works see OEM Windows Deployment and Imaging Walkthrough.

Where there is a problem with the setup, you should look into the Setup log files (Windows Preinstallation Environment phase) by pressing Ctrl+F10 to open a Command Prompt, and then use notepad.exe to open the Setup log files.

autounattend.xml was generated with the Windows System Image Manager (WSIM) application that is included in the Windows Assessment and Deployment Kit (ADK).

Windows ADK

To create, edit and validate the e:\autounattend.xml file you need to install the Deployment Tools that are included in the Windows ADK.

If you are having trouble installing the ADK (adksetup) or running WSIM (imgmgr) when your machine is on a Windows Domain and the log has:

Image path is [\??\C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\DISM\wimmount.sys]
Could not acquire privileges; GLE=0x514
Returning status 0x514

It means there's a group policy that is restricting your effective permissions, for an workaround, run adksetup and imgmgr from a SYSTEM shell, something like:

psexec -s -d -i cmd
adksetup
cd "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\WSIM"
imgmgr

For more information see Error installing Windows ADK.