Compare commits

..

9 Commits

Author SHA1 Message Date
rbalsleyMSFT ceeabd1ebc Add changelog for 2512.1 UI Preview release
Documents new features including shared cleanup module, Windows Security
Platform install delay, persistent KB folder for updates, and CU download
skipping when ESD is current.

Fixes WingetWin32Apps.json creation bug for pre-downloaded applications.
2026-01-05 12:34:58 -08:00
rbalsleyMSFT 15149ffa0b Bumps version to 2512.1Preview
Updates version string in BuildFFUVM.ps1 and ApplyFFU.ps1
from 2511.2Preview to 2512.1Preview for the new release.
2026-01-05 12:33:34 -08:00
rbalsleyMSFT 2f180747b7 Bumps version to 2511.2Preview
Updates version string in BuildFFUVM.ps1 and ApplyFFU.ps1
to reflect the new preview release.
2026-01-05 12:10:35 -08:00
rbalsleyMSFT 25fe90253c Regenerate Win32 app JSON for pre-downloaded content
Ensures CLI builds properly register silent install commands even when
app content already exists and download is skipped.
2026-01-05 12:07:16 -08:00
rbalsleyMSFT 86d122aacf Skips CU downloads when ESD version is current or newer
Extracts ESD metadata resolution into a separate function to enable
version comparison before downloading cumulative updates.

Parses Windows version from both ESD filenames and KB article search
results to determine if the ESD already contains the latest updates,
avoiding redundant downloads and installations.

Improves VHDX cache matching by tracking update names that were skipped
due to version matching, ensuring cached images are correctly reused
when updates are already integrated in the base image.

Adds check to skip downloading updates that already exist locally.

Removes prior behavior of always removing the KB folder. The `$RemoveUpdates` parameter now controls whether the KB folder is removed or not. This change was made due to the size of the Windows 11 CU being > 3-4GB. This will reduce bandwidth, however will require setting `$RemoveUpdates` to true to cleanup old update files.
2025-12-20 15:52:28 -08:00
rbalsleyMSFT 9737d5c930 Centralizes KB path cleanup into common cleanup module
Removes duplicated KB path cleanup logic scattered across multiple locations in the build script and consolidates it into the shared cleanup module.

Adds KBPath parameter to the cleanup function and handles removal of Windows/.NET cumulative update downloads when RemoveUpdates flag is set.

Improves maintainability by eliminating redundant cleanup code and ensures consistent cleanup behavior across different build scenarios including standard builds, VHDX caching, and restore defaults operations.
2025-12-16 21:18:42 -08:00
rbalsleyMSFT c6088d91fa Add 30 second delay to allow for Windows Security Platform to install in Update-Defender.ps1 2025-12-15 16:21:27 -08:00
rbalsleyMSFT 15fdf77ce4 Refactors cleanup logic into shared module
Consolidates duplicated cleanup code by moving logic into a shared function, eliminating redundant implementations across multiple locations.

Removes standalone cleanup functions (Remove-FFU, Remove-Apps, Remove-Updates) and replaces scattered cleanup calls with a single invocation of Invoke-FFUPostBuildCleanup.

Enhances driver cleanup to preserve configuration files (Drivers.json and DriverMapping.json) while removing other contents, preventing loss of driver mapping data.

