diff --git a/README.md b/README.md index c9b5d1d..81fdead 100644 --- a/README.md +++ b/README.md @@ -83,5 +83,7 @@ C:\ProgramData\__Softwarekatalog\Failed\ ## Notes - Requires Windows, Desktop App Installer, and `winget`. +- Catalog installs, uninstalls, exports, and automatic upgrades explicitly target + the `winget` community source, so an unavailable Microsoft Store source does not block them. - SYSTEM scheduled tasks validate and execute requests; keep catalog policy restricted to trusted admins. - Package IDs and silent machine-scope support depend on upstream `winget` packages. diff --git a/setup.ps1 b/setup.ps1 index fde3a10..be116ab 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -67,6 +67,7 @@ $DaemonTaskName = 'Softwarekatalog - Daemon' $RequestTaskName = 'Softwarekatalog - Requests' $QueueTaskName = 'Softwarekatalog - Queue' $UpgradeTaskName = 'Softwarekatalog - Winget Upgrade' +$WingetSourceName = 'winget' $WingetMutexName = 'Global\Softwarekatalog-Winget' $QueueStateMutexName = 'Global\Softwarekatalog-QueueState' $PowerShellExe = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' @@ -926,13 +927,82 @@ function Get-ProgressPercentFromLine { param([string]$Line) if ([string]::IsNullOrWhiteSpace($Line)) { return $null } + $match = [regex]::Match($Line, '(?\d+(?:[\.,]\d+)?)\s*(?TB|GB|MB|KB|B)\s*/\s*(?\d+(?:[\.,]\d+)?)\s*(?TB|GB|MB|KB|B)', + [System.Text.RegularExpressions.RegexOptions]::IgnoreCase + ) + if (-not $transferMatch.Success) { return $null } + + try { + $culture = [System.Globalization.CultureInfo]::InvariantCulture + $current = [double]::Parse($transferMatch.Groups['current'].Value.Replace(',', '.'), $culture) + $total = [double]::Parse($transferMatch.Groups['total'].Value.Replace(',', '.'), $culture) + + switch ($transferMatch.Groups['currentUnit'].Value.ToUpperInvariant()) { + 'TB' { $current *= 1099511627776 } + 'GB' { $current *= 1073741824 } + 'MB' { $current *= 1048576 } + 'KB' { $current *= 1024 } + } + switch ($transferMatch.Groups['totalUnit'].Value.ToUpperInvariant()) { + 'TB' { $total *= 1099511627776 } + 'GB' { $total *= 1073741824 } + 'MB' { $total *= 1048576 } + 'KB' { $total *= 1024 } + } + + if ($total -le 0) { return $null } + $value = [int][Math]::Round(($current / $total) * 100) + if ($value -lt 0) { return 0 } + if ($value -gt 100) { return 100 } + return $value + } + catch { + return $null + } +} + +function ConvertTo-WingetOutputLine { + param([string]$Line) + + if ([string]::IsNullOrWhiteSpace($Line)) { return $null } + + $text = [regex]::Replace($Line, '\x1B\[[0-?]*[ -/]*[@-~]', '').Trim() + if ([string]::IsNullOrWhiteSpace($text)) { return $null } + + $transferMatch = [regex]::Match( + $text, + '\d+(?:[\.,]\d+)?\s*(?:TB|GB|MB|KB|B)\s*/\s*\d+(?:[\.,]\d+)?\s*(?:TB|GB|MB|KB|B)', + [System.Text.RegularExpressions.RegexOptions]::IgnoreCase + ) + if ($transferMatch.Success) { + return $transferMatch.Value + } + + # Older winget versions emit spinner and block-bar frames even when stdout is + # redirected. Ignore lines that contain no useful ASCII text; this also drops + # progress glyphs decoded through an OEM code page (for example, "Ôûê"). + if ($text -notmatch '[A-Za-z0-9]') { return $null } + return $text +} + +function Set-WingetOutputEncoding { + try { + [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false) + } + catch { + # Some non-interactive hosts do not expose a mutable console encoding. + } } function Update-QueueItemProgress { @@ -2731,6 +2801,7 @@ function Invoke-WingetInstall { $arguments = @( 'install' '--id', $PackageId + '--source', $WingetSourceName '--exact' '--scope', 'machine' '--silent' @@ -2744,17 +2815,16 @@ function Invoke-WingetInstall { Update-QueueItemProgress -Id $QueueItemId -Stage 'Install' -StageText 'Installation läuft...' -IsIndeterminate:$true } - $outputLines = New-Object 'System.Collections.Generic.List[string]' + Set-WingetOutputEncoding & $WingetPath @arguments 2>&1 | ForEach-Object { $line = [string]$_ - [void]$outputLines.Add($line) - if ($line.Trim()) { - Write-Log "winget: $line" - if ($QueueItemId) { - $percent = Get-ProgressPercentFromLine -Line $line - if ($null -ne $percent) { - Update-QueueItemProgress -Id $QueueItemId -Stage 'Download' -StageText ("Download läuft... {0}%" -f $percent) -ProgressPercent $percent -IsIndeterminate:$false - } + $displayLine = ConvertTo-WingetOutputLine -Line $line + if ($displayLine) { Write-Log "winget: $displayLine" } + + if ($QueueItemId) { + $percent = Get-ProgressPercentFromLine -Line $line + if ($null -ne $percent) { + Update-QueueItemProgress -Id $QueueItemId -Stage 'Download' -StageText ("Download läuft... {0}%" -f $percent) -ProgressPercent $percent -IsIndeterminate:$false } } } @@ -2773,6 +2843,7 @@ function Invoke-WingetUninstall { $arguments = @( 'uninstall' '--id', $PackageId + '--source', $WingetSourceName '--exact' '--scope', 'machine' '--silent' @@ -2785,17 +2856,16 @@ function Invoke-WingetUninstall { Update-QueueItemProgress -Id $QueueItemId -Stage 'Uninstall' -StageText 'Deinstallation läuft...' -IsIndeterminate:$true } - $outputLines = New-Object 'System.Collections.Generic.List[string]' + Set-WingetOutputEncoding & $WingetPath @arguments 2>&1 | ForEach-Object { $line = [string]$_ - [void]$outputLines.Add($line) - if ($line.Trim()) { - Write-Log "winget: $line" - if ($QueueItemId) { - $percent = Get-ProgressPercentFromLine -Line $line - if ($null -ne $percent) { - Update-QueueItemProgress -Id $QueueItemId -Stage 'Uninstall' -StageText ("Deinstallation läuft... {0}%" -f $percent) -ProgressPercent $percent -IsIndeterminate:$false - } + $displayLine = ConvertTo-WingetOutputLine -Line $line + if ($displayLine) { Write-Log "winget: $displayLine" } + + if ($QueueItemId) { + $percent = Get-ProgressPercentFromLine -Line $line + if ($null -ne $percent) { + Update-QueueItemProgress -Id $QueueItemId -Stage 'Uninstall' -StageText ("Deinstallation läuft... {0}%" -f $percent) -ProgressPercent $percent -IsIndeterminate:$false } } } @@ -2813,6 +2883,7 @@ function Invoke-WingetUpgradeAll { $arguments = @( 'upgrade' '--all' + '--source', $WingetSourceName '--scope', 'machine' '--silent' '--accept-package-agreements' @@ -2825,17 +2896,16 @@ function Invoke-WingetUpgradeAll { Update-QueueItemProgress -Id $QueueItemId -Stage 'Upgrade' -StageText 'Updates laufen...' -IsIndeterminate:$true } - $outputLines = New-Object 'System.Collections.Generic.List[string]' + Set-WingetOutputEncoding & $WingetPath @arguments 2>&1 | ForEach-Object { $line = [string]$_ - [void]$outputLines.Add($line) - if ($line.Trim()) { - Write-Log "winget upgrade: $line" - if ($QueueItemId) { - $percent = Get-ProgressPercentFromLine -Line $line - if ($null -ne $percent) { - Update-QueueItemProgress -Id $QueueItemId -Stage 'Upgrade' -StageText ("Updates laufen... {0}%" -f $percent) -ProgressPercent $percent -IsIndeterminate:$false - } + $displayLine = ConvertTo-WingetOutputLine -Line $line + if ($displayLine) { Write-Log "winget upgrade: $displayLine" } + + if ($QueueItemId) { + $percent = Get-ProgressPercentFromLine -Line $line + if ($null -ne $percent) { + Update-QueueItemProgress -Id $QueueItemId -Stage 'Upgrade' -StageText ("Updates laufen... {0}%" -f $percent) -ProgressPercent $percent -IsIndeterminate:$false } } } @@ -2859,6 +2929,7 @@ function Update-InstalledPackagesSnapshot { $arguments = @( 'export' '--output', $tempFile + '--source', $WingetSourceName '--accept-source-agreements' '--disable-interactivity' ) @@ -2867,16 +2938,27 @@ function Update-InstalledPackagesSnapshot { if ($QueueItemId) { Update-QueueItemProgress -Id $QueueItemId -Stage 'Snapshot' -StageText 'Installationsdaten werden aktualisiert...' -IsIndeterminate:$true } + Set-WingetOutputEncoding $output = & $WingetPath @arguments 2>&1 | Out-String $exitCode = $LASTEXITCODE + $unmatchedPackageCount = 0 if ($output) { foreach ($line in ($output -split "`r?`n")) { - if ($line.Trim()) { - Write-Log "winget export: $line" + $displayLine = ConvertTo-WingetOutputLine -Line $line + if (-not $displayLine) { continue } + + if ($displayLine -match '^Installed package is not available from (?:any )?source:') { + $unmatchedPackageCount++ + continue } + + Write-Log "winget export: $displayLine" } } + if ($unmatchedPackageCount -gt 0) { + Write-Log "winget export ignored $unmatchedPackageCount installed packages that are not represented in source '$WingetSourceName'." + } if ($exitCode -ne 0) { Write-Log "winget export failed with code $exitCode; snapshot was not updated." 'WARN'