How to use RHEV to deploy Windows 7 virtual workstations in a VDI environment

Updated -

System builders and corporate IT departments who need to build or deploy standardized physical Windows workstations use a process based on a Microsoft utility named Sysprep. The process uses these general steps:

  • Build a reference computer with appropriate patches and layered software.
  • Seal the reference computer with the Sysprep utility and shut it down.
  • Duplicate the reference computer hard drive onto a group of target systems.
  • Deliver these target systems to end users.
  • When end users boot the target systems the first time, the systems run a mini-setup program that prompts for attributes such as computername that must be unique across the enterprise.

Microsoft provides an auto-answer capability to automate the first-boot mini-setup experience, The auto-answer file can drive the mini-setup program with no user intervention to provide a hostname, join the end user computer to a domain, and optionally create local user accounts and set a Windows Update strategy. An end user powers on her new computer for the first time, and it's ready to logon to the corporate domain a few minutes later.

This Microsoft Technet article describes the Sysprep process in more detail.

RHEV enhances the Sysprep process by exploiting virtualization technology to deploy virtual workstations in a pool based on a single template. RHEV builds a tailored auto-answer file for each virtual workstation in the pool, based on fields filled in when setting up the pool. Support for pools is a standard part of RHEV. The size of each pool and number of pools is limited only by hardware capacity.

Here are the general steps with RHEV to deploy a pool of virtual workstations:

  • Build a reference Windows virtual machine.
  • Seal it with Sysprep exactly like a physical system.
    • With Windows 7, run C:\windows\system32\sysprep\sysprep.exe.
    • Select "Enter System Out-of-Box Experience (OOBE)"
    • Check the Generalize checkbox.
    • Under "Shutdown Options" select "Shutdown."
    • Click OK.
  • Use the RHEV Management Portal to create a template based on the newly sealed virtual machine.
    • Right-click the newly sealed VM and click "Make Template."
    • Give the template a name and fill in other attributes as appropriate.
  • Use the RHEV Management Portal to create a pool of virtual workstations based on the template.
    • Various fields in the pool creation screen control how the auto-answer file is generated.
  • End users use the User Portal to access their virtual workstations, which will run the Windows mini-setup program at first-boot.
  • When set up correctly, end users will see a Windows logon screen into the Active Directory domain a few minutes after powering on their virtual workstation.

Pool Creation Details

Here are the detailed steps to create a new pool of VMs after generating a template based on a reference VM sealed with Sysprep.

  • To create a new pool, logon to the RHEV Management Portal. Choose the Pools tab and click "new."
  • Click "Show Advanced Options" in the bottom of the window. Select "General" in the left side menu bar.
  • Base the pool on a template and give the pool a name and optional description. Put in the number of VMs in the pool. If the pool is named, "mypool" with 5 VMs, this will create VMs named mypool-1 through mypool-5.
  • Now select "Initial Run" on the left side menu bar.
  • Assume the virtual machines in this pool will join an Active Directory domain named example.local in an OU named MyTestOU.
  • Check the checkbox for "Use Cloud-Init/Sysprep".
  • Leave the VM hostname field blank. RHEV will fill in the unique virtual machine name as the system hostname in the auto-answer file it generates for each VM in the pool.
  • Put in "example.local" without the quotes for Domain. Use the fully suffixed domain name, not just "example".
  • Put in an optional organization name.
  • For Active Directory OU, put in "OU=MyTestOU,DC=example,DC=local" without the quotes. The Active Directory OU field needs to be the full LDAP description for the OU that will contain the VMs. If that Active Directory OU field has any problem, the VM will join a Windows workgroup with the same name as the domain instead of joining the actual domain. So for testing, it may make sense to leave that Active Directory OU field blank, verify the settings to make the VM join the domain in the default "Computers" container, then destroy this test pool, create a new pool, and debug the LDAP settings in the "Active Directory OU" field.
  • Check the checkbox for Configure Time Zone and select an appropriate time zone.
  • The Admin Password and other fields are not relevant to joining the Active Directory domain. For now, leave them alone.
  • Click OK.

This will set up the pool with the desired number of VMs.

Active Directory Domain Credentials

When computers join an Active Director domain, they need to provide credentials for an Active Directory account with permissions to join the domain. RHEV needs these credentials so it can build them into the auto-answer files it generates. There are two ways to provide these credentials for RHEV. Both require an SSH or other terminal session into the RHEV-M system. There is currently no GUI interface for this.

Either execute these commands from RHEV-M as root:

engine-config -s "SysPrepDefaultPassword=interactive"
engine-config -s "SysPrepDefaultUser=example\administrator"

Or execute this command from RHEV-M as root:

