private/Step-BuildBootUpdatePowerShell.ps1
|
#Requires -PSEdition Core function Step-BuildBootUpdatePowerShell { <# .SYNOPSIS Updates PowerShell components and configuration in the mounted WinPE image .DESCRIPTION Creates or reuses a local package cache, copies default user folders into the mounted system profile, and creates required module, script, profile, and repository directories. It temporarily registers a trusted OSDeployCore repository, downloads configured PackageManagement, PowerShellGet, and NuGet packages when absent, installs the modules into the mounted image, and copies the cache into the image. The function also creates a trusted PSGallery configuration and PowerShell profile when absent. It loads the mounted DEFAULT hive to set user Path, TEMP, and TMP values, then applies unsigned INF files that set system-profile environment variables and the PowerShell execution policy to Bypass. .EXAMPLE PS> Step-BuildBootUpdatePowerShell Updates cached PowerShell packages and configures PowerShell in the mounted image. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.String. Returns unsuppressed native stdout from repository copy and registry commands. .NOTES Author: David Segura Company: Recast Software Version: 0.1.0 Date: 2026-08-28 Requires global BuildMedia.MountPath, global OSDeployModule.BootImage.psrepository, and script OSDeployCorePath. Requires PowerShellGet repository commands, Save-Module, Add-WindowsDriver, robocopy.exe, reg.exe, and network access for cache misses. Modifies the shared cache, current session repository registration, mounted files, registry hives, and execution policy. #> [CmdletBinding()] param () $MountPath = $global:BuildMedia.MountPath $CachePSRepository = Join-Path $Script:OSDeployCorePath 'cache' 'psrepository' $psRepoConfig = $global:OSDeployModule.BootImage.psrepository # Ensure cache directory exists if (-not (Test-Path -Path $CachePSRepository)) { New-Item -Path $CachePSRepository -ItemType Directory -Force | Out-Null } # Copy User Folders to the System Profile Write-HostDateTimeDarkGray 'Copy User Folders to the System Profile' $null = robocopy.exe "$MountPath\Users\Default" "$MountPath\Windows\System32\Config\SystemProfile" *.* /e /b /ndl /nfl /np /ts /r:0 /w:0 /xj /xf NTUSER.* # Create Required Folders $requiredFolders = @( "$MountPath\Program Files\WindowsPowerShell\Modules", "$MountPath\Program Files\WindowsPowerShell\Scripts", "$MountPath\Users\Default\AppData\Local", "$MountPath\Users\Default\AppData\Roaming", "$MountPath\Users\Default\Desktop", "$MountPath\Users\Default\Documents\WindowsPowerShell", "$MountPath\Windows\System32\WindowsPowerShell\v1.0\Modules", "$MountPath\Windows\System32\WindowsPowerShell\v1.0\Scripts" ) foreach ($item in $requiredFolders) { if (-not (Test-Path -Path $item)) { New-Item -Path $item -ItemType Directory -Force | Out-Null } } $WinPEPSRepository = "$MountPath\PSRepository" if (-not (Test-Path -Path $WinPEPSRepository)) { New-Item -Path $WinPEPSRepository -ItemType Directory -Force | Out-Null } $MountedPSModulesPath = "$MountPath\Program Files\WindowsPowerShell\Modules" if (Get-PSRepository -Name OSDeployCore -ErrorAction SilentlyContinue) { Unregister-PSRepository -Name OSDeployCore } Register-PSRepository -Name OSDeployCore -SourceLocation $CachePSRepository -InstallationPolicy Trusted #region PackageManagement $PackageName = $psRepoConfig.packagemanagement.filename $PSRepositoryModule = Join-Path $CachePSRepository $PackageName $PSModuleUrl = $psRepoConfig.packagemanagement.url if (-not (Test-Path $PSRepositoryModule)) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Downloading $PackageName" if (Get-Command 'curl.exe' -ErrorAction SilentlyContinue) { & curl.exe --silent --location --retry 5 --continue-at - --output $PSRepositoryModule $PSModuleUrl } else { Invoke-WebRequest -UseBasicParsing -Uri $PSModuleUrl -OutFile $PSRepositoryModule } } Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Updating WinPE PowerShell PackageManagement" Save-Module -Name PackageManagement -Repository OSDeployCore -Path $MountedPSModulesPath #endregion #region PowerShellGet $PackageName = $psRepoConfig.powershellget.filename $PSRepositoryModule = Join-Path $CachePSRepository $PackageName $PSModuleUrl = $psRepoConfig.powershellget.url if (-not (Test-Path $PSRepositoryModule)) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Downloading $PackageName" if (Get-Command 'curl.exe' -ErrorAction SilentlyContinue) { & curl.exe --silent --location --retry 5 --continue-at - --output $PSRepositoryModule $PSModuleUrl } else { Invoke-WebRequest -UseBasicParsing -Uri $PSModuleUrl -OutFile $PSRepositoryModule } } Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Updating WinPE PowerShell PowerShellGet" Save-Module -Name PowerShellGet -Repository OSDeployCore -Path $MountedPSModulesPath #endregion Unregister-PSRepository -Name OSDeployCore #region NuGet $PackageName = $psRepoConfig.nuget.filename $PSRepositoryModule = Join-Path $CachePSRepository $PackageName $PSModuleUrl = $psRepoConfig.nuget.url if (-not (Test-Path $PSRepositoryModule)) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Downloading $PackageName" if (Get-Command 'curl.exe' -ErrorAction SilentlyContinue) { & curl.exe --silent --location --retry 5 --continue-at - --output $PSRepositoryModule $PSModuleUrl } else { Invoke-WebRequest -UseBasicParsing -Uri $PSModuleUrl -OutFile $PSRepositoryModule } } Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Updating WinPE NuGet" $PSModuleDestination = "$MountPath\Windows\System32\Config\SystemProfile\AppData\Local\Microsoft\Windows\PowerShell\PowerShellGet" if (-not (Test-Path $PSModuleDestination)) { New-Item -Path $PSModuleDestination -ItemType Directory -Force | Out-Null } Copy-Item -Path $PSRepositoryModule -Destination $PSModuleDestination -Force #endregion #region PSRepositories.xml $PSRepositoriesContent = @' <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"> <Obj RefId="0"> <TN RefId="0"> <T>System.Collections.Hashtable</T> <T>System.Object</T> </TN> <DCT> <En> <S N="Key">PSGallery</S> <Obj N="Value" RefId="1"> <TN RefId="1"> <T>Microsoft.PowerShell.Commands.PSRepository</T> <T>System.Management.Automation.PSCustomObject</T> <T>System.Object</T> </TN> <MS> <S N="Name">PSGallery</S> <S N="SourceLocation">https://www.powershellgallery.com/api/v2</S> <S N="PublishLocation">https://www.powershellgallery.com/api/v2/package/</S> <S N="ScriptSourceLocation">https://www.powershellgallery.com/api/v2/items/psscript</S> <S N="ScriptPublishLocation">https://www.powershellgallery.com/api/v2/package/</S> <Obj N="Trusted" RefId="2"> <TN RefId="2"> <T>System.Management.Automation.SwitchParameter</T> <T>System.ValueType</T> <T>System.Object</T> </TN> <ToString>True</ToString> <Props> <B N="IsPresent">true</B> </Props> </Obj> <B N="Registered">true</B> <S N="InstallationPolicy">Trusted</S> <S N="PackageManagementProvider">NuGet</S> <Obj N="ProviderOptions" RefId="3"> <TNRef RefId="0" /> <DCT /> </Obj> </MS> </Obj> </En> </DCT> </Obj> </Objs> '@ $PSRepositoriesFile = "$MountPath\Windows\System32\Config\SystemProfile\AppData\Local\Microsoft\Windows\PowerShell\PowerShellGet\PSRepositories.xml" if (-not (Test-Path -Path $PSRepositoriesFile)) { Write-HostDateTimeDarkGray 'Create PSRepositories.xml and Trust PSGallery' $PSRepositoriesContent | Set-Content -Path $PSRepositoriesFile -Encoding utf8 -Force } #endregion #region PowerShell Profile $PowerShellProfileContent = @' # OSD PowerShell Profile [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $registryPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' $registryPath | ForEach-Object { $k = Get-Item $_ $k.GetValueNames() | ForEach-Object { $name = $_ $value = $k.GetValue($_) Set-Item -Path Env:\$name -Value $value } } '@ $PowerShellProfileFile = "$MountPath\Windows\System32\WindowsPowerShell\v1.0\profile.ps1" if (-not (Test-Path -Path $PowerShellProfileFile)) { Write-HostDateTimeDarkGray 'Create OSD PowerShell Profile' $PowerShellProfileContent | Set-Content -Path $PowerShellProfileFile -Encoding utf8 -Force } #endregion # Populate WinPE PSRepository & robocopy.exe "$CachePSRepository" "$WinPEPSRepository" *.* /e /ndl /nfl /np /njh /njs /r:0 /w:0 /xj #region User Environment Variables & reg.exe LOAD HKLM\Mount "$MountPath\Windows\System32\Config\DEFAULT" Start-Sleep -Seconds 3 reg.exe add "HKLM\Mount\Environment" /v Path /t REG_SZ /d "X:\Windows\System32\Config\SystemProfile\AppData\Local\Microsoft\WindowsApps" /f reg.exe add "HKLM\Mount\Environment" /v TEMP /t REG_SZ /d "X:\Windows\Temp" /f reg.exe add "HKLM\Mount\Environment" /v TMP /t REG_SZ /d "X:\Windows\Temp" /f Start-Sleep -Seconds 3 & reg.exe UNLOAD HKLM\Mount Start-Sleep -Seconds 3 #endregion #region WinPE Environment Variables INF $InfEnvironment = @' [Version] Signature = "$WINDOWS NT$" Class = System ClassGuid = {4D36E97d-E325-11CE-BFC1-08002BE10318} Provider = OSDeploy DriverVer = 01/29/2026,2026.01.29.0 [DefaultInstall] AddReg = AddReg [AddReg] ;rootkey,[subkey],[value],[flags],[data] ;0x00000 REG_SZ ;0x00001 REG_BINARY ;0x10000 REG_MULTI_SZ ;0x20000 REG_EXPAND_SZ ;0x10001 REG_DWORD ;0x20001 REG_NONE HKLM,"SYSTEM\ControlSet001\Control\Session Manager\Environment",APPDATA,0x00000,"X:\Windows\System32\Config\SystemProfile\AppData\Roaming" HKLM,"SYSTEM\ControlSet001\Control\Session Manager\Environment",HOMEDRIVE,0x00000,"X:" HKLM,"SYSTEM\ControlSet001\Control\Session Manager\Environment",HOMEPATH,0x00000,"\Windows\System32\Config\SystemProfile" HKLM,"SYSTEM\ControlSet001\Control\Session Manager\Environment",LOCALAPPDATA,0x00000,"X:\Windows\System32\Config\SystemProfile\AppData\Local" HKLM,"SYSTEM\ControlSet001\Control\Session Manager\Environment",USERDATA,0x00000,"X:\Windows\System32\Config\SystemProfile" '@ Write-HostDateTimeDarkGray 'PowerShell: Set WinPE Environment Variables' $InfFile = "$env:TEMP\Set-WinPEEnvironment.inf" New-Item -Path $InfFile -Force | Out-Null Set-Content -Path $InfFile -Value $InfEnvironment -Encoding Unicode -Force $null = Add-WindowsDriver -Path $MountPath -Driver $InfFile -ForceUnsigned #endregion #region WinPE Execution Policy INF $InfExecutionPolicy = @' [Version] Signature = "$WINDOWS NT$" Class = System ClassGuid = {4D36E97d-E325-11CE-BFC1-08002BE10318} Provider = OSDeploy DriverVer = 01/29/2026,2026.01.29.0 [DefaultInstall] AddReg = AddReg [AddReg] ;rootkey,[subkey],[value],[flags],[data] ;0x00000 REG_SZ ;0x00001 REG_BINARY ;0x10000 REG_MULTI_SZ ;0x20000 REG_EXPAND_SZ ;0x10001 REG_DWORD ;0x20001 REG_NONE HKLM,SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell,ExecutionPolicy,0x00000,"Bypass" '@ Write-HostDateTimeDarkGray 'PowerShell: Set WinPE PowerShell Execution Policy' $InfFile = "$env:TEMP\Set-WinPEExecutionPolicy.inf" New-Item -Path $InfFile -Force | Out-Null Set-Content -Path $InfFile -Value $InfExecutionPolicy -Encoding Unicode -Force $null = Add-WindowsDriver -Path $MountPath -Driver $InfFile -ForceUnsigned #endregion } |