Last year I have written a topic on Starwind to create VMs from PowerShell. That enables to automate the creation process without using a GUI, either from Virtual Machine Manager or Hyper-V Manager. But a VM deployment is not finished when the VM is created but when the application is deployed. Before deploying the application, the OS must also be installed and specialized. This topic shows you the method I use to deploy and specialize a VM without a single click.

Specialize OS from unattended file

If you read this documentation (Implicit Answer File Search Order section), you can see that we can specialize the OS from unattended file. This unattended.xml file will be placed in C:\Windows\Panther\Unattend. To prepare the unattended file, I use ADK (Assessment Deployment Kit). When you install the ADK, select Deployment Tools. Then you can open Windows System Image Manager.

Then select File > Select Windows Image and choose the install.wim of your Windows Server. For this example, I have chosen an install.wim from Windows Server 2016 ISO. Select the image you want and click on OK.

Once the image is loaded, you can create an answer file. Click on File > New Answer file. Below you can find the answer file I use:

<?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"> <ProductKey>C3RCX-M6NRP-6CXC9-TW2F2-4RHYD</ProductKey> <TimeZone>Romance Standard Time</TimeZone> </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> <AdministratorPassword> <Value>MyPassword</Value> <PlainText>false</PlainText> </AdministratorPassword> </UserAccounts> <AutoLogon> <Password> <Value>MyPassword</Value> <PlainText>false</PlainText> </Password> <Enabled>true</Enabled> <LogonCount>1</LogonCount> <Username>administrator</Username> </AutoLogon> <FirstLogonCommands> <SynchronousCommand wcm:action="add"> <CommandLine>powershell.exe -file C:\Windows\Panther\Unattend\ConfigureOS.ps1</CommandLine> <Order>1</Order> <Description>Specialize OS</Description> </SynchronousCommand> </FirstLogonCommands> </component> <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>fr-FR</InputLocale> <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> </component> </settings> <settings pass="generalize"> <component name="Microsoft-Windows-PnpSysprep" 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"> <PersistAllDeviceInstalls>true</PersistAllDeviceInstalls> </component> </settings> </unattend> 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 <? 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" > < ProductKey > C3RCX - M6NRP - 6CXC9 - TW2F2 - 4RHYD < / ProductKey > < TimeZone > Romance Standard Time < / TimeZone > < / 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 > < AdministratorPassword > < Value > MyPassword < / Value > < PlainText > false < / PlainText > < / AdministratorPassword > < / UserAccounts > < AutoLogon > < Password > < Value > MyPassword < / Value > < PlainText > false < / PlainText > < / Password > < Enabled > true < / Enabled > < LogonCount > 1 < / LogonCount > < Username > administrator < / Username > < / AutoLogon > < FirstLogonCommands > < SynchronousCommand wcm : action = "add" > < CommandLine > powershell . exe - file C : \ Windows \ Panther \ Unattend \ ConfigureOS . ps1 < / CommandLine > < Order > 1 < / Order > < Description > Specialize OS < / Description > < / SynchronousCommand > < / FirstLogonCommands > < / component > < 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 > fr - FR < / InputLocale > < SystemLocale > en - US < / SystemLocale > < UILanguage > en - US < / UILanguage > < UserLocale > en - US < / UserLocale > < / component > < / settings > < settings pass = "generalize" > < component name = "Microsoft-Windows-PnpSysprep" 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" > < PersistAllDeviceInstalls > true < / PersistAllDeviceInstalls > < / component > < / settings > < / unattend >

This unattended.xml file set the following element:

The timezone

The product key (AVMA Key)

An autologon which can run once time (to run a script automatically). The following script will be run the first time: C:\Windows\Panther\Unattend\ConfigureOS.ps1

The system language: Keyboard layout: French UI and system language: en-US

The PnP devices are not uninstalled during generalize pass. Because VMs have always the same hardware, I set this setting to save time during specialization

I save this with the following name: unattend.xml

PowerShell script for specific settings

I have written the bellow script which is run on the first logon on the server. Because the unattended file set the autologon once a time, this script is automatically run at the deployment.

This script takes settings from a XML file which I configure for each VM. This script set the following:

Rename network adapters (requires device naming enabled on vNIC)

Set IP Addresses with the configuration specified in XML file

The machine join the domain

xml and configuration xml are removed because sensitive information are stored in these files.

The VM is rebooted

