Provisioning MS Windows clones with sysprep and unattend.xml file

The procedure described here is for Windows Vista onwards. However, different versions of Windows add new options and remove obsolete ones; if in doubt, read the official documentation from Microsoft.

Automatically configuring and activating Windows desktops is specially useful when using volatile desktops: installations that are created, configured, used for a short time (from some minutes to a few days), and then deleted, to make room for fresh desktops. So in this this page we will refer to the Guests as volatiles, although this method can be used for long-life Guest machines too.

Sysprep

Sysprep is a utility (included with the OS) that allows to generalize a Windows system: erase all configuration information that makes that machine unique, to regenerate it on the next reboot. It is very useful for creating clones of a particular system image, as flexVDI does when creating clones with a desktop policy. The idea is to run sysprep, turn off the machine, turn it into a template and create clones that will be configured in their first boot. This configuration can be automated with an answer file.

You can find more information about sysprep in the following links:

Preparing a Windows Template with Sysprep

The right way to create a Windows Guest to serve as a template is to enter audit mode during the installation of Windows . Thus no local user will be created, other than the administrator. In audit mode, you can configure the template installing any programs you want. Once finished, you must run sysprep . This tool will shut down the guest at the end, so it must be started in RunOnce mode.

There is no limit to the number of times sysprep can be run on a system. But after executing it, Windows will need to be activated again, which can be done up to three times on a given Windows installation, or Windows will stop letting users to log in the system after 30 days. To avoid this problem, after configuring the template, create a copy where you will execute sysprep.  Thus, if you need to make changes to the template, you can make them on the original (which has not gone through the generalization/activation cycle) and repeat the procedure on new copies as often as necessary.

When you run sysprep, you will see the following window:

For the task at hand, we will select "Enter System Out-of-Box Experience (OOBE)", "generalize" and "Shutdown". Pressing "OK", the process of generalization is made, then the guest will turn off. From here we can turn it into a template.

New from Windows 8 / Windows Server 2012

From Windows 8 / Windows Server 2012, the sysprep command supports a new option mode, only from the command line. By passing the parameter "/mode:vm" hardware information will not be removed, so that the next boot is noticeably faster. However, all clones must run on machines with the same hardware profile. The complete command line would be:

> sysprep.exe /generalize /oobe /shutdown /mode:vm

Answer file

Having prepared a template with sysprep, an answer file can be used to automate the initial configuration at the next boot. That file is looked for in a series of predefined paths. In particular, one of them is " A:\unattend.xml " ( Implicit Answer File Search Order No. 4). For that reason, a Desktop Policy has a field to set up a file present in flexVDI Hosts as answer file. This file is searched:

  • First (requires flexvdi-agent 3.1.8 or upper) in /var/lib/flexvdi/image_storages/$IMAGE_STORAGE/$VOLUME, shared by all hosts, where $IMAGE_STORAGE and $VOLUME are the values of the fields with those names in the Desktop Policy.
  • Second in /var/lib/flexvdi/local in each host.

If this file exists in some of those paths, it will be copied into a floppy disk in each volatile clone, renamed to "unattend.xml", so that it can be used to automatically configure the clones. In this file, all appearances of strings in the form ${param} will be replaced by the value of the corresponding parameter param in the Desktop Policy, if it exists (see the section on Desktop Policy parameters to know more). In addition, the following variables are also available, with specific values for each volatile:

  • ${windows_domain} is replaced by the domain specified in the policy Windows desktop.
  • ${computer_name} is replaced by the name of the volatile. This name is generated in one of two ways:
    • vxxxxxxxx, where x represents the number of volatile (the guest name suffix that appears in the session table VDI).
    • An arbitrary name assigned in the LDAP attribute that defines the available desktops for a user, in the form "computer = xxxxx" as if it were one more desktop.
  • ${username} is replaced by:
    • uxxxxxxx, if the volatile has been precreated (done when the field "Precreated desktops" in the Desktop Policy is greater than 0), where x is the number of volatile. Precreation makes the specialization and activation of the desktop happen before the user logs in.
    • The name of the user who has been authenticated in flexVDI if the desktop has been created as a result of that request.
  • ${password} is replaced by the password used to authenticate in flexVDI, if it exists.

