Creating a WinPE bootable image with Powershell 4

Prerequisites: I did all the following from a Windows 8.1 preview that has Powershell 4.0 installed by default

Step 1: Download ADK 8.1

#Requires -Version 4
#Requires -RunAsAdministrator 

Function Get-ADKFiles {
Begin {
    $HT = @{}
    $HT += @{ ErrorAction = 'Stop'}
    # Validate target folder
    try {
        Get-Item $TargetFolder @HT | Out-Null
    } catch {
        Write-Warning -Message "The target folder specified as parameter does not exist"

Process {
    $adkGenericURL = (Invoke-WebRequest -Uri -MaximumRedirection 0 -ErrorAction SilentlyContinue)
    # There's an expected error saying:
    # The maximum redirection count has been exceeded. 
    # To increase the number of redirections allowed, supply a higher value to the -MaximumRedirection parameter.

    # 302 = redirect as moved temporarily
    if ($adkGenericURL.StatusCode -eq 302) {
        # Currently set to
        $MainURL = $adkGenericURL.Headers.Location

        $AllURLs = DATA {
            ConvertFrom-StringData @'
                0=Toolkit Documentation-x86_en-us.msi
                6=Application Compatibility Toolkit-x86_en-us.msi
                11=Microsoft Compatibility Monitor-x86_en-us.msi
                12=Application Compatibility Toolkit-x64_en-us.msi
                14=Microsoft Compatibility Monitor-x86_en-us.msi
                15=Windows Deployment Tools-x86_en-us.msi
                43=Windows System Image Manager on amd64-x86_en-us.msi
                44=Windows System Image Manager on x86-x86_en-us.msi
                45=Windows Deployment Customizations-x86_en-us.msi
                52=Windows PE x86 x64-x86_en-us.msi
                57=Windows PE x86 x64 wims-x86_en-us.msi
                60=User State Migration Tool-x86_en-us.msi
                63=Volume Activation Management Tool-x86_en-us.msi
                67=WPT Redistributables-x86_en-us.msi
                71=Windows Assessment Toolkit-x86_en-us.msi
                75=Windows Assessment Toolkit (AMD64 Architecture Specific)-x86_en-us.msi
                76=Windows Assessment Toolkit (X86 Architecture Specific)-x86_en-us.msi
                77=Assessments on Client-x86_en-us.msi
                140=Windows Assessment Services-x86_en-us.msi
                144=Windows Assessment Services - Client (Server SKU)-x86_en-us.msi
                147=Windows Assessment Services - Client (AMD64 Architecture Specific, Server SKU)-x86_en-us.msi
                148=Assessments on Server-x86_en-us.msi
                150=Windows Assessment Services - Client (Client SKU)-x86_en-us.msi
                151=Windows Assessment Services - Client (X86 Architecture Specific, Client SKU)-x86_en-us.msi
                152=Windows Assessment Services - Client (AMD64 Architecture Specific, Client SKU)-x86_en-us.msi
                153=Kits Configuration Installer-x86_en-us.msi

        # Create target folders if required as BIT doesn't accept missing folders
        If (-not(Test-Path (Join-Path -Path $TargetFolder -ChildPath Installers))) {
            try {
                New-Item -Path (Join-Path -Path $TargetFolder -ChildPath Installers) -ItemType Directory -Force @HT
                # New-Item -Path $TargetFolder -ItemType Directory -Force -ErrorAction Stop
            } catch {
                Write-Warning -Message "Failed to create folder $($TargetFolder)/Installers"

        # Get adksetup.exe
        iwr -Uri "$($MainURL)adksetup.exe" -OutFile  "$($TargetFolder)\adksetup.exe"

        # Create an job that will downlad our first file
        $job = Start-BitsTransfer -Suspended -Source "$($MainURL)Installers/$($AllURLs['0'])" -Asynchronous -Destination (Join-Path -Path $TargetFolder -ChildPath ("Installers/$($AllURLs['0'])")) 
        For ($i = 1 ; $i -lt $AllURLs.Count ; $i++) {
            $URL = $Destination = $null
            $URL = "$($MainURL)Installers/$($AllURLs[$i.ToString()])"
            $Destination = Join-Path -Path (Join-Path -Path $TargetFolder -ChildPath Installers) -ChildPath (([URI]$URL).Segments[-1] -replace '%20'," ")
            # Add-BitsFile
            $newjob = Add-BitsFile -BitsJob $job -Source  $URL -Destination $Destination
            Write-Progress -Activity "Adding file $($newjob.FilesTotal)" -Status "Percent completed: " -PercentComplete (($newjob.FilesTotal)*100/($AllURLs.Count))

        # Begin the download and show us the job
        Resume-BitsTransfer  -BitsJob $job -Asynchronous

        while ($job.JobState -in @('Connecting','Transferring','Queued')) {
            Write-Progress -activity "Downloading ADK files" -Status "Percent completed: " -PercentComplete ($job.BytesTransferred*100/$job.BytesTotal)
        Switch($job.JobState) {
         "Transferred" {
            Complete-BitsTransfer -BitsJob $job
         "Error" {
            # List the errors.
            $job | Format-List 
        default {
            # Perform corrective action.
End {}

# Dotsource the above script            
. C:\Get-ADK81.ps1            
# Download ADK 8.1            
Get-ADKFiles -TargetFolder C:\ADK.8.1            

Step 2: Install ADK 8.1

$MyADKPath = "C:\ADK.8.1"            
If (Test-Path -Path "$MyADKPath\adksetup.exe") {            
    & (gcm "$MyADKPath\adksetup.exe") @(            
     "/installpath", "${env:ProgramFiles(x86)}\Windows Kits\8.1",            
     "OptionId.DeploymentTools OptionId.WindowsPreinstallationEnvironment",            
$adkinstall = (Get-Process -Name "adksetup")[0]            
while (-not($adkinstall.HasExited)) {            
    Get-Content -Path "$env:TEMP\ADKsetup.log" -Tail 1             
 Start-Sleep -Seconds 1            

Step 3: Create a custom WinPE wim image

…and its ISO file as well to be either burnt on a CD/DVD or that can be used as a bootable media of a virtual machine 🙂

$Arch = "x64"
$TargetArch = "amd64"
$PEFolder = "C:\WINPE_$($Arch)"
$WAIKLOCATION = "${env:ProgramFiles(x86)}\Windows Kits\8.1\Assessment and Deployment Kit"
if (Test-Path $WAIKLOCATION) {

    $PEFolder,"$PEFolder\ISO","$PEFolder\ISO\Sources","$PEFolder\mount" | ForEach-Object {
        if (-not(Test-Path $_ -PathType Container)) {
            try {
                New-Item -Path $_ -ItemType Container -ErrorAction Stop -Force
            } catch {
                Write-Warning -Message "Failed to create target folders"

    try {
        Copy-Item -Path "$WAIKLOCATION\Windows Preinstallation Environment\$TargetArch\Media\bootmgr*" -Destination "$PEFolder\ISO\" -Force -ErrorAction Stop
        Copy-Item -Path "$WAIKLOCATION\Deployment Tools\$TargetArch\Oscdimg\" -Destination $PEFolder -Force -ErrorAction Stop
        Copy-Item -Path "$WAIKLOCATION\Deployment Tools\$TargetArch\Oscdimg\efisys.bin" -Destination $PEFolder -Force -ErrorAction Stop
        & (gcm "$env:systemroot\system32\robocopy.exe") @("$WAIKLOCATION\Windows Preinstallation Environment\$TargetArch\Media\boot","$PEFolder\ISO\boot",'/S','/r:0','/Z','/PURGE') | Out-Null
        Copy-Item -Path "$WAIKLOCATION\Windows Preinstallation Environment\$TargetArch\en-us\winpe.wim" -Destination $PEFolder -ErrorAction Stop -Force
    } catch {
        Write-Warning "Copy Failed"

    try {

        # Mount the image
        Get-WindowsImage -ImagePath "$PEFolder\winpe.wim" -ErrorAction Stop | ForEach-Object {
            Mount-WindowsImage -Path "$PEFolder\mount" -ErrorAction Stop -Index $_.ImageIndex -ImagePath $_.ImagePath

        # dism.exe /Get-MountedWimInfo
        Get-WindowsImage -Mounted -ErrorAction Stop

        # Add Packages
        "HTA","Scripting","WMI","Dot3Svc","NetFx","PowerShell","DismCmdlets" | ForEach-Object {
            Add-WindowsPackage -Path "$PEFolder\mount" -PackagePath "$WAIKLOCATION\Windows Preinstallation Environment\$TargetArch\WinPE_OCs\winpe-$" -ErrorAction Stop

        # Set the keyboard layout to French
        & (gcm "$WAIKLOCATION\Deployment Tools\$TargetArch\DISM\dism.exe") @("/image:$PEFolder\mount",'/Set-InputLocale:040c:0000040c')

        # Increase the writable memory space
        & (gcm "$WAIKLOCATION\Deployment Tools\$TargetArch\DISM\dism.exe") @("/image:$PEFolder\mount",'/Set-ScratchSpace:256')

        # Set the time zone to GMT+1 Brussels, Copenhagen, Madrid, Paris
        & (gcm "$WAIKLOCATION\Deployment Tools\$TargetArch\DISM\dism.exe") @("/image:$PEFolder\mount",'/Set-TimeZone:Romance Standard Time')

        # Unmount
        Get-WindowsImage -Mounted -ErrorAction Stop | ForEach-Object {
            # Save-WindowsImage -Path $_.ImagePath
            Dismount-WindowsImage -Path $_.Path -Save -ErrorAction Stop

        # Create the ISO file    
        Copy-Item -Path "$PEFolder\winpe.wim" -Destination "$PEFolder\ISO\Sources\boot.wim" -ErrorAction Stop

        $BOOTDATA='2#p0,e,b"{0}"#pEF,e,b"{1}"' -f "$PEFolder\","$PEFolder\efisys.bin"
        & (gcm "$WAIKLOCATION\Deployment Tools\amd64\Oscdimg\oscdimg.exe") @("-bootdata:$BOOTDATA",'-u1','-udfver102',"$PEFolder\ISO","$PEFolder\winpe.iso")

    } catch {
        Write-Warning "Failed to create ISO image"

Step 4: Prepare a USB stick

# Erase all the data on the USB stick ~ diskpart / clean            
(Get-Disk)[-1] | Clear-Disk -RemoveData:$true            

(Get-Disk)[-1] | Get-Partition            
# Create an active partition            
(Get-Disk)[-1] | New-Partition -UseMaximumSize:$true -IsActive -Verbose            

# List the new volume            
(Get-Disk)[-1] | Get-Partition | Get-Volume            
# Simulate the format of the volume            
(Get-Disk)[-1] | Get-Partition | Get-Volume | Format-Volume -FileSystem NTFS -WhatIf            
# Format the new partition            
(Get-Disk)[-1] | Get-Partition | Get-Volume | Format-Volume -FileSystem NTFS

If the partition doesn’t get a drive letter assigned automatically, you can do:

(Get-Disk)[-1] | Get-Partition  | Set-Partition -NewDriveLetter Z

Move the content to the USB media:

robocopy --% C:\WINPE_x64\ISO Z:\ /S /r:0 /Z

Booting from the ISO file a VM with 1024MB memory and no HDD attached


3 thoughts on “Creating a WinPE bootable image with Powershell 4

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.