[xml]$XML = Get-Content C:\Windows\Panther\Unattend\ConfigureOS.xml ## STEP 1: RENAME NET ADAPTER ## Foreach ($NetAdapter in Get-NetAdapter){ Write-Host "Renaming network adapters ..." -ForegroundColor Green -BackgroundColor Black $NetAdapterDisplayValue = (get-netAdapterAdvancedProperty | ?{($_.DisplayName -eq "Hyper-V Network Adapter Name") -and ($_.Name -eq $NetAdapter.Name)}).DisplayValue Rename-NetAdapter -Name $NetAdapter.Name -NewName $NetAdapterDisplayValue } ## STEP 2: SET IP ADDRESSES ## Foreach ($NetAdapter in $XML.Configuration.NetAdapters.NetAdapter){ Write-Host "Set IP Address on $($NetAdapter.Name)" -ForegroundColor Green -BackgroundColor Black if ($NetAdapter.Gw -notlike $Null){ New-NetIPAddress -InterfaceAlias $($NetAdapter.Name) ` -IPAddress $($NetAdapter.IP) ` -PrefixLength $($NetAdapter.Netmask) ` -DefaultGateway $($NetAdapter.GW) ` -Type Unicast | Out-Null } Else { New-NetIPAddress -InterfaceAlias $($NetAdapter.Name) ` -IPAddress $($NetAdapter.IP) ` -PrefixLength $($NetAdapter.Netmask) ` -Type Unicast | Out-Null } if ($NetAdapter.DNS -notlike $Null){ Set-DnsClientServerAddress -InterfaceAlias $($NetAdapter.Name) ` -ServerAddresses $($NetAdapter.DNS) | Out-Null } if (!($NetAdapter.RegisterDNS)){ Set-DNSClient -InterfaceAlias $($NetAdapter.Name) -RegisterThisConnectionsAddress $False } } ## STEP 3: JOIN DOMAIN ## Write-Host "Joining $($Xml.Configuration.Domain.Name) domain..." -ForegroundColor Green -BackgroundColor Black $Account = $XML.Configuration.Domain.Account $Password = ConvertTo-SecureString $XML.Configuration.Domain.Password -AsPlainText -Force $credential = New-Object -typename System.Management.Automation.PSCredential -argumentlist $Account, $Password Sleep 5 Add-Computer -DomainName $XML.Configuration.Domain.Name ` -OUPath $XML.Configuration.Domain.OUPath ` -NewName $XML.Configuration.ComputerName ` -Credential $Credential ## STEP 4: Remove Unattend file and XML file ## Write-Host "Removing sensitive files ..." -ForegroundColor Green -BackgroundColor Black Remove-Item C:\Windows\Panther\Unattend\Unattend.xml -Confirm:$False Remove-Item C:\Windows\Panther\Unattend\ConfigureOS.xml -Confirm:$False ## STEP 5: REBOOT ## Write-Host "Rebooting." -ForegroundColor Green -BackgroundColor Black Sleep 5 Restart-Computer -Force 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 [ xml ] $ XML = Get - Content C : \ Windows \ Panther \ Unattend \ ConfigureOS . xml ## STEP 1: RENAME NET ADAPTER ## Foreach ( $ NetAdapter in Get - NetAdapter ) { Write - Host "Renaming network adapters ..." - ForegroundColor Green - BackgroundColor Black $ NetAdapterDisplayValue = ( get - netAdapterAdvancedProperty | ? { ( $ _ . DisplayName - eq "Hyper-V Network Adapter Name" ) - and ( $ _ . Name - eq $ NetAdapter . Name ) } ) . DisplayValue Rename - NetAdapter - Name $ NetAdapter . Name - NewName $ NetAdapterDisplayValue } ## STEP 2: SET IP ADDRESSES ## Foreach ( $ NetAdapter in $ XML . Configuration . NetAdapters . NetAdapter ) { Write - Host "Set IP Address on $($NetAdapter.Name)" - ForegroundColor Green - BackgroundColor Black if ( $ NetAdapter . Gw - notlike $ Null ) { New - NetIPAddress - InterfaceAlias $ ( $ NetAdapter . Name ) ` - IPAddress $ ( $ NetAdapter . IP ) ` - PrefixLength $ ( $ NetAdapter . Netmask ) ` - DefaultGateway $ ( $ NetAdapter . GW ) ` - Type Unicast | Out - Null } Else { New - NetIPAddress - InterfaceAlias $ ( $ NetAdapter . Name ) ` - IPAddress $ ( $ NetAdapter . IP ) ` - PrefixLength $ ( $ NetAdapter . Netmask ) ` - Type Unicast | Out - Null } if ( $ NetAdapter . DNS - notlike $ Null ) { Set - DnsClientServerAddress - InterfaceAlias $ ( $ NetAdapter . Name ) ` - ServerAddresses $ ( $ NetAdapter . DNS ) | Out - Null } if ( ! ( $ NetAdapter . RegisterDNS ) ) { Set - DNSClient - InterfaceAlias $ ( $ NetAdapter . Name ) - RegisterThisConnectionsAddress $ False } } ## STEP 3: JOIN DOMAIN ## Write - Host "Joining $($Xml.Configuration.Domain.Name) domain..." - ForegroundColor Green - BackgroundColor Black $ Account = $ XML . Configuration . Domain . Account $ Password = ConvertTo - SecureString $ XML . Configuration . Domain . Password - AsPlainText - Force $ credential = New - Object - typename System . Management . Automation . PSCredential - argumentlist $ Account , $ Password Sleep 5 Add - Computer - DomainName $ XML . Configuration . Domain . Name ` - OUPath $ XML . Configuration . Domain . OUPath ` - NewName $ XML . Configuration . ComputerName ` - Credential $ Credential ## STEP 4: Remove Unattend file and XML file ## Write - Host "Removing sensitive files ..." - ForegroundColor Green - BackgroundColor Black Remove - Item C : \ Windows \ Panther \ Unattend \ Unattend . xml - Confirm : $ False Remove - Item C : \ Windows \ Panther \ Unattend \ ConfigureOS . xml - Confirm : $ False ## STEP 5: REBOOT ## Write - Host "Rebooting." - ForegroundColor Green - BackgroundColor Black Sleep 5 Restart - Computer - Force

