Refactor winget download logic and error handling

Improves the reliability and clarity of the application download process.

- Introduces specific error codes for download failures, such as publisher restrictions from the Microsoft Store.
- Blocks downloading the Company Portal app from the `winget` source due to packaging issues and provides a user-friendly message.
- Refines status messages in the UI for better user feedback.
- Installs the WinGet PowerShell module for all users to prevent context-related issues.
This commit is contained in:
rbalsleyMSFT
2025-07-23 17:28:12 -07:00
parent 298809686b
commit defd744ef0
3 changed files with 187 additions and 32 deletions
@@ -0,0 +1,149 @@
{
"apps": [
{
"name": "Windows Notepad",
"id": "9MSMLRH6LZF3",
"source": "msstore"
},
{
"name": "Windows Client Web Experience",
"id": "9MSSGKG348SP",
"source": "msstore"
},
{
"name": "Screen Sketch",
"id": "9MZ95KL8MR0L",
"source": "msstore"
},
{
"name": "Windows Terminal",
"id": "9N0DX20HK701",
"source": "msstore"
},
{
"name": "VP9 Video Extensions",
"id": "9N4D0MSMP0PT",
"source": "msstore"
},
{
"name": "MPEG2 Video Extension",
"id": "9N95Q1ZZPMH4",
"source": "msstore"
},
{
"name": "Store Purchase App",
"id": "9NBLGGH4LS1F",
"source": "msstore"
},
{
"name": "Windows Feedback Hub",
"id": "9NBLGGH4R32N",
"source": "msstore"
},
{
"name": "Todos",
"id": "9NBLGGH5R558",
"source": "msstore"
},
{
"name": "Raw Image Extension",
"id": "9NCTDW2W1BH8",
"source": "msstore"
},
{
"name": "Power Automate Desktop",
"id": "9NFTCH6J7FHV",
"source": "msstore"
},
{
"name": "Xbox TCUI",
"id": "9NKNC0LD5NN6",
"source": "msstore"
},
{
"name": "Your Phone",
"id": "9NMPJ99VJBWV",
"source": "msstore"
},
{
"name": "Xbox Gaming Overlay",
"id": "9NZKPSTSNW4P",
"source": "msstore"
},
{
"name": "Clipchamp",
"id": "9P1J8S7CCWWT",
"source": "msstore"
},
{
"name": "Paint",
"id": "9PCFS5B6T72H",
"source": "msstore"
},
{
"name": "Webp Image Extension",
"id": "9PG2DK419DRG",
"source": "msstore"
},
{
"name": "Get Help",
"id": "9PKDZBMV1H3T",
"source": "msstore"
},
{
"name": "HEIF Image Extension",
"id": "9PMMSR1CGPWG",
"source": "msstore"
},
{
"name": "Xbox Identity Provider",
"id": "9WZDNCRD1HKW",
"source": "msstore"
},
{
"name": "Microsoft Office Hub",
"id": "9WZDNCRD29V9",
"source": "msstore"
},
{
"name": "Bing News",
"id": "9WZDNCRFHVFW",
"source": "msstore"
},
{
"name": "Windows Sound Recorder",
"id": "9WZDNCRFHWKN",
"source": "msstore"
},
{
"name": "Windows Alarms",
"id": "9WZDNCRFJ3PR",
"source": "msstore"
},
{
"name": "Zune Music",
"id": "9WZDNCRFJ3PT",
"source": "msstore"
},
{
"name": "Bing Weather",
"id": "9WZDNCRFJ3Q2",
"source": "msstore"
},
{
"name": "Windows Camera",
"id": "9WZDNCRFJBBG",
"source": "msstore"
},
{
"name": "Windows Photos",
"id": "9WZDNCRFJBH4",
"source": "msstore"
},
{
"name": "Windows Store",
"id": "9WZDNCRFJBMP",
"source": "msstore"
}
]
}
@@ -25,6 +25,13 @@ function Get-Application {
[string]$OrchestrationPath [string]$OrchestrationPath
) )
# Block Company Portal from winget source
# I refuse to code around the poor packaging of this app
if ($AppId -eq 'Microsoft.CompanyPortal' -and $Source -eq 'winget') {
WriteLog "Skipping download of Company Portal from the 'winget' source. This version has packaging inconsistencies. Please use the 'msstore' source instead."
return 4 # Return specific error code for this case
}
# Determine base folder path for checking existence # Determine base folder path for checking existence
$appIsWin32ForCheck = ($Source -eq 'msstore' -and $AppId.StartsWith("XP")) $appIsWin32ForCheck = ($Source -eq 'msstore' -and $AppId.StartsWith("XP"))
$appBaseFolderPathForCheck = "" $appBaseFolderPathForCheck = ""
@@ -100,32 +107,29 @@ function Get-Application {
if ($wingetDownloadResult.status -eq 'NoApplicableInstallers' -or $wingetDownloadResult.status -eq 'NoApplicableInstallerFound') { if ($wingetDownloadResult.status -eq 'NoApplicableInstallers' -or $wingetDownloadResult.status -eq 'NoApplicableInstallerFound') {
WriteLog "No installer found for $arch architecture. Attempting to download without specifying architecture..." WriteLog "No installer found for $arch architecture. Attempting to download without specifying architecture..."
$wingetDownloadResult = Export-WinGetPackage -id $AppId -DownloadDirectory $appFolderPath -Source $Source $wingetDownloadResult = Export-WinGetPackage -id $AppId -DownloadDirectory $appFolderPath -Source $Source
if ($wingetDownloadResult.status -eq 'Ok') {
WriteLog "Downloaded $AppName without specifying architecture."
} }
else {
WriteLog "ERROR: No installer found for $AppName. Exiting" # Re-evaluate status after potential second attempt
Remove-Item -Path $appFolderPath -Recurse -Force if ($wingetDownloadResult.status -ne 'Ok') {
return 1 # Return error code # Handle Store-specific publisher restriction error
} if ($Source -eq 'msstore' -and $wingetDownloadResult.ExtendedErrorCode -match '0x8A150084') {
} $errorMessage = "The Microsoft Store app $AppName does not support downloads by the publisher. Please remove it from the AppList.json. If there's a winget source version of the application, try using that instead. Exiting."
# Handle Store-specific errors
elseif ($Source -eq 'msstore') {
# If download not supported by publisher
if ($wingetDownloadResult.ExtendedErrorCode -match '0x8A150084') {
$errorMessage = "ERROR: The Microsoft Store app $AppName does not support downloads by the publisher. Please remove it from the AppList.json. If there's a winget source version of the application, try using that instead. Exiting."
WriteLog $errorMessage WriteLog $errorMessage
Remove-Item -Path $appFolderPath -Recurse -Force Remove-Item -Path $appFolderPath -Recurse -Force
Write-Error $errorMessage Write-Error $errorMessage
return 1 # Return error code return 3 # Return specific error code for publisher restriction
}
} }
# Handle other download failures
else { else {
$errormsg = "ERROR: Download failed for $AppName with status: $($wingetDownloadResult.status) $($wingetDownloadResult.ExtendedErrorCode)" $errormsg = "Download failed for $AppName with status: $($wingetDownloadResult.status) $($wingetDownloadResult.ExtendedErrorCode)"
WriteLog $errormsg WriteLog $errormsg
Remove-Item -Path $appFolderPath -Recurse -Force Remove-Item -Path $appFolderPath -Recurse -Force
Write-Error $errormsg Write-Error $errormsg
return 1 # Return error code return 1 # Return generic error code
}
}
else {
WriteLog "Downloaded $AppName without specifying architecture."
} }
} }
@@ -245,7 +245,7 @@ function Install-WingetComponents {
} }
# Install/Update the module # Install/Update the module
Install-Module -Name Microsoft.WinGet.Client -Force -Repository 'PSGallery' Install-Module -Name Microsoft.WinGet.Client -Force -Repository 'PSGallery' -Scope AllUsers
# Restore original PSGallery trust setting # Restore original PSGallery trust setting
if ($PSGalleryTrust -eq 'Untrusted') { if ($PSGalleryTrust -eq 'Untrusted') {
@@ -400,7 +400,7 @@ function Start-WingetAppDownloadTask {
} }
else { else {
$appFound = $true $appFound = $true
$status = "Error: App in '$userAppListPath' but content missing/small in '$appFolder'. Copy content or remove from UserAppList.json." $status = "App in '$userAppListPath' but content missing/small in '$appFolder'. Copy content or remove from UserAppList.json."
Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status
WriteLog $status WriteLog $status
return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 1 } return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 1 }
@@ -408,7 +408,7 @@ function Start-WingetAppDownloadTask {
} }
else { else {
$appFound = $true $appFound = $true
$status = "Error: App in '$userAppListPath' but content folder '$appFolder' not found. Copy content or remove from UserAppList.json." $status = "App in '$userAppListPath' but content folder '$appFolder' not found. Copy content or remove from UserAppList.json."
Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status
WriteLog $status WriteLog $status
return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 1 } return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 1 }
@@ -473,7 +473,7 @@ function Start-WingetAppDownloadTask {
else { else {
# App entry exists in WinGetWin32Apps.json but folder is missing or incomplete # App entry exists in WinGetWin32Apps.json but folder is missing or incomplete
$appFound = $true $appFound = $true
$status = "Error: App in '$wingetWin32jsonFile' but content folder '$appFolder' not found or incomplete. Remove entry from WinGetWin32Apps.json or restore content." $status = "App in '$wingetWin32jsonFile' but content folder '$appFolder' not found or incomplete. Remove entry from WinGetWin32Apps.json or restore content."
Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status
WriteLog $status WriteLog $status
return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 1 } return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 1 }
@@ -571,7 +571,7 @@ function Start-WingetAppDownloadTask {
} }
catch { catch {
WriteLog "Error saving '$AppListJsonPath'. Error: $($_.Exception.Message)" WriteLog "Error saving '$AppListJsonPath'. Error: $($_.Exception.Message)"
$status = "Error saving AppList.json" $status = "Failed to save AppList.json: $($_.Exception.Message)"
Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status
return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 1 } return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 1 }
} }
@@ -609,8 +609,10 @@ function Start-WingetAppDownloadTask {
# Determine status based on result code # Determine status based on result code
switch ($resultCode) { switch ($resultCode) {
0 { $status = "Downloaded successfully" } 0 { $status = "Downloaded successfully" }
1 { $status = "Error: No win32 app installers were found" } 1 { $status = "Error: No app installers were found" }
2 { $status = "Silent install switch could not be found. Did not download." } 2 { $status = "Silent install switch could not be found. Did not download." }
3 { $status = "Error: Publisher does not support download" }
4 { $status = "Skipped: Use 'msstore' source instead." }
default { $status = "Downloaded with status: $resultCode" } # Should not happen with current Get-Application default { $status = "Downloaded with status: $resultCode" } # Should not happen with current Get-Application
} }
@@ -633,7 +635,7 @@ function Start-WingetAppDownloadTask {
} }
} }
catch { catch {
$status = "Error: $($_.Exception.Message)" $status = $_.Exception.Message
WriteLog "Download error for $($appName): $($_.Exception.Message)" WriteLog "Download error for $($appName): $($_.Exception.Message)"
$resultCode = 1 # Indicate error $resultCode = 1 # Indicate error
# Enqueue error status # Enqueue error status
@@ -662,7 +664,7 @@ function Start-WingetAppDownloadTask {
} }
catch { catch {
$status = "Error: $($_.Exception.Message)" $status = $_.Exception.Message
WriteLog "Unexpected error in Start-WingetAppDownloadTask for $($appName): $($_.Exception.Message)" WriteLog "Unexpected error in Start-WingetAppDownloadTask for $($appName): $($_.Exception.Message)"
$resultCode = 1 # Indicate error $resultCode = 1 # Indicate error
# Enqueue error status # Enqueue error status
@@ -671,7 +673,7 @@ function Start-WingetAppDownloadTask {
finally { finally {
# Ensure status is not empty before returning # Ensure status is not empty before returning
if ([string]::IsNullOrEmpty($status)) { if ([string]::IsNullOrEmpty($status)) {
$status = "Error: Unknown failure" # Provide a default error status $status = "Unknown failure" # Provide a default error status
WriteLog "Status was empty for $appName ($appId), setting to default error." WriteLog "Status was empty for $appName ($appId), setting to default error."
if ($resultCode -ne 0 -and $resultCode -ne 1 -and $resultCode -ne 2) { if ($resultCode -ne 0 -and $resultCode -ne 1 -and $resultCode -ne 2) {
$resultCode = -1 # Ensure resultCode reflects an error if it was empty $resultCode = -1 # Ensure resultCode reflects an error if it was empty