Compatibility with Windows XP

In Windows XP guests, the answer file is called "sysprep.inf". For that reason, if your answer file template contains the word "sysprep" in its name, it will be renamed as sysprep.inf instead of unattend.xml. If your Windows guests are not Windows XP, do not use the word "sysprep" in your answer file's name.

The answer file format in Windows XP is also different from the one in later versions of Windows, and it is out of the scope of this guide.

Compatibility with flexVDI 3.0

Prior to flexVDI 3.1, the only variables available for substitution in the answer file were WINDOWSDOMAIN, COMPUTER and VDIUSER. They are still supported as long as they are the only variables present in the file, and there is no ${param} string. Both formats cannot be mixed and the new one takes precedence.

The format of this file is XML. It describes the actions and values to be applied in the different Windows configuration passes during startup. Its format is similar to the following:

unattend.xml
<?xml version="1.0" encoding="UTF-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="...">
        <component name="..." 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">
            ...
        </component>
        ...
    </settings>
    ...
    <cpi:offlineImage cpi:source="..." xmlns:cpi="urn:schemas-microsoft-com:cpi" />
</unattend>


The tag settings includes a set of components that will be applied during a certain phase of the installer, specified with the attribute pass .

The tag component specifies a component of the installer, and the properties that it will be automatically assigned. The  processorArchitecture attribute indicates to which architecture is applied this section. The remaining attributes are fixed. You can duplicate each component tag, using a different architecture (x86 or amd64) in each one, to be used always.

The tag cpi:offlineImage refers to the location of the base image where the response file is applied. It only makes sense during the creation of the file.

Creating a response file

The best practices for authoring answer files is to use Windows System Image Manager (WSIM). The alternative is to write the file by hand and then optionally validate with WSIM. When you open WSIM see the following:

To create an answer file, it is necessary to start from a Windows image. We can simply take the file \sources\install.wim that comes in a Windows installation ISO. When opening the image, WSIM create a catalog file first, if it does not exist. To do this, the image must have read and write permission, so we have to copy the content of the ISO to the hard drive and remove the read-only flag. Once you open the image and create the catalog, we will create an initially empty answer file. Then you just have to drag the components of the image to the corresponding section in the answer file, and configure them. See Unattended Windows Setup Reference to understand the usage of each component.

Basic configuration

To set the language, the name of the machine and local users, add the following components:

  • Microsoft-Windows-International-Core to the oobeSystem section. The "InputLocale", "SystemLocale", "UserLocale" and "UILanguage" properties must be set to "en-US"
  • If you do not want to have any local user, Microsoft-Windows-Deployment to specialize section, adding a RunSynchronousCommand with the following properties:
    • Path = reg add HKLM\Software\Microsoft\Windows\CurrentVersion\Setup\OOBE /v UnattendCreatedUser /t REG_DWORD /d 1 /f
    • Order = 1
  • Microsoft-Windows-Shell-Setup in the specialize section with the following properties:
    • ComputerName = ${computer_name} (BEWARE: ComputerName cannot be longer than 15 chars; otherwise, the unattended setup process will fail)
    • TimeZone = (valid zones can be listed with "tzutil /l") for instance "Pacific Standard Time"
    • CopyProfile = true. It will overwrite the profile in "\Users\Default User" with the current user.
    • ShowWindowsLive = false. Do not show a link to Windows Live in the start menu.
    • ProductKey = product key to activate Windows.
  • Microsoft-Windows-Shell-Setup in the oobeSystem section with the following properties:
    • OOBE, to hide configuration pages to start first, with the following properties:
      • HideEULAPage = true
      • NetworkLocation = Work
      • ProtectYourPC = 2 (see reference for possible values)
    • UserAccounts, with the following properties:
      • AdministratorPassword/Value = administrator password. However, by default, the administrator account is disabled.
      • LocalAccounts, one for each local user you want to add. If only domain users will be used, add no user.
    • RegisteredOwner and RegisteredOrganization, optionally, with their corresponding values.

