Home » Uncategorized » Scripting Win10 system deployment

After streamlining in place upgrade and provisioning package for bringing new systems with OEM image to enterprise standard, customised windows 10 image is the next to meet ongoing requirements like system reinstallation, new systems that doesn’t come with OEM image or image other than windows 10 OS etc. Key considerations and inputs for defining the approach includes

  • UEFI is the way forward
    • UEFI requires GPT disk partition and conversation from earlier MBR to GPT would require disk clean up
    • Boot media to be FAT32 for UEFI to boot from it, be it local disk or installation media. A FAT32 filesystem supports max 4GB file and hence if the windows image is large than that, splitting of the image file is required
  • Windows Preinstallation Environment or WinPE that comes part of Windows 10 installation media (\sources\boot.wim) comes with features sufficient to start windows installation process and complete the OS deployment. However, a custom WinPE can be created to include additional features like PowerShell and thus bring automation and customization to OS deployment, outside the customization done at windows image level.
  • There is one more WIM file, winre.wim, that gives the windows recovery environment. This file can be located inside the mounted folder of windows 10 image at the location C:\Windows\System32\Recovery. Part of the UEFI OS deployment this file is often deployed into a separate partition. The captured image from it might be missing the same. Hence additional steps needed to have it extracted from original installation media and deploy as part of OS deployment.
    • Custom WinPE can also be used for this purpose of WinRE. However, WinRE comes with WinPE-SRT, an Optional component which is not available as an add-on like PowerShell to incorporate into WinPE. WinPE-SRT is required by the MDOP DaRT tool to produce a DaRT recovery image to be used instead of default WinRE image. In such case, instead of using the custom WinPE, default boot.wim or winre.wim is used.
  • Encrypting the disk with bitlocker prior to OS deployment hardly increase overall deployment time and can save hours of user / engineer time that goes in completing the encryption after OS deployment. This is because of lesser time required to write encrypted data sequentially to a blank hard drive partition with no OS activities. Compared to that, encryption after OS deployment has to read data from disk, encrypt and put it back replacing existing data and the running OS overheads like page file activities.
    • This pre-provisioning of bitlocker ensures disk is encrypted but not the data security. Data is not secure till the encryption key is protected by a KeyProtector like TPM and recovery password. Addtional process or tool like Microsoft BitLocker Administration and Monitoring (MBAM) can complete that part at no addition overhead as the disk is already encrypted.
  • Windows imaging format (WIM) brings single-instance storage advantage. So I can pack multiple windows 10 image, like once captured on a HP system with all drivers and tool installed, one on Lenovo on similar way and one on virtual machine with no OEM driver or tool installed. So resultant WIM file would consume space of common files like Windows OS, applications like Office etc. once. Here are three image, first one on VM i.e. no driver and next two having corresponding HP and Lenovo driver. Last one is the WIM file that contains all this three images.

    • This combined with PowerShell scripting from custom WinPE brings the opportunity to use something like a switch statement and apply image specific to the system model. In case of no match, deploy a generic one that has the OS and applications, leaving the scope for many driver installation based on need.
      • If this is the case and we are ok to wipe the hard disk, why ask any question or put some selection overhead? Let it be completely robotic

For custom winpe creation I am using a 64-bit windows 10 VM installed with Windows 10 ADK.

$64bitwin10WIMfile = "C:\Win10\Image\install64-Jul-16.wim"   # one my custom Windows 10 image, else simply mounting the windows 10 ISO and pointing to install.wim inside the sources folder would do
$winpeappbase = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment" # location of windows deployment kit on a 64-bit OS 
$OSCDImgRoot = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\AMD64\Oscdimg" 
$winperundir = "C:\Win10\Scripts\WinpeRUN" # folder containing custom PowerShell script to do the OS deployment 
$Winpecabfiles = Join-path $Winpeappbase "amd64\WinPE_OCs"
$WinpeENcabfiles = Join-path $Winpecabfiles "en-us"
$copypecmd = Join-Path $Winpeappbase "copype.cmd"
$WinPEbase = "C:\Win10\customwinpe" 
$winPEwimfile = Join-Path $WinPEbase "media\sources\boot.wim"
$winpemountdir = Join-Path $WinPEbase "mount"
$startnetcmd = Join-Path $winpemountdir "Windows\System32\startnet.cmd"

