param( [Parameter(Mandatory = $true)] [string]$BootResDllPath, [Parameter(Mandatory = $false)] [string]$BootResMuiPath, [Parameter(Mandatory = $true)] [string[]]$LogoPaths, [Parameter(Mandatory = $false)] [string]$CertificateThumbprint = "", [Parameter(Mandatory = $false)] [string]$PfxPath = "", [Parameter(Mandatory = $false)] [string]$PfxPassword = "", [switch]$UseLegacySizes, [switch]$IgnoreSecBoot ) #Requires -RunAsAdministrator # Boot logo sizes for Windows 10/11 $LogoSizes = @( @{Name = "winlogo1.bmp"; Width = 72; Height = 72; Description = "Small Logo" }, @{Name = "winlogo2.bmp"; Width = 90; Height = 90; Description = "Medium Logo" }, @{Name = "winlogo3.bmp"; Width = 115; Height = 115; Description = "Standard Logo" }, @{Name = "winlogo3n.bmp"; Width = 87; Height = 115; Description = "Narrow Logo" }, @{Name = "winlogo4.bmp"; Width = 214; Height = 214; Description = "Large Logo" }, @{Name = "winlogo5.bmp"; Width = 284; Height = 284; Description = "Extra Large Logo" } ) # Legacy Windows 8/10 sizes $LegacyLogoSizes = @( @{Name = "winlogo1.bmp"; Width = 82; Height = 72 }, @{Name = "winlogo2.bmp"; Width = 102; Height = 90 }, @{Name = "winlogo3.bmp"; Width = 129; Height = 115 }, @{Name = "winlogo3n.bmp"; Width = 98; Height = 115 }, @{Name = "winlogo4.bmp"; Width = 242; Height = 214 }, @{Name = "winlogo5.bmp"; Width = 321; Height = 284 } ) # Function to log messages function Write-Log { param( [string]$Message, [string]$Color = "White" ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" switch ($Color) { "Green" { Write-Host "[$timestamp] $Message" -ForegroundColor Green } "Yellow" { Write-Host "[$timestamp] $Message" -ForegroundColor Yellow } "Red" { Write-Host "[$timestamp] $Message" -ForegroundColor Red } "Cyan" { Write-Host "[$timestamp] $Message" -ForegroundColor Cyan } default { Write-Host "[$timestamp] $Message" } } } # Function to extract RCDATA resource from DLL function Extract-RCDataResource { param( [string]$DllPath, [string]$OutputPath, [string]$ResourceHackerPath ) Write-Log "Extracting RCDATA resource from DLL..." $rhArgs = @( "-open", "`"$DllPath`"", "-save", "`"$OutputPath`"", "-action", "extract", "-mask", "RCDATA,1," ) $process = Start-Process -FilePath $ResourceHackerPath -ArgumentList $rhArgs -Wait -NoNewWindow -PassThru if ($process.ExitCode -ne 0) { throw "ResourceHacker failed to extract RCDATA resource (exit code: $($process.ExitCode))" } if (-not (Test-Path $OutputPath)) { throw "RCDATA resource was not extracted to: $OutputPath" } # Verify it's a WIM $bytes = [System.IO.File]::ReadAllBytes($OutputPath) if ($bytes.Length -lt 5) { throw "Extracted file is too small to be a WIM" } $signature = [System.Text.Encoding]::ASCII.GetString($bytes[0..4]) if ($signature -ne "MSWIM") { throw "Extracted resource is not a valid WIM file (signature: $signature)" } Write-Log "RCDATA resource extracted: $([math]::Round((Get-Item $OutputPath).Length / 1KB, 2)) KB" -Color "Green" } # Function to inject RCDATA resource into DLL function Inject-RCDataResource { param( [string]$DllPath, [string]$ResourcePath, [string]$OutputDllPath, [string]$ResourceHackerPath ) Write-Log "Injecting modified RCDATA resource into DLL..." # Copy original DLL if output path differs if ($DllPath -ne $OutputDllPath) { Copy-Item -Path $DllPath -Destination $OutputDllPath -Force } # ResourceHacker CLI arguments $rhArgs = @( "-open", "`"$OutputDllPath`"", "-save", "`"$OutputDllPath`"", "-action", "addoverwrite", "-resource", "`"$ResourcePath`"", "-mask", "RCDATA,1,1033" ) $process = Start-Process ` -FilePath $ResourceHackerPath ` -ArgumentList $rhArgs ` -Wait ` -NoNewWindow ` -PassThru if ($process.ExitCode -ne 0) { throw "ResourceHacker failed to inject RCDATA resource (exit code: $($process.ExitCode))" } Write-Log "RCDATA resource injected successfully" -Color "Green" } # Function to check if 7-Zip is available function Test-7ZipAvailable { $7zipPaths = @( "C:\Program Files\7-Zip\7z.exe", "C:\Program Files (x86)\7-Zip\7z.exe", "$env:ProgramFiles\7-Zip\7z.exe" ) foreach ($path in $7zipPaths) { if (Test-Path $path) { return $path } } try { $null = Get-Command 7z -ErrorAction Stop return "7z" } catch { return $null } } # Function to find SignTool from Windows SDK function Find-SignTool { $sdkPaths = @( "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe", "C:\Program Files\Windows Kits\10\bin\*\x64\signtool.exe", "C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe", "C:\Program Files\Windows Kits\10\App Certification Kit\signtool.exe" ) foreach ($pathPattern in $sdkPaths) { $found = Get-ChildItem -Path $pathPattern -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1 if ($found) { return $found.FullName } } try { $cmd = Get-Command signtool.exe -ErrorAction Stop return $cmd.Source } catch { return $null } } # Function to sign a file with a certificate or PFX function Sign-FileWithCertificate { param( [string]$FilePath, [string]$SignToolPath, [string]$CertificateThumbprint = "", [string]$PfxPath = "", [string]$PfxPassword = "", [string]$TimestampServer = "http://timestamp.digicert.com" ) Write-Log "Signing file: $FilePath" if ($PfxPath -and (Test-Path $PfxPath)) { $signArgs = @( "sign", "/f", "`"$PfxPath`"" ) if ($PfxPassword) { $signArgs += @("/p", $PfxPassword) } $signArgs += @( "/fd", "SHA256", "/t", $TimestampServer, "`"$FilePath`"" ) } elseif ($CertificateThumbprint) { $signArgs = @( "sign", "/sha1", $CertificateThumbprint, "/fd", "SHA256", "/t", $TimestampServer, "`"$FilePath`"" ) } else { $signArgs = @( "sign", "/a", "/fd", "SHA256", "/t", $TimestampServer, "`"$FilePath`"" ) } $outputLog = Join-Path $env:TEMP "signtool_out.log" $errorLog = Join-Path $env:TEMP "signtool_err.log" $process = Start-Process -FilePath $SignToolPath -ArgumentList $signArgs -Wait -NoNewWindow -PassThru -RedirectStandardOutput $outputLog -RedirectStandardError $errorLog if ($process.ExitCode -eq 0) { Write-Log "File signed successfully" -Color "Green" return $true } else { $errors = Get-Content $errorLog -Raw -ErrorAction SilentlyContinue Write-Log "Signing failed: $errors" -Color "Yellow" return $false } } # Function to create a throwaway self-signed code-signing cert (IgnoreSecBoot path only) function New-ThrowawaySigningCert { $cert = New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=CustomTiny11 IgnoreSecBoot" ` -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeyUsage DigitalSignature ` -NotAfter (Get-Date).AddYears(5) return $cert } # Function to resize bitmap function Resize-Bitmap { param( [string]$SourcePath, [string]$DestPath, [int]$Width, [int]$Height ) Add-Type -AssemblyName System.Drawing $sourceImage = [System.Drawing.Image]::FromFile($SourcePath) $destImage = New-Object System.Drawing.Bitmap($Width, $Height) $graphics = [System.Drawing.Graphics]::FromImage($destImage) # High quality resize $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic $graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality $graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality $graphics.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality $graphics.DrawImage($sourceImage, 0, 0, $Width, $Height) $destImage.Save($DestPath, [System.Drawing.Imaging.ImageFormat]::Bmp) $graphics.Dispose() $destImage.Dispose() $sourceImage.Dispose() } # Function to update WIM with new bitmaps function Update-WimBitmaps { param( [string]$WimPath, [string]$BitmapsFolder, [string]$SevenZipPath ) Write-Log "Updating WIM with new bitmaps..." $bitmapFiles = Get-ChildItem -Path $BitmapsFolder -Filter "winlogo*.bmp" | ForEach-Object { "`"$($_.FullName)`"" } if ($bitmapFiles.Count -eq 0) { throw "No winlogo bitmaps found in: $BitmapsFolder" } $updateArgs = @("u", "`"$WimPath`"") + $bitmapFiles + @("-y") Write-Log "Updating WIM with $($bitmapFiles.Count) bitmaps..." $process = Start-Process -FilePath $SevenZipPath -ArgumentList $updateArgs -Wait -NoNewWindow -PassThru if ($process.ExitCode -ne 0) { throw "Failed to update WIM with bitmaps (7z exit code: $($process.ExitCode))" } Write-Log "WIM updated successfully" -Color "Green" } # Main script execution try { Write-Host "" Write-Host "========================================" -ForegroundColor Cyan Write-Host " Windows Boot Logo Changer" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan Write-Host "" Write-Log "Starting boot resource modification..." # Validate input files if (-not (Test-Path $BootResDllPath)) { throw "bootres.dll not found at: $BootResDllPath" } foreach ($logoPath in $LogoPaths) { if (-not (Test-Path $logoPath)) { throw "Logo file not found at: $logoPath" } } # ResourceHacker path $rhPath = Join-Path $PSScriptRoot "ResourceHacker.exe" if (-not (Test-Path $rhPath)) { throw "ResourceHacker.exe not found at: $rhPath" } Write-Log "Found ResourceHacker at: $rhPath" -Color "Green" # Check for 7-Zip $7zPath = Test-7ZipAvailable if (-not $7zPath) { throw "7-Zip is required but not found. Please install 7-Zip from https://www.7-zip.org/" } Write-Log "Found 7-Zip at: $7zPath" -Color "Green" # Create temporary working directory in script location $tempDir = Join-Path $PSScriptRoot "temp_bootres_$(Get-Date -Format 'yyyyMMdd_HHmmss')" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null Write-Log "Working directory: $tempDir" # Step 1: Extract RCDATA resource (WIM) from bootres.dll $extractedWim = Join-Path $tempDir "bootres.wim" Extract-RCDataResource -DllPath $BootResDllPath -OutputPath $extractedWim -ResourceHackerPath $rhPath # Step 2: Create modified WIM $modifiedWim = Join-Path $tempDir "bootres_modified.wim" Copy-Item -Path $extractedWim -Destination $modifiedWim -Force # Step 3: Create bitmaps directory $bitmapsDir = Join-Path $tempDir "bitmaps" New-Item -ItemType Directory -Path $bitmapsDir -Force | Out-Null # Choose which size set to use $sizesToUse = if ($UseLegacySizes) { Write-Log "Using legacy Windows 8/10 logo sizes" $LegacyLogoSizes } else { Write-Log "Using modern Windows 11 logo sizes" $LogoSizes } # Step 4: Generate resized bitmaps Write-Log "Generating resized bitmaps..." for ($i = 0; $i -lt $sizesToUse.Count; $i++) { $logoInfo = $sizesToUse[$i] $destPath = Join-Path $bitmapsDir $logoInfo.Name if ($LogoPaths.Count -eq 1) { $sourceLogo = $LogoPaths[0] } elseif ($i -lt $LogoPaths.Count) { $sourceLogo = $LogoPaths[$i] } else { $sourceLogo = $LogoPaths[-1] } Resize-Bitmap -SourcePath $sourceLogo -DestPath $destPath -Width $logoInfo.Width -Height $logoInfo.Height } # Step 5: Update WIM with new bitmaps Update-WimBitmaps -WimPath $modifiedWim -BitmapsFolder $bitmapsDir -SevenZipPath $7zPath # Step 6: Create modified bootres.dll with ResourceHacker $outputDll = Join-Path $tempDir "bootres_modified.dll" Inject-RCDataResource -DllPath $BootResDllPath -ResourcePath $modifiedWim -OutputDllPath $outputDll -ResourceHackerPath $rhPath # Step 7: Sign the DLL (if requested) $signToolPath = Find-SignTool $signed = $false if ($signToolPath) { Write-Log "Found SignTool at: $signToolPath" -Color "Green" if ($PfxPath -or $CertificateThumbprint) { $signed = Sign-FileWithCertificate -FilePath $outputDll -SignToolPath $signToolPath ` -CertificateThumbprint $CertificateThumbprint -PfxPath $PfxPath -PfxPassword $PfxPassword } elseif ($IgnoreSecBoot) { Write-Log "IgnoreSecBoot set and no certificate given - generating throwaway self-signed cert..." -Color "Yellow" $throwawayCert = New-ThrowawaySigningCert $signed = Sign-FileWithCertificate -FilePath $outputDll -SignToolPath $signToolPath ` -CertificateThumbprint $throwawayCert.Thumbprint } else { Write-Log "No certificate specified. Skipping code signing." -Color "Yellow" } } else { Write-Log "SignTool not found. Skipping code signing." -Color "Yellow" } # Final summary Write-Host "" Write-Host "========================================" -ForegroundColor Green Write-Host " SUCCESS!" -ForegroundColor Green Write-Host "========================================" -ForegroundColor Green Write-Host "" Write-Log "Modified bootres.dll created at: $outputDll" -Color "Green" Write-Host "" # Check if file is signed $finalSig = Get-AuthenticodeSignature -FilePath $outputDll if ($finalSig.Status -eq 'Valid') { Write-Host "[OK] File is digitally signed" -ForegroundColor Green Write-Host " Signed by: $($finalSig.SignerCertificate.Subject)" -ForegroundColor Gray } else { Write-Host "[!] File is NOT signed" -ForegroundColor Yellow Write-Host " Requires Test Signing mode or Secure Boot disabled" -ForegroundColor Gray } if ($IgnoreSecBoot) { Write-Host "" Write-Host "[IgnoreSecBoot] Signed with throwaway cert (not Microsoft-trusted)." -ForegroundColor Yellow Write-Host " Build the image with -IgnoreSecBoot on the maker script too - it flags first boot" -ForegroundColor Gray Write-Host " to run 'bcdedit /set testsigning on' and 'bcdedit /set nointegritychecks on'." -ForegroundColor Gray Write-Host " Those settings are IGNORED by Windows while Secure Boot is on in firmware." -ForegroundColor Gray Write-Host " Secure Boot must be disabled manually in UEFI setup for this to boot." -ForegroundColor Gray } # Copy final DLL to script directory $FinallDLL = Join-Path $PSScriptRoot "bootres_modified.dll" Copy-Item -Path $outputDll -Destination $FinallDLL -Force # Cleanup temporary directory Remove-Item -Path $tempDir -Recurse -Force Write-Host "" Write-Host "Modified DLL located at: $FinallDLL" -ForegroundColor Cyan Write-Host "" } catch { Write-Host "" Write-Host "========================================" -ForegroundColor Red Write-Host " ERROR!" -ForegroundColor Red Write-Host "========================================" -ForegroundColor Red Write-Host "" Write-Host "Error: $_" -ForegroundColor Red Write-Host "" Write-Host "Stack trace:" -ForegroundColor Gray Write-Host $_.ScriptStackTrace -ForegroundColor Gray exit 1 }