packages.ps1
|
#Requires -Version 5.0 $script:UPFProtectedPackagePatterns = @( 'Microsoft.DesktopAppInstaller*', 'Microsoft.StorePurchaseApp*', 'Microsoft.WindowsStore*', 'Microsoft.SecHealthUI*', 'Microsoft.UI.Xaml*', 'Microsoft.VCLibs*', 'Microsoft.NET.Native*', 'Microsoft.WindowsAppRuntime*', 'Microsoft.Services.Store.Engagement*', 'Microsoft.LockApp*', 'Microsoft.CredDialogHost*', 'Microsoft.ECApp*' ) function Test-PackageAdministrator { <# .SYNOPSIS Checks whether the current PowerShell session is running with Administrator rights. .DESCRIPTION Returns a Boolean indicating whether the current Windows identity belongs to the local Administrators role. Package operations that read or mutate the system provisioning store use this helper to fail softly when a non-elevated shell cannot perform the requested work. .EXAMPLE PS> Test-PackageAdministrator Returns True when the current process is elevated. .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([bool])] [CmdletBinding()] param () $_identity = [Security.Principal.WindowsIdentity]::GetCurrent() $_principal = New-Object Security.Principal.WindowsPrincipal($_identity) return $_principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function New-PackageLifecycleResult { <# .SYNOPSIS Creates a normalized result object for package lifecycle operations. .DESCRIPTION Builds the common structured output used by UPF AppX/MSIX, Win32 program, and WinGet-backed helpers. Callers can consume Target, Source, Action, Status, SkippedReason, and Error without parsing console text. .PARAMETER Target Package, program, file path, or registry entry targeted by the operation. .PARAMETER Source Package source category, such as UPFAppxPackage, Win32Program, WinGet, Installed, or Provisioned. .PARAMETER Action Lifecycle action represented by the result. .PARAMETER Status Outcome string such as Completed, Removed, Skipped, Failed, or ExitCode:n. .PARAMETER SkippedReason Optional reason when Status is Skipped. .PARAMETER ErrorMessage Optional error detail when Status is Failed. .EXAMPLE PS> New-PackageLifecycleResult -Target 'Microsoft.GetHelp' -Source 'UPFAppxPackage' -Action 'Uninstall' -Status 'Skipped' -SkippedReason 'NoMatch' .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Factory helper only creates a result object.')] [OutputType([PSCustomObject])] [CmdletBinding()] param ( [string]$Target, [string]$Source, [string]$Action, [string]$Status, [string]$SkippedReason, [string]$ErrorMessage ) New-OperationResult -Target $Target -Source $Source -Action $Action -Status $Status -SkippedReason $SkippedReason -ErrorMessage $ErrorMessage } function Get-InstalledProgramCount { <# .SYNOPSIS Counts visible Win32 programs registered in the Windows uninstall registry keys. .DESCRIPTION Uses Get-Win32Program to count unique, user-visible classic Win32 application registrations from HKLM 64-bit, HKLM 32-bit, and HKCU uninstall locations. Registry entries marked as SystemComponent are excluded by default through the inventory helper. .EXAMPLE PS> Get-InstalledProgramCount Returns the number of registered Win32 programs visible to normal uninstall inventory views. .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([int])] [CmdletBinding()] param () return @(Get-Win32Program).Count } function Get-AppxPackageCount { <# .SYNOPSIS Counts installed UPF AppX/MSIX packages for the current user. .DESCRIPTION Uses Get-UPFAppxPackage to count installed AppX/MSIX packages while excluding framework, resource, and bundle packages by default. This keeps fetch-style summaries focused on user-facing applications instead of runtime dependencies. .EXAMPLE PS> Get-AppxPackageCount Returns the current user's user-facing UPF AppX/MSIX package count. .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([int])] [CmdletBinding()] param () return @(Get-UPFAppxPackage -Installed).Count } function Get-PackageCount { <# .SYNOPSIS Returns reusable Win32 and UPF AppX/MSIX package counts for system summaries. .DESCRIPTION Combines Get-InstalledProgramCount and Get-AppxPackageCount into a single object with Programs, Appx, and Total properties. This is intentionally small and fast enough for fetch-style scripts that should not duplicate registry or package inventory logic. .EXAMPLE PS> Get-PackageCount Returns Programs, Appx, and Total package counts. .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param () $_programs = Get-InstalledProgramCount $_appx = Get-AppxPackageCount return [PSCustomObject]@{ Programs = $_programs Appx = $_appx Total = ($_programs + $_appx) } } function Get-Win32Program { <# .SYNOPSIS Returns normalized Win32 program inventory from Windows uninstall registry keys. .DESCRIPTION Reads machine-wide 64-bit, machine-wide 32-bit and current-user uninstall keys using winkit registry helpers, and returns normalized program metadata suitable for lifecycle operations. .PARAMETER Name Optional wildcard DisplayName filter. When omitted, all visible Win32 uninstall registrations are returned. .PARAMETER IncludeSystemComponent Include entries marked with SystemComponent=1. These are normally hidden from user-facing uninstall inventory and are excluded by default. .EXAMPLE PS> Get-Win32Program Lists visible Win32 programs registered on the machine and current user. .EXAMPLE PS> Get-Win32Program -Name '*OneDrive*' -IncludeSystemComponent Returns OneDrive-related uninstall entries, including hidden system component registrations. .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param ( [string]$Name, [switch]$IncludeSystemComponent ) $_uninstallRoots = @( @{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' Scope = 'Machine' View = '64-bit' }, @{ Path = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall' Scope = 'Machine' View = '32-bit' }, @{ Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' Scope = 'CurrentUser' View = 'Default' } ) $_programs = foreach ($_root in $_uninstallRoots) { $_key = Get-RegistryKey -Path $_root.Path -ErrorAction SilentlyContinue if (-not $_key) { continue } foreach ($_subKey in $_key.SubKeys) { $_path = Join-Path -Path $_root.Path -ChildPath $_subKey $_displayName = Get-RegistryValue -Path $_path -Name 'DisplayName' -ErrorAction SilentlyContinue if ([string]::IsNullOrWhiteSpace($_displayName)) { continue } if ($Name -and $_displayName -notlike $Name) { continue } $_systemComponent = Get-RegistryValue -Path $_path -Name 'SystemComponent' -ErrorAction SilentlyContinue if ($_systemComponent -and -not $IncludeSystemComponent) { continue } [PSCustomObject]@{ Source = 'Win32Program' Name = $_displayName DisplayName = $_displayName DisplayVersion = Get-RegistryValue -Path $_path -Name 'DisplayVersion' -ErrorAction SilentlyContinue Publisher = Get-RegistryValue -Path $_path -Name 'Publisher' -ErrorAction SilentlyContinue InstallLocation = Get-RegistryValue -Path $_path -Name 'InstallLocation' -ErrorAction SilentlyContinue InstallDate = Get-RegistryValue -Path $_path -Name 'InstallDate' -ErrorAction SilentlyContinue UninstallString = Get-RegistryValue -Path $_path -Name 'UninstallString' -ErrorAction SilentlyContinue QuietUninstallString = Get-RegistryValue -Path $_path -Name 'QuietUninstallString' -ErrorAction SilentlyContinue ModifyPath = Get-RegistryValue -Path $_path -Name 'ModifyPath' -ErrorAction SilentlyContinue EstimatedSize = Get-RegistryValue -Path $_path -Name 'EstimatedSize' -ErrorAction SilentlyContinue RegistryPath = $_path RegistryScope = $_root.Scope RegistryView = $_root.View SystemComponent = [bool]$_systemComponent } } } $_programs | Sort-Object -Property DisplayName, RegistryPath -Unique } function Find-Win32Program { <# .SYNOPSIS Finds Win32 program uninstall registrations by wildcard name or exact registry path. .DESCRIPTION Resolves classic Win32 application registrations from uninstall registry locations. Use -Name for broad wildcard matching or -RegistryPath when a caller has already selected a concrete uninstall key. .PARAMETER Name Wildcard DisplayName pattern to resolve. .PARAMETER RegistryPath Exact uninstall registry key path to normalize into a Win32 program object. .PARAMETER IncludeSystemComponent Include hidden SystemComponent entries when resolving by name. .EXAMPLE PS> Find-Win32Program -Name '*Visual Studio Code*' .EXAMPLE PS> Find-Win32Program -RegistryPath 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SomeApp' .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Name')] [string]$Name, [Parameter(Mandatory = $true, ParameterSetName = 'RegistryPath')] [string]$RegistryPath, [switch]$IncludeSystemComponent ) if ($PSCmdlet.ParameterSetName -eq 'RegistryPath') { $_displayName = Get-RegistryValue -Path $RegistryPath -Name 'DisplayName' -ErrorAction SilentlyContinue if ([string]::IsNullOrWhiteSpace($_displayName)) { return } return [PSCustomObject]@{ Source = 'Win32Program' Name = $_displayName DisplayName = $_displayName DisplayVersion = Get-RegistryValue -Path $RegistryPath -Name 'DisplayVersion' -ErrorAction SilentlyContinue Publisher = Get-RegistryValue -Path $RegistryPath -Name 'Publisher' -ErrorAction SilentlyContinue InstallLocation = Get-RegistryValue -Path $RegistryPath -Name 'InstallLocation' -ErrorAction SilentlyContinue InstallDate = Get-RegistryValue -Path $RegistryPath -Name 'InstallDate' -ErrorAction SilentlyContinue UninstallString = Get-RegistryValue -Path $RegistryPath -Name 'UninstallString' -ErrorAction SilentlyContinue QuietUninstallString = Get-RegistryValue -Path $RegistryPath -Name 'QuietUninstallString' -ErrorAction SilentlyContinue ModifyPath = Get-RegistryValue -Path $RegistryPath -Name 'ModifyPath' -ErrorAction SilentlyContinue EstimatedSize = Get-RegistryValue -Path $RegistryPath -Name 'EstimatedSize' -ErrorAction SilentlyContinue RegistryPath = $RegistryPath RegistryScope = $null RegistryView = $null SystemComponent = [bool](Get-RegistryValue -Path $RegistryPath -Name 'SystemComponent' -ErrorAction SilentlyContinue) } } Get-Win32Program -Name $Name -IncludeSystemComponent:$IncludeSystemComponent } function Install-Win32Program { <# .SYNOPSIS Runs a local Win32 installer executable or package with explicit arguments. .DESCRIPTION Starts a local installer path using Start-Process and returns a normalized package lifecycle result. This helper deliberately requires an explicit local path and argument list so scripts do not hide downloaded installer execution behind a generic package verb. .PARAMETER Path Local executable or installer package path to run. .PARAMETER ArgumentList Installer arguments, such as silent install flags. .PARAMETER NoWait Start the installer and return immediately instead of waiting for it to exit. .PARAMETER PassThru Return process exit code information in the lifecycle status. .PARAMETER DryRun Preview the installer invocation without starting the process. .EXAMPLE PS> Install-Win32Program -Path 'C:\Installers\AppSetup.exe' -ArgumentList '/quiet','/norestart' .EXAMPLE PS> Install-Win32Program -Path 'C:\Installers\AppSetup.exe' -ArgumentList '/quiet' -DryRun .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })] [string]$Path, [string[]]$ArgumentList, [switch]$NoWait, [switch]$PassThru, [switch]$DryRun ) if ($DryRun) { $WhatIfPreference = $true } $_target = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) $_argumentText = if ($ArgumentList) { $ArgumentList -join ' ' } else { '' } if (-not $PSCmdlet.ShouldProcess($_target, "Install Win32 program $_argumentText")) { return New-PackageLifecycleResult -Target $_target -Source 'Win32Program' -Action 'Install' -Status 'Skipped' -SkippedReason 'WhatIf' } try { $_params = @{ FilePath = $_target ErrorAction = 'Stop' } if ($ArgumentList) { $_params.ArgumentList = $ArgumentList } if (-not $NoWait) { $_params.Wait = $true } if ($PassThru) { $_params.PassThru = $true } $_process = Start-Process @_params $_status = 'Installed' if ($PassThru -and $_process) { $_status = "ExitCode:$($_process.ExitCode)" } New-PackageLifecycleResult -Target $_target -Source 'Win32Program' -Action 'Install' -Status $_status } catch { New-PackageLifecycleResult -Target $_target -Source 'Win32Program' -Action 'Install' -Status 'Failed' -ErrorMessage $_.Exception.Message } } function Uninstall-Win32Program { <# .SYNOPSIS Uninstalls Win32 programs using registered uninstall commands. .DESCRIPTION Resolves one or more Win32 uninstall registry entries and runs the selected uninstall command. Interactive uninstall strings require -Force; quiet uninstall strings are preferred when -Quiet is supplied. MSI install commands using /I are converted to /X for uninstall behavior. .PARAMETER Name Wildcard DisplayName pattern to resolve and uninstall. .PARAMETER InputObject Win32 program object from Get-Win32Program or Find-Win32Program. .PARAMETER Quiet Prefer QuietUninstallString when available. .PARAMETER Force Permit execution of a non-quiet uninstall command. .PARAMETER DryRun Preview resolved uninstall commands without running them. .EXAMPLE PS> Uninstall-Win32Program -Name '*OneDrive*' -Quiet -DryRun .EXAMPLE PS> Find-Win32Program -Name '*Example App*' | Uninstall-Win32Program -Quiet .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Name')] [string]$Name, [Parameter(Mandatory = $true, ParameterSetName = 'InputObject', ValueFromPipeline = $true)] [psobject]$InputObject, [switch]$Quiet, [switch]$Force, [switch]$DryRun ) begin { if ($DryRun) { $WhatIfPreference = $true } $_targets = @() } process { if ($PSCmdlet.ParameterSetName -eq 'InputObject') { $_targets += $InputObject } } end { if ($PSCmdlet.ParameterSetName -eq 'Name') { $_targets = @(Find-Win32Program -Name $Name) } if ($_targets.Count -eq 0) { return New-PackageLifecycleResult -Target $Name -Source 'Win32Program' -Action 'Uninstall' -Status 'Skipped' -SkippedReason 'NoMatch' } foreach ($_program in $_targets) { $_command = if ($Quiet -and $_program.QuietUninstallString) { $_program.QuietUninstallString } else { $_program.UninstallString } if ([string]::IsNullOrWhiteSpace($_command)) { New-PackageLifecycleResult -Target $_program.DisplayName -Source 'Win32Program' -Action 'Uninstall' -Status 'Skipped' -SkippedReason 'NoUninstallString' continue } if (-not $Force -and -not $Quiet) { New-PackageLifecycleResult -Target $_program.DisplayName -Source 'Win32Program' -Action 'Uninstall' -Status 'Skipped' -SkippedReason 'ForceRequired' continue } if (-not $PSCmdlet.ShouldProcess($_program.DisplayName, "Run uninstall command: $_command")) { New-PackageLifecycleResult -Target $_program.DisplayName -Source 'Win32Program' -Action 'Uninstall' -Status 'Skipped' -SkippedReason 'WhatIf' continue } try { if ($_command -match '^\s*"([^"]+)"\s*(.*)$') { $_file = $Matches[1] $_args = $Matches[2] } else { $_parts = $_command.Trim() -split '\s+', 2 $_file = $_parts[0] $_args = if ($_parts.Count -gt 1) { $_parts[1] } else { '' } } if ($_file -match '(?i)msiexec(\.exe)?$' -and $_args -match '(?i)\s/I\s*') { $_args = $_args -replace '(?i)\s/I\s*', ' /X ' } $_params = @{ FilePath = $_file Wait = $true PassThru = $true ErrorAction = 'Stop' } if (-not [string]::IsNullOrWhiteSpace($_args)) { $_params.ArgumentList = $_args } $_process = Start-Process @_params New-PackageLifecycleResult -Target $_program.DisplayName -Source 'Win32Program' -Action 'Uninstall' -Status "ExitCode:$($_process.ExitCode)" } catch { New-PackageLifecycleResult -Target $_program.DisplayName -Source 'Win32Program' -Action 'Uninstall' -Status 'Failed' -ErrorMessage $_.Exception.Message } } } } function Get-UPFAppxPackage { <# .SYNOPSIS Returns normalized installed and provisioned UPF AppX/MSIX package inventory. .DESCRIPTION Wraps Get-AppxPackage and Get-AppxProvisionedPackage into a common object shape so callers can reason about installed user packages and provisioned Windows image packages together. Framework, resource, and bundle packages are hidden by default to keep normal inventory focused on user-facing apps. .PARAMETER Name App package name or wildcard pattern. Defaults to all packages. .PARAMETER Installed Include installed packages for the current user, or for all users with -AllUsers. If neither -Installed nor -Provisioned is supplied, installed current-user inventory is returned. .PARAMETER Provisioned Include packages provisioned into the online Windows image. This requires elevation and returns no entries from a non-elevated session. .PARAMETER AllUsers Query installed packages for all user profiles. Requires elevation on many Windows builds. .PARAMETER IncludeFramework Include framework packages such as Microsoft.UI.Xaml and VCLibs. .PARAMETER IncludeResource Include resource packages. .PARAMETER IncludeBundle Include bundle packages. .EXAMPLE PS> Get-UPFAppxPackage -Name 'Microsoft.GetHelp' .EXAMPLE PS> Get-UPFAppxPackage -Name 'Microsoft.Bing*' -Installed -Provisioned .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param ( [string]$Name = '*', [switch]$Installed, [switch]$Provisioned, [switch]$AllUsers, [switch]$IncludeFramework, [switch]$IncludeResource, [switch]$IncludeBundle ) if (-not $Installed -and -not $Provisioned) { $Installed = $true } if ($Installed) { $_installed = if ($AllUsers) { @(Get-AppxPackage -AllUsers -Name $Name -ErrorAction SilentlyContinue) } else { @(Get-AppxPackage -Name $Name -ErrorAction SilentlyContinue) } foreach ($_package in $_installed) { $_packageProperties = $_package.PSObject.Properties.Name $_isFramework = ($_packageProperties -contains 'IsFramework') -and [bool]$_package.IsFramework $_isResourcePackage = ($_packageProperties -contains 'IsResourcePackage') -and [bool]$_package.IsResourcePackage $_isBundle = ($_packageProperties -contains 'IsBundle') -and [bool]$_package.IsBundle $_nonRemovable = ($_packageProperties -contains 'NonRemovable') -and [bool]$_package.NonRemovable $_userSecurityId = if ($_packageProperties -contains 'UserSecurityId') { $_package.UserSecurityId } else { $null } if (-not $IncludeFramework -and $_isFramework) { continue } if (-not $IncludeResource -and $_isResourcePackage) { continue } if (-not $IncludeBundle -and $_isBundle) { continue } [PSCustomObject]@{ Source = 'Installed' Name = $_package.Name DisplayName = $_package.Name PackageFullName = $_package.PackageFullName PackageName = $_package.PackageFullName PackageFamilyName = $_package.PackageFamilyName Publisher = $_package.Publisher Version = $_package.Version Architecture = $_package.Architecture IsFramework = $_isFramework IsResourcePackage = $_isResourcePackage IsBundle = $_isBundle NonRemovable = $_nonRemovable InstallLocation = $_package.InstallLocation User = $_userSecurityId } } } if ($Provisioned) { if (-not (Test-PackageAdministrator)) { Write-Verbose 'Provisioned UPF AppX/MSIX package inventory requires elevation.' return } $_provisioned = @(Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like $Name -or $_.PackageName -like $Name }) foreach ($_package in $_provisioned) { [PSCustomObject]@{ Source = 'Provisioned' Name = $_package.DisplayName DisplayName = $_package.DisplayName PackageFullName = $null PackageName = $_package.PackageName PackageFamilyName = $null Publisher = $null Version = $_package.Version Architecture = $_package.Architecture IsFramework = $false IsResourcePackage = $false IsBundle = $false NonRemovable = $false InstallLocation = $null User = $null } } } } function Find-UPFAppxPackage { <# .SYNOPSIS Resolves UPF AppX/MSIX wildcard patterns to concrete package inventory records. .DESCRIPTION Looks up installed and/or provisioned AppX/MSIX packages for each pattern and annotates every result with match and protection metadata. Unmatched patterns are returned as structured records so removal scripts can report what did and did not resolve. .PARAMETER Pattern One or more package name, full name, family name, display name, or provisioned package wildcard patterns. .PARAMETER Installed Search installed packages. .PARAMETER Provisioned Search provisioned packages in the online Windows image. .PARAMETER AllUsers Search installed packages for all users. .PARAMETER IncludeProtected Return protected package matches as removable candidates instead of marking them as protected-only results. .PARAMETER IncludeFramework Include framework packages during discovery. .PARAMETER IncludeResource Include resource packages during discovery. .PARAMETER IncludeBundle Include bundle packages during discovery. .EXAMPLE PS> Find-UPFAppxPackage -Pattern 'Microsoft.Zune*' -Installed .EXAMPLE PS> 'Microsoft.GetHelp','Microsoft.BingWeather*' | Find-UPFAppxPackage -Installed .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string[]]$Pattern, [switch]$Installed, [switch]$Provisioned, [switch]$AllUsers, [switch]$IncludeProtected, [switch]$IncludeFramework, [switch]$IncludeResource, [switch]$IncludeBundle ) process { foreach ($_pattern in $Pattern) { $_inventory = @(Get-UPFAppxPackage -Name $_pattern -Installed:$Installed -Provisioned:$Provisioned -AllUsers:$AllUsers -IncludeFramework:$IncludeFramework -IncludeResource:$IncludeResource -IncludeBundle:$IncludeBundle) if ($_inventory.Count -eq 0) { [PSCustomObject]@{ Pattern = $_pattern Matched = $false Protected = $false ProtectedReason = $null Package = $null } continue } foreach ($_package in $_inventory) { $_safety = Test-UPFAppxPackageRemovalSafety -InputObject $_package if ($_safety.Protected -and -not $IncludeProtected) { [PSCustomObject]@{ Pattern = $_pattern Matched = $true Protected = $true ProtectedReason = $_safety.Reason Package = $_package } continue } [PSCustomObject]@{ Pattern = $_pattern Matched = $true Protected = $_safety.Protected ProtectedReason = $_safety.Reason Package = $_package } } } } } function Test-UPFAppxPackageRemovalSafety { <# .SYNOPSIS Classifies whether a UPF AppX/MSIX package should be protected from removal by default. .DESCRIPTION Checks a normalized package inventory object for non-removable, framework, resource, and protected-name patterns. Removal helpers use this classification to skip Store, App Installer, framework runtimes, and shell/security-adjacent packages unless the caller deliberately opts into protected removal. .PARAMETER InputObject Package object returned by Get-UPFAppxPackage or embedded in a Find-UPFAppxPackage result. .EXAMPLE PS> Get-UPFAppxPackage -Name 'Microsoft.WindowsStore' | Test-UPFAppxPackageRemovalSafety .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [psobject]$InputObject ) process { $_nameCandidates = @( $InputObject.Name, $InputObject.DisplayName, $InputObject.PackageName, $InputObject.PackageFullName, $InputObject.PackageFamilyName ) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } $_reason = $null if ($InputObject.NonRemovable) { $_reason = 'NonRemovable' } elseif ($InputObject.IsFramework) { $_reason = 'Framework' } elseif ($InputObject.IsResourcePackage) { $_reason = 'ResourcePackage' } else { foreach ($_pattern in $script:UPFProtectedPackagePatterns) { if (@($_nameCandidates | Where-Object { $_ -like $_pattern }).Count -gt 0) { $_reason = "ProtectedPattern:$_pattern" break } } } [PSCustomObject]@{ Target = $InputObject.PackageName Protected = -not [string]::IsNullOrWhiteSpace($_reason) Reason = $_reason } } } function Install-UPFAppxPackage { <# .SYNOPSIS Installs or provisions a local UPF AppX/MSIX package file. .DESCRIPTION Installs a local .appx, .msix, .appxbundle, or .msixbundle file for the current user, or provisions it into the online Windows image with -Provisioned. This function intentionally uses a UPF-prefixed noun to avoid shadowing platform cmdlets such as Add-AppxPackage. .PARAMETER Path Local UPF AppX/MSIX package file path. .PARAMETER DependencyPath Optional dependency package paths. .PARAMETER LicensePath Optional license path used with -Provisioned. .PARAMETER Provisioned Add the package to the online Windows image for future users. .PARAMETER SkipLicense Skip license processing when provisioning. .PARAMETER ForceUpdateFromAnyVersion Permit updating from any package version when installing for the current user. .PARAMETER DryRun Preview the install/provision operation without applying it. .EXAMPLE PS> Install-UPFAppxPackage -Path 'C:\Packages\Example.msix' .EXAMPLE PS> Install-UPFAppxPackage -Path 'C:\Packages\Example.msixbundle' -Provisioned -SkipLicense .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })] [string]$Path, [string[]]$DependencyPath, [string]$LicensePath, [switch]$Provisioned, [switch]$SkipLicense, [switch]$ForceUpdateFromAnyVersion, [switch]$DryRun ) if ($DryRun) { $WhatIfPreference = $true } $_path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) $_action = if ($Provisioned) { 'Provision' } else { 'Install' } if (-not $PSCmdlet.ShouldProcess($_path, "$_action UPF AppX/MSIX package")) { return New-PackageLifecycleResult -Target $_path -Source 'UPFAppxPackage' -Action $_action -Status 'Skipped' -SkippedReason 'WhatIf' } try { if ($Provisioned) { $_params = @{ Online = $true PackagePath = $_path ErrorAction = 'Stop' } if ($DependencyPath) { $_params.DependencyPackagePath = $DependencyPath } if ($LicensePath) { $_params.LicensePath = $LicensePath } if ($SkipLicense) { $_params.SkipLicense = $true } $null = Add-AppxProvisionedPackage @_params } else { $_params = @{ Path = $_path ErrorAction = 'Stop' } if ($DependencyPath) { $_params.DependencyPath = $DependencyPath } if ($ForceUpdateFromAnyVersion) { $_params.ForceUpdateFromAnyVersion = $true } $null = Add-AppxPackage @_params } New-PackageLifecycleResult -Target $_path -Source 'UPFAppxPackage' -Action $_action -Status 'Completed' } catch { New-PackageLifecycleResult -Target $_path -Source 'UPFAppxPackage' -Action $_action -Status 'Failed' -ErrorMessage $_.Exception.Message } } function Update-UPFAppxPackage { <# .SYNOPSIS Updates an installed UPF AppX/MSIX package from a local package file. .DESCRIPTION Uses Install-UPFAppxPackage with ForceUpdateFromAnyVersion to install a local AppX/MSIX package over an existing package registration. This helper is for local package files; Store-delivered update orchestration is left to Windows Store or WinGet provider workflows. .PARAMETER Path Local UPF AppX/MSIX package file path. .PARAMETER DependencyPath Optional dependency package paths. .PARAMETER DryRun Preview the update without applying it. .EXAMPLE PS> Update-UPFAppxPackage -Path 'C:\Packages\Example-2.0.msix' .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })] [string]$Path, [string[]]$DependencyPath, [switch]$DryRun ) Install-UPFAppxPackage -Path $Path -DependencyPath $DependencyPath -ForceUpdateFromAnyVersion -DryRun:$DryRun -WhatIf:$WhatIfPreference } function Repair-UPFAppxPackage { <# .SYNOPSIS Repairs installed UPF AppX/MSIX packages by re-registering their manifests. .DESCRIPTION Resolves installed packages by name and runs Add-AppxPackage -Register against each package's AppxManifest.xml. This is useful for restoring package registrations without deleting app data. .PARAMETER Name Package name or wildcard pattern to repair. .PARAMETER AllUsers Resolve installed packages for all users. .PARAMETER DryRun Preview manifest registrations without applying them. .EXAMPLE PS> Repair-UPFAppxPackage -Name 'Microsoft.WindowsStore' .EXAMPLE PS> Repair-UPFAppxPackage -Name 'Microsoft.DesktopAppInstaller*' -DryRun .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true)] [string]$Name, [switch]$AllUsers, [switch]$DryRun ) if ($DryRun) { $WhatIfPreference = $true } $_packages = @(Get-UPFAppxPackage -Name $Name -Installed -AllUsers:$AllUsers -IncludeFramework -IncludeResource -IncludeBundle | Where-Object { -not [string]::IsNullOrWhiteSpace($_.InstallLocation) }) if ($_packages.Count -eq 0) { return New-PackageLifecycleResult -Target $Name -Source 'UPFAppxPackage' -Action 'Repair' -Status 'Skipped' -SkippedReason 'NoMatch' } foreach ($_package in $_packages) { $_manifest = Join-Path -Path $_package.InstallLocation -ChildPath 'AppxManifest.xml' if (-not (Test-Path -LiteralPath $_manifest)) { New-PackageLifecycleResult -Target $_package.PackageFullName -Source 'UPFAppxPackage' -Action 'Repair' -Status 'Skipped' -SkippedReason 'ManifestNotFound' continue } if (-not $PSCmdlet.ShouldProcess($_package.PackageFullName, 'Re-register UPF AppX/MSIX package')) { New-PackageLifecycleResult -Target $_package.PackageFullName -Source 'UPFAppxPackage' -Action 'Repair' -Status 'Skipped' -SkippedReason 'WhatIf' continue } try { $null = Add-AppxPackage -DisableDevelopmentMode -Register $_manifest -ErrorAction Stop New-PackageLifecycleResult -Target $_package.PackageFullName -Source 'UPFAppxPackage' -Action 'Repair' -Status 'Completed' } catch { New-PackageLifecycleResult -Target $_package.PackageFullName -Source 'UPFAppxPackage' -Action 'Repair' -Status 'Failed' -ErrorMessage $_.Exception.Message } } } function Reset-UPFAppxPackage { <# .SYNOPSIS Resets app data for installed UPF AppX/MSIX packages when the platform supports it. .DESCRIPTION Resolves installed packages by name and calls the platform Reset-AppxPackage cmdlet when it is available. The helper does not delete package folders manually; unsupported platforms return a skipped result. .PARAMETER Name Package name or wildcard pattern to reset. .PARAMETER DryRun Preview reset operations without applying them. .EXAMPLE PS> Reset-UPFAppxPackage -Name 'Microsoft.GetHelp' .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true)] [string]$Name, [switch]$DryRun ) if ($DryRun) { $WhatIfPreference = $true } if (-not (Get-Command -Name Reset-AppxPackage -ErrorAction SilentlyContinue)) { return New-PackageLifecycleResult -Target $Name -Source 'UPFAppxPackage' -Action 'Reset' -Status 'Skipped' -SkippedReason 'ResetAppxPackageUnavailable' } $_packages = @(Get-UPFAppxPackage -Name $Name -Installed) if ($_packages.Count -eq 0) { return New-PackageLifecycleResult -Target $Name -Source 'UPFAppxPackage' -Action 'Reset' -Status 'Skipped' -SkippedReason 'NoMatch' } foreach ($_package in $_packages) { if (-not $PSCmdlet.ShouldProcess($_package.PackageFullName, 'Reset UPF AppX/MSIX package data')) { New-PackageLifecycleResult -Target $_package.PackageFullName -Source 'UPFAppxPackage' -Action 'Reset' -Status 'Skipped' -SkippedReason 'WhatIf' continue } try { $null = Reset-AppxPackage -Package $_package.PackageFullName -ErrorAction Stop New-PackageLifecycleResult -Target $_package.PackageFullName -Source 'UPFAppxPackage' -Action 'Reset' -Status 'Completed' } catch { New-PackageLifecycleResult -Target $_package.PackageFullName -Source 'UPFAppxPackage' -Action 'Reset' -Status 'Failed' -ErrorMessage $_.Exception.Message } } } function Uninstall-UPFAppxPackage { <# .SYNOPSIS Uninstalls a single normalized UPF AppX/MSIX package inventory record. .DESCRIPTION Removes one installed or provisioned package object produced by Get-UPFAppxPackage or Find-UPFAppxPackage. Protected packages are skipped unless both -IncludeProtected and -Force are supplied. .PARAMETER InputObject Normalized package record to uninstall. .PARAMETER AllUsers Remove installed package registrations for all users. .PARAMETER IncludeProtected Allow protected package records into removal evaluation. .PARAMETER Force Required with -IncludeProtected to remove protected package records. .PARAMETER DryRun Preview the uninstall operation without applying it. .EXAMPLE PS> Find-UPFAppxPackage -Pattern 'Microsoft.ZuneMusic' -Installed | Where-Object Matched | ForEach-Object Package | Uninstall-UPFAppxPackage -DryRun .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [psobject]$InputObject, [switch]$AllUsers, [switch]$IncludeProtected, [switch]$Force, [switch]$DryRun ) process { if ($DryRun) { $WhatIfPreference = $true } $_safety = Test-UPFAppxPackageRemovalSafety -InputObject $InputObject if ($_safety.Protected -and (-not $IncludeProtected -or -not $Force)) { return New-PackageLifecycleResult -Target $InputObject.PackageName -Source $InputObject.Source -Action 'Uninstall' -Status 'Skipped' -SkippedReason $_safety.Reason } if ($InputObject.Source -eq 'Provisioned') { $_target = $InputObject.PackageName if (-not $PSCmdlet.ShouldProcess($_target, 'Remove provisioned UPF AppX/MSIX package')) { return New-PackageLifecycleResult -Target $_target -Source 'Provisioned' -Action 'Uninstall' -Status 'Skipped' -SkippedReason 'WhatIf' } try { $null = Remove-AppxProvisionedPackage -Online -PackageName $_target -ErrorAction Stop return New-PackageLifecycleResult -Target $_target -Source 'Provisioned' -Action 'Uninstall' -Status 'Removed' } catch { return New-PackageLifecycleResult -Target $_target -Source 'Provisioned' -Action 'Uninstall' -Status 'Failed' -ErrorMessage $_.Exception.Message } } $_target = $InputObject.PackageFullName if (-not $PSCmdlet.ShouldProcess($_target, 'Remove installed UPF AppX/MSIX package')) { return New-PackageLifecycleResult -Target $_target -Source 'Installed' -Action 'Uninstall' -Status 'Skipped' -SkippedReason 'WhatIf' } try { if ($AllUsers) { $null = Remove-AppxPackage -Package $_target -AllUsers -ErrorAction Stop } else { $null = Remove-AppxPackage -Package $_target -ErrorAction Stop } New-PackageLifecycleResult -Target $_target -Source 'Installed' -Action 'Uninstall' -Status 'Removed' } catch { New-PackageLifecycleResult -Target $_target -Source 'Installed' -Action 'Uninstall' -Status 'Failed' -ErrorMessage $_.Exception.Message } } } function Uninstall-UPFAppxPackageSet { <# .SYNOPSIS Resolves and uninstalls a set of UPF AppX/MSIX package wildcard patterns. .DESCRIPTION Sequentially resolves package patterns through Find-UPFAppxPackage and sends each concrete package to Uninstall-UPFAppxPackage. This set helper intentionally reuses the singular uninstall function so safety checks, WhatIf behavior, and result objects stay consistent. .PARAMETER Pattern One or more package wildcard patterns to resolve and remove. .PARAMETER Installed Remove installed packages. Enabled by default. .PARAMETER Provisioned Also remove provisioned packages from the online Windows image. .PARAMETER AllUsers Remove installed packages for all user profiles. .PARAMETER IncludeProtected Include protected package matches in removal evaluation. .PARAMETER Force Required with -IncludeProtected to remove protected matches. .PARAMETER PassThru Return structured lifecycle results instead of only writing a summary. .PARAMETER DryRun Preview all removals without changing the system. .EXAMPLE PS> Uninstall-UPFAppxPackageSet -Pattern 'Microsoft.Zune*' -DryRun .EXAMPLE PS> Uninstall-UPFAppxPackageSet -Pattern 'Microsoft.GetHelp','Microsoft.BingWeather*' -Provisioned -PassThru .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true)] [string[]]$Pattern, [switch]$Installed, [switch]$Provisioned, [switch]$AllUsers, [switch]$IncludeProtected, [switch]$Force, [switch]$PassThru, [switch]$DryRun ) if ($DryRun) { $WhatIfPreference = $true Write-Log -Message "DRY RUN - no UPF AppX/MSIX packages will be removed`n" -Color Yellow } $_includeInstalled = $Installed -or -not $PSBoundParameters.ContainsKey('Installed') $_results = New-Object System.Collections.ArrayList $_matches = @(Find-UPFAppxPackage -Pattern $Pattern -Installed:$_includeInstalled -Provisioned:$Provisioned -AllUsers:$AllUsers -IncludeProtected:$IncludeProtected) foreach ($_match in $_matches) { if (-not $_match.Matched) { [void]$_results.Add((New-PackageLifecycleResult -Target $_match.Pattern -Source 'UPFAppxPackage' -Action 'Resolve' -Status 'Skipped' -SkippedReason 'NoMatch')) continue } if ($_match.Protected -and (-not $IncludeProtected -or -not $Force)) { [void]$_results.Add((New-PackageLifecycleResult -Target $_match.Package.PackageName -Source $_match.Package.Source -Action 'Uninstall' -Status 'Skipped' -SkippedReason $_match.ProtectedReason)) continue } $_removeResult = Uninstall-UPFAppxPackage -InputObject $_match.Package -AllUsers:$AllUsers -IncludeProtected:$IncludeProtected -Force:$Force -DryRun:$DryRun -WhatIf:$WhatIfPreference [void]$_results.Add($_removeResult) } if ($PassThru) { return $_results } $_removed = @($_results | Where-Object { $_.Status -eq 'Removed' }).Count $_skipped = @($_results | Where-Object { $_.Status -eq 'Skipped' }).Count $_failed = @($_results | Where-Object { $_.Status -eq 'Failed' }).Count Write-Log -Message "UPF AppX/MSIX package removal complete. Removed: $_removed | Skipped: $_skipped | Failed: $_failed" -Color $(if ($_failed -gt 0) { 'Yellow' } else { 'Green' }) } function Install-UPFAppxPackageSet { <# .SYNOPSIS Sequentially installs a set of local UPF AppX/MSIX package files. .DESCRIPTION Calls Install-UPFAppxPackage once for each supplied path and optionally returns the collected lifecycle results. This avoids duplicating install logic while giving scripts a convenient set operation for manifests or prepared package folders. .PARAMETER Path One or more local package file paths. .PARAMETER DependencyPath Optional dependency package paths passed to each install operation. .PARAMETER Provisioned Provision each package into the online Windows image. .PARAMETER SkipLicense Skip license processing when provisioning. .PARAMETER ForceUpdateFromAnyVersion Permit package updates from any version during install. .PARAMETER PassThru Return lifecycle result objects. .PARAMETER DryRun Preview installs without applying them. .EXAMPLE PS> Install-UPFAppxPackageSet -Path 'C:\Packages\App1.msix','C:\Packages\App2.msix' -DryRun .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true)] [string[]]$Path, [string[]]$DependencyPath, [switch]$Provisioned, [switch]$SkipLicense, [switch]$ForceUpdateFromAnyVersion, [switch]$PassThru, [switch]$DryRun ) $_results = foreach ($_path in $Path) { Install-UPFAppxPackage -Path $_path -DependencyPath $DependencyPath -Provisioned:$Provisioned -SkipLicense:$SkipLicense -ForceUpdateFromAnyVersion:$ForceUpdateFromAnyVersion -DryRun:$DryRun -WhatIf:$WhatIfPreference } if ($PassThru) { return $_results } } function Install-Win32ProgramFromWinGet { <# .SYNOPSIS Installs a Win32 program through the Microsoft.WinGet.Client provider. .DESCRIPTION Uses Microsoft.WinGet.Client when available to install an application by name or exact package ID. The winkit function name is intentionally Win32ProgramFromWinGet so it does not shadow provider cmdlets exported by Microsoft.WinGet.Client. .PARAMETER Name Package name to install through WinGet. .PARAMETER Id Exact WinGet package identifier to install. .PARAMETER Version Optional package version. .PARAMETER Source Optional WinGet source. .PARAMETER DryRun Preview the install without invoking the provider command. .EXAMPLE PS> Install-Win32ProgramFromWinGet -Id 'Microsoft.VisualStudioCode' .EXAMPLE PS> Install-Win32ProgramFromWinGet -Name 'Visual Studio Code' -DryRun .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Name')] [string]$Name, [Parameter(Mandatory = $true, ParameterSetName = 'Id')] [string]$Id, [string]$Version, [string]$Source, [switch]$DryRun ) if ($DryRun) { $WhatIfPreference = $true } if (-not (Get-Module -ListAvailable -Name Microsoft.WinGet.Client -ErrorAction SilentlyContinue)) { return New-PackageLifecycleResult -Target $(if ($Id) { $Id } else { $Name }) -Source 'WinGet' -Action 'Install' -Status 'Skipped' -SkippedReason 'Microsoft.WinGet.ClientUnavailable' } Import-Module Microsoft.WinGet.Client -Force -ErrorAction Stop $_cmd = Get-Command -Name Install-WinGetPackage -Module Microsoft.WinGet.Client -ErrorAction Stop $_target = if ($PSCmdlet.ParameterSetName -eq 'Id') { $Id } else { $Name } if (-not $PSCmdlet.ShouldProcess($_target, 'Install WinGet package')) { return New-PackageLifecycleResult -Target $_target -Source 'WinGet' -Action 'Install' -Status 'Skipped' -SkippedReason 'WhatIf' } try { $_params = @{ ErrorAction = 'Stop' } if ($PSCmdlet.ParameterSetName -eq 'Id') { $_params.Id = $Id } else { $_params.Name = $Name } if ($Version) { $_params.Version = $Version } if ($Source) { $_params.Source = $Source } $null = & $_cmd @_params New-PackageLifecycleResult -Target $_target -Source 'WinGet' -Action 'Install' -Status 'Completed' } catch { New-PackageLifecycleResult -Target $_target -Source 'WinGet' -Action 'Install' -Status 'Failed' -ErrorMessage $_.Exception.Message } } function Update-Win32ProgramFromWinGet { <# .SYNOPSIS Updates one or all Win32 programs through the Microsoft.WinGet.Client provider. .DESCRIPTION Uses Microsoft.WinGet.Client when available to update a specific package by name or ID, or every eligible package with -All. The function name avoids shadowing Microsoft.WinGet.Client's own Update-WinGetPackage cmdlet. .PARAMETER Id Exact WinGet package identifier to update. .PARAMETER Name Package name to update. .PARAMETER All Update all eligible packages. .PARAMETER IncludeUnknown Include packages with unknown installed versions when the provider supports it. .PARAMETER Source Optional WinGet source. .PARAMETER DryRun Preview the update without invoking the provider command. .EXAMPLE PS> Update-Win32ProgramFromWinGet -Id 'Microsoft.VisualStudioCode' .EXAMPLE PS> Update-Win32ProgramFromWinGet -All -DryRun .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([PSCustomObject])] param ( [Parameter(ParameterSetName = 'Id')] [string]$Id, [Parameter(ParameterSetName = 'Name')] [string]$Name, [Parameter(ParameterSetName = 'All')] [switch]$All, [switch]$IncludeUnknown, [string]$Source, [switch]$DryRun ) if ($DryRun) { $WhatIfPreference = $true } if (-not (Get-Module -ListAvailable -Name Microsoft.WinGet.Client -ErrorAction SilentlyContinue)) { return New-PackageLifecycleResult -Target 'WinGet' -Source 'WinGet' -Action 'Update' -Status 'Skipped' -SkippedReason 'Microsoft.WinGet.ClientUnavailable' } Import-Module Microsoft.WinGet.Client -Force -ErrorAction Stop $_cmd = Get-Command -Name Update-WinGetPackage -Module Microsoft.WinGet.Client -ErrorAction SilentlyContinue if (-not $_cmd) { return New-PackageLifecycleResult -Target 'WinGet' -Source 'WinGet' -Action 'Update' -Status 'Skipped' -SkippedReason 'UpdateWinGetPackageUnavailable' } $_target = if ($Id) { $Id } elseif ($Name) { $Name } elseif ($All) { 'All' } else { 'All' } if (-not $PSCmdlet.ShouldProcess($_target, 'Update WinGet package')) { return New-PackageLifecycleResult -Target $_target -Source 'WinGet' -Action 'Update' -Status 'Skipped' -SkippedReason 'WhatIf' } try { $_params = @{ ErrorAction = 'Stop' } if ($Id) { $_params.Id = $Id } if ($Name) { $_params.Name = $Name } if ($All) { $_params.All = $true } if ($IncludeUnknown) { $_params.IncludeUnknown = $true } if ($Source) { $_params.Source = $Source } $null = & $_cmd @_params New-PackageLifecycleResult -Target $_target -Source 'WinGet' -Action 'Update' -Status 'Completed' } catch { New-PackageLifecycleResult -Target $_target -Source 'WinGet' -Action 'Update' -Status 'Failed' -ErrorMessage $_.Exception.Message } } function Uninstall-Win32ProgramFromWinGet { <# .SYNOPSIS Uninstalls a Win32 program through the Microsoft.WinGet.Client provider. .DESCRIPTION Uses Microsoft.WinGet.Client when available to uninstall an application by name or exact package ID. The wrapper returns winkit lifecycle result objects while avoiding command-name collisions with the provider module. .PARAMETER Name Package name to uninstall through WinGet. .PARAMETER Id Exact WinGet package identifier to uninstall. .PARAMETER DryRun Preview the uninstall without invoking the provider command. .EXAMPLE PS> Uninstall-Win32ProgramFromWinGet -Id 'Microsoft.OneDrive' -DryRun .EXAMPLE PS> Uninstall-Win32ProgramFromWinGet -Name 'Microsoft OneDrive' .LINK https://github.com/adnoctem/winkit/lib/packages.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Name')] [string]$Name, [Parameter(Mandatory = $true, ParameterSetName = 'Id')] [string]$Id, [switch]$DryRun ) if ($DryRun) { $WhatIfPreference = $true } if (-not (Get-Module -ListAvailable -Name Microsoft.WinGet.Client -ErrorAction SilentlyContinue)) { return New-PackageLifecycleResult -Target $(if ($Id) { $Id } else { $Name }) -Source 'WinGet' -Action 'Uninstall' -Status 'Skipped' -SkippedReason 'Microsoft.WinGet.ClientUnavailable' } Import-Module Microsoft.WinGet.Client -Force -ErrorAction Stop $_cmd = Get-Command -Name Uninstall-WinGetPackage -Module Microsoft.WinGet.Client -ErrorAction Stop $_target = if ($PSCmdlet.ParameterSetName -eq 'Id') { $Id } else { $Name } if (-not $PSCmdlet.ShouldProcess($_target, 'Uninstall WinGet package')) { return New-PackageLifecycleResult -Target $_target -Source 'WinGet' -Action 'Uninstall' -Status 'Skipped' -SkippedReason 'WhatIf' } try { $_params = @{ ErrorAction = 'Stop' } if ($PSCmdlet.ParameterSetName -eq 'Id') { $_params.Id = $Id } else { $_params.Name = $Name } $null = & $_cmd @_params New-PackageLifecycleResult -Target $_target -Source 'WinGet' -Action 'Uninstall' -Status 'Completed' } catch { New-PackageLifecycleResult -Target $_target -Source 'WinGet' -Action 'Uninstall' -Status 'Failed' -ErrorMessage $_.Exception.Message } } |