$env:WinPERoot = $winpeappbase
$env:OSCDImgRoot = $OSCDImgRoot

if ( Test-Path $WinPEbase ) { 
    Dismount-WindowsImage -Path $winpemountdir -Discard
    Remove-Item -Recurse $WinPEbase 
start -FilePath $copypecmd -ArgumentList "amd64 $WinPEbase" -ErrorVariable CopePE -Wait
if ( $CopePE ) { Throw " Unable to create Winpe structure using CopyPE script, existing"  }

# to copy the W
Mount-WindowsImage -Path $winpemountdir -ImagePath $64bitwin10WIMfile -Index 1 -ReadOnly -ErrorVariable MountOSMedia
if ( $MountOSMedia ) { Throw " Error mounting OS WIM to extract winre image" }
Copy-Item -Path "$winpemountdir\Windows\System32\Recovery\winre.wim" "$WinPEbase\media\sources\winre.wim" -Force
if ( Test-Path "$WinPEbase\media\sources\winre.wim" ) { disMount-WindowsImage -Path $winpemountdir -Discard }

Mount-WindowsImage -Path $winpemountdir -ImagePath $winPEwimfile -Index 1
Add-WindowsPackage -Path $winpemountdir -PackagePath "$Winpecabfiles\WinPE-WMI.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$Winpecabfiles\WinPE-NetFX.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$Winpecabfiles\WinPE-Scripting.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$Winpecabfiles\WinPE-PowerShell.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$Winpecabfiles\WinPE-StorageWMI.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$Winpecabfiles\WinPE-DismCmdlets.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$Winpecabfiles\WinPE-SecureStartup.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$Winpecabfiles\WinPE-SecureBootCmdlets.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$Winpecabfiles\WinPE-EnhancedStorage.cab"

Add-WindowsPackage -Path $winpemountdir -PackagePath "$WinpeENcabfiles\WinPE-WMI_en-us.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$WinpeENcabfiles\WinPE-NetFX_en-us.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$WinpeENcabfiles\WinPE-Scripting_en-us.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$WinpeENcabfiles\WinPE-PowerShell_en-us.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$WinpeENcabfiles\WinPE-StorageWMI_en-us.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$WinpeENcabfiles\WinPE-DismCmdlets_en-us.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$WinpeENcabfiles\WinPE-SecureStartup_en-us.cab"
Add-WindowsPackage -Path $winpemountdir -PackagePath "$WinpeENcabfiles\WinPE-EnhancedStorage_en-us.cab"

Copy-Item -Path $winperundir -Destination $winpemountdir -Recurse -Force

if ( Test-Path "$winpemountdir\WinpeRUN\win10install.ps1" ) {
    Add-Content -Path $startnetcmd "Powershell -executionPolicy bypass -command x:\WinpeRUN\win10install.ps1"
    } Else {
    throw "Script copy failed"
Dismount-WindowsImage -Path $winpemountdir -Save

Since this a FAT32 filesystem, it can’t take the 4+GB Windows image file. Using DISM.exe /Split-Image option, win10combined.wim or whatever the combined WIM file, need to be sliced around 4000MB files size and the resultant SWM files to be copied to media\sources folder inside the customwinpe.

Using the MakeWinPEMedia command this custom image can be put a USB. Or just copy the content of media folder inside the customwinpe folder to a fat32 formatted USB. However, care need to be taken to ensure the LABEL of this USB is WINPE as the deployment script depends on that.

Once a system or VM is booted from WinPE, it would start the command inside Windows\System32\startnet.cmd . PowerShell script \WinpeRUN\win10install.ps1 is part of the statnet.cmd and would initiate the OS deployment. Here is the script that I use and it does bellow

  • Prepare the DISK by clean-up, make GPT partition style and create required partitions
    • 500MB NTFS partition for Windows Recovery
    • 100MB FAT32 for EFI
    • 128MB Reserved partition
    • 100GB for OS deployment
    • Remaining space into two equal size volume for data (towards end of script)
  • Bitlocker pre provision
  • Detect the hardware and apply the corresponding image (index number) and sets the boot manager
  • Sets the Winre recovery environment for the installed OS
  • Sets SetupComplete.cmd to configure Direct Access and enable winre on a Laptop or just the winre on a desktop
  • Completes Bitlocker pre provision on additional data drives
$mypopupshell = new-object -comobject wscript.shell
$scripttitle = "Windows 10 unattended installation"
# Detect this system model
$thiscomputersystem = gwmi win32_computersystem 
$thissystemmodel = $thiscomputersystem.model
$PCtypeofthismachine = $ thiscomputersystem .PCSystemType

# Give a 30 sec pop-up stating disk would be clean-up. Some that someone by mistake does not run this on a system with data 
$mypopupshell.popup(“Installation of Windows 10 on this machine would be performed by DISK Clean-up. 
Existing Data, if any would be lost in this process.
You have 30 secs to turn off the machine to stop this process. “,30,$scripttitle,64)

# Lets prepare the disk layout and make W drive for OS, S for EFI and T for recovery image 
Write-Host "Cleaning up the system hard disk and creating partition for Win10 installation"
Get-Disk -Number 0 | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false
Get-Disk -Number 0 | Initialize-Disk -PartitionStyle GPT
New-Partition -DiskNumber 0 -Size 500MB -DriveLetter T -GptType "{de94bba4-06d1-4d40-a16a-bfd50179d6ac}" | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Windows RE tools" -Force -Confirm:$false # Recovery
New-Partition -DiskNumber 0 -Size 100MB | Format-Volume -FileSystem FAT32 -NewFileSystemLabel System -Force -Confirm:$false # ESP
Get-Partition -DiskNumber 0 -PartitionNumber 2|Set-Partition -GptType "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}" 
Get-Partition -DiskNumber 0 -PartitionNumber 2|Set-Partition -NewDriveLetter S
New-Partition -DiskNumber 0 -Size 128MB -GptType "{e3c9e316-0b5c-4db8-817d-f92df00215ae}"  # MSR
New-Partition -DiskNumber 0 -Size 100GB -DriveLetter W -GptType "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}" | Format-Volume -FileSystem NTFS -Confirm:$false -NewFileSystemLabel Windows -Force # OS

# the USB is labelled WINPE by MakeWinPEMedia , if copied manually set the Label as WINPE for the next section to work. 
$USBvolume = Get-Volume -FileSystemLabel WINPE
$USBdrive = $USBvolume.DriveLetter
if (!($USBdrive)) {
$mypopupshell.popup(“Unable to locate the USB drive containing Win10 image file.“,5,$scripttitle,16)
throw "Unable to locate the USB drive containing Win10 image file."
$imagefile = $USBdrive+":\sources\install.swm"
$imagefilepattern = $USBdrive+":\sources\install*.swm"
if (!(Test-Path $imagefile)) {
$mypopupshell.popup(“Unable to locate the Windows 10 image file in USB drive.“,5,$scripttitle,16)
throw "Unable to locate the Windows 10 image file in USB drive."

# Enable bitlocker encryption before deploying OS
manage-bde.exe -on -used "W:"

Switch ( $thissystemmodel ) {
"HP ProBook 440 G3" { $mypopupshell.popup(“Begining installation of  Windows 10 OS For this HP $thissystemmodel model machine",10,$scripttitle,64)
                    Expand-WindowsImage -ImagePath $imagefile -SplitImageFilePattern $imagefilepattern -Index 1 -ApplyPath w:\ }       
"20DSS1TU00" { $mypopupshell.popup(“Begining installation of  Windows 10 OS to this Lenovo ThinkPad L450 model machine",10,$scripttitle,64)
                    Expand-WindowsImage -ImagePath $imagefile -SplitImageFilePattern $imagefilepattern -Index 2 -ApplyPath w:\ }
"Virtual Machine" { $mypopupshell.popup(“Begining installation of  Windows 10 OS For this test virtual machine",10,$scripttitle,64)
                    Expand-WindowsImage -ImagePath $imagefile -SplitImageFilePattern $imagefilepattern -Index 3 -ApplyPath w:\ }
default { $mypopupshell.popup(“No image availble for this model of machine and hence installing the default Image",10,$scripttitle,16)
                    Expand-WindowsImage -ImagePath $imagefile -SplitImageFilePattern $imagefilepattern -Index 3 -ApplyPath w:\ }

if ( Test-Path  "w:\windows" ) { 
bcdboot "w:\windows" /s "S:" /f UEFI 
} Else {
$mypopupshell.popup(“Unbale to find Windows instalaltion folder, looks like installation went wrong",15,$scripttitle,16)
throw "Unbale to find Windows instalaltion folder, looks like installation went wrong"

# Lets compelte the Win RE part
$winrmfile = $USBdrive+":\sources\winre.wim"
$reagentxml = $USBdrive+":\sources\ReAgent.xml"
mkdir T:\Recovery\WindowsRE
Copy -Path $winrmfile -Destination T:\Recovery\WindowsRE\winre.wim -force
Copy -Path $reagentxml -Destination T:\Recovery\WindowsRE\ReAgent.xml -force
Copy -Path $reagentxml -Destination W:\Windows\System32\Recovery\ReAgent.xml -force
W:\Windows\System32\Reagentc.exe /setreimage /path T:\Recovery\WindowsRE /target W:\Windows

if ( $PCtypeofthismachine -eq 2 ) {
copy -Path "x:\WinpeRUN\SetupComplete_Laptop.cmd" -Destination "W:\Windows\Setup\Scripts\SetupComplete.cmd" -Force
if ( $PCtypeofthismachine -eq 1 ) {
copy -Path "x:\WinpeRUN\SetupComplete_Desktop.cmd" -Destination "W:\Windows\Setup\Scripts\SetupComplete.cmd" -Force

# use the remaining space on disk creating two equal size volume for user data

$systemdisk = Get-Disk -Number 0
$diskfreesize = $systemdisk.Size - $systemdisk.AllocatedSize
$newpartition = $systemdisk | New-Partition -Size ($diskfreesize/2) -AssignDriveLetter
$newpartdrveletter = $newpartition.DriveLetter +":"
$newpartition | Format-Volume -FileSystem NTFS -ShortFileNameSupport $false
manage-bde.exe -on -used $newpartdrveletter
$newpartition = $systemdisk | New-Partition -UseMaximumSize -AssignDriveLetter
$newpartdrveletter = $newpartition.DriveLetter +":"
$newpartition | Format-Volume -FileSystem NTFS -ShortFileNameSupport $false
manage-bde.exe -on -used $newpartdrveletter

Here is the content of SetupComplete_Laptop.cmd and for the desktop, it just the last line which enables the windows recovery

Start cmd.exe /c "Regedit /s C:\Windows\PkpnotesDAReg\Windows.reg"
Start cmd.exe /c "Regedit /s C:\Windows\PkpnotesDAReg\WindowsNT.reg"
Start cmd.exe /c "Regedit /s C:\Windows\PkpnotesDAReg\windowsfirewall.reg"
certutil -addstore root C:\Windows\PkpnotesDAReg\wipca.cer
sc config IKEEXT start= auto error= ignore
sc config PolicyAgent start= auto error= ignore
sc config iphlpsvc start= auto error= ignore
sc config MpsSvc start= auto error= ignore
sc config NcaSvc start= auto error= ignore
C:\Windows\System32\reagentc /enable



2 Replies to “Scripting Win10 system deployment”

  1. vali basha says:

    Sir, can I get script for Hyper V virtual machine deployment, plesae

    1. pkpanda says:

      Missed this one Vali, please let me know if you still require

Leave a Reply

Your email address will not be published. Required fields are marked *