engine-manage-domains add --domain=example.local --provider=ad

Power up the first test VM.

In the RHEV-M Administrative Portal, go to the "Virtual Machines" tab, pick a newly created VM from the pool at random and power it up.

Acquire a console to the VM. (Right-click...Console)

While the VM is booting and running through its unattended mini-setup, set up an RDP session to a Windows domain controller and logon as a domain administrator. Launch "Active Directory Users and Computers." Navigate to the OU that should contain this new VM. When the VM console displays a logon prompt, refresh the display in ADUC. That VM should now appear in the specified OU.

If not, logon to the VM via its console. Launch "Start...Computer" and look at a:\Unattend.xml. Debug as appropriate. This is how the domain section of Unattend.xml should look when the VM successfully joins the specified OU in the Active Directory domain.

<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
  <Identification>
    <Credentials>
      <Domain>
        <![CDATA[ example.local ]]>
      </Domain>
      <Password>
        <![CDATA[ mypassword ]]>
      </Password>
      <Username>
        <![CDATA[ administrator@EXAMPLE.LOCAL ]]>
      </Username>
    </Credentials>
    <JoinDomain>
      <![CDATA[ example.local ]]>
    </JoinDomain>
    <MachineObjectOU>
      <![CDATA[ OU=MyTestOU,DC=example,DC=local ]]>
    </MachineObjectOU>
  </Identification>
</component>

Support for earlier versions

The Windows 7 default sysprep auto-answer file is named Unattend.xml. By default, when virtual workstations boot, they will see it in a:\Unattend.xml.

Earlier RHEV versions and earlier versions of Windows used a different name for the auto-answer file, generally a:\sysprep.inf. Older Windows reference systems and templates may have this registry key set:

HKLM\system\setup\UnattendFile
Value data: a:\sysprep.inf

This is no longer true for Windows 7 and RHEV versions newer than 3.5.1. Newer reference VMs and templates should leave this registry setting alone and use the default filename of a:\Unattend.xml

If the template behind the virtual machines in a pool is based on an old VM with the registry change in place, and it's not convenient to update the template to match the new sysprep answer file location, here is a procedure to provide RHEVM with a different sysprep answer file name location as a workaround.

Only perform this workaround to accommodate old versions of Windows or pools based on older versions of RHEV.

  • SSH into the RHEVM server and look in this directory: /etc/ovirt-engine/osinfo.conf.d
  • See a file named 00-defaults.properties. This file contains default parameters for all supported guest operating systems.
  • Create a new file named similarly to 99-defaults.properties. Paste in the section corresponding to Windows 7 from the 00... file into the newly created 99... file and update the name and location of the sysprep answer file accordingly.
  • Customized settings in this newly created file will override settings on the factory supplied 00-file. Now VMs in the pool will find the answer file they need.

These steps are a workaround to accommodate older versions. They may also be useful in scenarios where the auto-answer file itself needs to be customized.

Customizations

In addition to customizing the name and location of the Sysprep auto-answer file, it may also be necessary to customize the answer file itself with attributes not specified in the RHEV-M GUI. The default answer file template for 64 bit Windows 7 is located on the RHEV-M server in:

/usr/share/ovirt-engine/conf/sysprep/sysprep.w7x64

To provide custom attributes such as a Windows Update policy to the generated VMs in the pool, copy this file to a convenient directory and edit the copy as appropriate. Reference the copy in the 99-defaults.properties file created above.

Troubleshooting the generated auto-answer file

Debug the generated auto-answer file by looking a:\<filename> within a test virtual machine in the pool. Sometimes, policy restrictions or other factors within the VM may prohibit looking at the generated auto-answer file from within the VM. To look at and debug the generated file outside the VM do the following:

  1. Start the VM.
  2. SSH to the hypervisor where it is running
  3. Look for the qemu-kvm process correspnding to that VM: ps -ef | grep qemu-kvm | grep TheNameOfTheVM
  4. Search for the word, "payload" in the command line parameters of that VM. The path will be similar to /var/run/vdsm/payload/{guid}.img. That is the file where the floppy image has been generated, so copy it somewhere, say /tmp/floppy.img
  5. Mount that floppy image: mount -o loop,ro /tmp/floppy.img /mnt

Then it can be inspected: find /mnt; cat /mnt/Unnatend.xml, etc.

Here is an example. The VM name is pool-1556-5 and it runs on the RHEV-H host named rhevb. The VM will join an OU named MyTestOU in an Active Directory domain named example.local.

First, find the process running the VM.

