public/Build-OSDWorkspaceWinPE.ps1
function Build-OSDWorkspaceWinPE { <# .SYNOPSIS Creates a new customized WinPE build in the OSDWorkspace environment. .DESCRIPTION The Build-OSDWorkspaceWinPE function creates a new Windows Preinstallation Environment (WinPE) build in the OSDWorkspace build directory. The function can use either a WinRE source image or the Windows Assessment and Deployment Kit (ADK) WinPE image as a base, then applies customizations including drivers, packages, scripts, and other settings. This function performs the following operations: 1. Validates administrator privileges 2. Creates necessary directory structure for the build 3. Sources a base WinPE image (from WinRE or Windows ADK) 4. Applies selected customizations (drivers, packages, scripts) 5. Generates boot media in various formats (WIM, ISO, USB-ready files) Build output is stored in the C:\OSDWorkspace\Build\WinPE directory by default, organized by architecture and build name. .EXAMPLE Build-OSDWorkspaceWinPE -Name 'MyBootMedia' -Architecture 'amd64' Creates a new WinPE build for x64 architecture named 'MyBootMedia' using WinRE as the source. .EXAMPLE Build-OSDWorkspaceWinPE -Name 'MyBootMedia' -Architecture 'arm64' Creates a new WinPE build for ARM64 architecture named 'MyBootMedia' using WinRE as the source. .EXAMPLE Build-OSDWorkspaceWinPE -Name 'MyBootMedia' -Architecture 'amd64' -AdkUseWinPE Creates a new WinPE build for x64 architecture named 'MyBootMedia' using the Windows ADK WinPE image. .EXAMPLE Build-OSDWorkspaceWinPE -Name 'MyBootMedia' -Architecture 'arm64' -AdkSelectCacheVersion Creates a new WinPE build for ARM64 architecture named 'MyBootMedia' and prompts to select which Windows ADK version to use as the source. .EXAMPLE Build-OSDWorkspaceWinPE -Name 'DeploymentMedia' -Verbose Creates a new WinPE build with detailed verbose output showing each step of the process. .OUTPUTS None. This function does not generate any output objects. .NOTES Author: David Segura Version: 1.0 Date: April 29, 2025 Prerequisites: - PowerShell 5.0 or higher - Windows 10 or higher - Run as Administrator - Windows ADK installed (if using -AdkUseWinPE or -AdkSelectCacheVersion) - WinRE source imported (if not using -AdkUseWinPE) The build process can take several minutes depending on the customizations applied. #> [CmdletBinding(DefaultParameterSetName = 'Default')] param ( # Specifies a friendly name for the WinPE build. # This name will be used in the build directory structure and media labels. [Parameter(Mandatory)] [System.String] $Name, # Specifies the processor architecture for the WinPE build. # Valid values are 'amd64' (64-bit x86) and 'arm64' (64-bit ARM). # Default value is 'amd64'. [Parameter(ParameterSetName = 'Default')] [Parameter(Mandatory, ParameterSetName = 'ADK')] [ValidateSet('amd64', 'arm64')] [System.String] $Architecture, # Windows ADK Languages to add to the BootImage. Default is en-US. [ValidateSet ( '*','ar-sa','bg-bg','cs-cz','da-dk','de-de','el-gr', 'en-gb','es-es','es-mx','et-ee','fi-fi', 'fr-ca','fr-fr','he-il','hr-hr','hu-hu','it-it', 'ja-jp','ko-kr','lt-lt','lv-lv','nb-no','nl-nl', 'pl-pl','pt-br','pt-pt','ro-ro','ru-ru','sk-sk', 'sl-si','sr-latn-rs','sv-se','th-th','tr-tr', 'uk-ua','zh-cn','zh-tw' )] [System.String[]] $Languages = 'en-US', # Sets all International settings in WinPE to the specified language. Default is en-US. [System.String] $SetAllIntl = 'en-US', # Sets the default InputLocale in WinPE to the specified Input Locale. Default is en-US. [System.String] $SetInputLocale = 'en-US', # Set the WinPE SetTimeZone. Default is the current SetTimeZone. [ValidateScript( { $tz = (tzutil /l) $validoptions = foreach ($t in $tz) { if (($tz.IndexOf($t) - 1) % 3 -eq 0) { $t.Trim() } } $validoptions -contains $_ })] [System.String] $SetTimeZone = (tzutil /g), # Update a OSDWorkspace USB drive with the new BootMedia. [System.Management.Automation.SwitchParameter] $UpdateUSB, # Select the Windows ADK version to use if multiple versions are present in the cache. [System.Management.Automation.SwitchParameter] $AdkSelectCacheVersion, # Skip adding the Windows ADK Optional Components. Useful for quick testing of the Library. [System.Management.Automation.SwitchParameter] $AdkSkipOcPackages, # Uses the Windows ADK winpe.wim instead of an imported winre.wim. [Parameter(Mandatory, ParameterSetName = 'ADK')] [System.Management.Automation.SwitchParameter] $AdkUseWinPE ) #================================================= $Error.Clear() Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Start" Initialize-OSDWorkspace #================================================= # Requires Run as Administrator $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $IsAdmin ) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] This function must be Run as Administrator" return } #================================================= # Import OSD.Workspace settings if (-not $global:OSDWorkspace) { Import-OSDWorkspaceSettings } $WindowsAdkWinpePackages = $global:OSDWorkspace.adkwinpepackages #================================================= # Start Main $BuildDateTime = $((Get-Date).ToString('yyMMdd-HHmm')) Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Starting $($MyInvocation.MyCommand.Name)" Block-WinPE Block-StandardUser Block-WindowsVersionNe10 Block-PowerShellVersionLt5 Block-NoCurl #================================================= #region UpdateUSB if ($UpdateUSB.IsPresent) { $UpdateUSB = $true } else { $UpdateUSB = $false } if ($AdkSkipOcPackages.IsPresent) { $AdkSkipOcPackages = $true } else { $AdkSkipOcPackages = $false } #endregion #================================================= #region Set TLS Defaults $PSDefaultParameterValues['Invoke-WebRequest:UseBasicParsing'] = $true $currentProgressPref = $ProgressPreference $ProgressPreference = 'SilentlyContinue' $regproxy = Get-ItemProperty -Path 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' $proxy = $regproxy.ProxyServer if ($proxy -and -not ([System.Net.Webrequest]::DefaultWebProxy).Address -and $regproxy.ProxyEnable) { [System.Net.Webrequest]::DefaultWebProxy = New-object System.Net.WebProxy $proxy [System.Net.Webrequest]::DefaultWebProxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials } $currentVersionTls = [Net.ServicePointManager]::SecurityProtocol $currentSupportableTls = [Math]::Max($currentVersionTls.value__, [Net.SecurityProtocolType]::Tls.value__) $availableTls = [enum]::GetValues('Net.SecurityProtocolType') | Where-Object { $_ -gt $currentSupportableTls } $availableTls | ForEach-Object { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor $_ } #endregion #================================================= #region Test-IsWindowsAdkInstalled $IsWindowsAdkInstalled = Test-IsWindowsAdkInstalled -WarningAction SilentlyContinue $WindowsAdkInstallVersion = Get-WindowsAdkInstallVersion -WarningAction SilentlyContinue $WindowsAdkInstallPath = Get-WindowsAdkInstallPath -WarningAction SilentlyContinue if ($IsWindowsAdkInstalled) { if ($WindowsAdkInstallVersion) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK install version is $WindowsAdkInstallVersion" } if ($WindowsAdkInstallPath) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK install path is $WindowsAdkInstallPath" } } else { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK is not installed." } #endregion #================================================= #region Get and Update the ADK Cache $WSCachePath = $OSDWorkspace.paths.cache $WSAdkVersionsPath = $OSDWorkspace.paths.adk_versions #endregion #================================================= #region If ADK is installed then we need to update the cache if ($IsWindowsAdkInstalled) { $WindowsAdkRootPath = Join-Path $WSAdkVersionsPath $WindowsAdkInstallVersion Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK cache content is $WindowsAdkRootPath" $null = robocopy.exe "$WindowsAdkInstallPath" "$WindowsAdkRootPath" *.* /e /z /ndl /nfl /np /r:0 /w:0 /xj /mt:128 } else { Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Cannot update the ADK cache because the ADK is not installed" $AdkSelectCacheVersion = $true Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] AdkSelectCacheVersion: $AdkSelectCacheVersion" } #endregion #================================================= #region Get the WindowsAdkCacheOptions $WindowsAdkCacheOptions = $null if (Test-Path $WSAdkVersionsPath) { $WindowsAdkCacheOptions = Get-ChildItem -Path "$WSAdkVersionsPath\*" -Directory -ErrorAction SilentlyContinue | Sort-Object -Property Name } #endregion #================================================= #region ADK is not installed and not present in the cache if (($IsWindowsAdkInstalled -eq $false) -and (-not $WindowsAdkCacheOptions)) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK is not installed" Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] ADK cache does not contain an offline Windows ADK" Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK will need to be installed before using this function" Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install" return } #endregion #================================================= #region There is no usable ADK in the cache if ($WindowsAdkCacheOptions.Count -eq 0) { # Something is wrong, there should always be at least one ADK in the cache Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] ADK cache does not contain an offline Windows ADK" Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK will need to be installed before using this function" return } #endregion #================================================= #region ADK is available by this point and we either have 1 or more to select from if ($WindowsAdkCacheOptions.Count -eq 1) { # Only one version of the ADK is present in the cache, so this must be used $WindowsAdkRootPath = $WindowsAdkCacheOptions.FullName Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] ADK cache contains 1 offline Windows ADK option" Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Using ADK cache at $WindowsAdkCacheSelected" # Can't select an ADK Version if there is only one $AdkSelectCacheVersion = $false } elseif ($WindowsAdkCacheOptions.Count -gt 1) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] $($WindowsAdkCacheOptions.Count) Windows ADK options are available to select from the ADK cache" if ($AdkSelectCacheVersion) { Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Select a Windows ADK option and press OK (Cancel to Exit)" Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] To remove a Windows ADK option, delete one of the ADK cache directories in $WSAdkVersionsPath" $WindowsAdkCacheSelected = $WindowsAdkCacheOptions | Select-Object FullName | Sort-Object FullName -Descending | Out-GridView -Title 'Select a Windows ADK to use and press OK (Cancel to Exit)' -OutputMode Single if ($WindowsAdkCacheSelected) { $WindowsAdkRootPath = $WindowsAdkCacheSelected.FullName } else { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Unable to set the ADK cache path" return } } else { Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Select a different Windows ADK with the -AdkSelectCacheVersion switch" } } else { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Something is wrong you should not be here" return } #endregion #================================================= #region Select BootImage <# We want to pick the BootImage first This will set the architecture automatically as the bootimage tells us and let that control what ADK architecture is going to be used. This way we don't have to prompt the user for the ADK architecture and can remove the parameter. #> if ($AdkUseWinPE) { Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Using WinPE from Windows ADK" $WimSourceType = 'WinPE' } else { Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Using WinRE from Select-OSDWSWinRESource" $WimSourceType = 'WinRE' if ($Architecture) { $GetWindowsImage = Select-OSDWSWinRESource -Architecture $Architecture } else { $GetWindowsImage = Select-OSDWSWinRESource } if ($GetWindowsImage.Count -eq 0) { # There are no images to run return } $Architecture = $GetWindowsImage.Architecture $ImportImageCorePath = $GetWindowsImage.Path + '\.core' $ImportImageOSFilesPath = $GetWindowsImage.Path + '\.core\os-files' Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Using Recovery Image at $($GetWindowsImage.ImagePath)" Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Architecture: $Architecture" Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] ImportImageCorePath: $ImportImageCorePath" Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] ImportImageOSFilesPath: $ImportImageOSFilesPath" } #endregion #================================================= #region Get ADK Paths if (($Architecture -notmatch 'amd64') -and ($Architecture -notmatch 'arm64')) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Unknown architecture $Architecture" return } $WindowsAdkPaths = Get-WindowsAdkPaths -Architecture $Architecture -AdkRoot $WindowsAdkRootPath -WarningAction SilentlyContinue if (-not $WindowsAdkPaths) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Something is wrong you should not be here" return } if ($WimSourceType -eq 'WinPE') { $GetWindowsImage = Get-WindowsImage -ImagePath $($WindowsAdkPaths.WimSourcePath) -Index 1 $ImportImageWimPath = $GetWindowsImage.ImagePath $MediaName = "$($BuildDateTime)-$($Architecture)" $MediaIsoLabel = $($BuildDateTime) } elseif ($WimSourceType -eq 'WinRE') { $ImportImageWimPath = $GetWindowsImage.ImagePath $ImportImageRootPath = $GetWindowsImage.Path $MediaName = "$($BuildDateTime)-$($Architecture)" $MediaIsoLabel = $($BuildDateTime) } # Append the Name to the MediaName if ($Name) { #$MediaName = "$MediaName $Name" } $WindowsAdkPaths.WimSourcePath = $ImportImageWimPath $MediaIsoName = 'BootMedia.iso' $MediaIsoNameEX = 'BootMediaEX.iso' $MediaRootPath = Join-Path $($OSDWorkspace.paths.build_windows_pe) $MediaName $global:BuildMediaCorePath = "$MediaRootPath\.core" $BuildMediaTempPath = "$MediaRootPath\.temp" $global:BuildMediaLogsPath = "$BuildMediaTempPath\logs" #endregion #================================================= #region Select-OSDWSWinPEBuildProfile $MyBuildProfile = $null $MyBuildProfile = Select-OSDWSWinPEBuildProfile #endregion #================================================= #region Select-OSDWSWinPEBuildDriver $WinPEDriver = $null if (-not $MyBuildProfile) { $OSDWorkspaceWinPEDriver= Select-OSDWSWinPEBuildDriver -Architecture $Architecture if ($OSDWorkspaceWinPEDriver) { $WinPEDriver = ($OSDWorkspaceWinPEDriver| Select-Object -ExpandProperty FullName) } } #endregion #================================================= #region Select-OSDWSWinPEBuildScript $WinPEAppScript = $null $WinPEScript = $null $WinPEMediaScript = $null if (-not $MyBuildProfile) { $OSDWorkspaceWinPEScript = @() $OSDWorkspaceWinPEScript = Select-OSDWSWinPEBuildScript if ($OSDWorkspaceWinPEScript | Where-Object { $_.Type -eq 'winpe-appscript' }) { $WinPEAppScript = ($OSDWorkspaceWinPEScript | Where-Object { $_.Type -eq 'winpe-appscript' } | Select-Object -ExpandProperty FullName) } if ($OSDWorkspaceWinPEScript | Where-Object { $_.Type -eq 'winpe-script' }) { $WinPEScript = ($OSDWorkspaceWinPEScript | Where-Object { $_.Type -eq 'winpe-script' } | Select-Object -ExpandProperty FullName) } if ($OSDWorkspaceWinPEScript | Where-Object { $_.Type -eq 'winpe-mediascript' }) { $WinPEMediaScript = ($OSDWorkspaceWinPEScript | Where-Object { $_.Type -eq 'winpe-mediascript' } | Select-Object -ExpandProperty FullName) } } #endregion #================================================= #region MyBuildProfile if ($MyBuildProfile) { $global:BuildProfile = $null $global:BuildProfile = Get-Content $MyBuildProfile.FullName -Raw | ConvertFrom-Json $WinPEDriver = $global:BuildProfile.WinPEDriver $WinPEAppScript = $global:BuildProfile.WinPEAppScript $WinPEScript = $global:BuildProfile.WinPEScript $WinPEMediaScript = $global:BuildProfile.WinPEMediaScript [System.String[]]$Languages = $global:BuildProfile.Languages $SetAllIntl = $global:BuildProfile.SetAllIntl $SetInputLocale = $global:BuildProfile.SetInputLocale $SetTimeZone = $global:BuildProfile.SetTimeZone $MyBuildProfilePath = $MyBuildProfile.FullName } else { $global:BuildProfile = $null $global:BuildProfile = [ordered]@{ WinPEDriver = $WinPEDriver WinPEAppScript = $WinPEAppScript WinPEScript = $WinPEScript WinPEMediaScript = $WinPEMediaScript Languages = [System.String[]]$Languages SetAllIntl = [System.String]$SetAllIntl SetInputLocale = [System.String]$SetInputLocale SetTimeZone = [System.String]$SetTimeZone } $BuildProfilePath = $OSDWorkspace.paths.winpe_buildprofile if (-not (Test-Path $BuildProfilePath)) { $null = New-Item -Path $BuildProfilePath -ItemType Directory -Force } $MyBuildProfilePath = "$BuildProfilePath\$Name.json" Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Exporting BootMedia Profile to $MyBuildProfilePath" $global:BuildProfile | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File $MyBuildProfilePath -Encoding utf8 -Force } #endregion #================================================= #region BuildProfile $global:BuildMedia = $null $global:BuildMedia = [ordered]@{ AdkInstallPath = $WindowsAdkInstallPath AdkInstallVersion = $WindowsAdkInstallVersion AdkSkipOcPackages = $AdkSkipOcPackages AdkRootPath = $WindowsAdkRootPath Architecture = [System.String]$Architecture BuildProfile = $MyBuildProfilePath ContentStartnet = [System.String]$ContentStartnet ContentWinpeshl = [System.String]$ContentWinpeshl InstalledApps = @() ImportImageRootPath = $ImportImageRootPath ImportImageWimPath = $ImportImageWimPath Languages = [System.String[]]$Languages WinPEAppScript = $WinPEAppScript WinPEScript = $WinPEScript WinPEMediaScript = $WinPEMediaScript WinPEDriver = $WinPEDriver MediaIsoLabel = $MediaIsoLabel MediaIsoName = $MediaIsoName MediaIsoNameEX = $MediaIsoNameEX MediaName = $MediaName MediaPath = Join-Path $MediaRootPath 'WinPE-Media' MediaPathEX = $null MediaRootPath = $MediaRootPath MountPath = $null Name = [System.String]$Name PEVersion = $GetWindowsImage.Version AdkSelectCacheVersion = $AdkSelectCacheVersion SetAllIntl = [System.String]$SetAllIntl SetInputLocale = [System.String]$SetInputLocale SetTimeZone = [System.String]$SetTimeZone UpdateUSB = [System.Boolean]$UpdateUSB AdkUseWinPE = $AdkUseWinPE WimSourceType = $WimSourceType WSCachePath = $WSCachePath WSCachePathAdk = $WSAdkVersionsPath } #endregion #================================================= # Point of No Return #================================================= Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Use the `$global:BuildMedia variable in your PowerShell Scripts for this BootMedia configuration" $global:BuildMedia | Out-Host Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Press CTRL+C to cancel" pause $BuildStartTime = Get-Date #================================================= #region Start Main if (-not (Test-Path $MediaRootPath)) { $null = New-Item -Path $MediaRootPath -ItemType Directory -Force } if (-not (Test-Path $BuildMediaLogsPath)) { $null = New-Item -Path $BuildMediaLogsPath -ItemType Directory -Force | Out-Null } $Transcript = "$((Get-Date).ToString('yyMMdd-HHmmss'))-Build-OPE.log" Start-Transcript -Path (Join-Path $BuildMediaLogsPath $Transcript) -ErrorAction SilentlyContinue #endregion #================================================= #region BuildMediaCorePath Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] BuildMediaCorePath: $BuildMediaCorePath" if ($ImportImageCorePath) { if (Test-Path $ImportImageCorePath) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Hydrate $BuildMediaCorePath" $null = robocopy.exe "$ImportImageCorePath" "$BuildMediaCorePath" *.json /nfl /ndl /np /r:0 /w:0 /xj /mt:128 /LOG+:$BuildMediaLogsPath\core.log $null = robocopy.exe "$ImportImageCorePath" "$BuildMediaCorePath" *.xml /nfl /ndl /np /r:0 /w:0 /xj /mt:128 /LOG+:$BuildMediaLogsPath\core.log } } $ImportId = @{id = $MediaName } if (-not (Test-Path $BuildMediaCorePath)) { $null = New-Item -Path $BuildMediaCorePath -ItemType Directory -Force | Out-Null } $ImportId | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File "$BuildMediaCorePath\id.json" -Encoding utf8 -Force #endregion #================================================= #region Build Media $MediaPath = $global:BuildMedia.MediaPath Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] MediaPath: $MediaPath" Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Hydrate $MediaPath" $null = robocopy.exe "$($WindowsAdkPaths.PathWinPEMedia)" "$MediaPath" *.* /mir /b /ndl /np /r:0 /w:0 /xj /njs /mt:128 /LOG+:$BuildMediaLogsPath\media.log Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Copying $ImportImageCorePath\os-boot\DVD\EFI\en-US\efisys.bin" Copy-Item -Path "$ImportImageCorePath\os-boot\DVD\EFI\en-US\efisys.bin" -Destination "$MediaPath\EFI\Microsoft\Boot\efisys.bin" -Force -ErrorAction SilentlyContinue Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Copying $ImportImageCorePath\os-boot\DVD\EFI\en-US\efisys_noprompt.bin" Copy-Item -Path "$ImportImageCorePath\os-boot\DVD\EFI\en-US\efisys_noprompt.bin" -Destination "$MediaPath\EFI\Microsoft\Boot\efisys_noprompt.bin" -Force -ErrorAction SilentlyContinue $Fonts = @('malgunn_boot.ttf', 'meiryon_boot.ttf', 'msjhn_boot.ttf', 'msyhn_boot.ttf', 'segoen_slboot.ttf') foreach ($Font in $Fonts) { if (Test-Path "$ImportImageCorePath\os-boot\Fonts\$Font") { Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Copying $ImportImageCorePath\os-boot\Fonts\$Font" Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts\$Font" -Destination "$MediaPath\EFI\Microsoft\Boot\Fonts\$Font" -Force -ErrorAction SilentlyContinue } } #endregion #================================================= #region Build MediaEX if (Test-Path "$ImportImageCorePath\os-boot\EFI_EX") { $global:BuildMedia.MediaPathEX = Join-Path $MediaRootPath 'WinPE-MediaEX' $MediaPathEX = $global:BuildMedia.MediaPathEX Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] MediaPathEX: $MediaPathEX" Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Hydrate $MediaPathEX" $null = robocopy.exe "$($WindowsAdkPaths.PathWinPEMedia)" "$MediaPathEX" *.* /mir /b /ndl /np /r:0 /w:0 /xj /mt:128 /LOG+:$BuildMediaLogsPath\mediaex.log Write-Host -ForegroundColor DarkGreen "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Mitigate CVE-2022-21894 Secure Boot Security Feature Bypass Vulnerability aka BlackLotus" Remove-Item -Path "$MediaPathEX\EFI\Microsoft\Boot\Fonts" -Recurse -Force if (-not (Test-Path "$MediaPathEX\EFI\Microsoft\Boot\Fonts")) { New-Item -Path "$MediaPathEX\EFI\Microsoft\Boot\Fonts" -ItemType Directory -Force | Out-Null } Copy-Item -Path "$ImportImageCorePath\os-boot\EFI_EX\bootmgr_ex.efi" -Destination "$MediaPathEX\bootmgr.efi" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\EFI_EX\bootmgfw_ex.efi" -Destination "$MediaPathEX\EFI\Boot\bootx64.efi" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\chs_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\chs_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\cht_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\cht_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\jpn_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\jpn_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\kor_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\kor_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\malgun_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\malgun_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\malgunn_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\malgunn_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\meiryo_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\meiryo_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\meiryon_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\meiryon_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\msjh_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\msjh_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\msjhn_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\msjhn_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\msyh_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\msyh_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\msyhn_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\msyhn_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\segmono_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\segmono_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\segoe_slboot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\segoe_slboot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\segoen_slboot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\segoen_slboot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts_EX\wgl4_boot_EX.ttf" -Destination "$MediaPathEX\EFI\Microsoft\Boot\Fonts\wgl4_boot.ttf" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\DVD_EX\EFI\en-US\efisys_EX.bin" -Destination "$MediaPathEX\EFI\Microsoft\Boot\efisys.bin" -Force -ErrorAction SilentlyContinue Copy-Item -Path "$ImportImageCorePath\os-boot\DVD_EX\EFI\en-US\efisys_noprompt_EX.bin" -Destination "$MediaPathEX\EFI\Microsoft\Boot\efisys_noprompt.bin" -Force -ErrorAction SilentlyContinue } else { Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Does not exist $ImportImageCorePath\os-boot\EFI_EX" $MediaPathEX = $null } #endregion #================================================= #region Build Sources $global:BuildMediaSourcesPath = Join-Path $MediaPath 'sources' if (-not (Test-Path "$BuildMediaSourcesPath")) { New-Item -Path "$BuildMediaSourcesPath" -ItemType Directory -Force -ErrorAction Stop | Out-Null } $buildMediaSourcesBootwimPath = Join-Path $BuildMediaSourcesPath 'boot.wim' Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Hydrate $buildMediaSourcesBootwimPath" Copy-Item -Path $WindowsAdkPaths.WimSourcePath -Destination $buildMediaSourcesBootwimPath -Force -ErrorAction Stop | Out-Null if (!(Test-Path $buildMediaSourcesBootwimPath)) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Unknown issue copying $buildMediaSourcesBootwimPath" Stop-Transcript Break } attrib -s -h -r $BuildMediaSourcesPath attrib -s -h -r $buildMediaSourcesBootwimPath if ($MediaPathEX) { $global:BuildMediaSourcesPathEX = Join-Path $MediaPathEX 'sources' if (-not (Test-Path "$BuildMediaSourcesPathEX")) { New-Item -Path "$BuildMediaSourcesPathEX" -ItemType Directory -Force -ErrorAction Stop | Out-Null } attrib -s -h -r $BuildMediaSourcesPathEX } #endregion #================================================= #region BootImage Mount $global:WindowsImage = Mount-MyWindowsImage $buildMediaSourcesBootwimPath $MountPath = $WindowsImage.Path $global:BuildMedia.MountPath = $MountPath Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] MountPath: $MountPath" #endregion #================================================= #region BootImage Registry Information Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Get WinPE HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" $RegKeyCurrentVersion = Get-RegCurrentVersion -Path $MountPath $RegKeyCurrentVersion | Out-Host #endregion #================================================= #region Export Get-WindowsPackage Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Export $BuildMediaCorePath\winpe-windowspackage.xml" $WindowsPackage = $WindowsImage | Get-WindowsPackage if ($WindowsPackage) { $WindowsPackage | Select-Object * | Export-Clixml -Path "$BuildMediaCorePath\winpe-windowspackage.xml" -Force } #endregion #================================================= #region Adding OS Files if ($ImportImageOSFilesPath) { if (Test-Path $ImportImageOSFilesPath) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Adding OS Files from $ImportImageOSFilesPath" $null = robocopy.exe "$ImportImageOSFilesPath" "$MountPath" *.* /s /b /ndl /nfl /np /ts /r:0 /w:0 /xf bcp47*.dll /xx /xj /mt:128 /LOG+:$BuildMediaLogsPath\os-files.log } } #endregion #================================================= #region Add ADK WinPE OCs if ($AdkSkipOcPackages -eq $false) { $WinPEOCs = $WindowsAdkPaths.WinPEOCs #================================================= #region OSDeploy Install Default en-us Language Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Adding ADK Packages for Language en-us" $Lang = 'en-us' foreach ($Package in $WindowsAdkWinpePackages) { $PackageFile = "$WinPEOCs\WinPE-$Package.cab" if (Test-Path $PackageFile) { Write-Host -ForegroundColor Gray "$PackageFile" $PackageName = "Add-WindowsPackage-WinPE-$Package" $CurrentLog = "$BuildMediaLogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-$PackageName.log" try { $WindowsImage | Add-WindowsPackage -PackagePath $PackageFile -LogPath "$CurrentLog" -ErrorAction Stop | Out-Null } catch { if ($_.Exception.ErrorCode -eq '-2148468766') { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 0x800f081e CBS_E_NOT_APPLICABLE The Windows ADK version you are using does not seem to support the WinPE version you are trying to service" } if ($_.Exception.ErrorCode -eq '-2146498512') { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 0x800f0830 CBS_E_IMAGE_UNSERVICEABLE The specified image is no longer serviceable and may be corrupted. Discard the modified image and start again" } <# Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] If this package is not essential, it is recommended to try again without this package as the Windows Image may now be unserviceable" Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Log: $CurrentLog" Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] ErrorCode: $($_.Exception.ErrorCode)" #> } } else { Write-Host -ForegroundColor DarkGray "$PackageFile (not present)" } } # Bail if the PowerShell package did not install if (-NOT ($WindowsImage | Get-WindowsPackage | Where-Object { $_.PackageName -match 'PowerShell' })) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Powershell Optional Component is not installed. Required ADK Packages did not install properly" Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Make sure the Windows ADK version you are using supports the WinRE version you are trying to service" Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Build will continue so you can review the logs for more information" Start-Sleep -Seconds 15 } $PackageFile = "$WinPEOCs\$Lang\lp.cab" if (Test-Path $PackageFile) { Write-Host -ForegroundColor Gray "$PackageFile" $PackageName = "Add-WindowsPackage-WinPE-lp_$Lang" $CurrentLog = "$BuildMediaLogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-$PackageName.log" try { $WindowsImage | Add-WindowsPackage -PackagePath $PackageFile -LogPath "$CurrentLog" -ErrorAction Stop | Out-Null } catch { if ($_.Exception.ErrorCode -eq '-2148468766') { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 0x800f081e CBS_E_NOT_APPLICABLE The Windows ADK version you are using does not seem to support the WinPE version you are trying to service" } if ($_.Exception.ErrorCode -eq '-2146498512') { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 0x800f0830 CBS_E_IMAGE_UNSERVICEABLE The specified image is no longer serviceable and may be corrupted. Discard the modified image and start again" } } } else { Write-Host -ForegroundColor DarkGray "$PackageFile (not present)" } foreach ($Package in $WindowsAdkWinpePackages) { $PackageFile = "$WinPEOCs\$Lang\WinPE-$Package`_$Lang.cab" if (Test-Path $PackageFile) { Write-Host -ForegroundColor Gray "$PackageFile" $PackageName = "Add-WindowsPackage-WinPE-$Package`_$Lang" $CurrentLog = "$BuildMediaLogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-$PackageName.log" try { $WindowsImage | Add-WindowsPackage -PackagePath $PackageFile -LogPath "$CurrentLog" -ErrorAction Stop | Out-Null } catch { if ($_.Exception.ErrorCode -eq '-2148468766') { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 0x800f081e CBS_E_NOT_APPLICABLE The Windows ADK version you are using does not seem to support the WinPE version you are trying to service" } if ($_.Exception.ErrorCode -eq '-2146498512') { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 0x800f0830 CBS_E_IMAGE_UNSERVICEABLE The specified image is no longer serviceable and may be corrupted. Discard the modified image and start again" } } } else { Write-Host -ForegroundColor DarkGray "$PackageFile (not present)" } } #endregion #================================================= Step-BuildMediaWindowsImageSave #================================================= #region OSDeploy Install Selected Languages if ($Languages -contains '*') { $Languages = Get-ChildItem $WinPEOCs -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne 'en-us' } | Select-Object -ExpandProperty Name } foreach ($Lang in $Languages) { if ($Lang -eq 'en-us') { Continue } Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Adding $Lang ADK Packages" $PackageFile = "$WinPEOCs\$Lang\lp.cab" if (Test-Path $PackageFile) { Write-Host -ForegroundColor Gray "$PackageFile" $PackageName = "Add-WindowsPackage-WinPE-lp_$Lang" $CurrentLog = "$BuildMediaLogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-$PackageName.log" try { $WindowsImage | Add-WindowsPackage -PackagePath $PackageFile -LogPath "$CurrentLog" -ErrorAction Stop | Out-Null } catch { if ($_.Exception.ErrorCode -eq '-2148468766') { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 0x800f081e CBS_E_NOT_APPLICABLE The Windows ADK version you are using does not seem to support the WinPE version you are trying to service" } if ($_.Exception.ErrorCode -eq '-2146498512') { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 0x800f0830 CBS_E_IMAGE_UNSERVICEABLE The specified image is no longer serviceable and may be corrupted. Discard the modified image and start again" } } } else { Write-Host -ForegroundColor DarkGray "$PackageFile (not present)" } foreach ($Package in $WindowsAdkWinpePackages) { $PackageFile = "$WinPEOCs\$Lang\WinPE-$Package`_$Lang.cab" if (Test-Path $PackageFile) { Write-Host -ForegroundColor Gray "$PackageFile" $PackageName = "Add-WindowsPackage-WinPE-$Package`_$Lang" $CurrentLog = "$BuildMediaLogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-$PackageName.log" try { $WindowsImage | Add-WindowsPackage -PackagePath $PackageFile -LogPath "$CurrentLog" -ErrorAction Stop | Out-Null } catch { if ($_.Exception.ErrorCode -eq '-2148468766') { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 0x800f081e CBS_E_NOT_APPLICABLE The Windows ADK version you are using does not seem to support the WinPE version you are trying to service" } if ($_.Exception.ErrorCode -eq '-2146498512') { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 0x800f0830 CBS_E_IMAGE_UNSERVICEABLE The specified image is no longer serviceable and may be corrupted. Discard the modified image and start again" } } } else { Write-Host -ForegroundColor DarkGray "$PackageFile (not present)" } } # Generates a new Lang.ini file which is used to define the language packs inside the image if ( (Test-Path -Path "$MountPath\sources\lang.ini") ) { Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Updating lang.ini" $CurrentLog = "$BuildMediaLogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-Gen-LangINI.log" dism.exe /image:"$MountPath" /Gen-LangINI /distribution:"$MountPath" /LogPath:"$CurrentLog" } Step-BuildMediaWindowsImageSave } #endregion } #endregion #================================================= Step-BuildMediaDismSettings Step-BuildMediaAddWallpaper Step-BuildMediaPowerShellUpdate Step-WinPEAppAzCopy Step-WinPEAppWirelessConnect Step-WinPEAppZip Step-BuildMediaWinPEAppScript Step-BuildMediaWindowsImageSave Step-BuildMediaRemoveWinpeshl Step-BuildMediaConsoleSettings Step-BuildMediaWinPEScript Step-BuildMediaWinPEDriver Step-BuildMediaExportWindowsDriverPE Step-BuildMediaExportWindowsPackagePE Step-BuildMediaRegCurrentVersionExport Step-BuildMediaDismGetIntl Step-BuildMediaGetContentStartnet Step-BuildMediaGetContentWinpeshl Step-BuildMediaWindowsImageDismount Step-BuildMediaWindowsImageExport Step-BuildMediaWinPEMediaScript Step-BuildMediaIso Step-BuildMediaUpdateUSB #================================================= #region Complete # Add the final ADKPaths information to the bootmedia object $global:BuildMedia.AdkPaths = $WindowsAdkPaths # Add the final WinPE information to the bootmedia object $global:BuildMedia.PEInfo = $GetWindowsImage Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Exporting BootMedia Profile to $BuildMediaCorePath\gv-buildprofile.json" $global:BuildProfile | Export-Clixml -Path "$BuildMediaCorePath\gv-buildprofile.xml" -Force $global:BuildProfile | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File "$BuildMediaCorePath\gv-buildprofile.json" -Encoding utf8 -Force Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Exporting BootMedia Properties to $BuildMediaCorePath\gv-buildmedia.json" $global:BuildMedia | Export-Clixml -Path "$BuildMediaCorePath\gv-buildmedia.xml" -Force $global:BuildMedia | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File "$BuildMediaCorePath\gv-buildmedia.json" -Encoding utf8 -Force # Restore TLS settings [Net.ServicePointManager]::SecurityProtocol = $currentVersionTls $ProgressPreference = $currentProgressPref # Update BootMedia Index $null = Get-OSDWSWinPEBuild $buildEndTime = Get-Date $buildTimeSpan = New-TimeSpan -Start $BuildStartTime -End $buildEndTime Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] $($MyInvocation.MyCommand.Name) completed in $($buildTimeSpan.ToString("mm' minutes 'ss' seconds'"))" Stop-Transcript #endregion #================================================= Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] End" #================================================= } |