I copy the above PowerShell script in the VHDX master here: c:\Windows\Panther\Unattend\ConfigureOS.ps1. Below you can find the XML file

<?xml version="1.0" encoding="UTF-8"?> <Configuration> <ComputerName>VMTST01</ComputerName> <NetAdapters> <NetAdapter Name="Management-0" IP="10.10.0.23" Netmask="24" GW="10.10.0.1" DNS="10.10.0.20,10.10.0.21" RegisterDNS="1" /> </NetAdapters> <Domain> <Name>HomeCloud.net</Name> <OUPath>OU=Computers,OU=Servers,DC=HomeCloud,DC=net</OUPath> < !-- Account which can add the computer to the domain --> <Account>sa-ads-join</Account> <Password>password</Password> </Domain> </Configuration> 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 <? xml version = "1.0" encoding = "UTF-8" ?> < Configuration > < ComputerName > VMTST01 < / ComputerName > < NetAdapters > < NetAdapter Name = "Management-0" IP = "10.10.0.23" Netmask = "24" GW = "10.10.0.1" DNS = "10.10.0.20,10.10.0.21" RegisterDNS = "1" / > < / NetAdapters > < Domain > < Name > HomeCloud . net < / Name > < OUPath > OU = Computers , OU = Servers , DC = HomeCloud , DC = net < / OUPath > < ! -- Account which can add the computer to the domain -- > < Account > sa - ads - join < / Account > < Password > password < / Password > < / Domain > < / Configuration >

As you can see, this file contains a sensitive information. This is why I leave the field password empty and I fill this information when I need it. The above XML file is copied in the VHDX master here: c:\windows\panther\Unattend\ConfigureOS.xml

StarWind HyperConverged Appliance is a turnkey, entirely software-defined hyperconverged platform purpose-built for intensive virtualization workloads. Bringing the desired performance and reducing downtime, the solution can be deployed by organizations with limited budgets and IT team resources. Also, it requires only one onsite node to deliver HA for your applications that make the solution even more cost-efficient. Find out more about ➡ StarWind HyperConverged Appliance

Prepare the VHDX master

To prepare a VHDX Master, I have deployed a Windows Server 2016 Standard Core VM. Then I have applied the updates. To finish I have run the following command:

C:\windows\system32\sysprep\sysprep.exe /generalize /oobe /shutdown /mode:VM 1 C : \ windows \ system32 \ sysprep \ sysprep . exe / generalize / oobe / shutdown / mode : VM

Then I have copied the VHDX in a repository. Next, mount the VHDX:

To finish, copy the unattend.xml, the ConfigureOS.ps1 and ConfigureOS.xml into the windows\panther\Unattend

Now unmount the VHDX and create a VM. You can copy the VHDX and attach it to the VM.

Test the OS deployment

Now the VHDX is attached to the VM, you can run the VM. Below you can find the screenshot of the VM configuration. First the VM apply the unattend.xml file:

Then Windows boot and the account Administrator is logged automatically

Then the Powershell script is automatically executed

At next logon, I’m able to connect with Active Directory account

The IP address, network adapter name and computer name are well set:

The computer is placed in the right Active Directory OU:

And to finish, the sensitive files are deleted:

If I want delete the ConfigureOS.ps1 script after deployment, I can create a GPO to make this. Just create a Files GPP as below:

Conclusion

Thanks to unattended file, you can customize OS and run specific PowerShell scripts. With this feature, you can specialize your OS from the deployment. Thanks to this method, you just have to deploy your application after the VM deployment. Virtual Machine Manager uses the same mechanism to specialize the OS after the VM creation. The automatic OS configuration is really appreciable to get a consistent environment. So why not use it?

Related materials:

Views All Time Views All Time 6 Views Today Views Today 15

Appreciate how useful this article was to you? No Ratings Yet

No Ratings Yet

Loading... Loading...