[root@rhevb ~]# ps -ef | grep qemu-kvm | grep pool-1556-5
qemu     26271     1 34 15:10 ?        00:00:51 /usr/libexec/qemu-kvm -name pool-1556-5 -S -M rhel6.5.0 -cpu Nehalem,hv_relaxed -enable-kvm -m 4096 -realtime mlock=off -smp 1,maxcpus=16,sockets=16,cores=1,threads=1 -uuid 36629bca-9776-4e6d-9892-99d44c9e7cf8 -smbios type=1,manufacturer=Red Hat,product=RHEV Hypervisor,version=6.6-20150128.0.el6ev,serial=10D73BB4-E369-DE11-86B6-8853C5339FF6,uuid=36629bca-9776-4e6d-9892-99d44c9e7cf8 -nodefconfig -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/pool-1556-5.monitor,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=2015-10-05T10:10:26,clock=vm,driftfix=slew -no-kvm-pit-reinjection -no-shutdown -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -device virtio-scsi-pci,id=scsi0,bus=pci.0,addr=0x5 -device virtio-serial-pci,id=virtio-serial0,max_ports=16,bus=pci.0,addr=0x6 -drive file=/var/run/vdsm/payload/36629bca-9776-4e6d-9892-99d44c9e7cf8.da2a0f6cd3724fafb6d0abc2ae7480cb.img,if=none,id=drive-fdc0-0-0,readonly=on,format=raw,serial= -global isa-fdc.driveA=drive-fdc0-0-0 -drive file=/rhev/data-center/mnt/10.10.11.3:_ISO/c0caabde-db34-416a-9fe5-221a7ce1145a/images/11111111-1111-1111-1111-111111111111/RHEV-toolsSetup_3.5_9.iso,if=none,media=cdrom,id=drive-ide0-1-0,readonly=on,format=raw,serial= -device ide-drive,bus=ide.1,unit=0,drive=drive-ide0-1-0,id=ide0-1-0 -drive file=/rhev/data-center/e0f6a7ec-46af-42ce-a002-c6b91c3c4059/c52073ab-73c4-41ad-8c3e-30b63b7f32fc/images/0afb75dd-ec81-41b6-8940-60b6d6b4e331/377d6112-e7b1-4be8-8417-8931cf4ba047,if=none,id=drive-virtio-disk0,format=qcow2,serial=0afb75dd-ec81-41b6-8940-60b6d6b4e331,cache=none,werror=stop,rerror=stop,aio=threads -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x7,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1 -netdev tap,fd=31,id=hostnet0,vhost=on,vhostfd=32 -device virtio-net-pci,netdev=hostnet0,id=net0,mac=00:1a:4a:92:c6:0a,bus=pci.0,addr=0x3 -chardev socket,id=charchannel0,path=/var/lib/libvirt/qemu/channels/36629bca-9776-4e6d-9892-99d44c9e7cf8.com.redhat.rhevm.vdsm,server,nowait -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=com.redhat.rhevm.vdsm -chardev socket,id=charchannel1,path=/var/lib/libvirt/qemu/channels/36629bca-9776-4e6d-9892-99d44c9e7cf8.org.qemu.guest_agent.0,server,nowait -device virtserialport,bus=virtio-serial0.0,nr=2,chardev=charchannel1,id=channel1,name=org.qemu.guest_agent.0 -chardev spicevmc,id=charchannel2,name=vdagent -device virtserialport,bus=virtio-serial0.0,nr=3,chardev=charchannel2,id=channel2,name=com.redhat.spice.0 -spice tls-port=5900,addr=10.10.10.22,x509-dir=/etc/pki/vdsm/libvirt-spice,tls-channel=main,tls-channel=display,tls-channel=inputs,tls-channel=cursor,tls-channel=playback,tls-channel=record,tls-channel=smartcard,tls-channel=usbredir,seamless-migration=on -k en-us -vga qxl -global qxl-vga.ram_size=67108864 -global qxl-vga.vram_size=33554432 -device intel-hda,id=sound0,bus=pci.0,addr=0x4 -device hda-duplex,id=sound0-codec0,bus=sound0.0,cad=0 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x8 -msg timestamp=on
[root@rhevb ~]# 

Found the process running the VM. This is the key parameter:
-drive file=/var/run/vdsm/payload/36629bca-9776-4e6d-9892-99d44c9e7cf8.da2a0f6cd3724fafb6d0abc2ae7480cb.img,if=none

Copy the floppy image somewhere convenient and mount it.

[root@rhevb ~]# 
[root@rhevb ~]# cd /var/run/vdsm/payload
[root@rhevb payload]# ls
36629bca-9776-4e6d-9892-99d44c9e7cf8.da2a0f6cd3724fafb6d0abc2ae7480cb.img
[root@rhevb payload]# mkdir /mnt/stuff
[root@rhevb payload]# cp 36629bca-9776-4e6d-9892-99d44c9e7cf8.da2a0f6cd3724fafb6d0abc2ae7480cb.img /tmp/floppy.img
[root@rhevb payload]# mount -o loop,ro /tmp/floppy.img /mnt/stuff
[root@rhevb payload]# cd /mnt/stuff

