Skip to content

Instantly share code, notes, and snippets.

@chrisoldwood
Created November 25, 2019 16:12
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save chrisoldwood/aeec1e6876dadcc407109896d8d8aac7 to your computer and use it in GitHub Desktop.
Save chrisoldwood/aeec1e6876dadcc407109896d8d8aac7 to your computer and use it in GitHub Desktop.
Example Packer configuration files for creating a Windows 10 VM on QEMU/KVM/libvirt.
<?xml version="1.0" encoding="utf-8"?>
<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>
<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">
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Type>Primary</Type>
<Extend>true</Extend>
</CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add">
<Active>true</Active>
<Extend>false</Extend>
<Format>NTFS</Format>
<Letter>C</Letter>
<Order>1</Order>
<PartitionID>1</PartitionID>
<Label>Windows 10</Label>
</ModifyPartition>
</ModifyPartitions>
<DiskID>0</DiskID>
<WillWipeDisk>true</WillWipeDisk>
</Disk>
</DiskConfiguration>
<ImageInstall>
<OSImage>
<InstallTo>
<DiskID>0</DiskID>
<PartitionID>1</PartitionID>
</InstallTo>
</OSImage>
</ImageInstall>
<UserData>
<AcceptEula>true</AcceptEula>
<FullName>Packer Admin</FullName>
<Organization></Organization>
<ProductKey>
<Key>W269N-WFGWX-YVC9B-4J6C9-T83GX</Key>
</ProductKey>
</UserData>
</component>
<component name="Microsoft-Windows-PnpCustomizationsWinPE" 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">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
<Path>E:\NetKVM\w10\amd64\</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
<Path>E:\viostor\2k16\amd64\</Path>
</PathAndCredentials>
</DriverPaths>
</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">
<UserAccounts>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Password>
<Value>packer</Value>
<PlainText>true</PlainText>
</Password>
<Description></Description>
<DisplayName>Packer Admin</DisplayName>
<Group>Administrators</Group>
<Name>packer</Name>
</LocalAccount>
</LocalAccounts>
</UserAccounts>
<AutoLogon>
<Password>
<Value>packer</Value>
<PlainText>true</PlainText>
</Password>
<Enabled>true</Enabled>
<Username>packer</Username>
</AutoLogon>
<OOBE>
<NetworkLocation>Work</NetworkLocation>
<HideEULAPage>true</HideEULAPage>
<ProtectYourPC>3</ProtectYourPC>
<SkipMachineOOBE>true</SkipMachineOOBE>
<SkipUserOOBE>true</SkipUserOOBE>
</OOBE>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine>
<Description>Set Execution Policy 64 Bit</Description>
<Order>1</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c powershell -File a:\fixnetwork.ps1</CommandLine>
<Description>Fix public network</Description>
<Order>2</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c powershell -File "a:\ConfigureRemotingForAnsible.ps1"</CommandLine>
<Description>Enable WinRM</Description>
<Order>3</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c reg add "HKLM\System\CurrentControlSet\Control\Network\NewNetworkWindowOff"</CommandLine>
<Description>Network prompt</Description>
<Order>4</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
</FirstLogonCommands>
</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>Win10-Packer</ComputerName>
<ProductKey>W269N-WFGWX-YVC9B-4J6C9-T83GX</ProductKey>
</component>
</settings>
</unattend>
{
"builders":
[
{
"vm_name": "windows-10",
"type": "qemu",
"accelerator": "kvm",
"cpus": 1,
"memory": 4096,
"disk_size": 15360,
"iso_url": "Win10_1909_English_x64.iso",
"iso_checksum": "86c16116ebacf9b29e4766dd479b5a79",
"iso_checksum_type": "md5",
"floppy_files":
[
"autounattend.xml",
"fixnetwork.ps1",
"ConfigureRemotingForAnsible.ps1"
],
"output_directory": "qemu-drives",
"qemuargs":
[
[ "-drive", "file=qemu-drives/{{ .Name }},if=virtio,cache=writeback,discard=ignore,format=qcow2,index=1" ],
[ "-drive", "file=./virtio-win.iso,media=cdrom,index=3" ]
],
"communicator": "winrm",
"winrm_username": "packer",
"winrm_password": "packer",
"winrm_use_ssl": "true",
"winrm_insecure": "true",
"winrm_timeout" : "1h",
"shutdown_command": "shutdown /s /t 30 /f",
"shutdown_timeout": "15m"
}
]
}
@chrisoldwood
Copy link
Author

