Tasks/360-Remove-ABInstalledPrograms.ps1
|
function Remove-ABInstalledPrograms { <# .SYNOPSIS Removes installed (e.g. OEM preinstalled) MSI products and programs using the PackageManagement module. .DESCRIPTION Finds installed packages with Get-Package (MSI and Programs providers by default) and removes each one that matches an entry in PackageIds with Uninstall-Package. An entry matches a package's name (wildcards such as "HP Wolf*" are allowed) or its ID: the MSI product code (with or without braces) or the Programs uninstall registry key name. On PowerShell 7 the MSI and Programs providers are only available in Windows PowerShell, so the PackageManagement module is imported with -UseWindowsPowerShell. The Programs provider's Uninstall-Package doesn't run every program's uninstaller, so when a Programs entry is still registered afterwards, its silent uninstall command is run directly: the QuietUninstallString, or an msiexec UninstallString changed to a quiet uninstall. Programs without a silent uninstall command are skipped (logged). .PARAMETER PackageIds Names or IDs of the packages to remove, one per line (nothing is removed when empty). Use Get-Package -ProviderName msi, Programs to list the installed packages: the Name column, or the MSI product code / uninstall key name (in FastPackageReference). .PARAMETER ProviderNames PackageManagement providers to search, one per line. .PARAMETER TimeoutSeconds How long to wait for a program's uninstall command before stopping it. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string[]] $PackageIds = @(), [Parameter(Mandatory = $false)] [string[]] $ProviderNames = @('msi', 'Programs'), [Parameter(Mandatory = $false)] [int] $TimeoutSeconds = 600 ) $PackageIds = @($PackageIds | Where-Object { $_ } | ForEach-Object { $_.Trim() } | Where-Object { $_ } | Select-Object -Unique) if ($PackageIds.Count -eq 0) { Write-ABLog 'No package IDs specified, nothing to remove' return } Write-ABLog 'Removing specified installed programs' # The MSI and Programs providers only exist in Windows PowerShell. On PowerShell 7, import PackageManagement into # the Windows PowerShell compatibility session and run Get-Package / Uninstall-Package there, returning only plain # values (returning the SoftwareIdentity objects through the proxy commands fails). The prefix keeps the proxy # commands separate from PowerShell 7's own PackageManagement module. $compatModule = $null $session = $null if ($PSVersionTable.PSEdition -eq 'Core') { $compatModule = Import-Module PackageManagement -UseWindowsPowerShell -Prefix ABWinPS -Force -PassThru -WarningAction SilentlyContinue -ErrorAction Stop $session = Get-PSSession -Name 'WinPSCompatSession' -ErrorAction Stop | Where-Object { $_.State -eq 'Opened' } | Select-Object -First 1 if (-not $session) { throw 'The Windows PowerShell compatibility session (WinPSCompatSession) is not available' } Write-ABLog 'Using PackageManagement through the Windows PowerShell compatibility session' } else { Import-Module PackageManagement -ErrorAction Stop } $invoke = { param ([scriptblock] $ScriptBlock, [object[]] $ArgumentList) if ($session) { Invoke-Command -Session $session -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList -ErrorAction Stop } else { & $ScriptBlock @ArgumentList } } $getBlock = { param ([string[]] $Providers) Get-Package -ProviderName $Providers -ErrorAction SilentlyContinue | ForEach-Object { [pscustomobject]@{ Name = [string]$_.Name Version = [string]$_.Version ProviderName = [string]$_.ProviderName FastPackageReference = [string]$_.FastPackageReference TagId = [string]$_.TagId } } } $uninstallBlock = { param ([hashtable] $Parameters) Uninstall-Package @Parameters | ForEach-Object { [pscustomobject]@{ Name = [string]$_.Name; Version = [string]$_.Version; Status = [string]$_.Status } } } try { $packages = @(& $invoke $getBlock @(, [string[]]$ProviderNames)) Write-ABLog "Found $($packages.Count) installed packages ($($ProviderNames -join ', '))" $removed = @{} foreach ($id in $PackageIds) { $idValue = $id.Trim('{}') $found = @($packages | Where-Object { $refs = @($_.TagId, ($_.FastPackageReference -split '\\')[-1]) | Where-Object { $_ } | ForEach-Object { ([string]$_).Trim('{}') } ($_.Name -like $id) -or ($refs -contains $idValue) }) if ($found.Count -eq 0) { Write-ABLog "No installed package matches $id" continue } foreach ($package in $found) { $key = "$($package.ProviderName)|$($package.Name)|$($package.Version)" if ($removed.ContainsKey($key)) { continue } $removed[$key] = $true try { Write-ABLog "Uninstalling $($package.Name) $($package.Version) ($($package.ProviderName))" $uninstall = @{ Name = $package.Name ProviderName = $package.ProviderName Force = $true ErrorAction = 'Stop' } if ($package.Version) { $uninstall.RequiredVersion = $package.Version } $result = @(& $invoke $uninstallBlock @(, $uninstall)) foreach ($r in $result) { Write-ABLog " $($r.Name) $($r.Version): $($r.Status)" } if ($package.ProviderName -eq 'Programs') { $entry = Get-ABUninstallEntry -FastPackageReference $package.FastPackageReference if ($entry) { Write-ABLog ' Still registered after Uninstall-Package, running its uninstall command' $exitCode = Invoke-ABUninstallCommand -Entry $entry -TimeoutSeconds $TimeoutSeconds Write-ABLog " Exit code: $exitCode" if (Get-ABUninstallEntry -FastPackageReference $package.FastPackageReference) { throw "still registered after running its uninstall command (exit code $exitCode)" } } Write-ABLog ' Uninstalled' } } catch { Write-ABLog " Unable to uninstall $($package.Name): $_" } } } } finally { if ($compatModule) { $compatModule | Remove-Module -Force -ErrorAction SilentlyContinue } } } |