Files
ms-private/Manage-Apps.ps1
T

63 lines
4.4 KiB
PowerShell

#Requires -Version 5.1
[CmdletBinding(SupportsShouldProcess)]
param(
[ValidateSet('List','Plan','Apply')][string]$Mode = 'Plan',
[string[]]$Browser = @('Zen'),
[string[]]$ExtraId = @(), [string[]]$ExcludeId = @(),
[switch]$SkipBundled, [switch]$KeepOneDrive, [switch]$KeepEdge,
[switch]$RemovePersonal, [switch]$RemoveStore, [switch]$Json,
[Parameter(DontShow)][object[]]$ReviewedPlan
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Import-Module (Join-Path $PSScriptRoot 'src/Wind.Core.psm1')
Import-Module (Join-Path $PSScriptRoot 'src/Wind.Apps.psm1')
$Browser=@($Browser|ForEach-Object{$_.Split(',')}|ForEach-Object{$_.Trim()}|Where-Object{$_}|Select-Object -Unique)
if(@($Browser|Where-Object{$_ -notin @('Zen','Helium','Tor')}).Count){throw 'Browser must contain only Zen, Helium or Tor.'}
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; RemovePersonal=$RemovePersonal; RemoveStore=$RemoveStore }
$plan = @(Get-WindAppPlan -Inventory $inventory @selection)
if ($PSBoundParameters.ContainsKey('ReviewedPlan')) {
$reviewed = @($ReviewedPlan | Where-Object Status -eq 'Remove' | Sort-Object Id | Select-Object Id,Name,Version,Publisher,Kind,Scope,Target)
$fresh = @($plan | Where-Object Status -eq 'Remove' | Sort-Object Id | Select-Object Id,Name,Version,Publisher,Kind,Scope,Target)
if ((ConvertTo-Json -InputObject $reviewed -Compress) -cne (ConvertTo-Json -InputObject $fresh -Compress)) { throw 'The app plan changed since review. Review the refreshed list before applying.' }
}
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 $(if($RemoveStore){'Microsoft Store is selected for current-user removal. Reinstallation may require an official recovery path.'}else{'Microsoft Store is protected unless -RemoveStore is explicitly selected.'})
Write-Output 'WebView2, runtimes, security tools, drivers, servicing and recovery components remain 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\MsPrivatePolicyTransaction')
$locked = $false
try {
try { $locked = $mutex.WaitOne(0) } catch [Threading.AbandonedMutexException] { $locked = $true }
if (-not $locked) { throw 'Another ms-private transaction is running.' }
$reportRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'MsPrivate\apps'
[IO.Directory]::CreateDirectory($reportRoot) | Out-Null
$reportPath = Join-Path $reportRoot ((Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + [guid]::NewGuid().ToString('N') + '.json')
$journalWriter = Get-Command Save-WindJournal -CommandType Function
$save = { param($data) & $journalWriter -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()
}