Example of how to create a Windows 10 VM on QMEU/KVM/libvirt using Packer. The .iso and product key are for the Windows 10 Professional evaluation edition (v1909 at the time of writing) and are both publicly accessible.

Links to the Windows VirtIO drivers .iso can be found on the KVM Windows VirtIO Drivers page. You'll also need two PowerShell scripts: fixnetwork.ps1 and ConfigureRemotingForAnsible.ps1 to enable WinRM correctly so that the Packer WinRM provisioner can connect.

packer build windows10.packer.json
virt-install -n windows-10 --vcpus 1 -r 4096 --disk qemu-drives/windows-10,device=disk,bus=virtio,format=qcow2 --os-type windows --print-xml > windows-10.libvirt.xml
virsh define windows-10.libvirt.xml
virsh start windows-10

@sundaramsrini
Copy link

Hi @chrisoldwood,

I know it has been three years. I have a requirement of creating windows image using packer which is not a very common requirement (as your thread is the only most promising information, that I came across). When I tried creating an image I encounter the following error:

image

Looks like packer is trying to boot from one of the '-drive's from the qemuargs. If I remove (just for testing) the qemu section from the packer config, it boots the Windows, but not continuing to configure the machine.

Should I make any change to the config? Please help.

My configuration is as follows:

qemu - 1:6.2+dfsg-2ubuntu6.5
Ubuntu - 22.04.1
packer - 1.8.4

Please let me know if you need more information in this regard.

@chrisoldwood
Copy link
Author

If I remove (just for testing) the qemu section from the packer config, it boots the Windows, but not continuing to configure the machine.

Our process has moved on a little bit from when I originally did this, e.g. using the UEFI firmware, which has added its own quirks. The last time I touched any of this I struggled to build a baseline .qcow2 image from the Windows 10 21H2 ISO. I ended up using a 20H1 or 21H1 ISO and then upgrading Windows in the resulting .qcow2 image to produce a baseline for those editions.

On the packer front we're still using 1.4.5 because when I upgraded to 1.6.6 it failed with some display related errors. At the time I was using Ubuntu 18.04 to build and host the VMs based on these images, whereas we're using 20.04 now. I haven't even tried our scripts / config on 22.04 as I don't have a suitable physical host to try it on.

I'm afraid I've now handed all this over so am not actively involved anymore. I'll post some additional comments with the changes that have happened in the intervening years in case they are useful but I can't really provide any other help.

@chrisoldwood
Copy link
Author

chrisoldwood commented Nov 15, 2022

Note: all the changes listed in these subsequent comments were the work of https://github.com/kinke, not me, so please thank him 🙂 .

The following changes were made to the above config files when switching to booting via UEFI:

Host

If you're building on Ubuntu you'll need the firmware package:

$ sudo apt install -y ovmf 

Packer

For the packer config we need to explicitly state what bios we're using (this assumes an Ubuntu based host) and also during boot time when no OS is initially installed we need to "press the enter key":

            "machine_type": "q35",

            "qemuargs":
            [
                [ "-bios", "/usr/share/OVMF/OVMF_CODE.fd" ],
            ],

            "boot_wait": "5s",
            "boot_command": [ "<enter>" ],

Note; the timing on how to long to wait before sending the keypress is quite sensitive, you can't be too quick or too slow. Also the machine_type may be unrelated, we were using libvirt to launch our VMs and this brought the defaults between packer and libvirt in-line.

autounattend.xml

