From 9dd508d069ce01ddb78d78082a22d101ca3d38e0 Mon Sep 17 00:00:00 2001 From: CodingWonders <101426328+CodingWonders@users.noreply.github.com> Date: Fri, 27 Dec 2024 09:48:55 +0100 Subject: [PATCH] Add DISM command fallback This fallback is triggered if an exception occurs while getting information with the cmdlets (I couldn't test this on my host as everything magically works now - sometimes it threw the Class not registered error) --- functions/microwin/Invoke-Microwin.ps1 | 6 +- .../microwin/Microwin-RemoveFeatures.ps1 | 84 +++++++++--- .../microwin/Microwin-RemovePackages.ps1 | 122 +++++++++++++----- .../Microwin-RemoveProvisionedPackages.ps1 | 101 +++++++++++---- 4 files changed, 229 insertions(+), 84 deletions(-) diff --git a/functions/microwin/Invoke-Microwin.ps1 b/functions/microwin/Invoke-Microwin.ps1 index 38a06db5..c6357e8a 100644 --- a/functions/microwin/Invoke-Microwin.ps1 +++ b/functions/microwin/Invoke-Microwin.ps1 @@ -163,12 +163,12 @@ public class PowerManagement { } Write-Host "Remove Features from the image" - Microwin-RemoveFeatures + Microwin-RemoveFeatures -UseCmdlets $true Write-Host "Removing features complete!" Write-Host "Removing OS packages" - Microwin-RemovePackages + Microwin-RemovePackages -UseCmdlets $true Write-Host "Removing Appx Bloat" - Microwin-RemoveProvisionedPackages + Microwin-RemoveProvisionedPackages -UseCmdlets $true # Detect Windows 11 24H2 and add dependency to FileExp to prevent Explorer look from going back - thanks @WitherOrNot and @thecatontheceiling if ((Microwin-TestCompatibleImage $imgVersion $([System.Version]::new(10,0,26100,1))) -eq $true) { diff --git a/functions/microwin/Microwin-RemoveFeatures.ps1 b/functions/microwin/Microwin-RemoveFeatures.ps1 index 5b37adb2..be5888dc 100644 --- a/functions/microwin/Microwin-RemoveFeatures.ps1 +++ b/functions/microwin/Microwin-RemoveFeatures.ps1 @@ -3,38 +3,80 @@ function Microwin-RemoveFeatures() { .SYNOPSIS Removes certain features from ISO image - .PARAMETER Name - No Params + .PARAMETER UseCmdlets + Determines whether or not to use the DISM cmdlets for processing. + - If true, DISM cmdlets will be used + - If false, calls to the DISM executable will be made whilst selecting bits and pieces from the output as a string (that was how MicroWin worked before + the DISM conversion to cmdlets) .EXAMPLE - Microwin-RemoveFeatures + Microwin-RemoveFeatures -UseCmdlets $true #> + param ( + [Parameter(Mandatory = $true, Position = 0)] [bool]$UseCmdlets + ) try { - $featlist = (Get-WindowsOptionalFeature -Path $scratchDir) + if ($UseCmdlets) { + $featlist = (Get-WindowsOptionalFeature -Path "$scratchDir") - $featlist = $featlist | Where-Object { - $_.FeatureName -NotLike "*Defender*" -AND - $_.FeatureName -NotLike "*Printing*" -AND - $_.FeatureName -NotLike "*TelnetClient*" -AND - $_.FeatureName -NotLike "*PowerShell*" -AND - $_.FeatureName -NotLike "*NetFx*" -AND - $_.FeatureName -NotLike "*Media*" -AND - $_.FeatureName -NotLike "*NFS*" -AND - $_.FeatureName -NotLike "*SearchEngine*" -AND - $_.FeatureName -NotLike "*RemoteDesktop*" -AND - $_.State -ne "Disabled" + $featlist = $featlist | Where-Object { + $_.FeatureName -NotLike "*Defender*" -AND + $_.FeatureName -NotLike "*Printing*" -AND + $_.FeatureName -NotLike "*TelnetClient*" -AND + $_.FeatureName -NotLike "*PowerShell*" -AND + $_.FeatureName -NotLike "*NetFx*" -AND + $_.FeatureName -NotLike "*Media*" -AND + $_.FeatureName -NotLike "*NFS*" -AND + $_.FeatureName -NotLike "*SearchEngine*" -AND + $_.FeatureName -NotLike "*RemoteDesktop*" -AND + $_.State -ne "Disabled" + } + } else { + $featList = dism /english /image="$scratchDir" /get-features | Select-String -Pattern "Feature Name : " -CaseSensitive -SimpleMatch + if ($?) { + $featList = $featList -split "Feature Name : " | Where-Object {$_} + # Exclude the same items. Note: for now, this doesn't exclude those features that are disabled. + # This will appear in the future + $featList = $featList | Where-Object { + $_ -NotLike "*Defender*" -AND + $_ -NotLike "*Printing*" -AND + $_ -NotLike "*TelnetClient*" -AND + $_ -NotLike "*PowerShell*" -AND + $_ -NotLike "*NetFx*" -AND + $_ -NotLike "*Media*" -AND + $_ -NotLike "*NFS*" -AND + $_ -NotLike "*SearchEngine*" -AND + $_ -NotLike "*RemoteDesktop*" + } + } else { + Write-Host "Features could not be obtained with DISM. MicroWin processing will continue, but features will be skipped." + return + } } - foreach($feature in $featlist) { - $status = "Removing feature $($feature.FeatureName)" - Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$featlist.Count*100) - Write-Debug "Removing feature $($feature.FeatureName)" - Disable-WindowsOptionalFeature -Path "$scratchDir" -FeatureName $($feature.FeatureName) -Remove -ErrorAction SilentlyContinue -NoRestart + if ($UseCmdlets) { + foreach ($feature in $featList) { + $status = "Removing feature $($feature.FeatureName)" + Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$featlist.Count*100) + Write-Debug "Removing feature $($feature.FeatureName)" + Disable-WindowsOptionalFeature -Path "$scratchDir" -FeatureName $($feature.FeatureName) -Remove -ErrorAction SilentlyContinue -NoRestart + } + } else { + foreach ($feature in $featList) { + $status = "Removing feature $feature" + Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$featlist.Count*100) + Write-Debug "Removing feature $feature" + dism /english /image="$scratchDir" /disable-feature /featurename=$feature /remove /quiet /norestart | Out-Null + if ($? -eq $false) { + Write-Host "Feature $feature could not be disabled." + } + } } Write-Progress -Activity "Removing features" -Status "Ready" -Completed Write-Host "You can re-enable the disabled features at any time, using either Windows Update or the SxS folder in \Sources." } catch { - Write-Host "Unable to get information about the features. MicroWin processing will continue, but features will not be processed" + Write-Host "Unable to get information about the features. A fallback will be used..." Write-Host "Error information: $($_.Exception.Message)" -ForegroundColor Yellow + Microwin-RemoveFeatures -UseCmdlets $false } } diff --git a/functions/microwin/Microwin-RemovePackages.ps1 b/functions/microwin/Microwin-RemovePackages.ps1 index 6901f2af..0f074483 100644 --- a/functions/microwin/Microwin-RemovePackages.ps1 +++ b/functions/microwin/Microwin-RemovePackages.ps1 @@ -1,44 +1,101 @@ function Microwin-RemovePackages { + <# + .SYNOPSIS + Removes certain packages from ISO image + + .PARAMETER UseCmdlets + Determines whether or not to use the DISM cmdlets for processing. + - If true, DISM cmdlets will be used + - If false, calls to the DISM executable will be made whilst selecting bits and pieces from the output as a string (that was how MicroWin worked before + the DISM conversion to cmdlets) + + .EXAMPLE + Microwin-RemovePackages -UseCmdlets $true + #> + param ( + [Parameter(Mandatory = $true, Position = 0)] [bool]$UseCmdlets + ) try { - $pkglist = (Get-WindowsPackage -Path "$scratchDir").PackageName + if ($useCmdlets) { + $pkglist = (Get-WindowsPackage -Path "$scratchDir").PackageName - $pkglist = $pkglist | Where-Object { - $_ -NotLike "*ApplicationModel*" -AND - $_ -NotLike "*indows-Client-LanguagePack*" -AND - $_ -NotLike "*LanguageFeatures-Basic*" -AND - $_ -NotLike "*Package_for_ServicingStack*" -AND - $_ -NotLike "*DotNet*" -AND - $_ -NotLike "*Notepad*" -AND - $_ -NotLike "*WMIC*" -AND - $_ -NotLike "*Ethernet*" -AND - $_ -NotLike "*Wifi*" -AND - $_ -NotLike "*FodMetadata*" -AND - $_ -NotLike "*Foundation*" -AND - $_ -NotLike "*LanguageFeatures*" -AND - $_ -NotLike "*VBSCRIPT*" -AND - $_ -NotLike "*License*" -AND - $_ -NotLike "*Hello-Face*" -AND - $_ -NotLike "*ISE*" + $pkglist = $pkglist | Where-Object { + $_ -NotLike "*ApplicationModel*" -AND + $_ -NotLike "*indows-Client-LanguagePack*" -AND + $_ -NotLike "*LanguageFeatures-Basic*" -AND + $_ -NotLike "*Package_for_ServicingStack*" -AND + $_ -NotLike "*DotNet*" -AND + $_ -NotLike "*Notepad*" -AND + $_ -NotLike "*WMIC*" -AND + $_ -NotLike "*Ethernet*" -AND + $_ -NotLike "*Wifi*" -AND + $_ -NotLike "*FodMetadata*" -AND + $_ -NotLike "*Foundation*" -AND + $_ -NotLike "*LanguageFeatures*" -AND + $_ -NotLike "*VBSCRIPT*" -AND + $_ -NotLike "*License*" -AND + $_ -NotLike "*Hello-Face*" -AND + $_ -NotLike "*ISE*" + } + } else { + $pkgList = dism /english /image="$scratchDir" /get-packages | Select-String -Pattern "Package Identity : " -CaseSensitive -SimpleMatch + if ($?) { + $pkgList = $pkgList -split "Package Identity : " | Where-Object {$_} + # Exclude the same items. + $pkgList = $pkgList | Where-Object { + $_ -NotLike "*ApplicationModel*" -AND + $_ -NotLike "*indows-Client-LanguagePack*" -AND + $_ -NotLike "*LanguageFeatures-Basic*" -AND + $_ -NotLike "*Package_for_ServicingStack*" -AND + $_ -NotLike "*DotNet*" -AND + $_ -NotLike "*Notepad*" -AND + $_ -NotLike "*WMIC*" -AND + $_ -NotLike "*Ethernet*" -AND + $_ -NotLike "*Wifi*" -AND + $_ -NotLike "*FodMetadata*" -AND + $_ -NotLike "*Foundation*" -AND + $_ -NotLike "*LanguageFeatures*" -AND + $_ -NotLike "*VBSCRIPT*" -AND + $_ -NotLike "*License*" -AND + $_ -NotLike "*Hello-Face*" -AND + $_ -NotLike "*ISE*" + } + } else { + Write-Host "Packages could not be obtained with DISM. MicroWin processing will continue, but packages will be skipped." + return } + } - $failedCount = 0 + if ($UseCmdlets) { + $failedCount = 0 - $erroredPackages = [System.Collections.Generic.List[ErroredPackage]]::new() + $erroredPackages = [System.Collections.Generic.List[ErroredPackage]]::new() - foreach ($pkg in $pkglist) { - try { - $status = "Removing $pkg" - Write-Progress -Activity "Removing Packages" -Status $status -PercentComplete ($counter++/$pkglist.Count*100) - Remove-WindowsPackage -Path "$scratchDir" -PackageName $pkg -NoRestart -ErrorAction SilentlyContinue - } catch { - # This can happen if the package that is being removed is a permanent one - $erroredPackages.Add([ErroredPackage]::new($pkg, $_.Exception.Message)) - $failedCount += 1 - continue + foreach ($pkg in $pkglist) { + try { + $status = "Removing $pkg" + Write-Progress -Activity "Removing Packages" -Status $status -PercentComplete ($counter++/$pkglist.Count*100) + Remove-WindowsPackage -Path "$scratchDir" -PackageName $pkg -NoRestart -ErrorAction SilentlyContinue + } catch { + # This can happen if the package that is being removed is a permanent one + $erroredPackages.Add([ErroredPackage]::new($pkg, $_.Exception.Message)) + $failedCount += 1 + continue + } + } + } else { + foreach ($package in $pkgList) { + $status = "Removing package $package" + Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$featlist.Count*100) + Write-Debug "Removing package $package" + dism /english /image="$scratchDir" /remove-package /packagename=$package /remove /quiet /norestart | Out-Null + if ($? -eq $false) { + Write-Host "Package $package could not be removed." + } } } Write-Progress -Activity "Removing Packages" -Status "Ready" -Completed - if ($failedCount -gt 0) + if ($UseCmdlets -and $failedCount -gt 0) { Write-Host "$failedCount package(s) could not be removed. Your image will still work fine, however. Below is information on what packages failed to be removed and why." if ($erroredPackages.Count -gt 0) @@ -63,7 +120,8 @@ function Microwin-RemovePackages { } } } catch { - Write-Host "Unable to get information about the packages. MicroWin processing will continue, but packages will not be processed" + Write-Host "Unable to get information about the packages. A fallback will be used..." Write-Host "Error information: $($_.Exception.Message)" -ForegroundColor Yellow + Microwin-RemovePackages -UseCmdlets $false } } diff --git a/functions/microwin/Microwin-RemoveProvisionedPackages.ps1 b/functions/microwin/Microwin-RemoveProvisionedPackages.ps1 index 30bc0d74..b148c2d9 100644 --- a/functions/microwin/Microwin-RemoveProvisionedPackages.ps1 +++ b/functions/microwin/Microwin-RemoveProvisionedPackages.ps1 @@ -3,49 +3,94 @@ function Microwin-RemoveProvisionedPackages() { .SYNOPSIS Removes AppX packages from a Windows image during MicroWin processing - .PARAMETER Name - No Params + .PARAMETER UseCmdlets + Determines whether or not to use the DISM cmdlets for processing. + - If true, DISM cmdlets will be used + - If false, calls to the DISM executable will be made whilst selecting bits and pieces from the output as a string (that was how MicroWin worked before + the DISM conversion to cmdlets) .EXAMPLE Microwin-RemoveProvisionedPackages #> + param ( + [Parameter(Mandatory = $true, Position = 0)] [bool]$UseCmdlets + ) try { - $appxProvisionedPackages = Get-AppxProvisionedPackage -Path "$($scratchDir)" | Where-Object { - $_.PackageName -NotLike "*AppInstaller*" -AND - $_.PackageName -NotLike "*Store*" -and - $_.PackageName -NotLike "*Notepad*" -and - $_.PackageName -NotLike "*Printing*" -and - $_.PackageName -NotLike "*YourPhone*" -and - $_.PackageName -NotLike "*Xbox*" -and - $_.PackageName -NotLike "*WindowsTerminal*" -and - $_.PackageName -NotLike "*Calculator*" -and - $_.PackageName -NotLike "*Photos*" -and - $_.PackageName -NotLike "*VCLibs*" -and - $_.PackageName -NotLike "*Paint*" -and - $_.PackageName -NotLike "*Gaming*" -and - $_.PackageName -NotLike "*Extension*" -and - $_.PackageName -NotLike "*SecHealthUI*" -and - $_.PackageName -NotLike "*ScreenSketch*" + if ($UseCmdlets) { + $appxProvisionedPackages = Get-AppxProvisionedPackage -Path "$($scratchDir)" | Where-Object { + $_.PackageName -NotLike "*AppInstaller*" -AND + $_.PackageName -NotLike "*Store*" -and + $_.PackageName -NotLike "*Notepad*" -and + $_.PackageName -NotLike "*Printing*" -and + $_.PackageName -NotLike "*YourPhone*" -and + $_.PackageName -NotLike "*Xbox*" -and + $_.PackageName -NotLike "*WindowsTerminal*" -and + $_.PackageName -NotLike "*Calculator*" -and + $_.PackageName -NotLike "*Photos*" -and + $_.PackageName -NotLike "*VCLibs*" -and + $_.PackageName -NotLike "*Paint*" -and + $_.PackageName -NotLike "*Gaming*" -and + $_.PackageName -NotLike "*Extension*" -and + $_.PackageName -NotLike "*SecHealthUI*" -and + $_.PackageName -NotLike "*ScreenSketch*" + } + } else { + $appxProvisionedPackages = dism /english /image="$scratchDir" /get-provisionedappxpackages | Select-String -Pattern "PackageName : " -CaseSensitive -SimpleMatch + if ($?) { + $appxProvisionedPackages = $appxProvisionedPackages -split "PackageName : " | Where-Object {$_} + # Exclude the same items. + $appxProvisionedPackages = $appxProvisionedPackages | Where-Object { + $_ -NotLike "*AppInstaller*" -AND + $_ -NotLike "*Store*" -and + $_ -NotLike "*Notepad*" -and + $_ -NotLike "*Printing*" -and + $_ -NotLike "*YourPhone*" -and + $_ -NotLike "*Xbox*" -and + $_ -NotLike "*WindowsTerminal*" -and + $_ -NotLike "*Calculator*" -and + $_ -NotLike "*Photos*" -and + $_ -NotLike "*VCLibs*" -and + $_ -NotLike "*Paint*" -and + $_ -NotLike "*Gaming*" -and + $_ -NotLike "*Extension*" -and + $_ -NotLike "*SecHealthUI*" -and + $_ -NotLike "*ScreenSketch*" + } + } else { + Write-Host "AppX packages could not be obtained with DISM. MicroWin processing will continue, but AppX packages will be skipped." + return + } } $counter = 0 - foreach ($appx in $appxProvisionedPackages) { - $status = "Removing Provisioned $($appx.PackageName)" - Write-Progress -Activity "Removing Provisioned Apps" -Status $status -PercentComplete ($counter++/$appxProvisionedPackages.Count*100) - try { - Remove-AppxProvisionedPackage -Path "$scratchDir" -PackageName $appx.PackageName -ErrorAction SilentlyContinue - } catch { - Write-Host "Application $($appx.PackageName) could not be removed" - continue + if ($UseCmdlets) { + foreach ($appx in $appxProvisionedPackages) { + $status = "Removing Provisioned $($appx.PackageName)" + Write-Progress -Activity "Removing Provisioned Apps" -Status $status -PercentComplete ($counter++/$appxProvisionedPackages.Count*100) + try { + Remove-AppxProvisionedPackage -Path "$scratchDir" -PackageName $appx.PackageName -ErrorAction SilentlyContinue + } catch { + Write-Host "Application $($appx.PackageName) could not be removed" + continue + } + } + } else { + foreach ($appx in $appxProvisionedPackages) { + $status = "Removing Provisioned $appx" + Write-Progress -Activity "Removing Provisioned Apps" -Status $status -PercentComplete ($counter++/$appxProvisionedPackages.Count*100) + dism /english /image="$scratchDir" /remove-provisionedappxpackage /packagename=$appx /quiet /norestart | Out-Null + if ($? -eq $false) { + Write-Host "AppX package $appx could not be removed." + } } } Write-Progress -Activity "Removing Provisioned Apps" -Status "Ready" -Completed } catch { - # This can happen if getting AppX packages fails - Write-Host "Unable to get information about the AppX packages. MicroWin processing will continue, but AppX packages will not be processed" + Write-Host "Unable to get information about the AppX packages. A fallback will be used..." Write-Host "Error information: $($_.Exception.Message)" -ForegroundColor Yellow + Microwin-RemoveProvisionedPackages -UseCmdlets $false } }