Summary: Use Windows PowerShell to apply an image to a Windows To Go key and make it bootable.
Hey, Scripting Guy! Now that I have a Windows To Go key partitioned and formatted for use, how do I apply the operating system image to it?
—MR
Hello MR,
Honorary Scripting Guy, Sean Kearney, is here to continue forth and show you the next step in creating a Windows To Go key.
Note This is the third post in a five-part series. Before you read this, you should read:
- Use PowerShell to Create Windows To Go Keys—Part 1
- Use PowerShell to Create Windows To Go Keys—Part 2
To use Windows To Go, we must have one of the following versions of the Windows operating system:
- Windows 10 Enterprise
- Windows 8.1 Enterprise
- Windows 8 Enterprise
The source of the image can be a gold image that is prepopulated with applications or the stock Install.wim file, which is found in the \Sources folder of the installation media.
To apply the image, I need to follow three steps:
- Apply the image to the media.
- Enable the Windows To Go key for booting.
- Apply the SAN policy to restrict access to drives.
To apply the image, I use the Expand-WindowsImage cmdlet, which does what DISM.exe would have done. The advantage to using this cmdlet is, of course, consistency in the parameters—being that it’s in PowerShell.
I need to supply parameters very similar to DISM.exe. I need to give the cmdlet the path to the .wim file, which contains the operating system, the index number that contains the image, and the target path for storing the image. This target path is the intended drive letter of the operating system for the Windows To Go key.
In my previous post, I assigned the following variable names for the system and operating system drive letters: $DriveSystem and $DriveOS. These variables contain only the actual letters, and they are not in the format of “C:” or “D:”.
To bypass this issue, I can simply populate the drive letter like this:
“$DriveOS`:”
This will format the drive letter as “C:” or “D:”, which is what the cmdlet needs.
To use Expand-WindowsImage (presuming Install.wim is in the same folder as the script), the script would look like this:
$Wimfile=’.\install.wim’
Expand-WindowsImage –imagepath "$wimfile" –index 1 –ApplyPath "$DriveOS`:\"
Typically on a USB 3.0 device, this could take 10 minutes. There is a progress bar at the top of the screen, but unfortunately, it only seems to have two states: Started and Done. It will not update or show a percentage of completion, and it disappears only when it’s done.
Next, I'll use the BCDBoot command in Windows to apply the boot code. Some articles refer to using BCDBoot in the destination partition. The drawback to this approach is if you’re trying to create a 32-bit Windows To Go on a 64-bit platform or vice versa.
One of the challenges I have had is passing variables to an older DOS application, including passing a variable as its path. To solve this, normally I could wrap the entire command as a string and leverage it against Invoke-Command.
The reason I didn’t choose this approach is that Invoke-Command will launch a new parent process that will lose me the resulting status code if anything bad happens. I can alleviate this issue by using the special & character to launch the command with variables within or as a path name.
To programmatically grab the folder that contains BCDBoot, I’ll use the $ENV:Windir variable:
& "$($env:windir)\system32\bcdboot" "$OSDrive`:\Windows" /f ALL /s "$Systemdrive`:"
I next need to apply the SAN-Policy.xml file. This is a single XML file very similar to Unattend.xml, and it defines what happens to the drive when attached to a host environment. The default configuration of a Windows To Go key when booted will leave any internal drives offline. Similarly by default, when accessed from a parent operating system, the Windows To Go key is not assigned a drive letter.
I could have a separate SAN-Policy.xml file sitting on the hard drive, but I prefer to keep the data in the script. For that purpose, I leverage a here string to place content in the script, and then rebuild it each time on the fly.
$Policy=@"
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="offlineServicing">
<component
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
language="neutral"
name="Microsoft-Windows-PartitionManager"
processorArchitecture="x86"
publicKeyToken="31bf3856ad364e35"
versionScope="nonSxS"
>
<SanPolicy>4</SanPolicy>
</component>
<component
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
language="neutral"
name="Microsoft-Windows-PartitionManager"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
versionScope="nonSxS"
>
<SanPolicy>4</SanPolicy>
</component>
</settings>
</unattend>
"@
I'll use a little Remove-Item and Add-Content to populate the file content for SAN-Policy.xml:
$SanPolicyFile=’.\san-policy.xml’
Remove-item $SanPolicyFile -erroraction SilentlyContinue
Add-content -path $SanPolicyFile -Value $Policy
At this point, I use the Use-WindowsUnattend cmdlet, which needs the drive letter of the operating system and the file name of the SAN-Policy.xml file I created:
Use-WindowsUnattend –unattendpath $SanPolicyFile –path "$OSdrive`:\"
In a short amount of time, the basics of a Windows To Go key are complete. You can stop right now if you want to. But there’s more we can look into doing. We could preset the time zone or inject needed drivers. For that, pop back tomorrow and I’ll show you how to polish up that Windows To Go key.
I invite you to follow The Scripting Guys on Twitter and Facebook. If you have any questions, send an email to The Scripting Guys at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, remember to eat your cmdlets every day with a dash of creativity.
Sean Kearney, Windows PowerShell MVP and Honorary Scripting Guy