Licensing volatile desktops

For volatile dekstops, KMS volume licensing or a similar method should be used to decrease the number of licenses in use when a clone is destroyed. MAK licenses are not supported.

It is likely that the boot process will requests a product key if it is not provided in the response file. To prevent this from happening, you can use one of the keys in file \sources\product.ini, which is in the Windows installation media. As a result, the boot process will not request the key, but Windows will not be activated.

Example of basic configuration
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <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>${computer_name}</ComputerName>
            <TimeZone>Pacific Standard Time</TimeZone>
            <CopyProfile>true</CopyProfile>
            <ShowWindowsLive>false</ShowWindowsLive>
        </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>
                <RunSynchronousCommand wcm:action="add">
                    <Order>1</Order>
                    <Path>reg add HKLM\Software\Microsoft\Windows\CurrentVersion\Setup\OOBE /v UnattendCreatedUser /t REG_DWORD /d 1 /f</Path>
                    <Description>Disable create user account</Description>
                </RunSynchronousCommand>
            </RunSynchronous>
        </component>
    </settings>
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-International-Core" 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">
            <InputLocale>en-US</InputLocale>
            <SystemLocale>en-US</SystemLocale>
            <UILanguage>en-US</UILanguage>
            <UserLocale>en-US</UserLocale>
        </component>
        <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>
                <NetworkLocation>Work</NetworkLocation>
                <ProtectYourPC>2</ProtectYourPC>
            </OOBE>
            <UserAccounts>
                <AdministratorPassword>
                    <Value>awBpAG8AcwBrAG8ALgAxADIAMwBBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByAFAAYQBzAHMAdwBvAHIAZAA=</Value>
                    <PlainText>false</PlainText>
                </AdministratorPassword>
            </UserAccounts>
            <RegisteredOrganization>flexVDI</RegisteredOrganization>
            <RegisteredOwner>flexVDI</RegisteredOwner>
        </component>
    </settings>
    <cpi:offlineImage cpi:source="catalog:d:/sources/install_windows 7 professional.clg" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
</unattend>

Add computer to a domain

To add the volatile desktop to the domain specified in desktop policy, add the following components:

  • Microsoft-Windows-UnattendedJoin in the specialize section with the following properties:
    • Identification/JoinDomain = ${windows_domain}
    • Identification/Credentials with user credentials with permission to add machines to the domain.
Component Example
<component name="Microsoft-Windows-UnattendedJoin" 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">
    <Identification>
        <Credentials>
            <Domain>${windows_domain}</Domain>
            <Password>Foobarz1!</Password>
            <Username>Administrator</Username>
        </Credentials>
        <JoinDomain>${windows_domain}</JoinDomain>
    </Identification>
</component>

Set network interface configuration

