After reading this excellent blogpost from Andrew Nelson (Distributed Systems Specialist at Nutanix) on Acropolis Image and Cloning Primer for Automation and combining that with this blogpost from Raghu Nandan (Director of Product Management) on Setting up Citrix XA/XD with an Acropolis Hypervisor cluster I figured we were missing one vendor on this list.. Microsoft Windows Server/Client OSs.
Although the blogpost from Raghu offers a solid way to deploy your Windows based VM(s) on Nutanix AHV I’ve been working on a way to do so without the need of client components on the VM side of things.
One way to do so is utilising Windows native PowerShell which is pretty straightforward except for one little caveat: Remote PowerShell does not play nicely with non-domain joined machines as there’s no easy way to authenticate. It could work with SSL certificates etc but that’s adding more complexity as it is so I went for a solution that could be executed with PsExec which is a light-weight telnet-replacement that lets you execute processes on other systems, complete with full interactivity for console applications, without having to manually install client software. PsExec’s most powerful uses include launching interactive command-prompts on remote systems and remote-enabling tools like IpConfig that otherwise do not have the ability to show information about remote systems.
The good thing about working with sysprep is that it will offer a way to execute it unattended wit an unattended.xml. You can describe your parameters in this XML file and sysprep.exe will parse them into the right commands. A detailed explanation of how this would work can be found on the TechNet forums, there’s an easy way to validate your unattended.xml file to using the Windows System Image Manager (Windows SIM).
With that being said, I’ve came up with the following PowerShell script to do a remote execution of a sysprep for your Windows VM:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
#Requires -Version 2.0 #This File is in Unicode format. Do not edit in an ASCII editor. <# .SYNOPSIS Runs an unattended version of Sysprep. .DESCRIPTION This script can be used to run an unattended version of Sysprep, focussed on Acropolis Hypervisor. Prerequisites: Windows 7/Windows 2008R2 and up Set your execution policy to unrestricted .PARAMETER ComputerName The hostname for this VM .PARAMETER DomainName The Domain name for the domain join .PARAMETER DomainUser Username for the domain join, need to be in domain\username format .PARAMETER DomainPwd Password for domain join .PARAMETER RegisteredName Setting the registered name for this VM .PARAMETER Organisation Setting the registered organisation name for this VM .EXAMPLE PS C:\PSScript > PsExec.exe RunSysPrep_vx.x.ps1 -ComputerName "VDI001" -DomainName "contoso.local" -DomainUser "Contoso\administrator" -DomainPwd "*******" -RegisterNamed "Nutanix" -Organization "Nutanix Inc" .INPUTS None. You cannot pipe objects to this script. .OUTPUTS No objects are output from this script. .NOTES NAME: RunSysPrep_vx.x.ps1 VERSION: 1.0 AUTHOR: Kees Baggerman with help from Iain Brighton LASTEDIT: July, 2015 #> [CmdletBinding()] param( [Parameter(Mandatory = $True)][String]$ComputerName, [Parameter(Mandatory = $True)][String]$DomainName, [Parameter(Mandatory = $True)][String]$DomainUser, [Parameter(Mandatory = $True)][String]$RegisteredName, [Parameter(Mandatory = $True)][String]$Organization, [Parameter(Mandatory = $True)][Security.SecureString]$DomainPwd ) # Taking a secure password and converting to plain text Function ConvertTo-PlainText( [security.securestring]$secure ) { $marshal = [Runtime.InteropServices.Marshal] $marshal::PtrToStringAuto( $marshal::SecureStringToBSTR($secure) ) } # Set the path variable to the XML file needed for sys prep $path = 'C:\Windows\Temp\unattend.xml' # Check if the script has already been executed and abort if that's the case If (Test-Path $path){ # // File exists Exit }Else{ # // File does not exist # create a template XML to hold data $template = @' <?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="x86" 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> <InputLocale>1033:00000409</InputLocale> <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> </component> <component name="Microsoft-Windows-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Diagnostics> <OptIn>false</OptIn> </Diagnostics> <UserData> <AcceptEula>true</AcceptEula> <FullName>administrator</FullName> <Organization>$organizationname</Organization> </UserData> <EnableFirewall>true</EnableFirewall> </component> </settings> <settings pass="generalize"> <component name="Microsoft-Windows-Security-SPP" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SkipRearm>1</SkipRearm> </component> </settings> <settings pass="specialize"> <component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SkipAutoActivation>true</SkipAutoActivation> </component> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="x86" 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>test</ComputerName> <ProductKey>HYF8J-CVRMY-CM74G-RPHKF-PW487</ProductKey> <TimeZone>Pacific Standard Time</TimeZone> </component> <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="x86" 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>contoso.local</Domain> <Password>password</Password> <Username>administrator</Username> </Credentials> <JoinDomain>contoso.local</JoinDomain> <UnsecureJoin>False</UnsecureJoin> </Identification> </component> </settings> <settings pass="oobeSystem"> <component name="Microsoft-Windows-International-Core" processorArchitecture="x86" 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>1033:00000409</InputLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> </component> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <RegisteredOwner>administrator</RegisteredOwner> <OOBE> <HideEULAPage>true</HideEULAPage> <NetworkLocation>Work</NetworkLocation> <ProtectYourPC>1</ProtectYourPC> <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> <SkipMachineOOBE>true</SkipMachineOOBE> <SkipUserOOBE>true</SkipUserOOBE> </OOBE> <DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet> <FirstLogonCommands> <SynchronousCommand wcm:action="add"> <RequiresUserInput>false</RequiresUserInput> <Order>1</Order> <Description>Disable Auto Updates</Description> <CommandLine>reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" /v AUOptions /t REG_DWORD /d 1 /f</CommandLine> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Description>Control Panel View</Description> <Order>2</Order> <CommandLine>reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel" /v StartupPage /t REG_DWORD /d 1 /f</CommandLine> <RequiresUserInput>true</RequiresUserInput> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>3</Order> <Description>Control Panel Icon Size</Description> <RequiresUserInput>false</RequiresUserInput> <CommandLine>reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel" /v AllItemsIconView /t REG_DWORD /d 1 /f</CommandLine> </SynchronousCommand> </FirstLogonCommands> <AutoLogon> <Password> <Value>password</Value> <PlainText>true</PlainText> </Password> <Enabled>true</Enabled> <Username>administrator</Username> </AutoLogon> <UserAccounts> <LocalAccounts> <LocalAccount wcm:action="add"> <Password> <Value>password</Value> <PlainText>true</PlainText> </Password> <Description></Description> <DisplayName>administrator</DisplayName> <Group>Administrators</Group> <Name>administrator</Name> </LocalAccount> </LocalAccounts> </UserAccounts> </component> </settings> <settings pass="offlineServicing"> <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <EnableLUA>false</EnableLUA> </component> </settings> </unattend> '@ # Writing the actual XML file with UTF8 encoding $template | Out-File $Path -encoding UTF8 # Loading the XML file to modify values $xml = New-Object XML $xml.Load($path) $xml.unattend.settings[0].component[1].UserData.FullName = $RegisteredName $xml.unattend.settings[0].component[1].UserData.Organization = $Organization $xml.unattend.settings[2].component[1].ComputerName = $ComputerName $xml.unattend.settings[2].component[2].identification.JoinDomain = $DomainName $xml.unattend.settings[2].component[2].identification.credentials.UserName = $DomainUser $xml.unattend.settings[2].component[2].identification.credentials.Password = ConvertTo-PlainText $DomainPwd $xml.unattend.settings[2].component[2].identification.credentials.Domain = $DomainName # Saving the XML $xml.Save($path) # Enabling the default administrator account net user administrator /active:yes # Running SysPrep adding /mode:vm when running Windows 8 or above to speed up the sysprep process $Version=(Get-WmiObject win32_operatingsystem).version if(!($version -eq "6.1")) {$argList = "/generalize /oobe /reboot /unattend:$path"} else {$argList = "/generalize /oobe /reboot /mode:vm /unattend:$path"} Start-Process -FilePath C:\Windows\System32\Sysprep\Sysprep.exe -ArgumentList $argList } |
As you can see it generates the unattended.xml on the local system so the remote execution of sysprep can find the XML file. One thing that I like about this approach is that it’s more flexible in terms of your VM naming scheme, OU placement and settings as you’re able to dynamically change/adjust the settings per VM. This script was tested on Windows 2008R2 and Windows 7 and comes ‘as-is’.
Pro-tip: If you think it’s too much work to launch SIM you could always try to use of the online generators for your unattended.xml which can be found here: http://windowsafg.no-ip.org/, please note that there are multiple options for your XML file depending on your Windows version so please choose the right OS before creating your xml file.
Kees Baggerman
Latest posts by Kees Baggerman (see all)
- Nutanix AHV and Citrix MCS: Adding a persistent disk via Powershell – v2 - November 19, 2019
- Recovering a Protection Domain snapshot to a VM - September 13, 2019
- Checking power settings on VMs using powershell - September 11, 2019
- Updated: VM Reporting Script for Nutanix with Powershell - July 3, 2019
- Updated (again!): VM Reporting Script for Nutanix AHV/vSphere with Powershell - June 17, 2019
Hi Kees,
You already have the XML template in a string so there’s no need to save it to disk and then load it as xml. It’s much easier to simply cast it as xml:
[codesyntax lang=”powershell” lines=”normal”]
$xml = [xml]$template
[/codesyntax]
As for setting the properties in XML, I don’t like hardcoded arrays since it makes the code more difficult to read (and I do read a script before executing it). It’s also more error prone as the order might change in the future.
So instead of this:
$xml.unattend.settings[0].component[1].UserData.FullName = $RegisteredName
I recommend this:
[codesyntax lang=”powershell” lines=”normal”]
$ns = New-Object Xml.XmlNamespaceManager($xml.NameTable)
$ns.AddNamespace(“unattend”, $xml.DocumentElement.NamespaceURI)
$msWinSetup = $xml.SelectSingleNode(“//unattend:settings[@pass=’windowsPE’]/unattend:component[@name=’Microsoft-Windows-Setup’]”, $ns)
$msWinSetup.UserData.FullName = $RegisteredName
$msWinSetup.UserData.Organization = $Organization
$msWinShellSetup = $xml.SelectSingleNode(“//unattend:settings[@pass=’specialize’]/unattend:component[@name=’Microsoft-Windows-Shell-Setup’]”, $ns)
$msWinShellSetup.ComputerName = $ComputerName
$msWinUnattendJoin = $xml.SelectSingleNode(“//unattend:settings[@pass=’specialize’]/unattend:component[@name=’Microsoft-Windows-UnattendedJoin’]”, $ns)
$msWinUnattendJoin.identification.JoinDomain = $DomainName
$msWinUnattendJoin.identification.credentials.UserName = $DomainUser
$msWinUnattendJoin.identification.credentials.Password = ConvertTo-PlainText $DomainPwd
$msWinUnattendJoin.identification.credentials.Domain = $DomainName
[/codesyntax]
[…] ‘Mass’ sysprep your Windows VMs for Nutanix AHV […]