Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions PowerTools/WDKBatchBuild/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
WDKBatchBuild

JakobL@2025/11/19

Build this repo in "batch mode" using multiple EWDKs.

Once batch build is complete you will find complete copies of built repo with generated artifacts ready for your favorite diff algorithm.

Files:
* WDKBatchBuild.ps1 <-- Main script. Change bottom part to refine functionality.
* WDKBatchBuild_Internal.ps1. Auxillary function.

Nifty Tricks:
* Nifty lazy ISO file copy. Only copies if necessary.
* Nifty lazy ISO extraction. Caches EWDKs. Only extracts what is necessary.
* Nifty NTFS Junction mount of each EWDK to same junction location, so that the EWDK Root is identical for each build. This eliminates any output changes due to EWDK root.
* Nifty trick to invoke build environment and build:
* Does following N times:
* Starts out in main PowerShell script
* Calls cmd script EWDK\BuildEnv\SetupBuildEnv.cmd to setup build environment
* Continuation into auxillary PowerShell script

Warning:
* Will run "clean -xdf" in root of repo thereby wiping out anything not checked in.
* Make sure to check changes to this script in before running.

Assumptions:
* Requires new pwsh (rather than old powershell).
* Hard coded in script WDKBatchBuild.ps1:
* Repo in D:\wds\wds1
* Input ISOs in D:\wds\ISOs
* Temporary EWDKs extracted to D:\wds\EWDKs
* Output runs in D:\wds\Runs

Issues:
* I suspect that occasionally among other the MSVC toolset's mspdbsrv does not terminate after runs. Leaving active processes. It seems this - much to my surprise - does not prevent removing the NTFS Junction (where as a recursive delete may fail). Report if you see this sort of issue please.
232 changes: 232 additions & 0 deletions PowerTools/WDKBatchBuild/WDKBatchBuild.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
function Assert-Pwsh {
[CmdletBinding()]
param()

# Always log current edition and version
Write-Host "Current PowerShell Edition: $($PSVersionTable.PSEdition)"
Write-Host "Current PowerShell Version: $($PSVersionTable.PSVersion)"

# Check edition
if ($PSVersionTable.PSEdition -ne 'Core') {
throw "This script requires PowerShell (pwsh) Core edition. Current edition: $($PSVersionTable.PSEdition)"
}

# Optional: enforce minimum version (e.g., 7.0)
if ($PSVersionTable.PSVersion -lt [Version]'7.0') {
throw "This script requires PowerShell 7.0 or later. Current version: $($PSVersionTable.PSVersion)"
}
}

function Copy-IfMissingSizeTime {
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$Source,
[Parameter(Mandatory)][string]$Destination
)

Write-Host "Copy-IfMissingSizeTime $Source {"

$ErrorActionPreference = 'Stop'

# Normalize UNC long path (\\server\share\... -> \\?\UNC\server\share\...)
function To-LongUnc([string]$p) {
if ($p.StartsWith('\\')) {
# Strip leading \\ then rebuild
$trim = $p.TrimStart('\')
return "\\?\UNC\" + $trim
}
return $p
}

# Use long-path version for source if UNC; destination is local so normal path is fine
$srcPath = if ($Source.StartsWith('\\')) { To-LongUnc $Source } else { $Source }

# Validate source file
$srcItem = Get-Item -LiteralPath $srcPath -ErrorAction Stop
if ($srcItem.PSIsContainer) { throw "Source '$Source' is a directory; this helper is for single files." }

# Ensure destination directory exists

$destDir = Split-Path -LiteralPath $Destination
if ($destDir -and -not (Test-Path -LiteralPath $destDir)) {
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
}

if (Test-Path -LiteralPath $Destination) {
$dstItem = Get-Item -LiteralPath $Destination
$sameSize = ($srcItem.Length -eq $dstItem.Length)
$sameTime = ($srcItem.LastWriteTimeUtc -eq $dstItem.LastWriteTimeUtc)

if ($sameSize -and $sameTime) {
Write-Verbose "Skipping: destination matches source by size and timestamp."
Write-Host "Copy-IfMissingSizeTime $Source }"
return $false
}

# Overwrite if different
Copy-Item -LiteralPath $srcPath -Destination $Destination -Force
# Align timestamp (sometimes Copy-Item preserves; enforce to be sure)
(Get-Item -LiteralPath $Destination).LastWriteTimeUtc = $srcItem.LastWriteTimeUtc
Write-Host "Copy-IfMissingSizeTime $Source }"
return $true
}
else {
# Destination missing -> copy
Copy-Item -LiteralPath $srcPath -Destination $Destination -Force
(Get-Item -LiteralPath $Destination).LastWriteTimeUtc = $srcItem.LastWriteTimeUtc
Write-Host "Copy-IfMissingSizeTime $Source }"
return $true
}
Write-Host "Copy-IfMissingSizeTime $Source }"
}


