private/BootMedia/Steps/Step-BootImagePowerShellUpdate.ps1

#Requires -PSEdition Core

function Step-BootImagePowerShellUpdate {
    <#
    .SYNOPSIS
        Injects PackageManagement, PowerShellGet, NuGet, PSRepository config,
        PowerShell profile, environment variables, and execution policy into WinPE.
 
    .NOTES
        Author: David Segura
        Version: 0.1.0
    #>

    [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-OSDeployCoreProgress '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-OSDeployCoreProgress "Downloading $PackageName"
        if (Get-Command 'curl.exe' -ErrorAction SilentlyContinue) {
            & curl.exe --silent --location --output $PSRepositoryModule $PSModuleUrl
        }
        else {
            Invoke-WebRequest -UseBasicParsing -Uri $PSModuleUrl -OutFile $PSRepositoryModule
        }
    }

    Write-OSDeployCoreProgress "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-OSDeployCoreProgress "Downloading $PackageName"
        if (Get-Command 'curl.exe' -ErrorAction SilentlyContinue) {
            & curl.exe --silent --location --output $PSRepositoryModule $PSModuleUrl
        }
        else {
            Invoke-WebRequest -UseBasicParsing -Uri $PSModuleUrl -OutFile $PSRepositoryModule
        }
    }

    Write-OSDeployCoreProgress "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-OSDeployCoreProgress "Downloading $PackageName"
        if (Get-Command 'curl.exe' -ErrorAction SilentlyContinue) {
            & curl.exe --silent --location --output $PSRepositoryModule $PSModuleUrl
        }
        else {
            Invoke-WebRequest -UseBasicParsing -Uri $PSModuleUrl -OutFile $PSRepositoryModule
        }
    }

    Write-OSDeployCoreProgress "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-OSDeployCoreProgress '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-OSDeployCoreProgress '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-OSDeployCoreProgress '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-OSDeployCoreProgress '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
}