And let's see what we've got.

[root@rhevb stuff]# ls -al
total 13
drwxr-xr-x. 2 root root 7168 1970-01-01 00:00 .
drwxr-xr-x. 3 root root   60 2015-10-05 15:17 ..
-rwxr-xr-x. 1 root root 5765 2015-10-05 15:10 Unattend.xml
[root@rhevb stuff]# 
[root@rhevb stuff]# cat Unattend.xml
<?xml version="1.0" encoding="UTF-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="windowsPE">
        <component name="Microsoft-Windows-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" version
Scope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <UserData>
                <ProductKey>
                    <Key><![CDATA[]]></Key>
                </ProductKey>
                <AcceptEula>true</AcceptEula>
                <FullName>"user"</FullName>
                <Organization><![CDATA[Example Corporation]]></Organization>
            </UserData>
            <ImageInstall>
                <OSImage>
                    <InstallToAvailablePartition>true</InstallToAvailablePartition>
                </OSImage>
            </ImageInstall>
        </component>
        <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" languag
e="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchem
a-instance">
            <SetupUILanguage>
                <UILanguage><![CDATA[en_US]]></UILanguage>
            </SetupUILanguage>
            <InputLocale><![CDATA[en_US]]></InputLocale>
            <UILanguage><![CDATA[en_US]]></UILanguage>
            <SystemLocale><![CDATA[en_US]]></SystemLocale>
            <UserLocale><![CDATA[en_US]]></UserLocale>
        </component>
    </settings>
    <settings pass="specialize">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" v
ersionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <Display>
                <ColorDepth>32</ColorDepth>
                <DPI>96</DPI>
                <HorizontalResolution>1024</HorizontalResolution>
                <RefreshRate>75</RefreshRate>
                <VerticalResolution>768</VerticalResolution>
            </Display>
            <ComputerName><![CDATA[pool-1556-5]]></ComputerName>
            <TimeZone><![CDATA[Central Standard Time]]></TimeZone>
        </component>
        <component name="Microsoft-Windows-International-Core" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neu
tral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance">
            <InputLocale><![CDATA[en_US]]></InputLocale>
            <UserLocale><![CDATA[en_US]]></UserLocale>
            <SystemLocale><![CDATA[en_US]]></SystemLocale>
            <UILanguage><![CDATA[en_US]]></UILanguage>
        </component>
        <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutr
al" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instan
ce">
            <Identification>
                <Credentials>
                    <Domain><![CDATA[example.local]]></Domain>
                    <Password><![CDATA[mypassword]]></Password>
                    <Username><![CDATA[administrator@EXAMPLE.LOCAL]]></Username>
                </Credentials>
                <JoinDomain><![CDATA[example.local]]></JoinDomain>
                <MachineObjectOU><![CDATA[OU=MyTestOU,DC=example,DC=local]]></MachineObjectOU>
            </Identification>
        </component>
    </settings>
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-International-Core" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neu
tral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance">
            <InputLocale><![CDATA[en_US]]></InputLocale>
            <UserLocale><![CDATA[en_US]]></UserLocale>
            <SystemLocale><![CDATA[en_US]]></SystemLocale>
            <UILanguage><![CDATA[en_US]]></UILanguage>
        </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>
                <ProtectYourPC>2</ProtectYourPC>
                <NetworkLocation>Work</NetworkLocation>
                <HideEULAPage>true</HideEULAPage>
            </OOBE>
            <UserAccounts>
                <AdministratorPassword>
                    <Value><![CDATA[mypassword]]></Value>
                    <PlainText>true</PlainText>
                </AdministratorPassword>
                <LocalAccounts>
                    <LocalAccount wcm:action="add">
                        <Password>
                            <Value><![CDATA[mypassword]]></Value>
                            <PlainText>true</PlainText>
                        </Password>
                        <Group>administrators</Group>
                        <Name>user</Name>
                        <DisplayName>user</DisplayName>
                    </LocalAccount>
                </LocalAccounts>
            </UserAccounts>
        </component>
    </settings>
    <cpi:offlineImage cpi:source="wim:d:/sources/install.wim#Windows 7 ENTERPRISE" xmlns:cpi="urn:schemas-microsoft-com:cpi"/>
</unattend>
[root@rhevb stuff]# cd
[root@rhevb ~]# umount /mnt/stuff
[root@rhevb ~]# 

Comments