function Copy-IsoContent {
[CmdletBinding()]
param(
[Parameter(Mandatory, Position = 0)]
[ValidateNotNullOrEmpty()]
[string] $IsoPath,

[Parameter(Mandatory, Position = 1)]
[ValidateNotNullOrEmpty()]
[string] $Destination
)

$ErrorActionPreference = 'Stop'

Write-Host "Copy-IsoContent $IsoPath {"

# Resolve paths
$isoFull = (Resolve-Path -LiteralPath $IsoPath).ProviderPath
$destFull = if (Test-Path -LiteralPath $Destination) {
(Resolve-Path -LiteralPath $Destination).ProviderPath
} else {
[IO.Path]::GetFullPath($Destination)
}

# Ensure destination exists
if (-not (Test-Path -LiteralPath $destFull)) {
New-Item -ItemType Directory -Path $destFull -Force | Out-Null
}

# Mount ISO
$diskImage = Mount-DiskImage -ImagePath $isoFull -PassThru
try {
# Get drive letter
$volume = Get-Volume -DiskImage $diskImage | Where-Object { $_.DriveLetter } | Select-Object -First 1
if (-not $volume) {
throw "Failed to determine a drive letter for mounted ISO: $isoFull"
}
$src = ($volume.DriveLetter + ':\')

# Build robocopy args (copy all, keep timestamps, avoid ACLs from ISO, clear R)
$args = @(
"$src", # source
"`"$destFull`"", # destination
'/E', # include empty directories
'/COPY:DAT', # Data, Attr, Timestamps (avoid ACL/Owner from ISO)
'/DCOPY:DAT', # preserve dir timestamps too
'/A-:R', # remove read-only on destination
'/R:2','/W:2', # quick retry policy
'/MT:16', # multithreaded copy
'/NP','/NFL','/NDL', # quieter output
'/XJ' # ignore junctions
)

$proc = Start-Process -FilePath 'robocopy.exe' -ArgumentList $args -NoNewWindow -PassThru -Wait
$rc = $proc.ExitCode

# Robocopy: 0..7 are success-ish; >=8 is failure
if ($rc -ge 8) {
throw "Robocopy failed with exit code $rc."
}

Write-Host "Copy-IsoContent $IsoPath }"
# Also reflect exit code in $LASTEXITCODE for callers
Set-Variable -Name LASTEXITCODE -Value $rc -Scope Global
#return $rc
return
}
finally {
try {
Dismount-DiskImage -ImagePath $isoFull -ErrorAction Stop | Out-Null
} catch {
Write-Warning "Failed to unmount ISO '$isoFull': $($_.Exception.Message)"
}
}
Write-Host "Copy-IsoContent $IsoPath }"
}

function WithEWDK {
[CmdletBinding()]
param(
[Parameter(Mandatory, Position = 1)]
[ValidateNotNullOrEmpty()]
[string] $EWDK
)

$ErrorActionPreference = 'Stop'

Write-Host "WithEWDK $EWDK {"

git clean -xdf
git status --ignored

if (Test-Path -LiteralPath "D:\wds\EWDK") {
Write-Host "Removing old junction D:\wds\EWDK {"
# Remove-Item -Recurse -Force -ErrorAction Stop -LiteralPath "D:\wds\EWDK"
Remove-Item -LiteralPath "D:\wds\EWDK" -ErrorAction Stop
Write-Host "Removing old junction D:\wds\EWDK }"
}
New-Item -ItemType Junction -Path "D:\wds\EWDK" -Target "D:\wds\EWDKs\$EWDK"
#robocopy /mir /nfl /ndl "D:\wds\EWDKs\$EWDK" "D:\wds\EWDK"

$CmdScriptPath = "D:\wds\EWDK\BuildEnv\SetupBuildEnv.cmd"

$pwshArgs = @(
'-NoLogo','-NoProfile',
'-ExecutionPolicy','Bypass',
'-File', 'D:\wds\wds1\PowerTools\WDKBatchBuild\WDKBatchBuild_Internal.ps1',
'-Phase','AfterCmd'
) -join ' '

$cmdPayload = ('call "{0}" && pwsh {1} || exit /b %errorlevel%' -f $CmdScriptPath, $pwshArgs)

Write-Host "& $env:ComSpec /s /c $cmdPayload"
& $env:ComSpec /s /c $cmdPayload

robocopy /mir /nfl /ndl "." "D:\wds\Runs\$EWDK"

git clean -xdf
git status --ignored

Write-Host "Removing junction D:\wds\EWDK {"
Remove-Item -LiteralPath "D:\wds\EWDK" -ErrorAction Stop
Write-Host "Removing junction D:\wds\EWDK }"

Write-Host "WithEWDK $EWDK }"
}

Assert-Pwsh

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

cd D:\wds\wds1

Write-Host "Copy-IsoContent {"
Copy-IsoContent -IsoPath 'D:\wds\ISOs\EWDK_19041.685.201201-2105.iso' -Destination 'D:\wds\EWDKs\EWDK_19041.685.201201-2105'
Copy-IsoContent -IsoPath 'D:\wds\ISOs\EWDK_19041.685.201201-2105.iso' -Destination 'D:\wds\EWDKs\EWDK_19041.685.201201-2105-copy'
Copy-IsoContent -IsoPath 'D:\wds\ISOs\EWDK_19041.5738.250408-1639.iso' -Destination 'D:\wds\EWDKs\EWDK_19041.5738.250408-1639'
Copy-IsoContent -IsoPath 'D:\wds\ISOs\EWDK_26100.1.240331-1435.iso' -Destination 'D:\wds\EWDKs\EWDK_26100.1.240331-1435'
Copy-IsoContent -IsoPath 'D:\wds\ISOs\EWDK_26100.6725.250925-1604.iso' -Destination 'D:\wds\EWDKs\EWDK_26100.6725.250925-1604'
Write-Host "Copy-IsoContent }"

Write-Host "Build {"
WithEWDK 'EWDK_19041.685.201201-2105'
WithEWDK 'EWDK_19041.685.201201-2105-copy'
WithEWDK 'EWDK_19041.5738.250408-1639'
WithEWDK 'EWDK_26100.1.240331-1435'
WithEWDK 'EWDK_26100.6725.250925-1604'
Write-Host "Build }"
1 change: 1 addition & 0 deletions PowerTools/WDKBatchBuild/WDKBatchBuild_Internal.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.\build-AllSamples -Verbose -Samples '^tools.sdv.samples.sdv-faildriver-wdm' -Configurations 'Debug' -Platforms 'x64'
Loading