private/Step-BuildBootSetEnvironmentVariables.ps1

#Requires -PSEdition Core

function Step-BuildBootSetEnvironmentVariables {
    <#
    .SYNOPSIS
        Sets configured and identity environment variables in the mounted WinPE image
 
    .DESCRIPTION
        Builds an environment-variable table from global
        OSDeployModule.BootImage.env, the installed OSDeploy module version, generated
        OSDeploy identifiers, and available license email and license GUID values. The
        device UUID is SHA-256 hashed for ID_OSDEPLOYDEVICE; a generated GUID is used when the
        hash is empty. A new ID_OSDEPLOYBOOT is generated on every invocation.
 
        The function loads the mounted SYSTEM hive, creates the environment registry
        key when needed, writes each value as REG_SZ, appends
        X:\Program Files\WindowsPowerShell\Scripts to the system Path as REG_EXPAND_SZ,
        and unloads the hive in a finally block. A missing SYSTEM hive produces a warning.
        Terminating errors are reported as warnings so they do not abort
        the calling build.
 
    .EXAMPLE
        PS> Step-BuildBootSetEnvironmentVariables
 
        Writes configured, version, device, and available license values into WinPE.
 
    .INPUTS
        None. This function does not accept pipeline input.
 
    .OUTPUTS
        None.
 
    .NOTES
        Author: David Segura
        Company: Recast Software
        Version: 0.1.0
        Date: 2026-08-28
 
        Requires global BuildMedia.MountPath, global OSDeployModule.BootImage.env,
        Get-OSDeployModuleVersion, Get-CimInstance, reg.exe, and access to load the
        mounted SYSTEM hive. Reads OSDCoreLicense from the caller's scope when present
        and modifies the mounted environment registry key, including the system Path.
    #>

    [CmdletBinding()]
    param ()

    try {
        $MountPath = $global:BuildMedia.MountPath

        # Build the env var table: static entries from module.json + dynamic OSDEPLOY_VERSION
        $envVars = [ordered]@{}

        $envConfig = $global:OSDeployModule.BootImage.env
        if ($envConfig) {
            foreach ($prop in $envConfig.PSObject.Properties) {
                $envVars[$prop.Name] = $prop.Value
            }
        }

        $moduleVersion = Get-OSDeployModuleVersion
        if ($moduleVersion) {
            $envVars['OSDEPLOY_VERSION'] = $moduleVersion.ToString()
        }

        # Unique identifier for this device, used for telemetry and license enforcement
        $deviceUUID = $null
        try {
            $deviceUUID = (Get-CimInstance -ClassName Win32_ComputerSystemProduct -ErrorAction Stop).UUID
        }
        catch {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Could not query system UUID. Falling back to build GUID."
        }
        [string]$distinctId = $null
        # Convert the UUID to a hash value to protect user privacy and ensure a consistent identifier across events
        if (-not [string]::IsNullOrWhiteSpace($deviceUUID)) {
            $sha256 = [System.Security.Cryptography.SHA256]::Create()
            try {
                $distinctId = [System.BitConverter]::ToString(
                    $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($deviceUUID))
                ).Replace("-", "")
            }
            finally {
                $sha256.Dispose()
            }
        }
        if ([string]::IsNullOrWhiteSpace($distinctId)) {
            $distinctId = [System.Guid]::NewGuid().ToString()
        }

        $envVars['ID_OSDEPLOYDEVICE'] = $distinctId
        $envVars['ID_OSDEPLOYBOOT'] = $global:BuildMedia.BootGuid
        if ($OSDCoreLicense -and $OSDCoreLicense.License -and $OSDCoreLicense.License.Email) {
            $envVars['ID_REGISTEREDEMAIL'] = $OSDCoreLicense.License.Email
        }
        if ($OSDCoreLicense -and $OSDCoreLicense.License -and $OSDCoreLicense.License.LicenseGuid) {
            $envVars['ID_REGISTEREDLICENSE'] = $OSDCoreLicense.License.LicenseGuid
        }

        if ($envVars.Count -eq 0) {
            Write-HostDateTimeDarkGray 'No environment variables to set in WinPE — skipping'
            return
        }

        Write-HostDateTimeDarkGray 'Setting environment variables in WinPE SYSTEM hive'

        $systemHivePath = Join-Path $MountPath 'Windows\System32\Config\SYSTEM'
        if (-not (Test-Path $systemHivePath)) {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] SYSTEM hive not found at $systemHivePath"
            return
        }
        #=================================================
        # Add Windows PowerShell scripts to the WinPE PATH
        $RegistryHiveLoaded = $false
        try {
            & reg.exe LOAD HKLM\Mount "$MountPath\Windows\System32\Config\SYSTEM"
            Start-Sleep -Seconds 3
            if ($LASTEXITCODE -ne 0) {
                Write-Warning "Failed to load SYSTEM hive from $systemHivePath (exit code $LASTEXITCODE)." -WarningAction Continue
            }
            $RegistryHiveLoaded = $true
            $RegistryKey = 'HKLM:\Mount\ControlSet001\Control\Session Manager\Environment'

            $CurrentPath = (Get-Item -path $RegistryKey).GetValue('Path', '', 'DoNotExpandEnvironmentNames')
            $NewPath = $CurrentPath + ';X:\Program Files\WindowsPowerShell\Scripts'
            $Result = New-ItemProperty -Path $RegistryKey -Name 'Path' -PropertyType ExpandString -Value $NewPath -Force
        }
        finally {
            if ($RegistryHiveLoaded) {
                Get-Variable Result -ErrorAction SilentlyContinue | Remove-Variable -ErrorAction SilentlyContinue
                Get-Variable RegistryKey -ErrorAction SilentlyContinue | Remove-Variable -ErrorAction SilentlyContinue
                [gc]::Collect()
                Start-Sleep -Seconds 3
                & reg.exe UNLOAD HKLM\Mount
                if ($LASTEXITCODE -ne 0) {
                    Write-Warning "Failed to unload SYSTEM hive from HKLM\Mount (exit code $LASTEXITCODE)." -WarningAction Continue
                }
            }
        }
    }
    catch {
        Write-Warning "[$($MyInvocation.MyCommand.Name)] $($_.Exception.Message)" -WarningAction Continue
    }
}