[Console]::OutputEncoding = [Text.Encoding]::UTF8 $ErrorActionPreference = 'Continue' $configPath = Join-Path $PSScriptRoot 'remove-apps-config.json' if (-not (Test-Path $configPath)) { throw "Config not found: $configPath" } $cfg = Get-Content $configPath -Raw -Encoding UTF8 | ConvertFrom-Json $results = @() function Record([string]$name, [string]$result, [string]$detail='') { $script:results += [pscustomobject]@{ Name = $name; Result = $result; Detail = $detail } } Write-Host "===== Removing Appx packages =====" foreach ($name in @($cfg.RemoveAppx)) { $pkgs = @(Get-AppxPackage -AllUsers -Name $name -ErrorAction SilentlyContinue) $prov = @(Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq $name }) if ($pkgs.Count -eq 0 -and $prov.Count -eq 0) { Write-Host "[=] $name : not installed" Record $name 'not-installed' continue } foreach ($p in $pkgs) { try { Write-Host "[*] Remove-AppxPackage $($p.PackageFullName)" Remove-AppxPackage -AllUsers -Package $p.PackageFullName -ErrorAction Stop } catch { # 0x80073D19 = NonRemovable system package. Worth noting but often unavoidable. if ($_.Exception.Message -match '0x80073D19|NonRemovable') { Write-Host "[!] $name marked NonRemovable by Windows - skipping" Record $name 'nonremovable' $_.Exception.Message } else { Write-Host "[!] $name install removal failed: $($_.Exception.Message)" Record $name 'failed-install' $_.Exception.Message } } } foreach ($p in $prov) { try { Remove-AppxProvisionedPackage -Online -PackageName $p.PackageName -ErrorAction Stop | Out-Null Write-Host "[*] Remove-AppxProvisionedPackage $($p.PackageName)" } catch { # Provisioned removal often fails silently when package was already deprovisioned by other tools. # Only worth logging, not flagging as a hard failure. } } # Verify post-state $after = @(Get-AppxPackage -AllUsers -Name $name -ErrorAction SilentlyContinue) if ($after.Count -eq 0) { Record $name 'removed' } elseif (($results | Where-Object Name -eq $name | Select-Object -Last 1).Result -ne 'nonremovable') { Record $name 'still-present' "after removal attempt: $(($after | Select -Expand PackageFullName) -join ',')" } } Write-Host "" Write-Host "===== Office (M365) removal =====" if ($cfg.RemoveOffice) { $c2rClient = 'C:\Program Files\Common Files\microsoft shared\ClickToRun\OfficeClickToRun.exe' if (Test-Path $c2rClient) { Write-Host "[*] Click-to-Run detected - running silent uninstall" $p = Start-Process -FilePath $c2rClient -ArgumentList 'scenario=uninstall','sourcetype=None','DisplayLevel=False' -Wait -PassThru Write-Host "[=] OfficeClickToRun exited $($p.ExitCode)" } else { Write-Host "[=] No Click-to-Run Office present" } $anyOfficeAppx = $false foreach ($pat in @('Microsoft.MicrosoftOfficeHub','Microsoft.Office.*','Microsoft.OutlookForWindows')) { $hit = @(Get-AppxPackage -AllUsers -Name $pat -ErrorAction SilentlyContinue) foreach ($p in $hit) { $anyOfficeAppx = $true try { Write-Host "[*] Remove-AppxPackage $($p.PackageFullName)" Remove-AppxPackage -AllUsers -Package $p.PackageFullName -ErrorAction Stop Record $p.Name 'removed' } catch { Write-Host "[!] $($p.Name) failed: $($_.Exception.Message)" Record $p.Name 'failed-install' $_.Exception.Message } } } if ((-not (Test-Path $c2rClient)) -and (-not $anyOfficeAppx)) { Write-Host "[=] No Office / M365 traces found - nothing to do" } } Write-Host "" Write-Host "===== Summary =====" $byResult = $results | Group-Object Result | Sort-Object Name foreach ($g in $byResult) { Write-Host ("{0,-15}: {1}" -f $g.Name, $g.Count) } $problem = $results | Where-Object { $_.Result -in 'failed-install','still-present' } if ($problem) { Write-Host "" Write-Host "Needs attention:" $problem | ForEach-Object { Write-Host " - $($_.Name) [$($_.Result)] $($_.Detail)" } }