mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Compare commits
9 Commits
v2511.1preview
...
2512.1
| Author | SHA1 | Date | |
|---|---|---|---|
| ceeabd1ebc | |||
| 15149ffa0b | |||
| 2f180747b7 | |||
| 25fe90253c | |||
| 86d122aacf | |||
| 9737d5c930 | |||
| c6088d91fa | |||
| 15fdf77ce4 | |||
| f7f001ac2e |
@@ -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
|
||||||
|
|||||||
+212
-204
@@ -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,47 +1993,102 @@ 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]+)') {
|
||||||
|
$esdVersion = $matches[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
$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
|
$OriginalVerbosePreference = $VerbosePreference
|
||||||
$VerbosePreference = 'SilentlyContinue'
|
$VerbosePreference = 'SilentlyContinue'
|
||||||
Mark-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $esdFilePath
|
Mark-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $esdFilePath
|
||||||
Start-BitsTransferWithRetry -Source $file.FilePath -Destination $esdFilePath
|
Start-BitsTransferWithRetry -Source $esdMetadata.FileUrl -Destination $esdFilePath
|
||||||
Clear-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $esdFilePath
|
Clear-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $esdFilePath
|
||||||
$VerbosePreference = $OriginalVerbosePreference
|
$VerbosePreference = $OriginalVerbosePreference
|
||||||
WriteLog "Download succeeded"
|
WriteLog "ESD download succeeded"
|
||||||
WriteLog "Cleanup cab and xml file"
|
|
||||||
Remove-Item -Path $cabFilePath -Force
|
|
||||||
Remove-Item -Path $xmlFilePath -Force
|
|
||||||
WriteLog "Cleanup done"
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
WriteLog "Found existing ESD at $esdFilePath, skipping download"
|
||||||
|
}
|
||||||
|
|
||||||
return $esdFilePath
|
return $esdFilePath
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Get-ODTURL {
|
function Get-ODTURL {
|
||||||
try {
|
try {
|
||||||
@@ -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,22 +80,26 @@ 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
|
||||||
|
$appUpdateDirs = @('Defender', 'Edge', 'MSRT', 'OneDrive')
|
||||||
|
foreach ($d in $appUpdateDirs) {
|
||||||
$target = Join-Path $AppsPath $d
|
$target = Join-Path $AppsPath $d
|
||||||
if (Test-Path -LiteralPath $target) {
|
if (Test-Path -LiteralPath $target) {
|
||||||
WriteLog "CommonCleanup: Removing update folder $target"
|
WriteLog "CommonCleanup: Removing update folder $target"
|
||||||
@@ -95,6 +107,12 @@ function Invoke-FFUPostBuildCleanup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user