Improves maintainability by centralizing cleanup operations and reducing code duplication, making future updates easier to implement consistently.
2025-12-15 16:20:01 -08:00
rbalsleyMSFT f7f001ac2e Update ChangeLog for version 2511.1
Added detailed changelog for version 2511.1, including major updates to driver handling, new hardware support, and various fixes.
2025-12-09 18:00:10 -08:00
6 changed files with 364 additions and 222 deletions
+95
View File
@@ -1,5 +1,100 @@
# Change Log # Change Log
# 2512.1 UI Preview
## What's Changed
### Refactored Cleanup logic into a shared module
Consolidates duplicated cleanup code by moving logic into a shared function, eliminating redundant implementations across multiple locations.
Removes standalone cleanup functions (Remove-FFU, Remove-Apps, Remove-Updates) and replaces scattered cleanup calls with a single invocation of Invoke-FFUPostBuildCleanup.
### Add 30 second delay to allow for Windows Security Platform to install
There was an issue where the Windows Security Platform would attempt to install in the VM during the build via `Update-Defender.ps1` however the install didn't always happen and on deployment of the FFU, Windows Update would show that the Windows Security Platform needed an update. I suspect this is related to the AppxSVC not being ready during Audit Mode. Adding a 30 second delay appears to work more reliably.
### Windows and .NET CU's now persist across builds
Content in the FFUDevelopment\KB folder was always deleted once it was used. Since the Windows CU is so large now, it doesn't make sense to delete it if a user wants it again and may not be using cached VHDX files.
Deletion of the KB folder is now correctly handled via the **Remove Downloaded Update Files** option on the Build tab.
### Skip CU downloads if the Windows ESD version is current or newer
Now that the Windows ESD media is kept up to date, there rarely will be a need to download the latest CU. There will always be a slight gap when the latest CU comes out and the updated media is available, but that's generally just a few days to a week.
The script will now do some parsing of the windows version of the ESD file and the latest CU and if the ESD is newer, the CU will not be downloaded.
### Fixes an issue with WingetWin32Apps.json file not being created if applications were pre-downloaded via the UI
Fixed a bug due to some code consolidation that broke scenarios where applications that were downloaded via the UI, but were not installing in the VM.
**Full Changelog**: https://github.com/rbalsleyMSFT/FFU/compare/v2511.1preview...v2511.2
# 2511.1 UI Preview
## What's Changed
### Major changes to drivers
A few weeks ago I wrote a [lengthy post](https://github.com/rbalsleyMSFT/FFU/discussions/350) asking for some help testing some changes that were added.
The summary of that post is that there have been significant changes for both Dell and HP driver downloads to leverage the SystemID for each model. This increases the total number of driver models that are exposed in the UI. This also requires the `DriverMapping.json` to be modified to require the SystemID and query the SystemID from WMI when doing automatic matching.
#### Driver folder structure changes on the USB drive - breaking change
Driver folder structure on the USB drive has also changed. The new structure is `Drivers\Make\Model` (e.g. `D:\Drivers\Lenovo\Lenovo 300w`). This structure is consistent with how the UI and `BuildFFUVM.ps1` script download and store drivers and automatically copy them. So if you've been following that, then no changes are required.
Please read [the post](https://github.com/rbalsleyMSFT/FFU/discussions/350) for more details on these changes to drivers.
### Windows 11 25H2 is now the default option for MCT/ESD downloads
For MCT/ESD downloads: Adds dynamic products.cab download functionality for Windows 11 using Windows Update service API instead of static MCT links. This is due to a change in how the MCT pulls the products.cab file. In other words, the Windows 11 25H2 ESD media is now updated each month (usually shortly after patch Tuesday)
### Added 8 new hardware manufactures for automatic driver matching during deployment
Extends hardware detection and driver mapping capabilities to support Panasonic, Viglen, AZW, Fujitsu, Getac, ByteSpeed, and Intel devices when applying the FFU to a device. This does not mean FFU Builder supports downloading drivers from these manufacturers. You'll still need to download the drivers for them manually. You can now create your own `DriverMapping.json` file to include these manufacturers.
Thanks to @arwidmark and the [Modern Driver Management](https://msendpointmgr.com/modern-driver-management/) team for the WMI queries.
### Fixed an issue with long paths when applying drivers from USB
Implemented SUBST drive mappings to shorten driver file paths within WinPE as some paths were causing dism to error when servicing drivers. You should see a Z:\ drive when applying drivers from the USB drive.
### Added an option to skip driver selection when multiple driver models are detected during deployment
Allows users to bypass driver installation by entering 0 at the selection prompt, providing flexibility for deployments that don't require driver updates.
### Add HTTP fallback for BITS transfer network authentication errors
Fixes an issue with standard users elevating PowerShell as Admin and getting BITS errors when trying to download content.
### Add -BitsPriority script parameter
Introduces a new parameter `-BitsPriority` with options `(Foreground, High, Normal, Low)` to control BITS download priority across the build system and UI, allowing users to optimize transfer speeds when needed.
The feature adds a priority selector to the UI with four options (Foreground, High, Normal, Low) and propagates the selection through the build script and common modules. Priority can be set via UI or command-line parameter with Normal as the default.
### BYO Apps: Add MSI path quoting to handle spaces in msiexec arguments
When specifying Build Your Own Apps msiexec arguments, if there were spaces in the argument list that weren't quoted properly, you'd get an error. This should now automatically add missing spaces in case you forget to add them or there are spaces in your application name.
### Misc Fixes
* Fixed some reliability issues when trying to download Lenovo drivers
* Fixed an issue with PPKG files with spaces
* Replaced SerialNumber with UniqueID for USB drive identification when building USB drives. USB drive manufacturers may use the same serial number for different drives, potentially causing data loss if the wrong drive is chosen.
* `-Threads` parameter has been added to `BuildFFUVM.ps1` which defaults to 5, matching the UI behavior. This value can be 1-64.
* ESD media downloads now use BITS by default
* Fixed an issue with multi-disk devices. Prior, if multiple disks were detected, ApplyFFU.ps1 would fail. Now a menu pops up asking the end user to select the disk they want to deploy the FFU to
## New Contributors
* @arwidmark made their first contribution in https://github.com/rbalsleyMSFT/FFU/pull/325
**Full Changelog**: https://github.com/rbalsleyMSFT/FFU/compare/v2509.1preview...v2511.1preview
# 2509.1 UI Preview # 2509.1 UI Preview
## What's Changed ## What's Changed
+217 -209
View File
@@ -443,7 +443,7 @@ param(
[switch]$Cleanup [switch]$Cleanup
) )
$ProgressPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue'
$version = '2511.1Preview' $version = '2512.1Preview'
# Remove any existing modules to avoid conflicts # Remove any existing modules to avoid conflicts
if (Get-Module -Name 'FFU.Common.Core' -ErrorAction SilentlyContinue) { if (Get-Module -Name 'FFU.Common.Core' -ErrorAction SilentlyContinue) {
@@ -1942,7 +1942,7 @@ function Get-ProductsCab {
return $OutFile return $OutFile
} }
function Get-WindowsESD { function Get-WindowsESDMetadata {
param( param(
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[ValidateSet(10, 11)] [ValidateSet(10, 11)]
@@ -1959,12 +1959,10 @@ function Get-WindowsESD {
[ValidateSet('consumer', 'business')] [ValidateSet('consumer', 'business')]
[string]$MediaType [string]$MediaType
) )
WriteLog "Downloading Windows $WindowsRelease ESD file" WriteLog "Resolving Windows $WindowsRelease ESD metadata"
WriteLog "Windows Architecture: $WindowsArch"
WriteLog "Windows Language: $WindowsLang"
WriteLog "Windows Media Type: $MediaType"
$cabFilePath = Join-Path $PSScriptRoot "tempCabFile.cab" $cabFilePath = Join-Path $PSScriptRoot "tempCabFile.cab"
$xmlFilePath = Join-Path $PSScriptRoot "products.xml"
$esdMetadata = $null
$OriginalVerbosePreference = $VerbosePreference $OriginalVerbosePreference = $VerbosePreference
$VerbosePreference = 'SilentlyContinue' $VerbosePreference = 'SilentlyContinue'
try { try {
@@ -1995,46 +1993,101 @@ function Get-WindowsESD {
else { else {
throw "Downloading Windows $WindowsRelease is not supported. Please use the -ISOPath parameter to specify the path to the Windows $WindowsRelease ISO file." throw "Downloading Windows $WindowsRelease is not supported. Please use the -ISOPath parameter to specify the path to the Windows $WindowsRelease ISO file."
} }
WriteLog "Download succeeded" WriteLog "products.cab download succeeded"
} }
finally { finally {
$VerbosePreference = $OriginalVerbosePreference $VerbosePreference = $OriginalVerbosePreference
} }
# Extract XML from cab file
WriteLog "Extracting Products XML from cab" WriteLog "Extracting Products XML from cab"
$xmlFilePath = Join-Path $PSScriptRoot "products.xml"
Invoke-Process Expand "-F:*.xml $cabFilePath $xmlFilePath" | Out-Null Invoke-Process Expand "-F:*.xml $cabFilePath $xmlFilePath" | Out-Null
WriteLog "Products XML extracted" WriteLog "Products XML extracted"
# Load XML content
[xml]$xmlContent = Get-Content -Path $xmlFilePath [xml]$xmlContent = Get-Content -Path $xmlFilePath
# Define the client type to look for in the FilePath
$clientType = if ($MediaType -eq 'consumer') { 'CLIENTCONSUMER' } else { 'CLIENTBUSINESS' } $clientType = if ($MediaType -eq 'consumer') { 'CLIENTCONSUMER' } else { 'CLIENTBUSINESS' }
# Find FilePath values based on WindowsArch, WindowsLang, and MediaType
foreach ($file in $xmlContent.MCT.Catalogs.Catalog.PublishedMedia.Files.File) { foreach ($file in $xmlContent.MCT.Catalogs.Catalog.PublishedMedia.Files.File) {
if ($file.Architecture -eq $WindowsArch -and $file.LanguageCode -eq $WindowsLang -and $file.FilePath -like "*$clientType*") { if ($file.Architecture -eq $WindowsArch -and $file.LanguageCode -eq $WindowsLang -and $file.FilePath -like "*$clientType*") {
$esdFilePath = Join-Path $PSScriptRoot (Split-Path $file.FilePath -Leaf) $fileName = Split-Path $file.FilePath -Leaf
#Download if ESD file doesn't already exist $esdFilePath = Join-Path $PSScriptRoot $fileName
If (-not (Test-Path $esdFilePath)) { $esdVersion = $null
WriteLog "Downloading $($file.filePath) to $esdFIlePath" if ($file.FileName -match '^([0-9]+\.[0-9]+)') {
$OriginalVerbosePreference = $VerbosePreference $esdVersion = $matches[1]
$VerbosePreference = 'SilentlyContinue'
Mark-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $esdFilePath
Start-BitsTransferWithRetry -Source $file.FilePath -Destination $esdFilePath
Clear-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $esdFilePath
$VerbosePreference = $OriginalVerbosePreference
WriteLog "Download succeeded"
WriteLog "Cleanup cab and xml file"
Remove-Item -Path $cabFilePath -Force
Remove-Item -Path $xmlFilePath -Force
WriteLog "Cleanup done"
} }
return $esdFilePath
$esdMetadata = [pscustomobject]@{
FileUrl = $file.FilePath
FileName = $fileName
LocalPath = $esdFilePath
Version = $esdVersion
}
break
} }
} }
if ($esdMetadata) {
WriteLog "Resolved ESD metadata: $($esdMetadata.FileName) (Version: $($esdMetadata.Version))"
}
else {
WriteLog "No matching ESD entry found in products.xml."
}
WriteLog "Cleaning up temporary cab and xml files"
Remove-Item -Path $cabFilePath -Force -ErrorAction SilentlyContinue
Remove-Item -Path $xmlFilePath -Force -ErrorAction SilentlyContinue
return $esdMetadata
}
function Get-WindowsESD {
param(
[Parameter(Mandatory = $false)]
[ValidateSet(10, 11)]
[int]$WindowsRelease,
[Parameter(Mandatory = $false)]
[ValidateSet('x86', 'x64', 'ARM64')]
[string]$WindowsArch,
[Parameter(Mandatory = $false)]
[string]$WindowsLang,
[Parameter(Mandatory = $false)]
[ValidateSet('consumer', 'business')]
[string]$MediaType,
[Parameter(Mandatory = $false)]
[pscustomobject]$Metadata
)
WriteLog "Downloading Windows $WindowsRelease ESD file"
WriteLog "Windows Architecture: $WindowsArch"
WriteLog "Windows Language: $WindowsLang"
WriteLog "Windows Media Type: $MediaType"
$esdMetadata = $Metadata
if (-not $esdMetadata) {
$esdMetadata = Get-WindowsESDMetadata -WindowsRelease $WindowsRelease -WindowsArch $WindowsArch -WindowsLang $WindowsLang -MediaType $MediaType
}
if (-not $esdMetadata) {
throw "Unable to resolve Windows ESD metadata."
}
$esdFilePath = $esdMetadata.LocalPath
if (-not (Test-Path $esdFilePath)) {
WriteLog "Downloading $($esdMetadata.FileUrl) to $esdFilePath"
$OriginalVerbosePreference = $VerbosePreference
$VerbosePreference = 'SilentlyContinue'
Mark-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $esdFilePath
Start-BitsTransferWithRetry -Source $esdMetadata.FileUrl -Destination $esdFilePath
Clear-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $esdFilePath
$VerbosePreference = $OriginalVerbosePreference
WriteLog "ESD download succeeded"
}
else {
WriteLog "Found existing ESD at $esdFilePath, skipping download"
}
return $esdFilePath
} }
function Get-ODTURL { function Get-ODTURL {
@@ -2123,17 +2176,24 @@ function Get-KBLink {
$results = Invoke-WebRequest -Uri "http://www.catalog.update.microsoft.com/Search.aspx?q=$Name" -Headers $Headers -UserAgent $UserAgent $results = Invoke-WebRequest -Uri "http://www.catalog.update.microsoft.com/Search.aspx?q=$Name" -Headers $Headers -UserAgent $UserAgent
$VerbosePreference = $OriginalVerbosePreference $VerbosePreference = $OriginalVerbosePreference
# Extract the first KB article ID from the HTML content and store it globally # Extract the first KB article ID and Windows version (if present) from the HTML content and store globally
# Edge and Defender do not have KB article IDs # Edge and Defender do not have KB article IDs
if ($Name -notmatch 'Defender|Edge') { if ($Name -notmatch 'Defender|Edge') {
if ($results.Content -match '>\s*([^\(<]+)\(KB(\d+)\)(?:\s*\([^)]+\))*\s*<') { $global:LastKBArticleID = $null
$global:LastKBWindowsVersion = $null
if ($results.Content -match '\(KB(\d+)\)[^(<]*\(([0-9]+\.[0-9]+)\)\s*<') {
$kbArticleID = "KB$($matches[1])"
$global:LastKBArticleID = $kbArticleID
$global:LastKBWindowsVersion = $matches[2]
WriteLog "Found KB article ID: $kbArticleID with Windows version $($matches[2])"
}
elseif ($results.Content -match '>\s*([^\(<]+)\(KB(\d+)\)(?:\s*\([^)]+\))*\s*<') {
$kbArticleID = "KB$($matches[2])" $kbArticleID = "KB$($matches[2])"
$global:LastKBArticleID = $kbArticleID $global:LastKBArticleID = $kbArticleID
WriteLog "Found KB article ID: $kbArticleID" WriteLog "Found KB article ID: $kbArticleID (no Windows version found)"
} }
else { else {
WriteLog "No KB article ID found in search results." WriteLog "No KB article ID found in search results."
$global:LastKBArticleID = $null
} }
} }
@@ -3809,21 +3869,10 @@ function Get-FFUEnvironment {
Remove-FFUUserShare Remove-FFUUserShare
WriteLog 'Removal complete' WriteLog 'Removal complete'
} }
if ($RemoveApps) {
WriteLog "Removing Apps in $AppsPath" #Run shared cleanup to avoid duplicated logic
Remove-Apps Invoke-FFUPostBuildCleanup -RootPath $FFUDevelopmentPath -AppsPath $AppsPath -DriversPath $DriversFolder -FFUCapturePath $FFUCaptureLocation -CaptureISOPath $CaptureISO -DeployISOPath $DeployISO -AppsISOPath $AppsISO -RemoveCaptureISO:$CleanupCaptureISO -RemoveDeployISO:$CleanupDeployISO -RemoveAppsISO:$CleanupAppsISO -RemoveDrivers:$CleanupDrivers -RemoveFFU:$RemoveFFU -RemoveApps:$RemoveApps -RemoveUpdates:$RemoveUpdates -KBPath:$KBPath
}
#Remove updates
if ($RemoveUpdates) {
WriteLog "Removing updates"
Remove-Updates
}
#Clean up $KBPath
If (Test-Path -Path $KBPath) {
WriteLog "Removing $KBPath"
Remove-Item -Path $KBPath -Recurse -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
# Remove existing Apps.iso # Remove existing Apps.iso
if (Test-Path -Path $AppsISO) { if (Test-Path -Path $AppsISO) {
WriteLog "Removing $AppsISO" WriteLog "Removing $AppsISO"
@@ -3841,12 +3890,6 @@ function Get-FFUEnvironment {
Remove-Item -Path "$FFUDevelopmentPath\dirty.txt" -Force Remove-Item -Path "$FFUDevelopmentPath\dirty.txt" -Force
WriteLog "Cleanup complete" WriteLog "Cleanup complete"
} }
function Remove-FFU {
#Remove all FFU files in the FFUCaptureLocation
WriteLog "Removing all FFU files in $FFUCaptureLocation"
Remove-Item -Path $FFUCaptureLocation\*.ffu -Force
WriteLog "Removal complete"
}
Function Remove-DisabledArtifacts { Function Remove-DisabledArtifacts {
# Remove Office artifacts if Install Office is disabled # Remove Office artifacts if Install Office is disabled
if (-not $InstallOffice) { if (-not $InstallOffice) {
@@ -3932,101 +3975,6 @@ Function Remove-DisabledArtifacts {
} }
} }
Function Remove-Updates {
if ($UpdateLatestDefender) {
#Clean up $installDefenderPath
WriteLog "Removing $installDefenderPath"
If (Test-Path -Path $installDefenderPath) {
Remove-Item -Path $installDefenderPath -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
#Clean up $DefenderPath
If (Test-Path -Path $DefenderPath) {
WriteLog "Removing $DefenderPath"
Remove-Item -Path $DefenderPath -Recurse -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
}
if ($UpdateLatestMSRT) {
# Clean up Update-MSRT.ps1
WriteLog "Removing $installMSRTPath"
If (Test-Path -Path $installMSRTPath) {
Remove-Item -Path $installMSRTPath -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
#Clean up $MSRTPath
If (Test-Path -Path $MSRTPath) {
WriteLog "Removing $MSRTPath"
Remove-Item -Path $MSRTPath -Recurse -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
}
if ($UpdateOneDrive) {
# Clean up Update-OneDrive.ps1
WriteLog "Removing $installODPath"
If (Test-Path -Path $installODPath) {
Remove-Item -Path $installODPath -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
#Clean up $OneDrivePath
If (Test-Path -Path $OneDrivePath) {
WriteLog "Removing $OneDrivePath"
Remove-Item -Path $OneDrivePath -Recurse -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
}
if ($UpdateEdge) {
# Clean up Update-Edge.ps1
WriteLog "Removing $installEdgePath"
If (Test-Path -Path $installEdgePath) {
Remove-Item -Path $installEdgePath -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
#Clean up $EdgePath
If (Test-Path -Path $EdgePath) {
WriteLog "Removing $EdgePath"
Remove-Item -Path $EdgePath -Recurse -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
}
}
function Remove-Apps {
# Check if the file exists before attempting to clear it
if (Test-Path -Path $wingetWin32jsonFile) {
WriteLog "Removing $wingetWin32jsonFile"
Remove-Item -Path $wingetWin32jsonFile -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
# Clean up Win32 and MSStore folders
if (Test-Path -Path "$AppsPath\Win32" -PathType Container) {
WriteLog "Cleaning up Win32 folder"
Remove-Item -Path "$AppsPath\Win32" -Recurse -Force
}
if (Test-Path -Path "$AppsPath\MSStore" -PathType Container) {
WriteLog "Cleaning up MSStore folder"
Remove-Item -Path "$AppsPath\MSStore" -Recurse -Force
}
#Remove the Office Download and ODT
if ($InstallOffice) {
$ODTPath = "$AppsPath\Office"
$OfficeDownloadPath = "$ODTPath\Office"
WriteLog 'Removing Office and ODT download'
Remove-Item -Path $OfficeDownloadPath -Recurse -Force
Remove-Item -Path "$ODTPath\setup.exe"
Remove-Item -Path "$orchestrationPath\Install-Office.ps1"
WriteLog 'Removal complete'
}
#Remove AppsISO
if ($CleanupAppsISO) {
WriteLog "Removing $AppsISO"
Remove-Item -Path $AppsISO -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
}
function Export-ConfigFile { function Export-ConfigFile {
[CmdletBinding()] [CmdletBinding()]
param ( param (
@@ -5409,6 +5357,11 @@ if ($InstallApps) {
} }
) )
# Add 30 second delay to allow for Windows Security Platform to install
# I suspect this is related to AppxSVC not being immediately ready when booting to audit mode
# Long-term solution would be the check for AppxSVC being started, but for now the 30 second sleep seems to work consistently
$installDefenderCommand = "Start-Sleep -Seconds 30`r`n"
# Download each update # Download each update
foreach ($update in $defenderUpdates) { foreach ($update in $defenderUpdates) {
WriteLog "Searching for $($update.Name) from Microsoft Update Catalog and saving to $DefenderPath" WriteLog "Searching for $($update.Name) from Microsoft Update Catalog and saving to $DefenderPath"
@@ -5668,6 +5621,28 @@ try {
$netUpdateInfos = [System.Collections.Generic.List[pscustomobject]]::new() $netUpdateInfos = [System.Collections.Generic.List[pscustomobject]]::new()
$netFeatureUpdateInfos = [System.Collections.Generic.List[pscustomobject]]::new() $netFeatureUpdateInfos = [System.Collections.Generic.List[pscustomobject]]::new()
$microcodeUpdateInfos = [System.Collections.Generic.List[pscustomobject]]::new() $microcodeUpdateInfos = [System.Collections.Generic.List[pscustomobject]]::new()
$cachedIncludedUpdateNames = [System.Collections.Generic.List[string]]::new()
$esdMetadata = $null
$esdVersion = $null
$cuKbWindowsVersion = $null
$cupKbWindowsVersion = $null
if ($WindowsRelease -eq 11 -and -not $ISOPath) {
try {
$esdMetadata = Get-WindowsESDMetadata -WindowsRelease $WindowsRelease -WindowsArch $WindowsArch -WindowsLang $WindowsLang -MediaType $mediaType
if ($esdMetadata -and $esdMetadata.Version) {
$esdVersion = $esdMetadata.Version
WriteLog "ESD version identified as $esdVersion"
}
elseif ($esdMetadata) {
WriteLog "ESD metadata resolved but no version could be parsed from filename."
}
}
catch {
WriteLog "Failed to resolve Windows ESD metadata: $($_.Exception.Message)"
}
}
if ($UpdateLatestCU -or $UpdatePreviewCU -or $UpdateLatestNet -or $UpdateLatestMicrocode) { if ($UpdateLatestCU -or $UpdatePreviewCU -or $UpdateLatestNet -or $UpdateLatestMicrocode) {
# Determine required updates without downloading them yet # Determine required updates without downloading them yet
@@ -5695,6 +5670,7 @@ try {
WriteLog "Searching for $Name from Microsoft Update Catalog" WriteLog "Searching for $Name from Microsoft Update Catalog"
(Get-UpdateFileInfo -Name $Name) | ForEach-Object { $cuUpdateInfos.Add($_) } (Get-UpdateFileInfo -Name $Name) | ForEach-Object { $cuUpdateInfos.Add($_) }
$cuKbArticleId = $global:LastKBArticleID $cuKbArticleId = $global:LastKBArticleID
$cuKbWindowsVersion = $global:LastKBWindowsVersion
} }
if ($UpdatePreviewCU -and $installationType -eq 'Client' -and $WindowsSKU -notlike "*LTSC") { if ($UpdatePreviewCU -and $installationType -eq 'Client' -and $WindowsSKU -notlike "*LTSC") {
@@ -5703,6 +5679,7 @@ try {
WriteLog "Searching for $Name from Microsoft Update Catalog" WriteLog "Searching for $Name from Microsoft Update Catalog"
(Get-UpdateFileInfo -Name $Name) | ForEach-Object { $cupUpdateInfos.Add($_) } (Get-UpdateFileInfo -Name $Name) | ForEach-Object { $cupUpdateInfos.Add($_) }
$cupKbArticleId = $global:LastKBArticleID $cupKbArticleId = $global:LastKBArticleID
$cupKbWindowsVersion = $global:LastKBWindowsVersion
} }
if ($UpdateLatestNet) { if ($UpdateLatestNet) {
@@ -5746,6 +5723,46 @@ try {
(Get-UpdateFileInfo -Name $name) | ForEach-Object { $microcodeUpdateInfos.Add($_) } (Get-UpdateFileInfo -Name $name) | ForEach-Object { $microcodeUpdateInfos.Add($_) }
} }
$esdVerObj = $null
$cuVerObj = $null
$cupVerObj = $null
if ($esdVersion) { try { $esdVerObj = [version]$esdVersion } catch { } }
if ($cuKbWindowsVersion) { try { $cuVerObj = [version]$cuKbWindowsVersion } catch { } }
if ($cupKbWindowsVersion) { try { $cupVerObj = [version]$cupKbWindowsVersion } catch { } }
if ($esdVerObj -and $cuVerObj) {
if ($esdVerObj -eq $cuVerObj -or $esdVerObj -gt $cuVerObj) {
$skipReason = if ($esdVerObj -eq $cuVerObj) { 'matches' } else { 'is newer than' }
WriteLog "Windows 11 ESD version $esdVersion $skipReason CU version $cuKbWindowsVersion. Skipping CU download and installation."
if ($AllowVHDXCaching -and $cuUpdateInfos -and $cuUpdateInfos.Count -gt 0) {
foreach ($cuUpdateInfo in $cuUpdateInfos) {
if (-not [string]::IsNullOrWhiteSpace($cuUpdateInfo.Name) -and -not $cachedIncludedUpdateNames.Contains($cuUpdateInfo.Name)) {
$cachedIncludedUpdateNames.Add($cuUpdateInfo.Name)
}
}
}
$cuUpdateInfos.Clear()
$UpdateLatestCU = $false
$CUPath = $null
}
}
if ($esdVerObj -and $cupVerObj) {
if ($esdVerObj -eq $cupVerObj -or $esdVerObj -gt $cupVerObj) {
$skipReason = if ($esdVerObj -eq $cupVerObj) { 'matches' } else { 'is newer than' }
WriteLog "Windows 11 ESD version $esdVersion $skipReason Preview CU version $cupKbWindowsVersion. Skipping Preview CU download and installation."
if ($AllowVHDXCaching -and $cupUpdateInfos -and $cupUpdateInfos.Count -gt 0) {
foreach ($cupUpdateInfo in $cupUpdateInfos) {
if (-not [string]::IsNullOrWhiteSpace($cupUpdateInfo.Name) -and -not $cachedIncludedUpdateNames.Contains($cupUpdateInfo.Name)) {
$cachedIncludedUpdateNames.Add($cupUpdateInfo.Name)
}
}
}
$cupUpdateInfos.Clear()
$UpdatePreviewCU = $false
$CUPPath = $null
}
}
$requiredUpdates.AddRange($ssuUpdateInfos) $requiredUpdates.AddRange($ssuUpdateInfos)
$requiredUpdates.AddRange($cuUpdateInfos) $requiredUpdates.AddRange($cuUpdateInfos)
$requiredUpdates.AddRange($cupUpdateInfos) $requiredUpdates.AddRange($cupUpdateInfos)
@@ -5762,10 +5779,23 @@ try {
$vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter '*_config.json' | Sort-Object -Property CreationTime -Descending) $vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter '*_config.json' | Sort-Object -Property CreationTime -Descending)
WriteLog "Found $($vhdxJsons.Count) cached VHDX config files" WriteLog "Found $($vhdxJsons.Count) cached VHDX config files"
# Extract file names from URLs for comparison # Build comparison list from update names and cached names
$requiredUpdateFileNames = @() $requiredUpdateFileNames = @()
if ($requiredUpdates.Count -gt 0) { if ($requiredUpdates.Count -gt 0) {
$requiredUpdateFileNames = @(($requiredUpdates.Url | ForEach-Object { ($_ -split '/')[-1] }) | Sort-Object) $requiredUpdateFileNames += $requiredUpdates | ForEach-Object {
if (-not [string]::IsNullOrWhiteSpace($_.Name)) {
$_.Name
}
elseif (-not [string]::IsNullOrWhiteSpace($_.Url)) {
($_.Url -split '/')[-1]
}
}
}
if ($cachedIncludedUpdateNames.Count -gt 0) {
$requiredUpdateFileNames += $cachedIncludedUpdateNames
}
if ($requiredUpdateFileNames.Count -gt 0) {
$requiredUpdateFileNames = @($requiredUpdateFileNames | Where-Object { $_ } | Sort-Object -Unique)
} }
foreach ($vhdxJson in $vhdxJsons) { foreach ($vhdxJson in $vhdxJsons) {
@@ -5837,6 +5867,15 @@ try {
if (-not (Test-Path -Path $destinationPath)) { if (-not (Test-Path -Path $destinationPath)) {
New-Item -Path $destinationPath -ItemType Directory -Force | Out-Null New-Item -Path $destinationPath -ItemType Directory -Force | Out-Null
} }
# Skip download if expected file is already present
$expectedFilePath = Join-Path -Path $destinationPath -ChildPath $update.Name
if (Test-Path -LiteralPath $expectedFilePath) {
WriteLog "Update already exists at $expectedFilePath, skipping download"
continue
}
WriteLog "Downloading $($update.Name) to $destinationPath" WriteLog "Downloading $($update.Name) to $destinationPath"
Start-BitsTransferWithRetry -Source $update.Url -Destination $destinationPath Start-BitsTransferWithRetry -Source $update.Url -Destination $destinationPath
} }
@@ -5891,7 +5930,7 @@ try {
$wimPath = Get-WimFromISO $wimPath = Get-WimFromISO
} }
else { else {
$wimPath = Get-WindowsESD -WindowsRelease $WindowsRelease -WindowsArch $WindowsArch -WindowsLang $WindowsLang -MediaType $mediaType $wimPath = Get-WindowsESD -WindowsRelease $WindowsRelease -WindowsArch $WindowsArch -WindowsLang $WindowsLang -MediaType $mediaType -Metadata $esdMetadata
} }
#If index not specified by user, try and find based on WindowsSKU #If index not specified by user, try and find based on WindowsSKU
if (-not($index) -and ($WindowsSKU)) { if (-not($index) -and ($WindowsSKU)) {
@@ -5968,14 +6007,24 @@ try {
WriteLog "KBs added to $WindowsPartition" WriteLog "KBs added to $WindowsPartition"
if ($AllowVHDXCaching) { if ($AllowVHDXCaching) {
$cachedVHDXInfo = [VhdxCacheItem]::new() $cachedVHDXInfo = [VhdxCacheItem]::new()
$includedUpdates = Get-ChildItem -Path $KBPath -File -Recurse # Record only updates from this build (current required updates and any cached names carried forward)
$includedUpdateNames = [System.Collections.Generic.List[string]]::new()
foreach ($includedUpdate in $includedUpdates) { foreach ($includedUpdate in $requiredUpdates) {
$cachedVHDXInfo.IncludedUpdates += ([VhdxCacheUpdateItem]::new($includedUpdate.Name)) if (-not [string]::IsNullOrWhiteSpace($includedUpdate.Name)) {
$includedUpdateNames.Add($includedUpdate.Name)
}
}
foreach ($cachedName in $cachedIncludedUpdateNames) {
if (-not [string]::IsNullOrWhiteSpace($cachedName)) {
$includedUpdateNames.Add($cachedName)
}
}
foreach ($includedName in ($includedUpdateNames | Sort-Object -Unique)) {
if (-not ($cachedVHDXInfo.IncludedUpdates | Where-Object { $_.Name -eq $includedName })) {
$cachedVHDXInfo.IncludedUpdates += ([VhdxCacheUpdateItem]::new($includedName))
}
} }
} }
WriteLog "Removing $KBPath"
Remove-Item -Path $KBPath -Recurse -Force | Out-Null
WriteLog 'Clean Up the WinSxS Folder' WriteLog 'Clean Up the WinSxS Folder'
WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient'
Dism /Image:$WindowsPartition /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null Dism /Image:$WindowsPartition /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null
@@ -6055,6 +6104,13 @@ try {
if ($null -eq $cachedVHDXInfo) { if ($null -eq $cachedVHDXInfo) {
$cachedVHDXInfo = [VhdxCacheItem]::new() $cachedVHDXInfo = [VhdxCacheItem]::new()
} }
if ($AllowVHDXCaching -and $cachedIncludedUpdateNames.Count -gt 0) {
foreach ($cachedName in $cachedIncludedUpdateNames) {
if (-not ($cachedVHDXInfo.IncludedUpdates | Where-Object { $_.Name -eq $cachedName })) {
$cachedVHDXInfo.IncludedUpdates += ([VhdxCacheUpdateItem]::new($cachedName))
}
}
}
$cachedVHDXInfo.VhdxFileName = $("$VMName.vhdx") $cachedVHDXInfo.VhdxFileName = $("$VMName.vhdx")
$cachedVHDXInfo.LogicalSectorSizeBytes = $LogicalSectorSizeBytes $cachedVHDXInfo.LogicalSectorSizeBytes = $LogicalSectorSizeBytes
$cachedVHDXInfo.WindowsSKU = $WindowsSKU $cachedVHDXInfo.WindowsSKU = $WindowsSKU
@@ -6220,30 +6276,6 @@ If ($InstallApps) {
Remove-FFUVM -VMName $VMName Remove-FFUVM -VMName $VMName
throw $_ throw $_
} }
#Clean up Apps
if ($RemoveApps) {
try {
WriteLog "Cleaning up $AppsPath"
Remove-Apps
}
catch {
Write-Host 'Cleaning up Apps failed'
Writelog "Cleaning up Apps failed with error $_"
throw $_
}
}
#Clean up Updates
if ($RemoveUpdates) {
try {
WriteLog "Cleaning up downloaded update files"
Remove-Updates
}
catch {
Write-Host 'Cleaning up downloaded update files failed'
Writelog "Cleaning up downloaded update files failed with error $_"
throw $_
}
}
} }
#Clean up VM or VHDX #Clean up VM or VHDX
try { try {
@@ -6315,35 +6347,11 @@ If ($BuildUSBDrive) {
throw $_ throw $_
} }
} }
If ($RemoveFFU) {
try {
Remove-FFU
}
catch {
Write-Host 'Removing FFU files failed'
Writelog "Removing FFU files failed with error $_"
throw $_
}
}
Set-Progress -Percentage 99 -Message "Finalizing and cleaning up..." Set-Progress -Percentage 99 -Message "Finalizing and cleaning up..."
# Delegated post-build cleanup to common module # Delegated post-build cleanup to common module
Invoke-FFUPostBuildCleanup -RootPath $FFUDevelopmentPath -AppsPath $AppsPath -DriversPath $Driversfolder -FFUCapturePath $FFUCaptureLocation -CaptureISOPath $CaptureISO -DeployISOPath $DeployISO -AppsISOPath $AppsISO -RemoveCaptureISO:$CleanupCaptureISO -RemoveDeployISO:$CleanupDeployISO -RemoveAppsISO:$CleanupAppsISO -RemoveDrivers:$CleanupDrivers -RemoveFFU:$RemoveFFU -RemoveApps:$RemoveApps -RemoveUpdates:$RemoveUpdates Invoke-FFUPostBuildCleanup -RootPath $FFUDevelopmentPath -AppsPath $AppsPath -DriversPath $DriversFolder -FFUCapturePath $FFUCaptureLocation -CaptureISOPath $CaptureISO -DeployISOPath $DeployISO -AppsISOPath $AppsISO -RemoveCaptureISO:$CleanupCaptureISO -RemoveDeployISO:$CleanupDeployISO -RemoveAppsISO:$CleanupAppsISO -RemoveDrivers:$CleanupDrivers -RemoveFFU:$RemoveFFU -RemoveApps:$RemoveApps -RemoveUpdates:$RemoveUpdates -KBPath:$KBPath
# Remove KBPath for cached vhdx files
if ($AllowVHDXCaching) {
try {
If (Test-Path -Path $KBPath) {
WriteLog "Removing $KBPath"
Remove-Item -Path $KBPath -Recurse -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
}
catch {
Writelog "Removing $KBPath failed with error $_"
throw $_
}
}
# Remove WinGetWin32Apps.json so it is always rebuilt next run # Remove WinGetWin32Apps.json so it is always rebuilt next run
if (Test-Path -Path $wingetWin32jsonFile -PathType Leaf) { if (Test-Path -Path $wingetWin32jsonFile -PathType Leaf) {
@@ -9,6 +9,7 @@ function Invoke-FFUPostBuildCleanup {
[string]$CaptureISOPath, [string]$CaptureISOPath,
[string]$DeployISOPath, [string]$DeployISOPath,
[string]$AppsISOPath, [string]$AppsISOPath,
[string]$KBPath,
[bool]$RemoveCaptureISO = $false, [bool]$RemoveCaptureISO = $false,
[bool]$RemoveDeployISO = $false, [bool]$RemoveDeployISO = $false,
[bool]$RemoveAppsISO = $false, [bool]$RemoveAppsISO = $false,
@@ -20,7 +21,7 @@ function Invoke-FFUPostBuildCleanup {
$originalProgressPreference = $ProgressPreference $originalProgressPreference = $ProgressPreference
$ProgressPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue'
try { try {
WriteLog "CommonCleanup: Starting cleanup (CaptureISO=$RemoveCaptureISO DeployISO=$RemoveDeployISO AppsISO=$RemoveAppsISO Drivers=$RemoveDrivers FFU=$RemoveFFU Apps=$RemoveApps Updates=$RemoveUpdates)." WriteLog "CommonCleanup: Starting cleanup (CaptureISO=$RemoveCaptureISO DeployISO=$RemoveDeployISO AppsISO=$RemoveAppsISO Drivers=$RemoveDrivers FFU=$RemoveFFU Apps=$RemoveApps Updates=$RemoveUpdates KBPath=$KBPath)."
# Primary ISO paths (new naming/location) # Primary ISO paths (new naming/location)
if ($RemoveCaptureISO -and -not [string]::IsNullOrWhiteSpace($CaptureISOPath) -and (Test-Path -LiteralPath $CaptureISOPath)) { if ($RemoveCaptureISO -and -not [string]::IsNullOrWhiteSpace($CaptureISOPath) -and (Test-Path -LiteralPath $CaptureISOPath)) {
@@ -49,8 +50,15 @@ function Invoke-FFUPostBuildCleanup {
} }
if ($RemoveDrivers -and -not [string]::IsNullOrWhiteSpace($DriversPath) -and (Test-Path -LiteralPath $DriversPath -PathType Container)) { if ($RemoveDrivers -and -not [string]::IsNullOrWhiteSpace($DriversPath) -and (Test-Path -LiteralPath $DriversPath -PathType Container)) {
WriteLog "CommonCleanup: Removing contents of $DriversPath" WriteLog "CommonCleanup: Removing contents of $DriversPath (preserving Drivers.json and DriverMapping.json)"
try { Get-ChildItem -LiteralPath $DriversPath -Force -ErrorAction SilentlyContinue | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue } catch { WriteLog "CommonCleanup: Driver content cleanup issue: $($_.Exception.Message)" } try {
# Preserve drivers json files
$driverItems = Get-ChildItem -LiteralPath $DriversPath -Force -ErrorAction SilentlyContinue | Where-Object { @('Drivers.json', 'DriverMapping.json') -notcontains $_.Name }
if ($driverItems) {
$driverItems | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue
}
}
catch { WriteLog "CommonCleanup: Driver content cleanup issue: $($_.Exception.Message)" }
} }
if ($RemoveFFU -and -not [string]::IsNullOrWhiteSpace($FFUCapturePath) -and (Test-Path -LiteralPath $FFUCapturePath -PathType Container)) { if ($RemoveFFU -and -not [string]::IsNullOrWhiteSpace($FFUCapturePath) -and (Test-Path -LiteralPath $FFUCapturePath -PathType Container)) {
@@ -72,28 +80,38 @@ function Invoke-FFUPostBuildCleanup {
try { Remove-Item -LiteralPath $store -Recurse -Force -ErrorAction Stop } catch { WriteLog "CommonCleanup: Failed removing $store : $($_.Exception.Message)" } try { Remove-Item -LiteralPath $store -Recurse -Force -ErrorAction Stop } catch { WriteLog "CommonCleanup: Failed removing $store : $($_.Exception.Message)" }
} }
$office = Join-Path $AppsPath 'Office' $office = Join-Path $AppsPath 'Office'
if (Test-Path -LiteralPath $office) { if ((Test-Path -LiteralPath $office) -and $InstallOffice) {
WriteLog "CommonCleanup: Cleaning Office artifacts" WriteLog "CommonCleanup: Checking for Office artifacts in $office"
$officeSub = Join-Path $office 'Office' $officeSub = Join-Path $office 'Office'
if (Test-Path -LiteralPath $officeSub) { if (Test-Path -LiteralPath $officeSub) {
WriteLog "CommonCleanup: Removing $officeSub"
try { Remove-Item -LiteralPath $officeSub -Recurse -Force -ErrorAction Stop } catch { WriteLog "CommonCleanup: Failed removing $officeSub : $($_.Exception.Message)" } try { Remove-Item -LiteralPath $officeSub -Recurse -Force -ErrorAction Stop } catch { WriteLog "CommonCleanup: Failed removing $officeSub : $($_.Exception.Message)" }
} }
$setupExe = Join-Path $office 'setup.exe' $setupExe = Join-Path $office 'setup.exe'
if (Test-Path -LiteralPath $setupExe) { if (Test-Path -LiteralPath $setupExe) {
WriteLog "CommonCleanup: Removing $setupExe"
try { Remove-Item -LiteralPath $setupExe -Force -ErrorAction Stop } catch { WriteLog "CommonCleanup: Failed removing $setupExe : $($_.Exception.Message)" } try { Remove-Item -LiteralPath $setupExe -Force -ErrorAction Stop } catch { WriteLog "CommonCleanup: Failed removing $setupExe : $($_.Exception.Message)" }
} }
} }
} }
if ($RemoveUpdates -and -not [string]::IsNullOrWhiteSpace($AppsPath) -and (Test-Path -LiteralPath $AppsPath)) { if ($RemoveUpdates) {
$updateDirs = @('Defender', 'Edge', 'MSRT', 'OneDrive', '.NET', 'CU', 'Microcode') if (-not [string]::IsNullOrWhiteSpace($AppsPath) -and (Test-Path -LiteralPath $AppsPath)) {
foreach ($d in $updateDirs) { # Remove per-run app update payloads stored under Apps
$target = Join-Path $AppsPath $d $appUpdateDirs = @('Defender', 'Edge', 'MSRT', 'OneDrive')
if (Test-Path -LiteralPath $target) { foreach ($d in $appUpdateDirs) {
WriteLog "CommonCleanup: Removing update folder $target" $target = Join-Path $AppsPath $d
try { Remove-Item -LiteralPath $target -Recurse -Force -ErrorAction Stop } catch { WriteLog "CommonCleanup: Failed removing $target : $($_.Exception.Message)" } if (Test-Path -LiteralPath $target) {
WriteLog "CommonCleanup: Removing update folder $target"
try { Remove-Item -LiteralPath $target -Recurse -Force -ErrorAction Stop } catch { WriteLog "CommonCleanup: Failed removing $target : $($_.Exception.Message)" }
}
} }
} }
if (-not [string]::IsNullOrWhiteSpace($KBPath) -and (Test-Path -LiteralPath $KBPath)) {
# Remove Windows/.NET CU downloads stored under KB
WriteLog "CommonCleanup: Removing downloaded updates in $KBPath"
try { Remove-Item -LiteralPath $KBPath -Recurse -Force -ErrorAction Stop } catch { WriteLog "CommonCleanup: Failed removing $KBPath : $($_.Exception.Message)" }
}
} }
WriteLog "CommonCleanup: Completed." WriteLog "CommonCleanup: Completed."
@@ -441,6 +441,26 @@ function Start-WingetAppDownloadTask {
$status = "Not Downloaded: Existing content found in $appFolder" $status = "Not Downloaded: Existing content found in $appFolder"
Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status
WriteLog "Found existing content for '$appName' in '$appFolder'. Skipping download to prevent duplicate entry." WriteLog "Found existing content for '$appName' in '$appFolder'. Skipping download to prevent duplicate entry."
# Regenerate WinGetWin32Apps.json for CLI builds when content already exists
# UI mode pre-downloads should not generate this file (SkipWin32Json)
if (-not $SkipWin32Json) {
$archFolders = Get-ChildItem -Path $appFolder -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -in @('x86', 'x64', 'arm64') }
if ($archFolders) {
foreach ($archFolder in $archFolders) {
WriteLog "Adding silent install command for pre-downloaded $sanitizedAppName ($($archFolder.Name)) to $OrchestrationPath\WinGetWin32Apps.json"
Add-Win32SilentInstallCommand -AppFolder $sanitizedAppName -AppFolderPath $archFolder.FullName -OrchestrationPath $OrchestrationPath -SubFolder $archFolder.Name | Out-Null
}
}
else {
WriteLog "Adding silent install command for pre-downloaded $sanitizedAppName to $OrchestrationPath\WinGetWin32Apps.json"
Add-Win32SilentInstallCommand -AppFolder $sanitizedAppName -AppFolderPath $appFolder -OrchestrationPath $OrchestrationPath | Out-Null
}
}
else {
WriteLog "Skipping WinGetWin32Apps.json regeneration for pre-downloaded $sanitizedAppName (UI mode)."
}
return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 0 } return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 0 }
} }
} }
@@ -870,6 +870,7 @@ function Invoke-RestoreDefaults {
-CaptureISOPath $captureISOPath ` -CaptureISOPath $captureISOPath `
-DeployISOPath $deployISOPath ` -DeployISOPath $deployISOPath `
-AppsISOPath $appsISOPath ` -AppsISOPath $appsISOPath `
-KBPath (Join-Path $rootPath 'KB') `
-RemoveCaptureISO:$true ` -RemoveCaptureISO:$true `
-RemoveDeployISO:$true ` -RemoveDeployISO:$true `
-RemoveAppsISO:$true ` -RemoveAppsISO:$true `
@@ -794,7 +794,7 @@ $LogFileName = 'ScriptLog.txt'
$USBDrive = Get-USBDrive $USBDrive = Get-USBDrive
New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null
$LogFile = $USBDrive + $LogFilename $LogFile = $USBDrive + $LogFilename
$version = '2511.1Preview' $version = '2512.1Preview'
WriteLog 'Begin Logging' WriteLog 'Begin Logging'
WriteLog "Script version: $version" WriteLog "Script version: $version"