#Requires -Version 5.1 [CmdletBinding(SupportsShouldProcess)] param( [ValidateSet('List','Plan','Apply')][string]$Mode = 'Plan', [ValidateSet('Zen','Helium','Tor')][string[]]$Browser = @('Zen'), [string[]]$ExtraId = @(), [string[]]$ExcludeId = @(), [switch]$SkipBundled, [switch]$KeepOneDrive, [switch]$KeepEdge, [switch]$Json ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' Import-Module (Join-Path $PSScriptRoot 'src/Wind.Core.psm1') -Force Import-Module (Join-Path $PSScriptRoot 'src/Wind.Apps.psm1') -Force if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) { throw 'Windows is required.' } if ($Json -and $Mode -eq 'Apply') { throw '-Json is for List/Plan. Apply displays installer output.' } $inventory = @(Get-WindAppInventory) $selection = @{ ExtraId=$ExtraId; ExcludeId=$ExcludeId; SkipBundled=$SkipBundled; KeepOneDrive=$KeepOneDrive; KeepEdge=$KeepEdge } $plan = @(Get-WindAppPlan -Inventory $inventory @selection) if ($Mode -eq 'List') { if ($Json) { ConvertTo-Json -InputObject @($plan) -Depth 5 } else { $plan | Select-Object Name,Status,Scope,Id,Reason | Format-Table -Wrap } return } if ($Json) { [pscustomobject]@{ Browsers=@($Browser); Apps=@($plan | Where-Object Status -ne 'Keep'); Scope='Appx: current user. Desktop: displayed registration scope.' } | ConvertTo-Json -Depth 6 return } Write-Output ('Browsers: ' + ($Browser -join ', ')) $plan | Where-Object Status -ne 'Keep' | Select-Object Name,Status,Scope,Reason | Format-Table -Wrap Write-Output 'Removal can delete app settings/data. Export mail, browser data and unsynced work first. OneDrive cloud-only files remain online; folders are not manually deleted.' Write-Output 'Store, WebView2, runtimes, security tools and drivers are protected. Appx removal affects this user; provisioning and other users are unchanged.' if ($Mode -eq 'Plan') { return } if (-not $PSCmdlet.ShouldProcess('Displayed app plan', 'Install selected browsers and remove selected apps')) { return } Assert-WindMutationAllowed $context = Get-WindContext if ($context.Build -notin @(19045,22631,26100,26200)) { throw 'App changes are gated to Windows builds 19045, 22631, 26100 and 26200. Use Windows Installed apps on other builds.' } $mutex = [Threading.Mutex]::new($false,'Global\WindPrivacyPolicyTransaction') $locked = $false try { try { $locked = $mutex.WaitOne(0) } catch [Threading.AbandonedMutexException] { $locked = $true } if (-not $locked) { throw 'Another Wind transaction is running.' } $reportRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'WindPrivacy\apps' [IO.Directory]::CreateDirectory($reportRoot) | Out-Null $reportPath = Join-Path $reportRoot ((Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + [guid]::NewGuid().ToString('N') + '.json') $save = { param($data) Save-WindJournal -Journal $data -Path $reportPath }.GetNewClosure() Write-Output "Operation report: $reportPath" $result = Invoke-WindAppChanges -Plan $plan -Browser $Browser -Backend (New-WindAppBackend) -Save $save -Confirm:$false Write-Output "App operation: $($result.Status). Set your default browser in Windows Settings and verify it opens links." } finally { if ($locked) { $mutex.ReleaseMutex() } $mutex.Dispose() }