private/WinpeStartup/Import-WinpeStartupAssets.ps1
|
#requires -Version 5.1 function Import-WinpeStartupAssets { <# .SYNOPSIS Imports WinpeStartup environment, registry, and root-certificate files. .DESCRIPTION Imports immediate *.env files, winpe-reg\*.reg files, and winpe-root-cer\*.cer files from explicit source folders in WinPE. Includes hidden files and sorts full paths within each asset type. Environment entries are literal process-scoped NAME=VALUE assignments. Invalid entries, discovery failures, and import failures warn and continue. Values and native tool output are not logged. Requires a valid Recast license (Get-OSDCoreLicense IsValid). Without one, skips all asset discovery and imports quietly. .PARAMETER Path Source folders containing assets. An empty list is allowed. .PARAMETER AssetType Asset types to import, in environment, registry, certificate order. .PARAMETER DiscoverySucceeded Whether upstream folder discovery succeeded. A failed discovery marks each requested asset-type summary as failed while importing known files. .PARAMETER SystemDrive System drive captured by the caller before applying environment overrides. Defaults to the current SystemDrive for direct calls. .EXAMPLE Import-WinpeStartupAssets -Path 'H:\WinpeStartup\profiles\BranchOffice' Imports assets from the selected profile folder. .NOTES Requires WinPE, reg.exe, certutil.exe, and permission to modify the WinPE registry and local machine Root certificate store. #> [CmdletBinding()] [OutputType([void])] param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [string[]]$Path, [Parameter()] [ValidateSet('Environment', 'Registry', 'Certificate')] [string[]]$AssetType = @('Environment', 'Registry', 'Certificate'), [Parameter()] [bool]$DiscoverySucceeded = $true, [Parameter()] [string]$SystemDrive = $env:SystemDrive ) $Error.Clear() if (-not (Test-WinpeStartupAssetLicense)) { return } if ($SystemDrive -ne 'X:') { Write-Warning 'Import-WinpeStartupAssets: Not running in WinPE (SystemDrive is not X:). Exiting.' return } Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Importing startup assets" if ($AssetType -contains 'Environment') { $allEnvironmentImportsSucceeded = $DiscoverySucceeded $environmentFiles = @( foreach ($folderPath in $Path) { try { if (Test-Path -LiteralPath $folderPath -PathType Container -ErrorAction Stop) { Get-ChildItem -LiteralPath $folderPath -File -Filter '*.env' -Force -ErrorAction Stop | Where-Object { $_.Name -like '*.env' } } } catch { $allEnvironmentImportsSucceeded = $false Write-Warning "Import-WinpeStartupAssets: Failed to discover environment files in '$folderPath'." } } ) foreach ($environmentFile in ($environmentFiles | Sort-Object FullName)) { try { $environmentLines = @(Get-Content -LiteralPath $environmentFile.FullName -ErrorAction Stop) } catch { $allEnvironmentImportsSucceeded = $false Write-Warning "Import-WinpeStartupAssets: Failed to read environment file '$($environmentFile.FullName)'." continue } $lineNumber = 0 foreach ($environmentLine in $environmentLines) { $lineNumber++ $line = $environmentLine.Trim() if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith('#')) { continue } $separatorIndex = $line.IndexOf('=') if ($separatorIndex -le 0) { $allEnvironmentImportsSucceeded = $false Write-Warning "Import-WinpeStartupAssets: Invalid environment entry in '$($environmentFile.FullName)' at line $lineNumber. Expected NAME=VALUE." continue } $name = $line.Substring(0, $separatorIndex).Trim() $value = $line.Substring($separatorIndex + 1).Trim() if ($name -match '[\s=\x00]' -or [string]::IsNullOrWhiteSpace($name) -or $value.Contains([char]0)) { $allEnvironmentImportsSucceeded = $false Write-Warning "Import-WinpeStartupAssets: Invalid environment name or value in '$($environmentFile.FullName)' at line $lineNumber." continue } if ($value.StartsWith('"') -or $value.StartsWith("'")) { if ($value.Length -lt 2 -or $value[$value.Length - 1] -ne $value[0]) { $allEnvironmentImportsSucceeded = $false Write-Warning "Import-WinpeStartupAssets: Unmatched environment value quotes in '$($environmentFile.FullName)' at line $lineNumber." continue } $value = $value.Substring(1, $value.Length - 2) } try { [System.Environment]::SetEnvironmentVariable($name, $value, [System.EnvironmentVariableTarget]::Process) Write-Verbose "Import-WinpeStartupAssets: Loaded environment entry from '$($environmentFile.FullName)' at line $lineNumber." } catch { $allEnvironmentImportsSucceeded = $false Write-Warning "Import-WinpeStartupAssets: Failed to set environment entry from '$($environmentFile.FullName)' at line $lineNumber." } } } if ($environmentFiles.Count -gt 0 -or -not $allEnvironmentImportsSucceeded) { $environmentStatus = if ($allEnvironmentImportsSucceeded) { 'PASS' } else { 'FAIL' } Write-Host "[$(Get-Date -format s)] [$environmentStatus] Initialize env" } } $nativeImports = @( @{ Type = 'Registry' Folder = 'winpe-reg' Filter = '*.reg' Executable = 'reg.exe' Arguments = @('import') Status = 'Initialize registry' } @{ Type = 'Certificate' Folder = 'winpe-root-cer' Filter = '*.cer' Executable = 'certutil.exe' Arguments = @('-addstore', 'root') Status = 'Initialize certificates' } ) foreach ($nativeImport in $nativeImports) { if ($AssetType -notcontains $nativeImport.Type) { continue } $allImportsSucceeded = $DiscoverySucceeded $importFiles = @( foreach ($folderPath in $Path) { $importPath = Join-Path -Path $folderPath -ChildPath $nativeImport.Folder try { if (Test-Path -LiteralPath $importPath -PathType Container -ErrorAction Stop) { Get-ChildItem -LiteralPath $importPath -File -Filter $nativeImport.Filter -Force -ErrorAction Stop | Where-Object { $_.Name -like $nativeImport.Filter } } } catch { $allImportsSucceeded = $false Write-Warning "Import-WinpeStartupAssets: Failed to discover '$($nativeImport.Filter)' files in '$importPath'." } } ) foreach ($importFile in ($importFiles | Sort-Object FullName)) { $standardOutputPath = $null $standardErrorPath = $null try { # Start-Process joins arguments, so retain quotes around paths with spaces. $importArguments = @($nativeImport.Arguments) + @("`"$($importFile.FullName)`"") $startProcessParameters = @{ FilePath = $nativeImport.Executable ArgumentList = $importArguments NoNewWindow = $true Wait = $true PassThru = $true ErrorAction = 'Stop' } $standardOutputPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "$([System.IO.Path]::GetRandomFileName()).stdout" $standardErrorPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "$([System.IO.Path]::GetRandomFileName()).stderr" $startProcessParameters.RedirectStandardOutput = $standardOutputPath $startProcessParameters.RedirectStandardError = $standardErrorPath $importProcess = Start-Process @startProcessParameters if ($importProcess.ExitCode -ne 0) { $allImportsSucceeded = $false Write-Warning "Import-WinpeStartupAssets: '$($nativeImport.Executable)' failed for '$($importFile.FullName)' with exit code $($importProcess.ExitCode)." continue } Write-Verbose "Import-WinpeStartupAssets: Imported '$($importFile.FullName)' using '$($nativeImport.Executable)'." } catch { $allImportsSucceeded = $false Write-Warning "Import-WinpeStartupAssets: Failed to import '$($importFile.FullName)' using '$($nativeImport.Executable)': $($_.Exception.Message)" } finally { @($standardOutputPath, $standardErrorPath) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { Remove-Item -LiteralPath $_ -Force -ErrorAction SilentlyContinue } } } if ($importFiles.Count -gt 0 -or -not $allImportsSucceeded) { $importStatus = if ($allImportsSucceeded) { 'PASS' } else { 'FAIL' } Write-Host "[$(Get-Date -format s)] [$importStatus] $($nativeImport.Status)" } } } |