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>