mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
feat: Add per-app architecture selection and multi-arch downloads
Introduces the ability to specify and download multiple architectures for a single application. - Adds an "Architecture" dropdown column to the Winget UI, allowing users to select the desired architecture(s) for each app (e.g., x86, x64, arm64, or 'x86 x64'). - Updates the download logic to process each specified architecture, creating separate subfolders for multi-architecture Win32 apps. - Modifies the app list format to save and load the selected architecture for each application. - Improves the download process by checking if an application has already been downloaded before attempting a new download.
This commit is contained in:
@@ -157,12 +157,14 @@ function Invoke-ParallelProcessing {
|
|||||||
switch ($localTaskType) {
|
switch ($localTaskType) {
|
||||||
'WingetDownload' {
|
'WingetDownload' {
|
||||||
# Pass the progress queue to the task function
|
# Pass the progress queue to the task function
|
||||||
$taskResult = Start-WingetAppDownloadTask -ApplicationItemData $currentItem `
|
$wingetTaskArgs = @{
|
||||||
-AppListJsonPath $localJobArgs['AppListJsonPath'] `
|
ApplicationItemData = $currentItem
|
||||||
-AppsPath $localJobArgs['AppsPath'] `
|
AppListJsonPath = $localJobArgs['AppListJsonPath']
|
||||||
-WindowsArch $localJobArgs['WindowsArch'] `
|
AppsPath = $localJobArgs['AppsPath']
|
||||||
-OrchestrationPath $localJobArgs['OrchestrationPath'] `
|
OrchestrationPath = $localJobArgs['OrchestrationPath']
|
||||||
-ProgressQueue $localProgressQueue
|
ProgressQueue = $localProgressQueue
|
||||||
|
}
|
||||||
|
$taskResult = Start-WingetAppDownloadTask @wingetTaskArgs
|
||||||
if ($null -ne $taskResult) {
|
if ($null -ne $taskResult) {
|
||||||
$resultIdentifier = $taskResult.Id
|
$resultIdentifier = $taskResult.Id
|
||||||
$resultStatus = $taskResult.Status
|
$resultStatus = $taskResult.Status
|
||||||
|
|||||||
@@ -25,6 +25,26 @@ function Get-Application {
|
|||||||
[string]$OrchestrationPath
|
[string]$OrchestrationPath
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Determine base folder path for checking existence
|
||||||
|
$appIsWin32ForCheck = ($Source -eq 'msstore' -and $AppId.StartsWith("XP"))
|
||||||
|
$appBaseFolderPathForCheck = ""
|
||||||
|
if ($Source -eq 'winget' -or $appIsWin32ForCheck) {
|
||||||
|
$appBaseFolderPathForCheck = Join-Path -Path "$AppsPath\Win32" -ChildPath $AppName
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$appBaseFolderPathForCheck = Join-Path -Path "$AppsPath\MSStore" -ChildPath $AppName
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if the app (any architecture) has already been downloaded by checking for its content folder.
|
||||||
|
# This prevents re-downloading if BuildFFUVM.ps1 is run after downloading via the UI.
|
||||||
|
if (Test-Path -Path $appBaseFolderPathForCheck -PathType Container) {
|
||||||
|
# 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."
|
||||||
|
return 0 # Success, already present
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Validate app exists in repository
|
# Validate app exists in repository
|
||||||
$wingetSearchResult = Find-WinGetPackage -id $AppId -MatchOption Equals -Source $Source
|
$wingetSearchResult = Find-WinGetPackage -id $AppId -MatchOption Equals -Source $Source
|
||||||
if (-not $wingetSearchResult) {
|
if (-not $wingetSearchResult) {
|
||||||
@@ -34,137 +54,153 @@ function Get-Application {
|
|||||||
}
|
}
|
||||||
WriteLog "$AppName not found in $Source repository."
|
WriteLog "$AppName not found in $Source repository."
|
||||||
WriteLog "Check the AppList.json file and make sure the AppID is correct."
|
WriteLog "Check the AppList.json file and make sure the AppID is correct."
|
||||||
Exit 1
|
return 1 # Return error code
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine app type and folder path
|
# Determine architectures to download
|
||||||
$appIsWin32 = ($Source -eq 'msstore' -and $AppId.StartsWith("XP"))
|
$architecturesToDownload = if ($WindowsArch -eq 'x86 x64') { @('x86', 'x64') } else { @($WindowsArch) }
|
||||||
if ($Source -eq 'winget' -or $appIsWin32) {
|
$overallResult = 0
|
||||||
$appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $AppName
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$appFolderPath = Join-Path -Path "$AppsPath\MSStore" -ChildPath $AppName
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create app folder
|
foreach ($arch in $architecturesToDownload) {
|
||||||
New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null
|
WriteLog "Processing '$AppName' for architecture '$arch'."
|
||||||
|
|
||||||
# Log download information
|
# Determine app type and folder path
|
||||||
WriteLog "Downloading $AppName for $WindowsArch architecture..."
|
$appIsWin32 = ($Source -eq 'msstore' -and $AppId.StartsWith("XP"))
|
||||||
if ($Source -eq 'msstore') {
|
if ($Source -eq 'winget' -or $appIsWin32) {
|
||||||
WriteLog 'MSStore app downloads require authentication with an Entra ID account. You may be prompted twice for credentials, once for the app and another for the license file.'
|
$appBaseFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $AppName
|
||||||
}
|
|
||||||
WriteLog "WinGet command: Export-WinGetPackage -id $AppId -DownloadDirectory $appFolderPath -Architecture $WindowsArch -Source $Source"
|
|
||||||
|
|
||||||
# Download the app
|
|
||||||
$wingetDownloadResult = Export-WinGetPackage -id $AppId -DownloadDirectory $appFolderPath -Architecture $WindowsArch -Source $Source
|
|
||||||
|
|
||||||
# Handle download status
|
|
||||||
if ($wingetDownloadResult.status -ne 'Ok') {
|
|
||||||
# Try downloading without architecture if no applicable installer found
|
|
||||||
if ($wingetDownloadResult.status -eq 'NoApplicableInstallers' -or $wingetDownloadResult.status -eq 'NoApplicableInstallerFound') {
|
|
||||||
WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..."
|
|
||||||
$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"
|
|
||||||
Remove-Item -Path $appFolderPath -Recurse -Force
|
|
||||||
Exit 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Handle Store-specific errors
|
|
||||||
elseif ($Source -eq 'msstore') {
|
|
||||||
# If download not supported by publisher
|
|
||||||
if ($wingetDownloadResult.ExtendedErrorCode -match '0x8A150084') {
|
|
||||||
WriteLog "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."
|
|
||||||
Remove-Item -Path $appFolderPath -Recurse -Force
|
|
||||||
Write-Error "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."
|
|
||||||
Exit 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$errormsg = "ERROR: Download failed for $AppName with status: $($wingetDownloadResult.status) $($wingetDownloadResult.ExtendedErrorCode)"
|
$appBaseFolderPath = Join-Path -Path "$AppsPath\MSStore" -ChildPath $AppName
|
||||||
WriteLog $errormsg
|
|
||||||
Remove-Item -Path $appFolderPath -Recurse -Force
|
|
||||||
Write-Error $errormsg
|
|
||||||
Exit 1
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
WriteLog "$AppName downloaded to $appFolderPath"
|
# If downloading multiple archs for a Win32 app, create a subfolder
|
||||||
|
$appFolderPath = $appBaseFolderPath
|
||||||
|
$subFolderForCommand = $null
|
||||||
|
if ($architecturesToDownload.Count -gt 1 -and ($Source -eq 'winget' -or $appIsWin32)) {
|
||||||
|
$appFolderPath = Join-Path -Path $appBaseFolderPath -ChildPath $arch
|
||||||
|
$subFolderForCommand = $arch
|
||||||
|
}
|
||||||
|
|
||||||
# Handle winget source apps that have appx, appxbundle, msix, or msixbundle extensions but were downloaded to the Win32 folder
|
# Create app folder
|
||||||
$installerPath = Get-ChildItem -Path "$appFolderPath\*" -Exclude "*.yaml", "*.xml" -File -ErrorAction Stop
|
New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null
|
||||||
$uwpExtensions = @(".appx", ".appxbundle", ".msix", ".msixbundle")
|
|
||||||
|
|
||||||
if ($uwpExtensions -contains $installerPath.Extension -and $appFolderPath -match 'Win32') {
|
# Log download information
|
||||||
# Handle UWP apps
|
WriteLog "Downloading $AppName for $arch architecture..."
|
||||||
$NewAppPath = "$AppsPath\MSStore\$AppName"
|
if ($Source -eq 'msstore') {
|
||||||
WriteLog "$AppName is a UWP app. Moving to $NewAppPath"
|
WriteLog 'MSStore app downloads require authentication with an Entra ID account. You may be prompted twice for credentials, once for the app and another for the license file.'
|
||||||
WriteLog "Creating $NewAppPath"
|
}
|
||||||
New-Item -Path "$AppsPath\MSStore\$AppName" -ItemType Directory -Force | Out-Null
|
WriteLog "WinGet command: Export-WinGetPackage -id $AppId -DownloadDirectory `"$appFolderPath`" -Architecture $arch -Source $Source"
|
||||||
WriteLog "Moving $AppName to $NewAppPath"
|
|
||||||
Move-Item -Path "$appFolderPath\*" -Destination "$AppsPath\MSStore\$AppName" -Force
|
|
||||||
WriteLog "Removing $appFolderPath"
|
|
||||||
Remove-Item -Path $appFolderPath -Force -Recurse
|
|
||||||
WriteLog "$AppName moved to $NewAppPath"
|
|
||||||
# Set-InstallStoreAppsFlag
|
|
||||||
$result = 0 # Success for UWP app
|
|
||||||
}
|
|
||||||
# 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
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# For any other case, set result to 0 (success)
|
|
||||||
$result = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Handle MSStore specific post-processing
|
# Download the app
|
||||||
if ($Source -eq 'msstore' -and $appFolderPath -match 'MSStore') {
|
$wingetDownloadResult = Export-WinGetPackage -id $AppId -DownloadDirectory $appFolderPath -Architecture $arch -Source $Source
|
||||||
# Set-InstallStoreAppsFlag
|
|
||||||
|
|
||||||
# Handle ARM64-specific dependencies
|
# Handle download status
|
||||||
if ($WindowsArch -eq 'ARM64') {
|
if ($wingetDownloadResult.status -ne 'Ok') {
|
||||||
WriteLog 'Windows architecture is ARM64. Removing dependencies that are not ARM64.'
|
# Try downloading without architecture if no applicable installer found
|
||||||
$dependencies = Get-ChildItem -Path "$appFolderPath\Dependencies" -ErrorAction SilentlyContinue
|
if ($wingetDownloadResult.status -eq 'NoApplicableInstallers' -or $wingetDownloadResult.status -eq 'NoApplicableInstallerFound') {
|
||||||
if ($dependencies) {
|
WriteLog "No installer found for $arch architecture. Attempting to download without specifying architecture..."
|
||||||
foreach ($dependency in $dependencies) {
|
$wingetDownloadResult = Export-WinGetPackage -id $AppId -DownloadDirectory $appFolderPath -Source $Source
|
||||||
if ($dependency.Name -notmatch 'ARM64') {
|
if ($wingetDownloadResult.status -eq 'Ok') {
|
||||||
WriteLog "Removing dependency file $($dependency.FullName)"
|
WriteLog "Downloaded $AppName without specifying architecture."
|
||||||
Remove-Item -Path $dependency.FullName -Recurse -Force
|
}
|
||||||
|
else {
|
||||||
|
WriteLog "ERROR: No installer found for $AppName. Exiting"
|
||||||
|
Remove-Item -Path $appFolderPath -Recurse -Force
|
||||||
|
return 1 # Return error code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# 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
|
||||||
|
Remove-Item -Path $appFolderPath -Recurse -Force
|
||||||
|
Write-Error $errorMessage
|
||||||
|
return 1 # Return error code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$errormsg = "ERROR: Download failed for $AppName with status: $($wingetDownloadResult.status) $($wingetDownloadResult.ExtendedErrorCode)"
|
||||||
|
WriteLog $errormsg
|
||||||
|
Remove-Item -Path $appFolderPath -Recurse -Force
|
||||||
|
Write-Error $errormsg
|
||||||
|
return 1 # Return error code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLog "$AppName ($arch) downloaded to $appFolderPath"
|
||||||
|
|
||||||
|
# Handle winget source apps that have appx, appxbundle, msix, or msixbundle extensions but were downloaded to the Win32 folder
|
||||||
|
$installerPath = Get-ChildItem -Path "$appFolderPath\*" -Exclude "*.yaml", "*.xml" -File -ErrorAction Stop
|
||||||
|
$uwpExtensions = @(".appx", ".appxbundle", ".msix", ".msixbundle")
|
||||||
|
|
||||||
|
if ($uwpExtensions -contains $installerPath.Extension -and $appFolderPath -match 'Win32') {
|
||||||
|
# Handle UWP apps
|
||||||
|
$NewAppPath = "$AppsPath\MSStore\$AppName"
|
||||||
|
WriteLog "$AppName is a UWP app. Moving to $NewAppPath"
|
||||||
|
WriteLog "Creating $NewAppPath"
|
||||||
|
New-Item -Path "$AppsPath\MSStore\$AppName" -ItemType Directory -Force | Out-Null
|
||||||
|
WriteLog "Moving $AppName to $NewAppPath"
|
||||||
|
Move-Item -Path "$appFolderPath\*" -Destination "$AppsPath\MSStore\$AppName" -Force
|
||||||
|
WriteLog "Removing $appFolderPath"
|
||||||
|
Remove-Item -Path $appFolderPath -Force -Recurse
|
||||||
|
WriteLog "$AppName moved to $NewAppPath"
|
||||||
|
$result = 0 # Success for UWP app
|
||||||
|
}
|
||||||
|
# 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
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# For any other case, set result to 0 (success)
|
||||||
|
$result = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result -ne 0) { $overallResult = $result }
|
||||||
|
|
||||||
|
# Handle MSStore specific post-processing
|
||||||
|
if ($Source -eq 'msstore' -and $appFolderPath -match 'MSStore') {
|
||||||
|
# Handle ARM64-specific dependencies
|
||||||
|
if ($arch -eq 'ARM64') {
|
||||||
|
WriteLog 'Windows architecture is ARM64. Removing dependencies that are not ARM64.'
|
||||||
|
$dependencies = Get-ChildItem -Path "$appFolderPath\Dependencies" -ErrorAction SilentlyContinue
|
||||||
|
if ($dependencies) {
|
||||||
|
foreach ($dependency in $dependencies) {
|
||||||
|
if ($dependency.Name -notmatch 'ARM64') {
|
||||||
|
WriteLog "Removing dependency file $($dependency.FullName)"
|
||||||
|
Remove-Item -Path $dependency.FullName -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean up multiple versions (keep only the latest)
|
||||||
|
WriteLog "$AppName has completed downloading. Identifying the latest version of $AppName."
|
||||||
|
$packages = Get-ChildItem -Path "$appFolderPath\*" -Exclude "Dependencies\*", "*.xml", "*.yaml" -File -ErrorAction Stop
|
||||||
|
|
||||||
|
# Find latest version based on signature date
|
||||||
|
$latestPackage = $packages | Sort-Object { (Get-AuthenticodeSignature $_.FullName).SignerCertificate.NotBefore } -Descending | Select-Object -First 1
|
||||||
|
|
||||||
|
# Remove older versions
|
||||||
|
WriteLog "Latest version of $AppName has been identified as $latestPackage. Removing old versions of $AppName that may have downloaded."
|
||||||
|
foreach ($package in $packages) {
|
||||||
|
if ($package.FullName -ne $latestPackage.FullName) {
|
||||||
|
try {
|
||||||
|
WriteLog "Removing $($package.FullName)"
|
||||||
|
Remove-Item -Path $package.FullName -Force
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
WriteLog "Failed to delete: $($package.FullName) - $_"
|
||||||
|
throw $_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} # End foreach ($arch in $architecturesToDownload)
|
||||||
|
|
||||||
# Clean up multiple versions (keep only the latest)
|
return $overallResult
|
||||||
WriteLog "$AppName has completed downloading. Identifying the latest version of $AppName."
|
|
||||||
$packages = Get-ChildItem -Path "$appFolderPath\*" -Exclude "Dependencies\*", "*.xml", "*.yaml" -File -ErrorAction Stop
|
|
||||||
|
|
||||||
# Find latest version based on signature date
|
|
||||||
$latestPackage = $packages | Sort-Object { (Get-AuthenticodeSignature $_.FullName).SignerCertificate.NotBefore } -Descending | Select-Object -First 1
|
|
||||||
|
|
||||||
# Remove older versions
|
|
||||||
WriteLog "Latest version of $AppName has been identified as $latestPackage. Removing old versions of $AppName that may have downloaded."
|
|
||||||
foreach ($package in $packages) {
|
|
||||||
if ($package.FullName -ne $latestPackage) {
|
|
||||||
try {
|
|
||||||
WriteLog "Removing $($package.FullName)"
|
|
||||||
Remove-Item -Path $package.FullName -Force
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
WriteLog "Failed to delete: $($package.FullName) - $_"
|
|
||||||
throw $_
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result
|
|
||||||
}
|
}
|
||||||
function Get-Apps {
|
function Get-Apps {
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
@@ -217,7 +253,8 @@ function Get-Apps {
|
|||||||
|
|
||||||
foreach ($wingetApp in $wingetApps) {
|
foreach ($wingetApp in $wingetApps) {
|
||||||
try {
|
try {
|
||||||
Get-Application -AppName $wingetApp.Name -AppId $wingetApp.Id -Source 'winget' -AppsPath $AppsPath -WindowsArch $WindowsArch -OrchestrationPath $OrchestrationPath
|
$appArch = if ($wingetApp.PSObject.Properties['architecture']) { $wingetApp.architecture } else { $WindowsArch }
|
||||||
|
Get-Application -AppName $wingetApp.Name -AppId $wingetApp.Id -Source 'winget' -AppsPath $AppsPath -WindowsArch $appArch -OrchestrationPath $OrchestrationPath
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
WriteLog "Error occurred while processing $($wingetApp.Name): $_"
|
WriteLog "Error occurred while processing $($wingetApp.Name): $_"
|
||||||
@@ -234,7 +271,8 @@ function Get-Apps {
|
|||||||
|
|
||||||
foreach ($storeApp in $StoreApps) {
|
foreach ($storeApp in $StoreApps) {
|
||||||
try {
|
try {
|
||||||
Get-Application -AppName $storeApp.Name -AppId $storeApp.Id -Source 'msstore' -AppsPath $AppsPath -WindowsArch $WindowsArch -OrchestrationPath $OrchestrationPath
|
$appArch = if ($storeApp.PSObject.Properties['architecture']) { $storeApp.architecture } else { $WindowsArch }
|
||||||
|
Get-Application -AppName $storeApp.Name -AppId $storeApp.Id -Source 'msstore' -AppsPath $AppsPath -WindowsArch $appArch -OrchestrationPath $OrchestrationPath
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
WriteLog "Error occurred while processing $($storeApp.Name): $_"
|
WriteLog "Error occurred while processing $($storeApp.Name): $_"
|
||||||
@@ -321,7 +359,8 @@ function Add-Win32SilentInstallCommand {
|
|||||||
[string]$AppFolder,
|
[string]$AppFolder,
|
||||||
[string]$AppFolderPath,
|
[string]$AppFolderPath,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$OrchestrationPath
|
[string]$OrchestrationPath,
|
||||||
|
[string]$SubFolder
|
||||||
)
|
)
|
||||||
$appName = $AppFolder
|
$appName = $AppFolder
|
||||||
$installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include "*.exe", "*.msi" -File -ErrorAction Stop
|
$installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include "*.exe", "*.msi" -File -ErrorAction Stop
|
||||||
@@ -339,12 +378,18 @@ function Add-Win32SilentInstallCommand {
|
|||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
$installer = Split-Path -Path $installerPath -Leaf
|
$installer = Split-Path -Path $installerPath -Leaf
|
||||||
|
|
||||||
|
$basePath = "D:\win32\$AppFolder"
|
||||||
|
if (-not [string]::IsNullOrEmpty($SubFolder)) {
|
||||||
|
$basePath = "$basePath\$SubFolder"
|
||||||
|
}
|
||||||
|
|
||||||
if ($installerPath.Extension -eq ".exe") {
|
if ($installerPath.Extension -eq ".exe") {
|
||||||
$silentInstallCommand = "D:\win32\$appFolder\$installer"
|
$silentInstallCommand = "$basePath\$installer"
|
||||||
}
|
}
|
||||||
elseif ($installerPath.Extension -eq ".msi") {
|
elseif ($installerPath.Extension -eq ".msi") {
|
||||||
$silentInstallCommand = "msiexec"
|
$silentInstallCommand = "msiexec"
|
||||||
$silentInstallSwitch = "/i `"D:\win32\$appFolder\$installer`" $silentInstallSwitch"
|
$silentInstallSwitch = "/i `"$basePath\$installer`" $silentInstallSwitch"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Path to the JSON file
|
# Path to the JSON file
|
||||||
@@ -367,7 +412,7 @@ function Add-Win32SilentInstallCommand {
|
|||||||
# Create new app entry
|
# Create new app entry
|
||||||
$newApp = [PSCustomObject]@{
|
$newApp = [PSCustomObject]@{
|
||||||
Priority = $highestPriority
|
Priority = $highestPriority
|
||||||
Name = $appName
|
Name = if (-not [string]::IsNullOrEmpty($SubFolder)) { "$appName ($SubFolder)" } else { $appName }
|
||||||
CommandLine = $silentInstallCommand
|
CommandLine = $silentInstallCommand
|
||||||
Arguments = $silentInstallSwitch
|
Arguments = $silentInstallSwitch
|
||||||
}
|
}
|
||||||
@@ -375,7 +420,7 @@ function Add-Win32SilentInstallCommand {
|
|||||||
$appsData += $newApp
|
$appsData += $newApp
|
||||||
$appsData | ConvertTo-Json -Depth 10 | Set-Content -Path $wingetWin32AppsJson
|
$appsData | ConvertTo-Json -Depth 10 | Set-Content -Path $wingetWin32AppsJson
|
||||||
|
|
||||||
WriteLog "Added $appName to WinGetWin32Apps.json with priority $highestPriority"
|
WriteLog "Added $($newApp.Name) to WinGetWin32Apps.json with priority $highestPriority"
|
||||||
|
|
||||||
# Return 0 for success
|
# Return 0 for success
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -384,8 +384,48 @@ function Initialize-DynamicUIElements {
|
|||||||
Add-SortableColumn -gridView $wingetGridView -header "Id" -binding "Id" -width 200 -headerHorizontalAlignment Left
|
Add-SortableColumn -gridView $wingetGridView -header "Id" -binding "Id" -width 200 -headerHorizontalAlignment Left
|
||||||
Add-SortableColumn -gridView $wingetGridView -header "Version" -binding "Version" -width 100 -headerHorizontalAlignment Left
|
Add-SortableColumn -gridView $wingetGridView -header "Version" -binding "Version" -width 100 -headerHorizontalAlignment Left
|
||||||
Add-SortableColumn -gridView $wingetGridView -header "Source" -binding "Source" -width 100 -headerHorizontalAlignment Left
|
Add-SortableColumn -gridView $wingetGridView -header "Source" -binding "Source" -width 100 -headerHorizontalAlignment Left
|
||||||
Add-SortableColumn -gridView $wingetGridView -header "Download Status" -binding "DownloadStatus" -width 150 -headerHorizontalAlignment Left
|
|
||||||
|
|
||||||
|
# --- START: Add Architecture Column ---
|
||||||
|
$archColumn = New-Object System.Windows.Controls.GridViewColumn
|
||||||
|
$archHeader = New-Object System.Windows.Controls.GridViewColumnHeader
|
||||||
|
$archHeader.Tag = "Architecture" # For sorting
|
||||||
|
$archHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left
|
||||||
|
|
||||||
|
# Create header content with correct padding to match other columns
|
||||||
|
$commonPaddingForHeader = New-Object System.Windows.Thickness(5, 2, 5, 2)
|
||||||
|
$headerTextElementFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBlock])
|
||||||
|
$headerTextElementFactory.SetValue([System.Windows.Controls.TextBlock]::TextProperty, "Architecture")
|
||||||
|
$headerTextBlockPadding = New-Object System.Windows.Thickness($commonPaddingForHeader.Left, $commonPaddingForHeader.Top, $commonPaddingForHeader.Right, $commonPaddingForHeader.Bottom)
|
||||||
|
$headerTextElementFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, $headerTextBlockPadding)
|
||||||
|
$headerTextElementFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center)
|
||||||
|
|
||||||
|
$headerDataTemplate = New-Object System.Windows.DataTemplate
|
||||||
|
$headerDataTemplate.VisualTree = $headerTextElementFactory
|
||||||
|
$archHeader.ContentTemplate = $headerDataTemplate
|
||||||
|
|
||||||
|
$archColumn.Header = $archHeader
|
||||||
|
$archColumn.Width = 120
|
||||||
|
|
||||||
|
# Create the CellTemplate with a ComboBox
|
||||||
|
$archCellTemplate = New-Object System.Windows.DataTemplate
|
||||||
|
$comboBoxFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.ComboBox])
|
||||||
|
|
||||||
|
# The ItemsSource for the ComboBox
|
||||||
|
$availableArchitectures = @('x86', 'x64', 'arm64', 'x86 x64')
|
||||||
|
$comboBoxFactory.SetValue([System.Windows.Controls.ItemsControl]::ItemsSourceProperty, $availableArchitectures)
|
||||||
|
|
||||||
|
# Bind the text property to the 'Architecture' property of the data item.
|
||||||
|
# This ensures the initial value is displayed correctly.
|
||||||
|
$binding = New-Object System.Windows.Data.Binding("Architecture")
|
||||||
|
$binding.Mode = [System.Windows.Data.BindingMode]::TwoWay
|
||||||
|
$comboBoxFactory.SetBinding([System.Windows.Controls.ComboBox]::TextProperty, $binding)
|
||||||
|
|
||||||
|
$archCellTemplate.VisualTree = $comboBoxFactory
|
||||||
|
$archColumn.CellTemplate = $archCellTemplate
|
||||||
|
$wingetGridView.Columns.Add($archColumn)
|
||||||
|
# --- END: Add Architecture Column ---
|
||||||
|
|
||||||
|
Add-SortableColumn -gridView $wingetGridView -header "Download Status" -binding "DownloadStatus" -width 150 -headerHorizontalAlignment Left
|
||||||
$State.Controls.lstWingetResults.AddHandler(
|
$State.Controls.lstWingetResults.AddHandler(
|
||||||
[System.Windows.Controls.GridViewColumnHeader]::ClickEvent,
|
[System.Windows.Controls.GridViewColumnHeader]::ClickEvent,
|
||||||
[System.Windows.RoutedEventHandler] {
|
[System.Windows.RoutedEventHandler] {
|
||||||
|
|||||||
@@ -27,9 +27,12 @@ function Search-WingetApps {
|
|||||||
# Store selected apps from the current view
|
# Store selected apps from the current view
|
||||||
$selectedAppsFromView = @($currentItemsInListView | Where-Object { $_.IsSelected })
|
$selectedAppsFromView = @($currentItemsInListView | Where-Object { $_.IsSelected })
|
||||||
|
|
||||||
|
# Get default architecture from the UI
|
||||||
|
$defaultArch = $State.Controls.cmbWindowsArch.SelectedItem
|
||||||
|
|
||||||
# Search for new apps, which are streamed directly as PSCustomObjects
|
# Search for new apps, which are streamed directly as PSCustomObjects
|
||||||
# with the required properties for performance.
|
# with the required properties for performance.
|
||||||
$searchedAppResults = Search-WingetPackagesPublic -Query $searchQuery
|
$searchedAppResults = Search-WingetPackagesPublic -Query $searchQuery -DefaultArchitecture $defaultArch
|
||||||
WriteLog "Found $($searchedAppResults.Count) apps matching query '$searchQuery'."
|
WriteLog "Found $($searchedAppResults.Count) apps matching query '$searchQuery'."
|
||||||
|
|
||||||
$finalAppList = [System.Collections.Generic.List[object]]::new()
|
$finalAppList = [System.Collections.Generic.List[object]]::new()
|
||||||
@@ -73,9 +76,10 @@ function Save-WingetList {
|
|||||||
$appList = @{
|
$appList = @{
|
||||||
apps = @($selectedApps | ForEach-Object {
|
apps = @($selectedApps | ForEach-Object {
|
||||||
[ordered]@{
|
[ordered]@{
|
||||||
name = $_.Name
|
name = $_.Name
|
||||||
id = $_.Id
|
id = $_.Id
|
||||||
source = $_.Source.ToLower()
|
source = $_.Source.ToLower()
|
||||||
|
architecture = $_.Architecture
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -116,6 +120,9 @@ function Import-WingetList {
|
|||||||
$newAppListForItemsSource = [System.Collections.Generic.List[object]]::new()
|
$newAppListForItemsSource = [System.Collections.Generic.List[object]]::new()
|
||||||
|
|
||||||
if ($null -ne $importedAppsData.apps) {
|
if ($null -ne $importedAppsData.apps) {
|
||||||
|
# Get default architecture from the UI for fallback
|
||||||
|
$defaultArch = $State.Controls.cmbWindowsArch.SelectedItem
|
||||||
|
|
||||||
foreach ($appInfo in $importedAppsData.apps) {
|
foreach ($appInfo in $importedAppsData.apps) {
|
||||||
$newAppListForItemsSource.Add([PSCustomObject]@{
|
$newAppListForItemsSource.Add([PSCustomObject]@{
|
||||||
IsSelected = $true # Imported apps are marked as selected
|
IsSelected = $true # Imported apps are marked as selected
|
||||||
@@ -123,6 +130,7 @@ function Import-WingetList {
|
|||||||
Id = $appInfo.id
|
Id = $appInfo.id
|
||||||
Version = "" # Will be populated when searching or if data exists
|
Version = "" # Will be populated when searching or if data exists
|
||||||
Source = $appInfo.source
|
Source = $appInfo.source
|
||||||
|
Architecture = if ($appInfo.PSObject.Properties['architecture']) { $appInfo.architecture } else { $defaultArch }
|
||||||
DownloadStatus = ""
|
DownloadStatus = ""
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -145,7 +153,9 @@ function Search-WingetPackagesPublic {
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$Query
|
[string]$Query,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$DefaultArchitecture
|
||||||
)
|
)
|
||||||
|
|
||||||
WriteLog "Searching Winget packages with query: '$Query'"
|
WriteLog "Searching Winget packages with query: '$Query'"
|
||||||
@@ -160,6 +170,7 @@ function Search-WingetPackagesPublic {
|
|||||||
Id,
|
Id,
|
||||||
Version,
|
Version,
|
||||||
Source,
|
Source,
|
||||||
|
@{Name = 'Architecture'; Expression = { $DefaultArchitecture } },
|
||||||
@{Name = 'DownloadStatus'; Expression = { '' } }
|
@{Name = 'DownloadStatus'; Expression = { '' } }
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -342,8 +353,6 @@ function Start-WingetAppDownloadTask {
|
|||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$AppsPath, # Pass necessary paths
|
[string]$AppsPath, # Pass necessary paths
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$WindowsArch,
|
|
||||||
[Parameter(Mandatory = $true)]
|
|
||||||
[string]$OrchestrationPath,
|
[string]$OrchestrationPath,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[System.Collections.Concurrent.ConcurrentQueue[hashtable]]$ProgressQueue # Add queue parameter
|
[System.Collections.Concurrent.ConcurrentQueue[hashtable]]$ProgressQueue # Add queue parameter
|
||||||
@@ -361,7 +370,7 @@ function Start-WingetAppDownloadTask {
|
|||||||
WriteLog "Starting download task for $($appName) with ID $($appId) from source $($source)."
|
WriteLog "Starting download task for $($appName) with ID $($appId) from source $($source)."
|
||||||
# WriteLog "Apps Path: $($AppsPath)"
|
# WriteLog "Apps Path: $($AppsPath)"
|
||||||
# WriteLog "AppList JSON Path: $($AppListJsonPath)"
|
# WriteLog "AppList JSON Path: $($AppListJsonPath)"
|
||||||
# WriteLog "Windows Architecture: $($WindowsArch)"
|
# WriteLog "Windows Architecture: $($ApplicationItemData.Architecture)"
|
||||||
# WriteLog "Orchestration Path: $($OrchestrationPath)"
|
# WriteLog "Orchestration Path: $($OrchestrationPath)"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -449,7 +458,7 @@ function Start-WingetAppDownloadTask {
|
|||||||
}
|
}
|
||||||
# For now, assuming Get-Application uses $global variables set in the main script or $using: scope.
|
# For now, assuming Get-Application uses $global variables set in the main script or $using: scope.
|
||||||
# $global:AppsPath = $AppsPath # Potentially redundant if set globally before parallel call
|
# $global:AppsPath = $AppsPath # Potentially redundant if set globally before parallel call
|
||||||
# $global:WindowsArch = $WindowsArch # Potentially redundant
|
# $global:WindowsArch = $ApplicationItemData.Architecture # Potentially redundant
|
||||||
# $global:orchestrationPath = $OrchestrationPath # Potentially redundant
|
# $global:orchestrationPath = $OrchestrationPath # Potentially redundant
|
||||||
|
|
||||||
$wingetWin32jsonFile = Join-Path -Path $OrchestrationPath -ChildPath "WinGetWin32Apps.json"
|
$wingetWin32jsonFile = Join-Path -Path $OrchestrationPath -ChildPath "WinGetWin32Apps.json"
|
||||||
@@ -577,7 +586,7 @@ function Start-WingetAppDownloadTask {
|
|||||||
# Ensure variables needed by Get-Application are accessible
|
# Ensure variables needed by Get-Application are accessible
|
||||||
# (Assuming they are available via $using: scope or global scope from main script)
|
# (Assuming they are available via $using: scope or global scope from main script)
|
||||||
# $global:AppsPath = $AppsPath # Potentially redundant
|
# $global:AppsPath = $AppsPath # Potentially redundant
|
||||||
# $global:WindowsArch = $WindowsArch # Potentially redundant
|
# $global:WindowsArch = $ApplicationItemData.Architecture # Potentially redundant
|
||||||
# $global:orchestrationPath = $OrchestrationPath # Potentially redundant"
|
# $global:orchestrationPath = $OrchestrationPath # Potentially redundant"
|
||||||
WriteLog "Orchestration Path: $($OrchestrationPath)"
|
WriteLog "Orchestration Path: $($OrchestrationPath)"
|
||||||
if (-not (Test-Path -Path $OrchestrationPath -PathType Container)) {
|
if (-not (Test-Path -Path $OrchestrationPath -PathType Container)) {
|
||||||
@@ -594,7 +603,7 @@ function Start-WingetAppDownloadTask {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
# Call Get-Application
|
# Call Get-Application
|
||||||
$resultCode = Get-Application -AppName $appName -AppId $appId -Source $source -AppsPath $AppsPath -WindowsArch $WindowsArch -OrchestrationPath $OrchestrationPath -ErrorAction Stop
|
$resultCode = Get-Application -AppName $appName -AppId $appId -Source $source -AppsPath $AppsPath -WindowsArch $ApplicationItemData.Architecture -OrchestrationPath $OrchestrationPath -ErrorAction Stop
|
||||||
|
|
||||||
# Determine status based on result code
|
# Determine status based on result code
|
||||||
switch ($resultCode) {
|
switch ($resultCode) {
|
||||||
@@ -713,13 +722,11 @@ function Invoke-WingetDownload {
|
|||||||
$taskArguments = @{
|
$taskArguments = @{
|
||||||
AppsPath = $localAppsPath
|
AppsPath = $localAppsPath
|
||||||
AppListJsonPath = $localAppListJsonPath
|
AppListJsonPath = $localAppListJsonPath
|
||||||
WindowsArch = $localWindowsArch
|
|
||||||
OrchestrationPath = $localOrchestrationPath
|
OrchestrationPath = $localOrchestrationPath
|
||||||
}
|
}
|
||||||
|
|
||||||
# Select only necessary properties before passing to Invoke-ParallelProcessing
|
# Select only necessary properties before passing to Invoke-ParallelProcessing
|
||||||
$itemsToProcess = $selectedApps | Select-Object Name, Id, Source, Version # Include Version if needed
|
$itemsToProcess = $selectedApps | Select-Object Name, Id, Source, Version, Architecture # Include Version and Architecture if needed
|
||||||
|
|
||||||
# Invoke the centralized parallel processing function
|
# Invoke the centralized parallel processing function
|
||||||
# Pass task type and task-specific arguments
|
# Pass task type and task-specific arguments
|
||||||
Invoke-ParallelProcessing -ItemsToProcess $itemsToProcess `
|
Invoke-ParallelProcessing -ItemsToProcess $itemsToProcess `
|
||||||
|
|||||||
Reference in New Issue
Block a user