Templates/unattend.xml
|
<?xml version="1.0" encoding="utf-8"?>
<!-- THE ANSWER FILE New-HDTWorkspace SEEDS ONTO A SHARE, and the one Windows Setup reads out of Windows\Panther after the image is applied. ITS ANCESTOR DEPLOYED A REAL MACHINE. tests/fixtures/unattend/win11-client.xml was captured off a Windows 11 Enterprise LTSC machine SPIKES S7 had just built - ComputerName applied in specialize, OOBE skipped, the built-in Administrator enabled, FirstLogonCommands executed, autologon armed. This document is that one plus the elements listed in the allow-list of tests/contract/UnattendTemplate.Contract.Tests.ps1, each with its reason. EVERY ELEMENT HERE MUST BE A REAL SETTING, IN A PASS THAT ACCEPTS IT. That is not advice, it is the difference between a deployment and a machine sitting at a "Windows could not parse or process the unattend answer file" dialog with the operating system already on the disk. Setup validates the whole document: ONE element it does not recognise and NOTHING in the file is applied. On 2026-08-28 that happened here, at step 10 of 11, over two additions: * EnableFirstLogonAnimation, which is not an unattend setting at all - it is a Group Policy / Policy CSP value (Policy CSP WindowsLogon). Setup named the oobeSystem pass and refused the file. If the first-logon animation is ever worth suppressing, it is a registry write from a FirstLogonCommands entry, in the pass that can actually do it. * Microsoft-Windows-PnpCustomizationsNonWinPE, in specialize. See below. BOTH ARE NOW A CONTRACT TEST, driven off this file as parsed XML against the documented passes - so the next one fails in the suite instead of on a bench. THE DriverPaths BLOCK, AND WHY IT IS IN offlineServicing This block was briefly DELETED, on the reasoning that it sat in the specialize pass (which does not accept it) and that offlineServicing could never run here anyway. The first half was right and the second was wrong, and the wrong half cost the feature: ApplyDrivers went on copying packages to <OSVolume>\Drivers with nothing anywhere telling Windows to install them. BOTH AUTHORITIES PUT IT IN offlineServicing, and they agree to the line: MDT Templates\Unattend_x64.xml:168-175 (Microsoft Deployment Toolkit) PSD Templates\Unattend_x64.xml:124-131 (friendsOfMDT/PSD, MIT) offlineServicing is processed when the answer file is APPLIED TO AN OFFLINE IMAGE, and MDT does exactly that at LTIApply.wsf:1021-1043 - it copies the document into Panther and then runs dism.exe /Image:<osvolume>\ /Apply-Unattend:<osvolume>\Windows\Panther\Unattend.xml /ScratchDir:... whose own comment reads "This takes care of driver injection and servicing". PSD does the same thing through Use-WindowsUnattend (PSDConfigure.ps1:151). HDT had neither; Invoke-HDTApplyUnattendStep now makes that call through IImageService.ApplyUnattend after staging this file, so the pass below is REACHED rather than merely declared. THE PATH IS IMAGE-ROOT-RELATIVE, WHICH IS WHY IT HAS NO DRIVE LETTER. DISM is handed the OS volume as the image root, so \Drivers resolves to <OSVolume>\Drivers - the exact folder ApplyDrivers stages to. That the two agree is asserted in tests/contract/UnattendTemplate.Contract.Tests.ps1 by running the staging step and comparing where it wrote against what this document declares, because the two drifting apart is the whole defect this block was deleted over. It is also the right value once the machine boots and the OS volume is C:, since \Drivers on C: is C:\Drivers either way. MDT ALSO WRITES DevicePath, and that is deliberately NOT copied here. ZTIDrivers.wsf:527 loads the offline SOFTWARE hive and appends to HKLM\...\CurrentVersion\DevicePath; its sibling UpdateOEMPath (:446) writes OemPnPDriversPath into a sysprep.inf, which is the XP-era text answer file and is downlevel only. The answer file is the mechanism MDT's own comment credits with installing the drivers, and it is the one HDT uses. WHY EVERY SUBSTITUTED VALUE IS XML-ESCAPED Invoke-HDTApplyUnattendStep escapes each value as it substitutes it, and the history of that line matters because deleting it is what broke this file. There was once a New-HDTDeploymentPassword whose alphabet excluded the five XML metacharacters and the per cent sign ON PURPOSE, so a minted password substituted into this document without escaping. That command is gone - DESIGN 4.5.2 settled it, "the administrator sets the password; HDT does not invent one" - and its guarantee went with it, while the substitution stayed unescaped. Every password reaching this file is now a string a human typed, and 'Pa&ss' is a legal Windows password that produces an answer file Setup cannot parse. NOTE THAT THIS COMMENT NAMES TOKENS WITHOUT THEIR PER CENT SIGNS, on purpose: expansion runs over the WHOLE document, comments included, so a comment written with the token in it would put the machine's local Administrator password into a comment in the deployed answer file. --> <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"> <ComputerName>%HDTComputerName%</ComputerName> <RegisteredOwner>%HDTFullName%</RegisteredOwner> <RegisteredOrganization>%HDTOrgName%</RegisteredOrganization> <!-- REMOVED ENTIRELY when no key is supplied, never left empty: an empty ProductKey fails the specialize pass, and so does the unexpanded token. Invoke-HDTApplyUnattendStep does the removing. --> <ProductKey>%HDTProductKey%</ProductKey> <TimeZone>%HDTTimeZone%</TimeZone> </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"> <InputLocale>%HDTKeyboardLocale%</InputLocale> <SystemLocale>%HDTSystemLocale%</SystemLocale> <UILanguage>%HDTUILanguage%</UILanguage> <UserLocale>%HDTUserLocale%</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"> <OOBE> <HideEULAPage>true</HideEULAPage> <HideLocalAccountScreen>true</HideLocalAccountScreen> <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> <HideOnlineAccountScreens>true</HideOnlineAccountScreens> <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> <NetworkLocation>Work</NetworkLocation> <ProtectYourPC>3</ProtectYourPC> <UnattendEnableRetailDemo>false</UnattendEnableRetailDemo> </OOBE> <UserAccounts> <AdministratorPassword> <Value>%HDTAdminPassword%</Value> <PlainText>true</PlainText> </AdministratorPassword> </UserAccounts> <!-- DESIGN 4.5.1: the oobeSystem AutoLogon block is what arms the FIRST logon of the deployed machine. Every leg after that is armed by Set-HDTAutoLogon against the machine's own registry and LSA store. The password token appears twice in this document because Setup reads UserAccounts and AutoLogon separately. --> <AutoLogon> <Enabled>true</Enabled> <LogonCount>999</LogonCount> <Username>Administrator</Username> <Password> <Value>%HDTAdminPassword%</Value> <PlainText>true</PlainText> </Password> </AutoLogon> <FirstLogonCommands> <SynchronousCommand wcm:action="add"> <Order>1</Order> <CommandLine>reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v StartShownOnUpgrade /t REG_DWORD /d 1 /f</CommandLine> <Description>Hephaestus - show Start on upgrade</Description> <RequiresUserInput>false</RequiresUserInput> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>2</Order> <CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\HDT\Start-HDTResume.ps1</CommandLine> <Description>Hephaestus - resume the task sequence</Description> <RequiresUserInput>false</RequiresUserInput> </SynchronousCommand> </FirstLogonCommands> </component> </settings> <!-- WHERE ApplyDrivers PUT THE DRIVERS, declared so Windows installs them. Shape and pass taken from MDT Templates\Unattend_x64.xml:168-175 and PSD Templates\Unattend_x64.xml:124-131 (MIT); see NOTICE.md and the header above. \Drivers has no drive letter because it is relative to the image root DISM is given, which is the OS volume. --> <settings pass="offlineServicing"> <component name="Microsoft-Windows-PnpCustomizationsNonWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"> <DriverPaths> <PathAndCredentials wcm:keyValue="1" wcm:action="add"> <Path>\Drivers</Path> </PathAndCredentials> </DriverPaths> </component> </settings> </unattend> |