The biggest change when switching to UEFI is the layout of the disk. Without it we have a single partition but with it we have many little partitions before the main one where we install the OS:

      <DiskConfiguration>
        <Disk wcm:action="add">
          <CreatePartitions>
            <!-- System partition (ESP) -->
            <CreatePartition wcm:action="add">
              <Order>1</Order>
              <Type>EFI</Type>
              <Size>100</Size>
            </CreatePartition>
            <!-- Microsoft reserved partition (MSR) -->
            <CreatePartition wcm:action="add">
              <Order>2</Order>
              <Type>MSR</Type>
              <Size>16</Size>
            </CreatePartition>
            <!-- Windows partition -->
            <CreatePartition wcm:action="add">
              <Order>3</Order>
              <Type>Primary</Type>
              <Extend>true</Extend>
            </CreatePartition>
          </CreatePartitions>
          <ModifyPartitions>
            <!-- System partition (ESP) -->
            <ModifyPartition wcm:action="add">
              <Order>1</Order>
              <PartitionID>1</PartitionID>
              <Label>System</Label>
              <Format>FAT32</Format>
            </ModifyPartition>
            <!-- Windows partition -->
            <ModifyPartition wcm:action="add">
              <Order>2</Order>
              <PartitionID>3</PartitionID>
              <Label>Windows 10</Label>
              <Letter>C</Letter>
              <Format>NTFS</Format>
            </ModifyPartition>
          </ModifyPartitions>
          <DiskID>0</DiskID>
          <WillWipeDisk>true</WillWipeDisk>
        </Disk>
      </DiskConfiguration>

@chrisoldwood
Copy link
Author

One other change which came in later was a simplification of the qemuargs -drive entries. In the early days if you added a -drive entry for the CDROM drive where the virtio drivers were located you also had to manually add any other -drive entries, such as for the main HDD. In later versions you can use -cdrom instead and let packer control the entry for the HDD:

            "qemuargs":
            [
                [ "-cdrom", "./virtio-win.iso" ]
            ],

@chrisoldwood
Copy link
Author

To enable remote access via SSH (as well as, or instead of PowerShell remoting) you can add the following to autounattend.xml:

      <FirstLogonCommands>

        <SynchronousCommand wcm:action="add">
          <CommandLine>powershell -c "Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0"</CommandLine>
          <Description>Install OpenSSH server</Description>
          <Order>13</Order>
          <RequiresUserInput>true</RequiresUserInput>
        </SynchronousCommand>
        <SynchronousCommand wcm:action="add">
          <CommandLine>powershell -c "Set-Service -Name sshd -StartupType Automatic"</CommandLine>
          <Description>Set OpenSSH service to autostart</Description>
          <Order>14</Order>
          <RequiresUserInput>true</RequiresUserInput>
        </SynchronousCommand>
        <SynchronousCommand wcm:action="add">
          <CommandLine>powershell -c "Start-Service sshd"</CommandLine>
          <Description>Start OpenSSH server</Description>
          <Order>15</Order>
          <RequiresUserInput>true</RequiresUserInput>
        </SynchronousCommand>

      </FirstLogonCommands>

@chrisoldwood
Copy link
Author

To enable a shared clipboard between host and guest when using the console, e.g. via virt-manager you need to install the SPICE agent:

        {
            "type": "powershell",
            "inline":
            [
                "$ErrorActionPreference = 'stop'",
                "# Install SPICE agent for shared clipboard",
                "(New-Object System.Net.WebClient).DownloadFile('https://www.spice-space.org/download/windows/vdagent/vdagent-win-0.10.0/spice-vdagent-x64-0.10.0.msi', 'spice-vdagent.msi')",
                "Start-Process -FilePath msiexec -ArgumentList '/i spice-vdagent.msi /qn /norestart' -NoNewWindow -Wait",
                "Remove-Item spice-vdagent.msi"
            ]
        },

@chrisoldwood
Copy link
Author

If you prefer to use the Git for Windows Bash implementation for the Windows-side shell for SSH you can enable it like so:

        {
            "type": "powershell",
            "inline":
            [
                "$ErrorActionPreference = 'stop'",
                "# Install Chocolatey package manager",
                "Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))",
                "# Install git",
                "choco install git -y"
                "# Set bash as OpenSSH shell (as last step, in order not to interfere with powershell provisioners above)",
                "Set-ItemProperty -Path HKLM:\\SOFTWARE\\OpenSSH -Name DefaultShell -Value 'C:\\Program Files\\Git\\bin\\bash.exe' -Force"
            ]
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment