Production-grade Windows PE builder with PowerShell 7 integration. Features: - PowerShell 7 integrated into WinPE environment - Automatic script launcher for external media - Configurable startup and fallback scripts - Custom console colors - Maximized console window on boot - PowerShell module integration - Windows ADK auto-installation - Defender optimization for faster builds Project Structure: - Build-PE.ps1: Main build orchestrator - config.json: Configuration file - lib/: Build modules (Dependencies, WinPEBuilder, PowerShell7, ModuleManager, DefenderOptimization) - resources/: Runtime scripts (Invoke-ScriptLauncher, Invoke-ScriptMenu, Driver-Manager) - CLAUDE.md: Complete project documentation - README.md: Quick start guide Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
277 lines
8.1 KiB
PowerShell
277 lines
8.1 KiB
PowerShell
$script:WorkDir = ".\work"
|
|
$script:MountDir = ".\work\mount"
|
|
$script:MediaDir = ".\work\media"
|
|
$script:BootWimPath = ".\work\media\sources\boot.wim"
|
|
|
|
function Initialize-WinPEWorkspace {
|
|
<#
|
|
.SYNOPSIS
|
|
Creates WinPE workspace using copype.cmd from Windows ADK.
|
|
#>
|
|
[CmdletBinding()]
|
|
param()
|
|
|
|
if (Test-Path $script:WorkDir) {
|
|
Write-Host "Cleaning existing workspace..." -ForegroundColor Yellow
|
|
Remove-Item $script:WorkDir -Recurse -Force
|
|
}
|
|
|
|
Write-Host "Initializing WinPE workspace..." -ForegroundColor Yellow
|
|
|
|
$adkPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit"
|
|
$copypePath = Join-Path $adkPath "Windows Preinstallation Environment\copype.cmd"
|
|
$setEnvPath = Join-Path $adkPath "Deployment Tools\DandISetEnv.bat"
|
|
|
|
if (-not (Test-Path $copypePath)) {
|
|
throw "copype.cmd not found. Is Windows ADK installed?"
|
|
}
|
|
|
|
if (-not (Test-Path $setEnvPath)) {
|
|
throw "DandISetEnv.bat not found. ADK installation may be incomplete."
|
|
}
|
|
|
|
$currentLocation = Get-Location
|
|
$workPath = Join-Path $currentLocation "work"
|
|
|
|
# Create a temporary batch file to run both commands in sequence
|
|
$tempBatchFile = Join-Path $env:TEMP "winpe-init-$(Get-Random).cmd"
|
|
|
|
try {
|
|
# Write batch file that calls DandISetEnv, then copype
|
|
$batchContent = @"
|
|
@echo off
|
|
call "$setEnvPath"
|
|
call "$copypePath" amd64 "$workPath"
|
|
exit /b %ERRORLEVEL%
|
|
"@
|
|
Set-Content -Path $tempBatchFile -Value $batchContent -Encoding ASCII
|
|
|
|
# Execute the batch file
|
|
$process = Start-Process -FilePath "cmd.exe" `
|
|
-ArgumentList "/c", $tempBatchFile `
|
|
-Wait -PassThru -NoNewWindow
|
|
|
|
$exitCode = $process.ExitCode
|
|
}
|
|
finally {
|
|
# Clean up temp file
|
|
if (Test-Path $tempBatchFile) {
|
|
Remove-Item $tempBatchFile -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
if ($exitCode -ne 0) {
|
|
throw "Failed to initialize WinPE workspace. Exit code: $exitCode"
|
|
}
|
|
|
|
if (-not (Test-Path $script:BootWimPath)) {
|
|
throw "boot.wim not found after workspace initialization"
|
|
}
|
|
|
|
Write-Host " ✓ WinPE workspace initialized" -ForegroundColor Green
|
|
}
|
|
|
|
function Mount-WinPEImage {
|
|
<#
|
|
.SYNOPSIS
|
|
Mounts the WinPE boot.wim image for modification.
|
|
#>
|
|
[CmdletBinding()]
|
|
param()
|
|
|
|
if (-not (Test-Path $script:MountDir)) {
|
|
New-Item -ItemType Directory -Path $script:MountDir | Out-Null
|
|
}
|
|
|
|
Write-Host "Mounting WinPE image..." -ForegroundColor Yellow
|
|
|
|
# Use DISM directly
|
|
& dism.exe /Mount-Image /ImageFile:$script:BootWimPath /Index:1 /MountDir:$script:MountDir | Out-Host
|
|
|
|
# Verify mount succeeded by checking for Windows directory in mount point
|
|
$windowsDir = Join-Path $script:MountDir "Windows"
|
|
if (-not (Test-Path $windowsDir)) {
|
|
throw "Failed to mount WinPE image. Mount verification failed - Windows directory not found."
|
|
}
|
|
|
|
Write-Host " ✓ WinPE image mounted at: $script:MountDir" -ForegroundColor Green
|
|
}
|
|
|
|
function Add-WinPEOptionalComponent {
|
|
<#
|
|
.SYNOPSIS
|
|
Adds an optional component (and its language pack) to the mounted WinPE image.
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Name
|
|
)
|
|
|
|
$adkPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit"
|
|
$ocPath = Join-Path $adkPath "Windows Preinstallation Environment\amd64\WinPE_OCs"
|
|
|
|
$componentCab = Join-Path $ocPath "$Name.cab"
|
|
$languageCab = Join-Path $ocPath "en-us\$Name`_en-us.cab"
|
|
|
|
if (-not (Test-Path $componentCab)) {
|
|
throw "Optional component not found: $componentCab"
|
|
}
|
|
|
|
Write-Host " Adding $Name..." -ForegroundColor Cyan
|
|
|
|
& dism.exe /Add-Package /Image:$script:MountDir /PackagePath:$componentCab
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Failed to add component $Name. Exit code: $LASTEXITCODE"
|
|
}
|
|
|
|
if (Test-Path $languageCab) {
|
|
Write-Host " Adding $Name language pack..." -ForegroundColor Cyan
|
|
|
|
& dism.exe /Add-Package /Image:$script:MountDir /PackagePath:$languageCab
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Warning "Failed to add language pack for $Name (non-critical)"
|
|
}
|
|
}
|
|
|
|
Write-Host " ✓ $Name installed" -ForegroundColor Green
|
|
}
|
|
|
|
function Set-WinPEScratchSpace {
|
|
<#
|
|
.SYNOPSIS
|
|
Sets the scratch space size for better WinPE performance.
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $false)]
|
|
[int]$SizeMB = 512
|
|
)
|
|
|
|
Write-Host "Setting scratch space to ${SizeMB}MB..." -ForegroundColor Yellow
|
|
|
|
& dism.exe /Set-ScratchSpace:$SizeMB /Image:$script:MountDir
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Warning "Failed to set scratch space (non-critical)"
|
|
} else {
|
|
Write-Host " ✓ Scratch space configured" -ForegroundColor Green
|
|
}
|
|
}
|
|
|
|
function Dismount-WinPEImage {
|
|
<#
|
|
.SYNOPSIS
|
|
Unmounts and commits changes to the WinPE image.
|
|
#>
|
|
[CmdletBinding()]
|
|
param()
|
|
|
|
Write-Host "Unmounting WinPE image and committing changes..." -ForegroundColor Yellow
|
|
|
|
& dism.exe /Unmount-Image /MountDir:$script:MountDir /Commit
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "Failed to unmount image. Attempting cleanup..."
|
|
|
|
& dism.exe /Cleanup-Mountpoints
|
|
|
|
throw "Failed to unmount WinPE image. Exit code: $LASTEXITCODE"
|
|
}
|
|
|
|
Write-Host " ✓ WinPE image unmounted successfully" -ForegroundColor Green
|
|
}
|
|
|
|
function New-BootableMedia {
|
|
<#
|
|
.SYNOPSIS
|
|
Creates bootable ISO file from WinPE workspace.
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $false)]
|
|
[string]$OutputPath = ".\output"
|
|
)
|
|
|
|
if (-not (Test-Path $OutputPath)) {
|
|
New-Item -ItemType Directory -Path $OutputPath | Out-Null
|
|
}
|
|
|
|
$isoPath = Join-Path $OutputPath "WinPE.iso"
|
|
|
|
if (Test-Path $isoPath) {
|
|
Write-Host "Removing existing ISO..." -ForegroundColor Yellow
|
|
Remove-Item $isoPath -Force
|
|
}
|
|
|
|
Write-Host "Creating bootable ISO..." -ForegroundColor Yellow
|
|
|
|
$adkPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit"
|
|
$makeWinPEMediaPath = Join-Path $adkPath "Windows Preinstallation Environment\MakeWinPEMedia.cmd"
|
|
$setEnvPath = Join-Path $adkPath "Deployment Tools\DandISetEnv.bat"
|
|
|
|
if (-not (Test-Path $makeWinPEMediaPath)) {
|
|
throw "MakeWinPEMedia.cmd not found"
|
|
}
|
|
|
|
if (-not (Test-Path $setEnvPath)) {
|
|
throw "DandISetEnv.bat not found. ADK installation may be incomplete."
|
|
}
|
|
|
|
$currentLocation = Get-Location
|
|
$workPath = Join-Path $currentLocation "work"
|
|
|
|
# Create a temporary batch file - need to set up ADK environment first
|
|
$tempBatchFile = Join-Path $env:TEMP "winpe-makeiso-$(Get-Random).cmd"
|
|
|
|
try {
|
|
# Write batch file that calls DandISetEnv, then MakeWinPEMedia
|
|
$batchContent = @"
|
|
@echo off
|
|
call "$setEnvPath"
|
|
call "$makeWinPEMediaPath" /ISO "$workPath" "$isoPath"
|
|
exit /b %ERRORLEVEL%
|
|
"@
|
|
Set-Content -Path $tempBatchFile -Value $batchContent -Encoding ASCII
|
|
|
|
# Execute the batch file
|
|
$process = Start-Process -FilePath "cmd.exe" `
|
|
-ArgumentList "/c", $tempBatchFile `
|
|
-Wait -PassThru -NoNewWindow
|
|
|
|
$exitCode = $process.ExitCode
|
|
}
|
|
finally {
|
|
# Clean up temp file
|
|
if (Test-Path $tempBatchFile) {
|
|
Remove-Item $tempBatchFile -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
if ($exitCode -ne 0) {
|
|
throw "Failed to create ISO. Exit code: $exitCode"
|
|
}
|
|
|
|
if (-not (Test-Path $isoPath)) {
|
|
throw "ISO creation reported success but file not found"
|
|
}
|
|
|
|
$isoSize = (Get-Item $isoPath).Length / 1MB
|
|
Write-Host " ✓ Bootable ISO created: $isoPath ($([math]::Round($isoSize, 2)) MB)" -ForegroundColor Green
|
|
}
|
|
|
|
function Get-MountDirectory {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns the current mount directory path.
|
|
#>
|
|
return $script:MountDir
|
|
}
|
|
|
|
Export-ModuleMember -Function @(
|
|
'Initialize-WinPEWorkspace',
|
|
'Mount-WinPEImage',
|
|
'Add-WinPEOptionalComponent',
|
|
'Set-WinPEScratchSpace',
|
|
'Dismount-WinPEImage',
|
|
'New-BootableMedia',
|
|
'Get-MountDirectory'
|
|
)
|