Refactor WinGet app handling and add command overrides

Improves the reliability of WinGet app processing by making several key changes.

The build process now deletes the `WinGetWin32Apps.json` file before each run to ensure it is always freshly generated.

The UI no longer relies on `WinGetWin32Apps.json` to detect previously downloaded content. Instead, it checks directly for the application's content on disk, preventing unnecessary re-downloads.

This change also introduces a feature allowing users to override the default silent install commands for Win32 apps. By specifying `CommandLine` or `Arguments` properties in `AppList.json`, these values will be used to update the corresponding entries in `WinGetWin32Apps.json` during the build process.
This commit is contained in:
rbalsleyMSFT
2025-08-21 16:46:17 -07:00
parent 9aed707a77
commit 7d4567efbe
3 changed files with 123 additions and 65 deletions
+7
View File
@@ -5871,6 +5871,13 @@ if ($AllowVHDXCaching) {
throw $_
}
}
# Remove WinGetWin32Apps.json so it is always rebuilt next run
if (Test-Path -Path $wingetWin32jsonFile -PathType Leaf) {
WriteLog "Removing $wingetWin32jsonFile"
Remove-Item -Path $wingetWin32jsonFile -Force -ErrorAction SilentlyContinue
WriteLog "Removal complete"
}
#Set $LongPathsEnabled registry value back to original value. $LongPathsEnabled could be $null if the registry value was not found
if ($null -eq $LongPathsEnabled) {
Remove-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -ErrorAction SilentlyContinue
@@ -22,7 +22,8 @@ function Get-Application {
[Parameter(Mandatory = $true)]
[string]$WindowsArch,
[Parameter(Mandatory = $true)]
[string]$OrchestrationPath
[string]$OrchestrationPath,
[switch]$SkipWin32Json
)
# Block Company Portal from winget source
@@ -48,6 +49,29 @@ function Get-Application {
# Check if the folder is not empty.
if (Get-ChildItem -Path $appBaseFolderPathForCheck -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1) {
WriteLog "Application '$AppName' appears to be already downloaded as content exists in '$appBaseFolderPathForCheck'. Skipping download."
# Add silent install command(s) only if not skipping JSON generation (build-time scenario)
$appIsWin32Existing = ($Source -eq 'winget' -or ($Source -eq 'msstore' -and $AppId.StartsWith('XP')))
if ($appIsWin32Existing -and -not $SkipWin32Json) {
$win32BasePath = Join-Path -Path "$AppsPath\Win32" -ChildPath $AppName
if (Test-Path -Path $win32BasePath -PathType Container) {
$archFolders = Get-ChildItem -Path $win32BasePath -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -in @('x86', 'x64', 'arm64') }
if ($archFolders) {
foreach ($archFolder in $archFolders) {
WriteLog "Adding silent install command for pre-downloaded $AppName ($($archFolder.Name)) to $OrchestrationPath\WinGetWin32Apps.json"
Add-Win32SilentInstallCommand -AppFolder $AppName -AppFolderPath $archFolder.FullName -OrchestrationPath $OrchestrationPath -SubFolder $archFolder.Name | Out-Null
}
}
else {
WriteLog "Adding silent install command for pre-downloaded $AppName to $OrchestrationPath\WinGetWin32Apps.json"
Add-Win32SilentInstallCommand -AppFolder $AppName -AppFolderPath $win32BasePath -OrchestrationPath $OrchestrationPath | Out-Null
}
}
}
elseif ($appIsWin32Existing -and $SkipWin32Json) {
WriteLog "Skipping WinGetWin32Apps.json regeneration for pre-downloaded $AppName (UI mode)."
}
return 0 # Success, already present
}
}
@@ -196,8 +220,14 @@ function Get-Application {
}
# If app is in Win32 folder, add the silent install command to the WinGetWin32Apps.json file
elseif ($appFolderPath -match 'Win32') {
WriteLog "$AppName is a Win32 app. Adding silent install command to $OrchestrationPath\WinGetWin32Apps.json"
$result = Add-Win32SilentInstallCommand -AppFolder $AppName -AppFolderPath $appFolderPath -OrchestrationPath $OrchestrationPath -SubFolder $subFolderForCommand
if (-not $SkipWin32Json) {
WriteLog "$AppName is a Win32 app. Adding silent install command to $OrchestrationPath\WinGetWin32Apps.json"
$result = Add-Win32SilentInstallCommand -AppFolder $AppName -AppFolderPath $appFolderPath -OrchestrationPath $OrchestrationPath -SubFolder $subFolderForCommand
}
else {
WriteLog "$AppName is a Win32 app. Skipping WinGetWin32Apps.json generation (UI mode)."
$result = 0
}
}
else {
# For any other case, set result to 0 (success)
@@ -314,7 +344,7 @@ function Get-Apps {
if (-not (Test-Path -Path $storeAppsFolder -PathType Container)) {
New-Item -Path $storeAppsFolder -ItemType Directory -Force | Out-Null
}
foreach ($storeApp in $StoreApps) {
try {
$appArch = if ($storeApp.PSObject.Properties['architecture']) { $storeApp.architecture } else { $WindowsArch }
@@ -326,6 +356,62 @@ function Get-Apps {
}
}
}
# Post-processing: Override CommandLine / Arguments from AppList.json if provided
# Users may supply custom silent install commands or arguments. These optional
# properties (CommandLine, Arguments) in AppList.json replace the auto-generated
# values in WinGetWin32Apps.json. Keyed by Name.
try {
$overrideMap = @{}
foreach ($app in $apps.apps) {
if ($app.source -in @('winget', 'msstore')) {
$hasCmd = ($app.PSObject.Properties['CommandLine'] -and -not [string]::IsNullOrWhiteSpace($app.CommandLine))
$hasArgs = ($app.PSObject.Properties['Arguments'] -and -not [string]::IsNullOrWhiteSpace($app.Arguments))
if ($hasCmd -or $hasArgs) {
$overrideMap[$app.name] = @{
CommandLine = if ($hasCmd) { $app.CommandLine } else { $null }
Arguments = if ($hasArgs) { $app.Arguments } else { $null }
}
}
}
}
if ($overrideMap.Count -gt 0) {
$winGetWin32Path = Join-Path -Path $OrchestrationPath -ChildPath 'WinGetWin32Apps.json'
if (Test-Path -Path $winGetWin32Path) {
[array]$appsDataUpdated = Get-Content -Path $winGetWin32Path -Raw | ConvertFrom-Json
$changed = $false
foreach ($entry in $appsDataUpdated) {
if ($overrideMap.ContainsKey($entry.Name)) {
$ov = $overrideMap[$entry.Name]
if ($ov.CommandLine) {
WriteLog "Override (AppList.json) CommandLine for $($entry.Name)"
$entry.CommandLine = $ov.CommandLine
$changed = $true
}
if ($ov.Arguments) {
WriteLog "Override (AppList.json) Arguments for $($entry.Name)"
$entry.Arguments = $ov.Arguments
$changed = $true
}
}
}
if ($changed) {
$appsDataUpdated | ConvertTo-Json -Depth 10 | Set-Content -Path $winGetWin32Path
WriteLog "Applied AppList.json command overrides to WinGetWin32Apps.json"
}
else {
WriteLog "No matching apps required command overrides."
}
}
else {
WriteLog "WinGetWin32Apps.json not found; no overrides applied."
}
}
}
catch {
WriteLog "Failed to apply AppList.json command overrides: $($_.Exception.Message)"
}
}
function Install-WinGet {
param (
@@ -450,70 +450,35 @@ function Start-WingetAppDownloadTask {
}
}
# 2. Check previous Winget download
if (-not $appFound) {
if (-not $appFound) {
$wingetWin32jsonFile = Join-Path -Path $OrchestrationPath -ChildPath "WinGetWin32Apps.json"
if (Test-Path -Path $wingetWin32jsonFile) {
try {
$wingetAppsJson = Get-Content -Path $wingetWin32jsonFile -Raw | ConvertFrom-Json
# Check if app already exists in WinGetWin32Apps.json
# For multi-arch apps, there might be entries like "AppName (x86)" and "AppName (x64)"
$existingWin32Entries = @($wingetAppsJson | Where-Object {
$_.Name -eq $appName -or
$_.Name -eq "$appName (x86)" -or
$_.Name -eq "$appName (x64)"
})
if ($existingWin32Entries.Count -gt 0) {
$appFolder = Join-Path -Path "$AppsPath\Win32" -ChildPath $appName
$appContentFound = $false
# Check if it's a multi-arch app with subfolders
if ($ApplicationItemData.Architecture -eq 'x86 x64') {
$x86Folder = Join-Path -Path $appFolder -ChildPath "x86"
$x64Folder = Join-Path -Path $appFolder -ChildPath "x64"
if ((Test-Path -Path $x86Folder -PathType Container) -and (Test-Path -Path $x64Folder -PathType Container)) {
$x86Size = (Get-ChildItem -Path $x86Folder -Recurse | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
$x64Size = (Get-ChildItem -Path $x64Folder -Recurse | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
if ($x86Size -gt 1MB -and $x64Size -gt 1MB) {
$appContentFound = $true
}
}
}
else {
# Single architecture app
if (Test-Path -Path $appFolder -PathType Container) {
$folderSize = (Get-ChildItem -Path $appFolder -Recurse | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
if ($folderSize -gt 1MB) {
$appContentFound = $true
}
}
}
if ($appContentFound) {
$appFound = $true
$status = "Not Downloaded: App already in $wingetWin32jsonFile and found in $appFolder"
Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status
WriteLog "Found '$appName' in WinGetWin32Apps.json and content exists in '$appFolder'. Skipping download to prevent duplicate entry."
return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 0 }
}
else {
# App entry exists in WinGetWin32Apps.json but folder is missing or incomplete
$appFound = $true
$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
WriteLog $status
return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 1 }
}
# 2. Check existing downloaded Win32 content (folder-based; no WinGetWin32Apps.json dependency)
if (-not $appFound -and $source -eq 'winget') {
$appFolder = Join-Path -Path "$AppsPath\Win32" -ChildPath $appName
if (Test-Path -Path $appFolder -PathType Container) {
$contentFound = $false
if ($ApplicationItemData.Architecture -eq 'x86 x64') {
$x86Folder = Join-Path -Path $appFolder -ChildPath "x86"
$x64Folder = Join-Path -Path $appFolder -ChildPath "x64"
if ((Test-Path -Path $x86Folder -PathType Container) -and (Test-Path -Path $x64Folder -PathType Container)) {
$x86Size = (Get-ChildItem -Path $x86Folder -Recurse | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
$x64Size = (Get-ChildItem -Path $x64Folder -Recurse | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
if ($x86Size -gt 1MB -and $x64Size -gt 1MB) {
$contentFound = $true
}
}
catch {
WriteLog "Warning: Could not read or parse '$wingetWin32jsonFile'. Error: $($_.Exception.Message)"
}
else {
$folderSize = (Get-ChildItem -Path $appFolder -Recurse | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
if ($folderSize -gt 1MB) {
$contentFound = $true
}
}
if ($contentFound) {
$appFound = $true
$status = "Not Downloaded: Existing content found in $appFolder"
Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appId -Status $status
WriteLog "Found existing content for '$appName' in '$appFolder'. Skipping download to prevent duplicate entry."
return [PSCustomObject]@{ Id = $appId; Status = $status; ResultCode = 0 }
}
}
}
@@ -634,7 +599,7 @@ function Start-WingetAppDownloadTask {
try {
# Call Get-Application
$resultCode = Get-Application -AppName $appName -AppId $appId -Source $source -AppsPath $AppsPath -WindowsArch $ApplicationItemData.Architecture -OrchestrationPath $OrchestrationPath -ErrorAction Stop
$resultCode = Get-Application -AppName $appName -AppId $appId -Source $source -AppsPath $AppsPath -WindowsArch $ApplicationItemData.Architecture -OrchestrationPath $OrchestrationPath -SkipWin32Json -ErrorAction Stop
# Determine status based on result code
switch ($resultCode) {