Windows Image Tools | Unattend.xml
Getting started
The second item need to automate the creation of a VM is a way to bootstrap said automation of a fresh windows image. That requires an unattend.xml
prerequisites
Now to work with Unattent.xml you need a deep understanding of XML structure and namespace… Just kidding. I’m going to use New-UnattendXml part of WindowsImageTools
You can install WindowsImageTools from the PowerShell Gallery with install-module WindowsImageTools
Creating a basic Unattent.xml
Lets start off with a basic Unattend. Minimum requirements for this is changing the administrator password
New-UnattendXml -AdminCredential (Get-Credential) |
Get-Content
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Credential
WARNING: C:\Users\BLADE_~1\AppData\Local\Temp\hqfyjyg2\unattend.xml only usable on a server SKU, for a client OS, use
either -EnableAdministrator or -UserAccount
Notice the warning. Setting the Admin password on a server is all you need, as the build in Administrator account is allready enabled. On a client OS you have to either enable the administrator or add a second one or three. It spits out this mass of XML
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="specialize">
<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">
</component>
<component name="Microsoft-Windows-Deployment" 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">
</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">
<ComputerName>*</ComputerName>
</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>*</ComputerName>
</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-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>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>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>1</ProtectYourPC>
<SkipUserOOBE>true</SkipUserOOBE>
<SkipMachineOOBE>true</SkipMachineOOBE>
</OOBE>
<TimeZone>GMT Standard Time</TimeZone>
<UserAccounts>
<AdministratorPassword>
<Value>YQBkAHMAZgBBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByAFAAYQBzAHMAdwBvAHIAZAA=</Value>
<PlainText>false</PlainText>
</AdministratorPassword>
</UserAccounts>
<RegisteredOrganization>Generic Organization</RegisteredOrganization>
<RegisteredOwner>Generic Owner</RegisteredOwner>
</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">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>1</ProtectYourPC>
<SkipUserOOBE>true</SkipUserOOBE>
<SkipMachineOOBE>true</SkipMachineOOBE>
</OOBE>
<TimeZone>GMT Standard Time</TimeZone>
<UserAccounts>
<AdministratorPassword>
<Value>YQBkAHMAZgBBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByAFAAYQBzAHMAdwBvAHIAZAA=</Value>
<PlainText>false</PlainText>
</AdministratorPassword>
</UserAccounts>
<RegisteredOrganization>Generic Organization</RegisteredOrganization>
<RegisteredOwner>Generic Owner</RegisteredOwner>
</component>
</settings>
</unattend>
Anatomy of Unattent.xml
Takeing a closer look at each section
Specialize : Deployment
Contrary to popular belief, the unattend.xml can have one file that can be use for both 32 and 64bit. All you have to do is repeat the section with a different architecture. With one caveat that I will get into later. In the basic Unattent the Deployment section is empty.
<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">
</component>
<component name="Microsoft-Windows-Deployment" 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">
</component>
Specialize : Shell Setup
Basic settings lets windows pick a random computer name.
<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>*</ComputerName>
</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>*</ComputerName>
</component>
oobeSystem : International Core
The language and input are localized to en-US by default, This can be changed via parameters
<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-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>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>
oobeSystem : Shell-Setup
This section is a bit larger. It going to hide or skip everything it can to avoid any prompting. Afterall this is suposed to be automated. We also set the Timezone to a default of GMT and set the admin password Now the Admin Password may look encrypted, but it’s not that is 64bit encodeing. The best thing to do is set a password here for troubleshooting, then change it via LAPS or DSC once windows is up and running.
<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>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>1</ProtectYourPC>
<SkipUserOOBE>true</SkipUserOOBE>
<SkipMachineOOBE>true</SkipMachineOOBE>
</OOBE>
<TimeZone>GMT Standard Time</TimeZone>
<UserAccounts>
<AdministratorPassword>
<Value>YQBkAHMAZgBBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByAFAAYQBzAHMAdwBvAHIAZAA=</Value>
<PlainText>false</PlainText>
</AdministratorPassword>
</UserAccounts>
<RegisteredOrganization>Generic Organization</RegisteredOrganization>
<RegisteredOwner>Generic Owner</RegisteredOwner>
</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">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>1</ProtectYourPC>
<SkipUserOOBE>true</SkipUserOOBE>
<SkipMachineOOBE>true</SkipMachineOOBE>
</OOBE>
<TimeZone>GMT Standard Time</TimeZone>
<UserAccounts>
<AdministratorPassword>
<Value>YQBkAHMAZgBBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByAFAAYQBzAHMAdwBvAHIAZAA=</Value>
<PlainText>false</PlainText>
</AdministratorPassword>
</UserAccounts>
<RegisteredOrganization>Generic Organization</RegisteredOrganization>
<RegisteredOwner>Generic Owner</RegisteredOwner>
</component>
This is all well and good but it’s still only get’s us the first step. To automate deployment we need to do more then bypass prompts and set a password.
More usefull Unattent.xml
Now we will set a few more items.
$param = @{
AdminCredential = (Get-Credential)
UserAccount = (Get-Credential), (Get-Credential)
FirstBootScriptPath = 'C:\PsTemp\FirstBoot.ps1'
TimeZone = 'Central Standard Time'
RegisteredOwner = 'Employee'
RegisteredOrganization = 'Contoso'
enableAdministrator = $true
}
New-UnattendXml @param |
get-content
specialize : Deployment
One thing to notice here is the amd64 component is still blank. The reason for this is 64bit OS’s will run the commands in both the 32 and 64bit sections.
We have two commands in the RunSynchronous section. The first one enables the Administrator account. the second launches PowerShell with the script path we asked for.
<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">
</component>
<component name="Microsoft-Windows-Deployment" 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">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Description>Enable Administrator</Description>
<Order>1</Order>
<Path>net user administrator /active:yes</Path>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>PowerShell First boot script</Description>
<Order>2</Order>
<Path>%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\PsTemp\FirstBoot.ps1"</Path>
</RunSynchronousCommand>
</RunSynchronous>
</component>
oobeSystem : Shell Setup
The next section that is diferent is the shell setup. You will notice I set two additional user accounts. Remember that the passwords are encoded not encrypted. I also have entries to set the ownership.
<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>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>1</ProtectYourPC>
<SkipUserOOBE>true</SkipUserOOBE>
<SkipMachineOOBE>true</SkipMachineOOBE>
</OOBE>
<TimeZone>Central Standard Time</TimeZone>
<UserAccounts>
<AdministratorPassword>
<Value>UABAAHMAcwB3ADAAcgBkAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAUABhAHMAcwB3AG8AcgBkAA==</Value>
<PlainText>false</PlainText>
</AdministratorPassword>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Password>
<Value>UABAAHMAcwB3ADAAcgBkADEAMgAzAFAAYQBzAHMAdwBvAHIAZAA=</Value>
<PlainText>false</PlainText>
</Password>
<DisplayName>Don</DisplayName>
<Group>Administrators</Group>
<Name>Don</Name>
</LocalAccount>
<LocalAccount wcm:action="add">
<Password>
<Value>UABAAHMAcwB3ADAAcgBkADQANQA2AFAAYQBzAHMAdwBvAHIAZAA=</Value>
<PlainText>false</PlainText>
</Password>
<DisplayName>Mike</DisplayName>
<Group>Administrators</Group>
<Name>Mike</Name>
</LocalAccount>
</LocalAccounts>
</UserAccounts>
<RegisteredOrganization>Contoso</RegisteredOrganization>
<RegisteredOwner>Employee</RegisteredOwner>
</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">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>1</ProtectYourPC>
<SkipUserOOBE>true</SkipUserOOBE>
<SkipMachineOOBE>true</SkipMachineOOBE>
</OOBE>
<TimeZone>Central Standard Time</TimeZone>
<UserAccounts>
<AdministratorPassword>
<Value>UABAAHMAcwB3ADAAcgBkAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAUABhAHMAcwB3AG8AcgBkAA==</Value>
<PlainText>false</PlainText>
</AdministratorPassword>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Password>
<Value>UABAAHMAcwB3ADAAcgBkADEAMgAzAFAAYQBzAHMAdwBvAHIAZAA=</Value>
<PlainText>false</PlainText>
</Password>
<DisplayName>Don</DisplayName>
<Group>Administrators</Group>
<Name>Don</Name>
</LocalAccount>
<LocalAccount wcm:action="add">
<Password>
<Value>UABAAHMAcwB3ADAAcgBkADQANQA2AFAAYQBzAHMAdwBvAHIAZAA=</Value>
<PlainText>false</PlainText>
</Password>
<DisplayName>Mike</DisplayName>
<Group>Administrators</Group>
<Name>Mike</Name>
</LocalAccount>
</LocalAccounts>
</UserAccounts>
<RegisteredOrganization>Contoso</RegisteredOrganization>
<RegisteredOwner>Employee</RegisteredOwner>
<FirstLogonCommands />
<LogonCommands />
</component>
Puting it together
New-UnattentXML returns a path object to the file created, and by default that is in $env:TEMP. so we will store the path in a variable and use it pluss the path to our FirstBoot script when creating the VHDX
Adding the unattend and script file when creating a VHDX
$UnattentParam = @{
AdminCredential = (Get-Credential)
UserAccount = (Get-Credential), (Get-Credential)
FirstBootScriptPath = 'C:\PsTemp\FirstBoot.ps1'
TimeZone = 'Central Standard Time'
RegisteredOwner = 'Employee'
RegisteredOrganization = 'Contoso'
enableAdministrator = $true
}
$UnattentPath = (New-UnattendXml @UnattentParam ).FullName
$FirstBootContent = {
## Do something Cool
}
New-Item -Path "G:\filesToInject\PsTemp" -Name FirstBoot.ps1 -ItemType 'file' -Value $FirstBootContent
$ConverParm = @{
Path = 'G:\vhd\2012r2_eval_Core.vhdx'
Size = 60gb
Dynamic = $true
DiskLayout = 'UEFI'
SourcePath = 'G:\iso\Srv2012r2Eval.ISO'
Index = 1
Feature = 'NetFx3'
Unattend = $UnattentPath
filesToInject = 'G:\filesToInject\PsTemp'
}
Convert-Wim2VHD @ConverParm -Verbose
Below is part of the verbose output that covers Unattend and filesToInject
VERBOSE: [Set-VHDPartition] [2012r2_eval_Core.vhdx] Windows Partition [3] : Adding files from G:\filesToInject
VERBOSE: [Set-VHDPartition] [2012r2_eval_Core.vhdx] Windows Partition [3] : Adding Unattend.xml (C:\Users\Blade_000\AppData\Local\Temp\djebkgrz\unattend.xml)
So now when this vhdx first boots it will process Unattend creating users and running our PowerShell script
Leave a Comment
Your email address will not be published. Required fields are marked *