To configure network interfaces, use the following components, all of them in the specialize section:

  • Microsoft-Windows-TCPIP to set the TCP/IP properties of each interface, with the following properties IN THIS SAME ORDER:
    • Ipv4Settings/DhcpEnabled = true/false, to enable or disable DHCP on IPv4 addresses.
    • Ipv6Settings/DhcpEnabled for IPv6.
    • Identifier = interface name or MAC address of the interface. Since flexVDI Agent 3.1.1, you can use ${mac_addresses} to get a comma-separated list of MAC addresses of the virtual desktop. However, they are in the form aa:bb:cc:dd:ee:ff, and Sysprep expects it in the form aa-bb-cc-dd-ee-ff, so you should use a script to parse and format them.
    • UnicastIpAddresses/IpAddress to add a static IP address to the interface, either IPv4 or IPv6. You can use Desktop Policy parameters and scripting to generate suitable static addresses for your virtual desktops automatically.
    • Routes, to add routing rules. This is also used to add the default gateway, using the following values:
      • Route/Prefix = 0.0.0.0/0
      • Route/NextHopAddress = gateway IP address
  • Microsoft-Windows-DNS-Client to set the DNS server addresses and options for each interface:
    • Identifier = interface name or MAC address.
    • DNSServerSearchOrder/IpAddress to add a DNS server IP address. DNS servers are queried in the order set by the wcm:keyValue attribute.
  • Microsoft-Windows-NetBT to set the NetBIOS server and options for each interface.
    • Identifier = interface name or MAC address.
    • NameServerList/IpAddress to add a NetBIOS server IP address.
    • NetbiosOptions, with a value between 0 and 2, meaning:
      • 0: Use DHCP-provided settings if available, otherwise the settings of the interface.
      • 1: Use the NetBIOS settings of the interface.
      • 2: Disable NetBIOS

If you are going to set static IP addresses for your interfaces, do not set a static IP address in the template. Enable DHCP instead, so that the previous IP address does not get mixed up with the new one, resulting in duplicated addresses and unwanted behavior.

Network components example
<component name="Microsoft-Windows-TCPIP" 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">
    <Interfaces>
        <Interface wcm:action="add">
            <Ipv4Settings>
                <DhcpEnabled>false</DhcpEnabled>
            </Ipv4Settings>
            <Ipv6Settings>
                <DhcpEnabled>false</DhcpEnabled>
            </Ipv6Settings>
            <Identifier>${mac}</Identifier>
            <UnicastIpAddresses>
                <IpAddress wcm:action="add" wcm:keyValue="1">${ip}/24</IpAddress>
            </UnicastIpAddresses>
            <Routes>
                <Route wcm:action="add">
                    <Identifier>1</Identifier>
                    <Metric>10</Metric>
                    <NextHopAddress>192.168.1.1</NextHopAddress>
                    <Prefix>0.0.0.0/0</Prefix>
                </Route>
            </Routes>
        </Interface>
    </Interfaces>
</component>
<component name="Microsoft-Windows-DNS-Client" 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">
    <Interfaces>
        <Interface wcm:action="add">
            <Identifier>${mac}</Identifier>
            <DNSServerSearchOrder>
                <IpAddress wcm:action="add" wcm:keyValue="1">192.168.1.1</IpAddress>
                <IpAddress wcm:action="add" wcm:keyValue="2">8.8.8.8</IpAddress>
            </DNSServerSearchOrder>
            <EnableAdapterDomainNameRegistration>true</EnableAdapterDomainNameRegistration>
            <DisableDynamicUpdate>false</DisableDynamicUpdate>
        </Interface>
    </Interfaces>
</component>
<component name="Microsoft-Windows-NetBT" 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">
    <Interfaces>
        <Interface wcm:action="add">
            <NameServerList>
                <IpAddress wcm:action="add" wcm:keyValue="1">192.168.1.10</IpAddress>
            </NameServerList>
            <NetbiosOptions>1</NetbiosOptions>
            <Identifier>${mac}</Identifier>
        </Interface>
    </Interfaces>
</component>

Delete Unattend.xml cached files

Answer files are cached in C:\Windows\Panther. This is a security problem because they contain credentials that users should not be able to see. Therefore, it is best to delete files A:\unattend.xml and C:\Windows\Panther\unattend.xml. The most common option is to create a batch command in the template called C:\Windows\Setup\Scripts\SetupComplete.cmd with the following content:

del /Q /F a:\unattend.xml
del /Q /F c:\windows\panther\unattend.xml

This command will be executed at the end of the preparation of the volatile guest, removing both answer files.

More information

You can find more information about response files in the following links: