Windows 7, like Vista, uses an XML answer file to configure the OS install. What’s neat about this is that even though you use the WAIK‘s WSIM tool to edit and validate it, you can customize it and add your own sections for software packages etc. as you can see from the example below, though these custom sections will need to be inserted after the sections that WSIM validates. This answer file can easily be parsed with VBScript using MSXML DOM, allowing for variables like passwords, driver sets, product keys and so on to be inserted at build time.
Why use an unattended install?
If you’ve always used an unattended install to build your workstations, you’ll know that they can be extremely versatile. If you already have a scripted build for XP with applications then chances are you’ll want to tweak those scripts to work with Windows 7. Sure, Sysprep images are handy too, but unless all your machines are the same, or all your packaged app requirements are identical, then you’ll need to add a load of scripted customization to them anyway. Which begs the question: why not just use an unattended install? That way you eliminate application problems that sometimes surface after image-based deployment. In a previous job I remember Roxio deployment in particular was a nightmare for this reason.
Microsoft have certainly made things considerably easier with the release of the Microsoft Deployment Toolkit 2010 but, though it offers a great introduction into the process of automated system building, it lacks the flexibility of rolling your own build process – in particular if you already have a host database. When I decide to rebuild a machine, it boots Windows PE from WDS, looks up its MAC address in the host database and reads the model type from WMI and will offer default choices in my build script menus based on that. It also works out which is the nearest site file server to use for the install.
The problem with Sysprep deployment if you have a very mixed hardware environment is that you either have to:
- Create a WIM image for each different hardware type (lots of boring maintenance when changes are required)
- Add a huge bulk of drivers to a single WIM image with DISM
- Use the AuditSystem phase to connect to a driver share and re-detect all the hardware
Since Windows Vista and later versions effectively just install a WIM and run a hardware detection phase during their normal install process, Sysprep no longer offers much of a speed improvement over an unattended build.
Device drivers
Additional mass storage and networking drivers that will be essential during setup are detected in the Windows PE instance (which we booted from WDS) Driver Store and ‘reflected’ into the installed OS. As you build your custom Windows PE boot image, add these using DSIM as described in my post on the subject.
Since we can read the PC model name from the BIOS via WMI, we can add a tailored device path to the offlineServicing phase of setup. This allows for easy maintenance of driver bundles, since we can arrange them by model type (which certainly beats having dozens of SoundMAX audio drivers lumped together), and we can limit which drivers are offered to each model – particularly useful when a driver causes problems for some models, as with the GMA950 driver and Adobe Flash on Intel 945G motherboards.
I noticed that this is in fact considerably neater that the method used in MDT2010’s ZTIDrivers.wsf script, which copies drivers to the local system then invokes the AuditSystem phase, running the PnP detection routines a second time, slowing down the install. Useful for an OEM like Dell I suppose, whose PCs are often started from their factory image with no connectivity, but not ideal for corporate LANs.
Sample autounattend.xml with custom sections at the end
Note 1 – I couldn’t find any examples of this online, but I discovered that the values for pre-populating the Internet Explorer 8 Search Providers can be obtained by configuring a workstation, then harvesting the registry settings from HKCU\Software\Microsoft\Internet Explorer\SearchScopes. I have highlighted the relevant lines in the XML below.
Note 2 – Somewhat confusingly, in the offlineServicing phase Microsoft-Windows-PnpCustomizationsNonWinPE will fail to connect to your file server for drivers unless you connect to its FQDN (assuming the unattended launched the OS build from a share referencing just the NetBIOS name). Fail to do this and %systemroot%\panther\setupact.log will reveal that it fails to connect with error 0x4C3 (multiple credentials on connection to the same server). What’s bizarre is that there certainly aren’t multiple credentials in use – I use the same ones throughout. I wrote up this problem and solution in this thread on the MSFN Forums. I suspect this might be caused because I launch setup.exe from a network drive rather than mounting the OS WIM image from a WDS server (I wanted to maintain consistency with my other legacy OS builds). I have highlighted this on line 57.
<?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="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">
<SetupUILanguage>
<UILanguage>en-US</UILanguage>
</SetupUILanguage>
<InputLocale>0809:00000809</InputLocale>
<SystemLocale>en-GB</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-GB</UserLocale>
</component>
<component name="Microsoft-Windows-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">
<ComplianceCheck>
<DisplayReport>Never</DisplayReport>
</ComplianceCheck>
<Diagnostics>
<OptIn>false</OptIn>
</Diagnostics>
<DiskConfiguration>
<WillShowUI>Always</WillShowUI>
</DiskConfiguration>
<DynamicUpdate>
<Enable>true</Enable>
<WillShowUI>OnError</WillShowUI>
</DynamicUpdate>
<ImageInstall>
<OSImage>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/NAME</Key>
<Value>Windows 7 PROFESSIONAL</Value>
</MetaData>
</InstallFrom>
</OSImage>
</ImageInstall>
<UserData>
<AcceptEula>true</AcceptEula>
<FullName>IT</FullName>
<Organization>My company</Organization>
<ProductKey>
<WillShowUI>OnError</WillShowUI>
</ProductKey>
</UserData>
<EnableNetwork>true</EnableNetwork>
</component>
</settings>
<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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="common">
<Credentials>
<Domain>domain.com</Domain>
<Password>Password</Password>
<Username>unattended</Username>
</Credentials>
<Path>\\myserver.domain.com\UNATTENDED\drivers\7-x64\common</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="build">
<Credentials>
<Domain>*value to be set by install.vbs*</Domain>
<Password>*value to be set by install.vbs*</Password>
<Username>*value to be set by install.vbs*</Username>
</Credentials>
<Path>*value to be set by install.vbs*</Path>
</PathAndCredentials>
</DriverPaths>
</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>
<settings pass="specialize">
<component name="Microsoft-Windows-IE-InternetExplorer" 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">
<Home_Page>http://mycompany.com</Home_Page>
<CompanyName>My Company</CompanyName>
<FavoritesOnTop>true</FavoritesOnTop>
<FilterLevel>High</FilterLevel>
<Help_Page></Help_Page>
<DisableFirstRunWizard>true</DisableFirstRunWizard>
<DisableWelcomePage>true</DisableWelcomePage>
<PlaySound>true</PlaySound>
<ShowInformationBar>true</ShowInformationBar>
<UserAgent></UserAgent>
<Window_Title_CN></Window_Title_CN>
<SearchScopes>
<Scope wcm:action="add">
<ScopeDefault>true</ScopeDefault>
<ScopeKey>Search1</ScopeKey>
<ScopeDisplayName>Google</ScopeDisplayName>
<ScopeUrl>http://www.google.com/search?q={searchTerms}&sourceid=ie7&rls=com.microsoft:{language}:{referrer:source}&ie={inputEncoding?}&oe={outputEncoding?}</ScopeUrl>
<FaviconURL>http://www.google.com/favicon.ico</FaviconURL>
<SuggestionsURL>http://clients5.google.com/complete/search?q={searchTerms}&client=ie8&mw={ie:maxWidth}&sh={ie:sectionHeight}&rh={ie:rowHeight}&inputencoding={inputEncoding}&outputencoding={outputEncoding}</SuggestionsURL>
</Scope>
<Scope wcm:action="add">
<ScopeKey>Search2</ScopeKey>
<ScopeDisplayName>Bing</ScopeDisplayName>
<FaviconURL>http://www.bing.com/favicon.ico</FaviconURL>
<ScopeUrl>http://www.bing.com/search?q={searchTerms}&form=IE8SRC&src=IE-SearchBox</ScopeUrl>
<SuggestionsURL>http://api.bing.com/qsml.aspx?query={searchTerms}&market={Language}&form=IE8SSC&maxwidth={ie:maxWidth}&rowheight={ie:rowHeight}&sectionHeight={ie:sectionHeight}</SuggestionsURL>
</Scope>
</SearchScopes>
<EnableLinksBar>false</EnableLinksBar>
<PrintBackground>true</PrintBackground>
</component>
<component name="Microsoft-Windows-RemoteAssistance-Exe" 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">
<fAllowFullControl>true</fAllowFullControl>
<fAllowToGetHelp>true</fAllowToGetHelp>
</component>
<component name="Microsoft-Windows-Security-SPP-UX" 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">
<SkipAutoActivation>true</SkipAutoActivation>
</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>
<ProductKey>XXXXX-YYYYY-ZZZZZ-YYYYY-XXXXX</ProductKey>
<RegisteredOrganization>My Company</RegisteredOrganization>
<RegisteredOwner>IT</RegisteredOwner>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" 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">
<fDenyTSConnections>false</fDenyTSConnections>
</component>
<component name="Microsoft-Windows-UnattendedJoin" 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">
<Identification>
<Credentials>
<Domain>*value to be set by install.vbs*</Domain>
<Password>*value to be set by install.vbs*</Password>
<Username>*value to be set by install.vbs*</Username>
</Credentials>
<JoinDomain>domain.com</JoinDomain>
<MachineObjectOU>OU=Windows 7,OU=Workstations,DC=domain,DC=com</MachineObjectOU>
</Identification>
</component>
<component name="Networking-MPSSVC-Svc" 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">
<DomainProfile_EnableFirewall>false</DomainProfile_EnableFirewall>
</component>
<component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" 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">
<UserAuthentication>1</UserAuthentication>
</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">
<SystemLocale>en-GB</SystemLocale>
<UILanguage>en-GB</UILanguage>
<UserLocale>0809:00000809</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">
<RegisteredOrganization>My Company</RegisteredOrganization>
<RegisteredOwner>IT</RegisteredOwner>
<TimeZone>GMT Standard Time</TimeZone>
<OEMInformation>
<Manufacturer>*</Manufacturer>
<Model>*</Model>
</OEMInformation>
<OOBE>
<HideEULAPage>true</HideEULAPage>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>1</ProtectYourPC>
</OOBE>
<UserAccounts>
<AdministratorPassword>
<Value>MwBifhgftytredjghAcwB0AHIAYQ584jkhkgtEAcwBzAHcAbwByAGQA</Value>
<PlainText>false</PlainText>
</AdministratorPassword>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Password>
<Value>QQBTAEUANABoAGchgfhgfd357wrysAHMAcwB3AG8AcgBkAA==</Value>
<PlainText>false</PlainText>
</Password>
<Name>cust_localuser</Name>
<DisplayName>cust_localuser</DisplayName>
<Description>Dummy user required for unattended - delete later</Description>
<Group>users</Group>
</LocalAccount>
</LocalAccounts>
</UserAccounts>
<VisualEffects>
<FontSmoothing>ClearType</FontSmoothing>
</VisualEffects>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<Order>1</Order>
<Description>Connect to unattended share</Description>
<CommandLine>net use *value to be set by install.vbs*</CommandLine>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>2</Order>
<Description>Launch package installer</Description>
<CommandLine>cscript *value to be set by install.vbs*</CommandLine>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
</FirstLogonCommands>
<AutoLogon>
<Password>
<Value>MwBifhgftytredjghAcwB0AHIAYQ584jkhkgtEAcwBzAHcAbwByAGQA</Value>
<PlainText>false</PlainText>
</Password>
<Username>Administrator</Username>
<LogonCount>1</LogonCount>
<Enabled>true</Enabled>
</AutoLogon>
</component>
</settings>
<cpi:offlineImage cpi:source="wim://myserver/unattended/os/7-x64/sources/install.wim#Windows 7 PROFESSIONAL" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
<!-- MY COMPANY CUSTOMIZATIONS BELOW-->
<mycompany:custom xmlns:mycompany="urn:schemas-domain-com:mycompany">
<!-- build description -->
<cust_description>standard workstation</cust_description>
<!-- list of packages to install in order. Descriptions are read from U:\scripts\packages.csv -->
<cust_packages>office2k7.cmd,adobe.cmd,flash.cmd,java.cmd,fonts.cmd</cust_packages>
<!-- start of the model name string, as reported by WMI, comma separated - allows auto-selection of build. -->
<cust_models>OptiPlex 745,OptiPlex GX620,VMware Virtual Platform</cust_models>
<!-- do we want packages.vbs to activate Windows? -->
<cust_activate>false</cust_activate>
<!-- do we want a hibernation file wasting disk space? If set to false packages.vbs will disable hibernation support -->
<cust_hibernate>false</cust_hibernate>
</mycompany:custom>
</unattend>
Sample VBScript code for parsing the XML using MSXML DOM
This is not a complete script – it’s intended purely to illustrate the concept.
'New OS - 7/Vista/2008/2008R2
'read the answer file
Set objXML = CreateObject("Microsoft.XMLDOM")
objXML.Async = "False"
objXML.Load(strMedia & "\sifs\" & strOS & "\" & strBuild & ".xml")
'Additional mass storage and Networking drivers that will be essential for the rest of the build are detected in the
'running Windows PE instance's Driver Store and reflected into the installed OS
'insert the non-WinPE PnP driver discovery paths and credentials for the offlineServicing pass
strXPath = "/unattend/settings[@pass='offlineServicing']/component[@name='Microsoft-Windows-PnpCustomizationsNonWinPE']/DriverPaths/PathAndCredentials/Credentials"
'we need to iterate since there may be several sets of credentials (one for each driver path)
Set colNodes=objXML.selectNodes(strXPath & "/Domain")
For Each objNode In colNodes
objNode.Text = strDomainName
Next
Set colNodes=objXML.selectNodes(strXPath & "/Password")
For Each objNode In colNodes
objNode.Text = strPass
Next
Set colNodes=objXML.selectNodes(strXPath & "/Username")
For Each objNode In colNodes
objNode.Text = strUser
Next
'Insert hostname
strXPath = "/unattend/settings[@pass='specialize']/component[@name='Microsoft-Windows-Shell-Setup']/ComputerName"
Set objNode=objXML.selectSingleNode(strXPath)
objNode.Text = strComputerName
'insert credentials for Domain join
On Error Resume Next
strXPath = "/unattend/settings[@pass='specialize']/component[@name='Microsoft-Windows-UnattendedJoin']/Identification/Credentials"
Set objNode=objXML.selectSingleNode(strXPath & "/Domain")
If Err.Number = 0 Then
'Microsoft-Windows-UnattendedJoin exists - carry on modifying (workgroup builds won't have this section in the XML)
objNode.Text = strDomainName
Set objNode=objXML.selectSingleNode(strXPath & "/Password")
objNode.Text = strPass
Set objNode=objXML.selectSingleNode(strXPath & "/Username")
objNode.Text = strUser
End If
On Error Goto 0
'insert manufacturer and model into OEMinfo
strXPath = "/unattend/settings[@pass='oobeSystem']/component[@name='Microsoft-Windows-Shell-Setup']/OEMInformation/Manufacturer"
Set objNode=objXML.selectSingleNode(strXPath)
objNode.Text = strManufacturer
strXPath = "/unattend/settings[@pass='oobeSystem']/component[@name='Microsoft-Windows-Shell-Setup']/OEMInformation/Model"
Set objNode=objXML.selectSingleNode(strXPath)
objNode.Text = strModel & " - Your Company"
strXPath = "/unattend/settings[@pass='oobeSystem']/component[@name='Microsoft-Windows-Shell-Setup']/FirstLogonCommands/SynchronousCommand/CommandLine"
Set colNodes=objXML.selectNodes(strXPath)
For Each objNode In ColNodes
'Insert the unattended share info and credentials
If InStr (objNode.Text,"net use *value to be set by install.vbs*") Then
objNode.Text = "net use " & strMedia & " \\" & strInstallServer & "\" & strShare & " /user:" & strUser & "@" & strDomainName & " " & strPass & " /persistent:no"
End If
'Record our build file selection for the package installer later
If InStr (objNode.Text,"cscript *value to be set by install.vbs*") Then
objNode.Text = "cscript //nologo " & strMedia & "\scripts\packages.vbs " & strMedia & " " & strOS & " " & strBuild & ".xml"
End If
Next
Set objNode = Nothing
'write out the answer file
objXML.Save "x:\autounattend.xml"
Set objXML = Nothing