public/Build-OSDeployBootMedia.ps1
|
#Requires -PSEdition Core #Requires -Version 7.4 function Build-OSDeployBootMedia { <# .SYNOPSIS Builds a customized WinPE boot image from a WinRE or WinPE source. .DESCRIPTION Creates a bootable WinPE media from an imported WinRE image or Windows ADK WinPE. Applies ADK optional components, drivers, PowerShell updates, applications, console settings, wallpaper, and user scripts. Output is written to %ProgramData%\OSDeployCore\boot-media. .PARAMETER Name Specifies a friendly name for the build. .PARAMETER Architecture Processor architecture: 'amd64' or 'arm64'. Default is 'amd64'. .PARAMETER Languages Windows ADK language packs to add. Default is 'en-us'. .PARAMETER SetAllIntl Sets all international settings. Default is 'en-us'. .PARAMETER SetInputLocale Sets the default input locale. Default is 'en-us'. .PARAMETER SetTimeZone Sets the WinPE timezone. Default is the current system timezone. .PARAMETER UpdateUSB Copies the completed media to any USB partition labeled 'USB-WinPE'. .PARAMETER SkipAdkPackages Skips adding ADK optional component packages. Useful for quick testing. .PARAMETER UseAdkWinPE Uses the Windows ADK winpe.wim instead of an imported WinRE source. .EXAMPLE Build-OSDeployBootMedia -Name 'MyPE' .EXAMPLE Build-OSDeployBootMedia -Name 'TestBuild' -SkipAdkPackages .INPUTS None. This function does not accept pipeline input. .OUTPUTS None. Results are written to %ProgramData%\OSDeployCore\boot-media. .NOTES Author: David Segura Company: Recast Software Requires: Windows 11 25H2+, PowerShell 7.6, Windows ADK, Run as Administrator #> [CmdletBinding(DefaultParameterSetName = 'Default', SupportsShouldProcess)] param ( [Parameter(Mandatory)] [System.String] $Name, [Parameter(ParameterSetName = 'Default')] [Parameter(Mandatory, ParameterSetName = 'ADK')] [ValidateSet('amd64', 'arm64')] [System.String] $Architecture, [ValidateSet( '*', 'ar-sa', 'bg-bg', 'cs-cz', 'da-dk', 'de-de', 'el-gr', 'en-gb', 'en-us', '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, [System.String] $SetAllIntl, [System.String] $SetInputLocale, [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), [System.Management.Automation.SwitchParameter] $SkipAdkPackages, [Parameter(Mandatory, ParameterSetName = 'ADK')] [System.Management.Automation.SwitchParameter] $UseAdkWinPE, [System.Management.Automation.SwitchParameter] $UpdateUSB ) Write-OSDeployBanner #region Prerequisites # Requires Run as Administrator $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $IsAdmin) { Write-Warning "Build-OSDeployBootMedia must be Run as Administrator" return } # Requires Windows 11 $osVersion = [System.Environment]::OSVersion.Version if ($osVersion.Build -lt 26100) { Write-Warning "Build-OSDeployBootMedia requires Windows 11 25H2 or later (Build 26100+)" return } # Requires OSDCloud module 26.4.17.1 or newer $RequiredOSDCloudVersion = [System.Version]'26.4.17.1' if (-not (Get-Command -Name 'Get-OSDCloudModuleVersion' -ErrorAction SilentlyContinue)) { Write-Warning "Build-OSDeployBootMedia requires OSDCloud module $RequiredOSDCloudVersion or newer. OSDCloud module is not loaded." return } $OSDCloudVersion = Get-OSDCloudModuleVersion if ($OSDCloudVersion -lt $RequiredOSDCloudVersion) { Write-Warning "Build-OSDeployBootMedia requires OSDCloud module $RequiredOSDCloudVersion or newer. Loaded version: $OSDCloudVersion" return } # Ensure directory structure exists Initialize-OSDeployCoreBootImage $BuildDateTime = (Get-Date).ToString('yyMMdd-HHmm') Write-OSDeployCoreProgress "Starting Build-OSDeployBootMedia" #endregion #region TLS and Proxy $PSDefaultParameterValues['Invoke-WebRequest:UseBasicParsing'] = $true $currentProgressPref = $ProgressPreference $ProgressPreference = 'SilentlyContinue' $regProxy = Get-ItemProperty -Path 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -ErrorAction SilentlyContinue if ($regProxy -and $regProxy.PSObject.Properties['ProxyServer'] -and $regProxy.ProxyServer -and -not ([System.Net.WebRequest]::DefaultWebProxy).Address -and $regProxy.ProxyEnable) { [System.Net.WebRequest]::DefaultWebProxy = New-Object System.Net.WebProxy $regProxy.ProxyServer [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 ADK Detection $AdkInfo = Get-WindowsAdkInstallInfo if (-not $AdkInfo.IsInstalled) { Write-Warning "Windows ADK is not installed" Write-Warning "Install the Windows ADK from https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install" return } $AdkRootPath = $AdkInfo.InstallPath Write-OSDeployCoreProgress "Windows ADK version $($AdkInfo.InstallVersion) at $AdkRootPath" #region Select Boot Image Source if ($UseAdkWinPE) { $WimSourceType = 'WinPE' } else { $WimSourceType = 'WinRE' if ($Architecture) { $GetWindowsImage = Select-OSDeployCoreWinRESource -Architecture $Architecture } else { $GetWindowsImage = Select-OSDeployCoreWinRESource } if (-not $GetWindowsImage -or $GetWindowsImage.Count -eq 0) { Write-Warning "No WinRE source selected. Defaulting to Windows ADK WinPE." $WimSourceType = 'WinPE' if (-not $Architecture) { $Architecture = ($env:PROCESSOR_ARCHITECTURE).ToLower() -replace 'x86_', '' if ($Architecture -notin @('amd64', 'arm64')) { Write-Warning "Unsupported architecture '$Architecture'. Only 'amd64' and 'arm64' are supported." return } } } else { $Architecture = $GetWindowsImage.Architecture $ImportImageCorePath = Join-Path $GetWindowsImage.Path '.core' $ImportImageOSFilesPath = Join-Path $GetWindowsImage.Path '.core' 'os-files' Write-OSDeployCoreProgress "Using Recovery Image at $($GetWindowsImage.ImagePath)" } } #endregion #region ADK Paths if (($Architecture -ne 'amd64') -and ($Architecture -ne 'arm64')) { Write-Warning "Unknown architecture: $Architecture" return } $WindowsAdkPaths = Get-WindowsAdkPaths -Architecture $Architecture -AdkRoot $AdkRootPath if (-not $WindowsAdkPaths) { Write-Warning "Unable to resolve Windows ADK paths for architecture $Architecture" return } if ($WimSourceType -eq 'WinPE') { $GetWindowsImage = Get-WindowsImage -ImagePath $WindowsAdkPaths.WimSourcePath -Index 1 $ImportImageWimPath = $GetWindowsImage.ImagePath $sourceVersion = $GetWindowsImage.Version.ToString() } elseif ($WimSourceType -eq 'WinRE') { $ImportImageWimPath = $GetWindowsImage.ImagePath $sourceVersion = $GetWindowsImage.Version } # Build the media name as {build}.{revision}-{architecture}-{name} $versionParts = $sourceVersion.Split('.') $trimmedVersion = if ($versionParts.Count -ge 4) { "$($versionParts[2]).$($versionParts[3])" } else { $sourceVersion } $MediaName = "$trimmedVersion-$Architecture-$Name" $MediaIsoLabel = "$trimmedVersion-$Name" $WindowsAdkPaths.WimSourcePath = $ImportImageWimPath #endregion #region Build Paths $BuildsPath = Join-Path $Script:OSDeployCorePath 'boot-media' # Handle duplicate build names by appending -001, -002, etc. $MediaRootPath = Join-Path $BuildsPath $MediaName if (Test-Path -Path $MediaRootPath) { $suffix = 1 do { $candidateName = '{0}-{1:d3}' -f $MediaName, $suffix $candidatePath = Join-Path $BuildsPath $candidateName $suffix++ } while (Test-Path -Path $candidatePath) $MediaName = $candidateName $MediaRootPath = $candidatePath } $CorePath = Join-Path $MediaRootPath '.core' $TempPath = Join-Path $MediaRootPath '.temp' $LogsPath = Join-Path $TempPath 'logs' $MediaPath = Join-Path $MediaRootPath 'bootmedia' $SourcesPath = Join-Path $MediaPath 'sources' #endregion #region Select Profile, Drivers, Scripts $MyBuildProfile = Select-OSDeployCoreBuildProfile if ($MyBuildProfile) { $BuildProfile = Get-Content $MyBuildProfile.FullName -Raw | ConvertFrom-Json $WinPEDriver = Expand-OSDeployBuildProfileToken $BuildProfile.WinPEDriver if ($WimSourceType -eq 'WinPE' -and $WinPEDriver) { Write-OSDeployCoreProgress 'ADK WinPE does not support wireless hardware - excluding Wi-Fi drivers' $WinPEDriver = $WinPEDriver | Where-Object { (Split-Path $_ -Leaf) -notmatch 'wifi|wireless' } } $WinPEAppScript = Expand-OSDeployBuildProfileToken $BuildProfile.WinPEAppScript $WinPEScript = Expand-OSDeployBuildProfileToken $BuildProfile.WinPEScript $WinPEMediaScript = Expand-OSDeployBuildProfileToken $BuildProfile.WinPEMediaScript $WinPEStartupProfile = Expand-OSDeployBuildProfileToken $BuildProfile.WinPEStartupProfile $WinPECustomWallpaper = Expand-OSDeployBuildProfileToken $BuildProfile.WinPECustomWallpaper [System.String[]]$Languages = $BuildProfile.Languages $SetAllIntl = $BuildProfile.SetAllIntl $SetInputLocale = $BuildProfile.SetInputLocale $SetTimeZone = $BuildProfile.SetTimeZone $MyBuildProfilePath = $MyBuildProfile.FullName } else { $skipWifi = $WimSourceType -eq 'WinPE' if ($skipWifi) { Write-OSDeployCoreProgress 'ADK WinPE does not support wireless hardware - excluding Wi-Fi drivers' } $OSDWorkspaceWinPEDriver = Select-OSDeployCoreBuildDriver -Architecture $Architecture -SkipWifiDrivers:$skipWifi if ($OSDWorkspaceWinPEDriver) { $WinPEDriver = $OSDWorkspaceWinPEDriver | Select-Object -ExpandProperty FullName } else { $WinPEDriver = $null } $OSDWorkspaceWinPEScript = @() $result = Select-OSDeployCoreBuildScript if ($null -ne $result) { $OSDWorkspaceWinPEScript = @($result) } $WinPEAppScript = $null $WinPEScript = $null $WinPEMediaScript = $null $WinPEStartupProfile = $null 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 'media-script' }) { $WinPEMediaScript = $OSDWorkspaceWinPEScript | Where-Object { $_.Type -eq 'media-script' } | Select-Object -ExpandProperty FullName } $OSDWorkspaceStartupProfile = Select-OSDeployCoreBuildStartupProfile if ($OSDWorkspaceStartupProfile) { $WinPEStartupProfile = $OSDWorkspaceStartupProfile | Select-Object -ExpandProperty FullName } $WinPECustomWallpaper = $null $wallpaperRoots = @($Script:OSDeployCoreRepositoryPath, (Join-Path $Script:OSDeployModuleBase 'core' 'OSDRe')) $osdCloudBase = if (Get-Command -Name 'Get-OSDCloudModulePath' -ErrorAction SilentlyContinue) { Get-OSDCloudModulePath } if ($osdCloudBase) { $wallpaperRoots += Join-Path $osdCloudBase 'core' 'OSDRe' } $osdBase = if (Get-Command -Name 'Get-OSDModulePath' -ErrorAction SilentlyContinue) { Get-OSDModulePath } if ($osdBase) { $wallpaperRoots += Join-Path $osdBase 'core' 'OSDRe' } $WallpaperFiles = @() foreach ($root in $wallpaperRoots) { $wpPath = Join-Path $root 'winpe-wallpaper' if (Test-Path -LiteralPath $wpPath) { $WallpaperFiles += Get-ChildItem -LiteralPath $wpPath -Filter '*.jpg' -File -ErrorAction SilentlyContinue } } $WallpaperFiles = @($WallpaperFiles | Sort-Object FullName -Unique) if ($WallpaperFiles.Count -eq 1) { $WinPECustomWallpaper = $WallpaperFiles[0].FullName } elseif ($WallpaperFiles.Count -gt 1) { $SelectedWallpaper = $WallpaperFiles | Select-Object Name, FullName | Out-GridView -Title 'Select WinPE Wallpaper' -OutputMode Single if ($SelectedWallpaper) { $WinPECustomWallpaper = $SelectedWallpaper.FullName } } $BuildProfile = [ordered]@{ Architecture = [System.String]$Architecture WinPEDriver = ConvertTo-OSDeployBuildProfileToken $WinPEDriver WinPEAppScript = ConvertTo-OSDeployBuildProfileToken $WinPEAppScript WinPEScript = ConvertTo-OSDeployBuildProfileToken $WinPEScript WinPEMediaScript = ConvertTo-OSDeployBuildProfileToken $WinPEMediaScript WinPEStartupProfile = ConvertTo-OSDeployBuildProfileToken $WinPEStartupProfile WinPECustomWallpaper = ConvertTo-OSDeployBuildProfileToken $WinPECustomWallpaper Languages = [System.String[]]$Languages SetAllIntl = [System.String]$SetAllIntl SetInputLocale = [System.String]$SetInputLocale SetTimeZone = [System.String]$SetTimeZone } $BuildProfileCachePath = Join-Path $script:OSDeployCoreRepositoryPath 'build-profiles' if (-not (Test-Path $BuildProfileCachePath)) { New-Item -Path $BuildProfileCachePath -ItemType Directory -Force | Out-Null } $MyBuildProfilePath = Join-Path $BuildProfileCachePath "$Name.json" Write-OSDeployCoreProgress "Exporting Build Profile to $MyBuildProfilePath" $BuildProfile | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File $MyBuildProfilePath -Encoding utf8 -Force } #endregion #region BuildMedia $global:BuildMedia = [ordered]@{ AdkRootPath = $AdkRootPath AdkPaths = $WindowsAdkPaths SkipAdkPackages = [System.Boolean]$SkipAdkPackages Architecture = [System.String]$Architecture BuildProfile = $MyBuildProfilePath ContentStartnet = [System.String]'' ContentWinpeshl = [System.String]'' CorePath = $CorePath InstalledApps = @() ImportImageWimPath = $ImportImageWimPath Languages = [System.String[]]$Languages LogsPath = $LogsPath MediaIsoLabel = $MediaIsoLabel MediaName = $MediaName MediaPath = $MediaPath MediaPathEX = $null MediaRootPath = $MediaRootPath MountPath = $null Name = [System.String]$Name SetAllIntl = [System.String]$SetAllIntl SetInputLocale = [System.String]$SetInputLocale SetTimeZone = [System.String]$SetTimeZone SourcesPath = $SourcesPath SourcesPathEX = $null UpdateUSB = [System.Boolean]$UpdateUSB WimSourceType = $WimSourceType WindowsImage = $null WinPEAppScript = $WinPEAppScript WinPEAppsPath = Join-Path $Script:OSDeployCorePath 'cache' 'winpe-apps' WinPEDriver = $WinPEDriver WinPEMediaScript = $WinPEMediaScript WinPEScript = $WinPEScript WinPEStartupProfile = $WinPEStartupProfile WinPECustomWallpaper = $WinPECustomWallpaper } #endregion #region Point of No Return Write-OSDeployCoreProgress 'Build Configuration' $global:BuildMedia | Out-Host Write-Host -ForegroundColor DarkCyan 'Press CTRL+C to cancel' pause $BuildStartTime = Get-Date #endregion #region Create Build Directories if (-not $PSCmdlet.ShouldProcess($MediaRootPath, 'Create build directories')) { return } foreach ($dir in @($MediaRootPath, $CorePath, $TempPath, $LogsPath)) { if (-not (Test-Path $dir)) { New-Item -Path $dir -ItemType Directory -Force | Out-Null } } $Transcript = "$((Get-Date).ToString('yyMMdd-HHmmss'))-Build-BootImage.log" Start-Transcript -Path (Join-Path $LogsPath $Transcript) -ErrorAction SilentlyContinue #endregion #region Hydrate Core Metadata if ($WimSourceType -eq 'WinRE' -and $ImportImageCorePath -and (Test-Path $ImportImageCorePath)) { Write-OSDeployCoreProgress "Hydrate $CorePath" $null = robocopy.exe "$ImportImageCorePath" "$CorePath" *.json /nfl /ndl /np /r:0 /w:0 /xj /mt:128 /LOG+:"$LogsPath\core.log" $null = robocopy.exe "$ImportImageCorePath" "$CorePath" *.xml /nfl /ndl /np /r:0 /w:0 /xj /mt:128 /LOG+:"$LogsPath\core.log" } $ImportId = @{ id = $MediaName } if (-not (Test-Path $CorePath)) { New-Item -Path $CorePath -ItemType Directory -Force | Out-Null } $ImportId | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File "$CorePath\id.json" -Encoding utf8 -Force #endregion #region Hydrate Media Write-OSDeployCoreProgress "Hydrate $MediaPath" $null = robocopy.exe "$($WindowsAdkPaths.PathWinPEMedia)" "$MediaPath" *.* /mir /b /ndl /np /r:0 /w:0 /xj /njs /mt:128 /LOG+:"$LogsPath\media.log" if ($WimSourceType -eq 'WinRE' -and $ImportImageCorePath) { Copy-Item -Path "$ImportImageCorePath\os-boot\DVD\EFI\en-US\efisys.bin" -Destination "$MediaPath\EFI\Microsoft\Boot\efisys.bin" -Force -ErrorAction SilentlyContinue 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") { Copy-Item -Path "$ImportImageCorePath\os-boot\Fonts\$Font" -Destination "$MediaPath\EFI\Microsoft\Boot\Fonts\$Font" -Force -ErrorAction SilentlyContinue } } } #endregion #region Build MediaEX (BlackLotus CVE-2022-21894 Mitigation) $MediaPathEX = $null if ($WimSourceType -eq 'WinRE' -and $ImportImageCorePath -and (Test-Path "$ImportImageCorePath\os-boot\EFI_EX")) { $MediaPathEX = Join-Path $MediaRootPath 'bootmedia-ca2023' $global:BuildMedia.MediaPathEX = $MediaPathEX Write-OSDeployCoreProgress "Hydrate $MediaPathEX" $null = robocopy.exe "$($WindowsAdkPaths.PathWinPEMedia)" "$MediaPathEX" *.* /mir /b /ndl /np /r:0 /w:0 /xj /mt:128 /LOG+:"$LogsPath\mediaex.log" Write-OSDeployCoreProgress 'Mitigate CVE-2022-21894 Secure Boot Security Feature Bypass Vulnerability' Remove-Item -Path "$MediaPathEX\EFI\Microsoft\Boot\Fonts" -Recurse -Force -ErrorAction SilentlyContinue if (-not (Test-Path "$MediaPathEX\EFI\Microsoft\Boot\Fonts")) { New-Item -Path "$MediaPathEX\EFI\Microsoft\Boot\Fonts" -ItemType Directory -Force | Out-Null } $ExFiles = @( @{ Src = "$ImportImageCorePath\os-boot\EFI_EX\bootmgr_ex.efi"; Dst = "$MediaPathEX\bootmgr.efi" }, @{ Src = "$ImportImageCorePath\os-boot\EFI_EX\bootmgfw_ex.efi"; Dst = "$MediaPathEX\EFI\Boot\bootx64.efi" } ) foreach ($f in $ExFiles) { Copy-Item -Path $f.Src -Destination $f.Dst -Force -ErrorAction SilentlyContinue } $ExFonts = @( 'chs_boot', 'cht_boot', 'jpn_boot', 'kor_boot', 'malgun_boot', 'malgunn_boot', 'meiryo_boot', 'meiryon_boot', 'msjh_boot', 'msjhn_boot', 'msyh_boot', 'msyhn_boot', 'segmono_boot', 'segoe_slboot', 'segoen_slboot', 'wgl4_boot' ) foreach ($fontBase in $ExFonts) { $srcFont = "$ImportImageCorePath\os-boot\Fonts_EX\${fontBase}_EX.ttf" $dstFont = "$MediaPathEX\EFI\Microsoft\Boot\Fonts\$fontBase.ttf" Copy-Item -Path $srcFont -Destination $dstFont -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 } #endregion #region Build Sources (boot.wim) if (-not (Test-Path $SourcesPath)) { New-Item -Path $SourcesPath -ItemType Directory -Force -ErrorAction Stop | Out-Null } $bootWimPath = Join-Path $SourcesPath 'boot.wim' Write-OSDeployCoreProgress "Hydrate $bootWimPath" Copy-Item -Path $WindowsAdkPaths.WimSourcePath -Destination $bootWimPath -Force -ErrorAction Stop | Out-Null if (-not (Test-Path $bootWimPath)) { Write-Warning "Failed to copy boot.wim to $bootWimPath" Stop-Transcript return } attrib -s -h -r $SourcesPath attrib -s -h -r $bootWimPath if ($MediaPathEX) { $SourcesPathEX = Join-Path $MediaPathEX 'sources' $global:BuildMedia.SourcesPathEX = $SourcesPathEX if (-not (Test-Path $SourcesPathEX)) { New-Item -Path $SourcesPathEX -ItemType Directory -Force -ErrorAction Stop | Out-Null } attrib -s -h -r $SourcesPathEX } #endregion #region Mount Image Write-OSDeployCoreProgress 'Mount Windows Image' $MountPath = Join-Path $TempPath 'mount' if (-not (Test-Path $MountPath)) { New-Item -Path $MountPath -ItemType Directory -Force | Out-Null } $CurrentLog = "$LogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-Mount-WindowsImage.log" $WindowsImage = Mount-WindowsImage -ImagePath $bootWimPath -Index 1 -Path $MountPath -LogPath $CurrentLog $global:BuildMedia.MountPath = $MountPath $global:BuildMedia.WindowsImage = $WindowsImage Write-OSDeployCoreProgress "MountPath: $MountPath" #endregion #region Registry Information Write-OSDeployCoreProgress 'Get WinPE Registry CurrentVersion' $softwareHivePath = Join-Path $MountPath 'Windows\System32\Config\SOFTWARE' if (Test-Path $softwareHivePath) { $hiveName = 'OSDeployCoreBoot' try { reg.exe LOAD "HKLM\$hiveName" "$softwareHivePath" 2>&1 | Out-Null $regPath = "HKLM:\$hiveName\Microsoft\Windows NT\CurrentVersion" if (Test-Path $regPath) { Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue | Select-Object -Property CurrentBuild, BuildLabEx, EditionID, InstallationType, ProductName | Out-Host } } finally { Start-Sleep -Seconds 3 reg.exe UNLOAD "HKLM\$hiveName" 2>&1 | Out-Null } } #endregion #region Export Initial Packages Write-OSDeployCoreProgress "Export initial Get-WindowsPackage to $CorePath" $WindowsPackage = $WindowsImage | Get-WindowsPackage if ($WindowsPackage) { $WindowsPackage | Select-Object * | Export-Clixml -Path "$CorePath\winpe-windowspackage-initial.xml" -Force } #endregion #region Adding OS Files if ($WimSourceType -eq 'WinRE' -and $ImportImageOSFilesPath -and (Test-Path $ImportImageOSFilesPath)) { Write-OSDeployCoreProgress "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+:"$LogsPath\os-files.log" } #endregion #region Adding OA3Tool $OA3ToolPath = $WindowsAdkPaths.oa3toolexe if ($OA3ToolPath -and (Test-Path $OA3ToolPath)) { Write-OSDeployCoreProgress "Adding OA3Tool from $OA3ToolPath" Copy-Item -Path $OA3ToolPath -Destination "$MountPath\Windows\System32\oa3tool.exe" -Force -ErrorAction SilentlyContinue | Out-Null } #endregion #region ADK Optional Component Packages if (-not $SkipAdkPackages) { $WinPEOCs = $WindowsAdkPaths.WinPEOCs $WindowsAdkWinpePackages = $global:OSDeployModule.BootImage.adkwinpepackages # Install default en-us packages Write-OSDeployCoreProgress '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 = "$LogsPath\$((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 '0x800f081e CBS_E_NOT_APPLICABLE The Windows ADK version does not support this WinPE version' } if ($_.Exception.ErrorCode -eq '-2146498512') { Write-Warning '0x800f0830 CBS_E_IMAGE_UNSERVICEABLE The image may be corrupted. Discard and start again' } } } } # Verify PowerShell was installed if (-not ($WindowsImage | Get-WindowsPackage | Where-Object { $_.PackageName -match 'PowerShell' })) { Write-Warning 'PowerShell Optional Component did not install. Required ADK Packages did not install properly.' Write-Warning 'Ensure the Windows ADK version supports the WinRE version being serviced.' Write-Warning 'Build will continue so you can review the logs.' } # Install en-us language pack $PackageFile = "$WinPEOCs\$Lang\lp.cab" if (Test-Path $PackageFile) { Write-Host -ForegroundColor Gray "$PackageFile" $CurrentLog = "$LogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-Add-WindowsPackage-WinPE-lp_$Lang.log" try { $WindowsImage | Add-WindowsPackage -PackagePath $PackageFile -LogPath "$CurrentLog" -ErrorAction Stop | Out-Null } catch { if ($_.Exception.ErrorCode -eq '-2148468766') { Write-Warning '0x800f081e CBS_E_NOT_APPLICABLE' } if ($_.Exception.ErrorCode -eq '-2146498512') { Write-Warning '0x800f0830 CBS_E_IMAGE_UNSERVICEABLE' } } } # Install en-us language-specific OCs foreach ($Package in $WindowsAdkWinpePackages) { $PackageFile = "$WinPEOCs\$Lang\WinPE-${Package}_$Lang.cab" if (Test-Path $PackageFile) { Write-Host -ForegroundColor Gray "$PackageFile" $CurrentLog = "$LogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-Add-WindowsPackage-WinPE-${Package}_$Lang.log" try { $WindowsImage | Add-WindowsPackage -PackagePath $PackageFile -LogPath "$CurrentLog" -ErrorAction Stop | Out-Null } catch { if ($_.Exception.ErrorCode -eq '-2148468766') { Write-Warning '0x800f081e CBS_E_NOT_APPLICABLE' } if ($_.Exception.ErrorCode -eq '-2146498512') { Write-Warning '0x800f0830 CBS_E_IMAGE_UNSERVICEABLE' } } } } # Save after default language Step-BootImageSave # Install additional 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-OSDeployCoreProgress "Adding ADK Packages for Language $Lang" $PackageFile = "$WinPEOCs\$Lang\lp.cab" if (Test-Path $PackageFile) { Write-Host -ForegroundColor Gray "$PackageFile" $CurrentLog = "$LogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-Add-WindowsPackage-WinPE-lp_$Lang.log" try { $WindowsImage | Add-WindowsPackage -PackagePath $PackageFile -LogPath "$CurrentLog" -ErrorAction Stop | Out-Null } catch { if ($_.Exception.ErrorCode -eq '-2148468766') { Write-Warning '0x800f081e CBS_E_NOT_APPLICABLE' } if ($_.Exception.ErrorCode -eq '-2146498512') { Write-Warning '0x800f0830 CBS_E_IMAGE_UNSERVICEABLE' } } } foreach ($Package in $WindowsAdkWinpePackages) { $PackageFile = "$WinPEOCs\$Lang\WinPE-${Package}_$Lang.cab" if (Test-Path $PackageFile) { Write-Host -ForegroundColor Gray "$PackageFile" $CurrentLog = "$LogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-Add-WindowsPackage-WinPE-${Package}_$Lang.log" try { $WindowsImage | Add-WindowsPackage -PackagePath $PackageFile -LogPath "$CurrentLog" -ErrorAction Stop | Out-Null } catch { if ($_.Exception.ErrorCode -eq '-2148468766') { Write-Warning '0x800f081e CBS_E_NOT_APPLICABLE' } if ($_.Exception.ErrorCode -eq '-2146498512') { Write-Warning '0x800f0830 CBS_E_IMAGE_UNSERVICEABLE' } } } } # Update lang.ini if (Test-Path "$MountPath\sources\lang.ini") { Write-OSDeployCoreProgress "Updating lang.ini for $Lang" $CurrentLog = "$LogsPath\$((Get-Date).ToString('yyMMdd-HHmmss'))-Gen-LangINI.log" dism.exe /image:"$MountPath" /Gen-LangINI /distribution:"$MountPath" /LogPath:"$CurrentLog" } Step-BootImageSave } } #endregion #region Step Functions Step-BootImageDismSettings Step-BootImageAddWallpaper Step-BootImageCustomWallpaper Step-BootImagePowerShellUpdate Step-BootImageAppAzCopy Step-BootImageAppSysinternals Step-BootImageAppCurl Step-BootImageAppZip Step-BootImageAppScript Step-BootImageSave Step-BootImageRemoveWinpeshl Step-BootImageConsoleSettings Step-BootImageSetEnvVars Step-BootImageCopyOSDModule Step-BootImageCopyOSDCloudModule Step-BootImageScript Step-BootImageSetStartnet Step-BootImageStartupProfile Step-BootImageDriver Step-BootImageExportDrivers Step-BootImageExportPackages Step-BootImageRegCurrentVersion Step-BootImageDismGetIntl Step-BootImageGetStartnet Step-BootImageGetWinpeshl Step-BootImageDismount Step-BootImageExport Step-BootImageMediaScript Step-BootImageIso Step-BootImageUpdateUSB #endregion #region Complete Write-OSDeployCoreProgress "Exporting Build Profile to $CorePath\buildprofile.json" $BuildProfile | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File "$CorePath\buildprofile.json" -Encoding utf8 -Force Write-OSDeployCoreProgress "Exporting Build Context to $CorePath\buildcontext.json" $global:BuildMedia | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File "$CorePath\buildcontext.json" -Encoding utf8 -Force #region Write properties.json Write-OSDeployCoreProgress "Writing properties.json to $MediaRootPath" $bootWimFinal = Join-Path $SourcesPath 'boot.wim' $wimInfo = Get-WindowsImage -ImagePath $bootWimFinal -Index 1 $buildProperties = [ordered]@{ Type = 'WinPE' Id = $MediaName Name = $Name ModifiedTime = $wimInfo.ModifiedTime InstallationType = $wimInfo.InstallationType Version = $wimInfo.Version.ToString() Architecture = $Architecture Languages = [System.String[]]$Languages SetAllIntl = $SetAllIntl InputLocale = $SetInputLocale TimeZone = $SetTimeZone ContentStartnet = $global:BuildMedia.ContentStartnet ContentWinpeshl = $global:BuildMedia.ContentWinpeshl InstalledApps = $global:BuildMedia.InstalledApps AdkVersion = $AdkInfo.InstallVersion BuildProfile = $MyBuildProfilePath WinPEAppScript = $WinPEAppScript WinPEScript = $WinPEScript WinPEDriver = $WinPEDriver WinPEMediaScript = $WinPEMediaScript CreatedTime = $wimInfo.CreatedTime ImageName = $wimInfo.ImageName ImagePath = $bootWimFinal ImageIndex = 1 ImageSize = $wimInfo.ImageSize DirectoryCount = $wimInfo.DirectoryCount FileCount = $wimInfo.FileCount Path = $MediaRootPath } # Add OS source info if WinRE-based if ($WimSourceType -eq 'WinRE' -and $GetWindowsImage.OSCreatedTime) { $buildProperties.OSCreatedTime = $GetWindowsImage.OSCreatedTime $buildProperties.OSModifiedTime = $GetWindowsImage.OSModifiedTime $buildProperties.OSImageName = $GetWindowsImage.OSImageName $buildProperties.OSEditionId = $GetWindowsImage.OSEditionId $buildProperties.OSVersion = $GetWindowsImage.OSVersion } $buildProperties | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File (Join-Path $MediaRootPath 'properties.json') -Encoding utf8 -Force #endregion # Restore settings [Net.ServicePointManager]::SecurityProtocol = $currentVersionTls $ProgressPreference = $currentProgressPref $BuildEndTime = Get-Date $BuildTimeSpan = New-TimeSpan -Start $BuildStartTime -End $BuildEndTime Write-OSDeployCoreProgress "Build-OSDeployBootMedia completed in $($BuildTimeSpan.ToString("mm' minutes 'ss' seconds'"))" Stop-Transcript #endregion } |