Files
Win11Debloat/Scripts/AppRemoval/GetInstalledAppsViaWinget.ps1

110 lines
3.3 KiB
PowerShell
Raw Permalink Normal View History

<#
.SYNOPSIS
Returns a list of installed apps from winget as structured objects.
.DESCRIPTION
Runs `winget list` and parses the output into PSCustomObject arrays.
Use -NonBlocking to keep the UI responsive in GUI mode; otherwise
runs synchronously with an optional timeout.
.PARAMETER TimeOut
Maximum seconds to wait for winget to complete. Default is 10.
.PARAMETER NonBlocking
When set, runs via Invoke-NonBlocking so the GUI thread stays responsive.
.OUTPUTS
PSCustomObject[] with Name and Id properties. Returns $null on
failure, or an empty array when winget succeeds but lists no apps.
#>
function GetInstalledAppsViaWinget {
param (
[int]$TimeOut = 10,
[switch]$NonBlocking
)
if (-not $script:WingetInstalled) { return $null }
$fetchBlock = {
param($timeOut)
$job = Start-Job {
$rawOutput = $null
try {
$originalEncoding = [Console]::OutputEncoding
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
try {
$rawOutput = winget list --accept-source-agreements --disable-interactivity
}
finally {
[Console]::OutputEncoding = $originalEncoding
}
return $rawOutput
}
catch {
return $null
}
}
$done = $job | Wait-Job -Timeout $timeOut
if ($done) {
$result = Receive-Job -Job $job
Remove-Job -Job $job -ErrorAction SilentlyContinue
if (-not $result) { return $null }
# winget list outputs:
# [progress line] / [blank] / header / --- separator / data rows
$textOutput = $result -join "`n"
$lines = $textOutput -split "`r`n|`n"
# Find the separator line to know where data starts
$dataStart = -1
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^-{3,}') {
$dataStart = $i + 1
break
}
}
if ($dataStart -lt 0 -or $dataStart -ge $lines.Count) { return @() }
$apps = [System.Collections.Generic.List[object]]::new()
for ($i = $dataStart; $i -lt $lines.Count; $i++) {
$line = $lines[$i]
if ($line.Trim() -eq '') { continue }
try {
# Split on 2+ spaces; extract Name and Id columns.
$fields = [regex]::Split($line.Trim(), '\s{2,}')
if ($fields.Count -lt 2) { continue }
$name = $fields[0].Trim()
$id = $fields[1].Trim()
if (-not $id) { continue }
$null = $apps.Add([PSCustomObject]@{
Name = $name
Id = $id
})
}
catch {
# Skip lines that can't be parsed
}
}
return @($apps)
}
Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
return $null
}
if ($NonBlocking) {
return Invoke-NonBlocking -ScriptBlock $fetchBlock -ArgumentList $TimeOut
}
else {
return & $fetchBlock $TimeOut
}
}