mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Allow custom BYO app list file paths in UI
Updates the FFU UI and orchestration scripts to allow users to specify custom file paths for their Bring Your Own (BYO) app lists, rather than forcing the use of `UserAppList.json` in a specific directory. Also modifies the orchestration to sync this custom path via `AppInstallConfig.json` so that the runtime orchestration phase resolves the correct file name and path during installation. Refreshes the Apps ISO if the custom BYO app list is updated.
This commit is contained in:
@@ -27,6 +27,22 @@ function Update-BYOAppsActionButtonsState {
|
||||
}
|
||||
}
|
||||
|
||||
# Function to resolve the configured BYO app list path
|
||||
function Get-BYOApplicationListPath {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[psobject]$State
|
||||
)
|
||||
|
||||
# Fall back to the legacy default path when the textbox is empty.
|
||||
if (-not [string]::IsNullOrWhiteSpace($State.Controls.txtUserAppListPath.Text)) {
|
||||
return $State.Controls.txtUserAppListPath.Text
|
||||
}
|
||||
|
||||
return (Join-Path -Path $State.Controls.txtApplicationPath.Text -ChildPath 'UserAppList.json')
|
||||
}
|
||||
|
||||
# Function to remove all selected BYO applications
|
||||
function Remove-SelectedBYOApplications {
|
||||
[CmdletBinding()]
|
||||
@@ -76,10 +92,10 @@ function Remove-SelectedBYOApplications {
|
||||
}
|
||||
|
||||
# Ask user if they want to save the changes
|
||||
$result = [System.Windows.MessageBox]::Show("The selected applications have been removed from the list. Do you want to save these changes to UserAppList.json now?", "Save Changes", [System.Windows.MessageBoxButton]::YesNo, [System.Windows.MessageBoxImage]::Question)
|
||||
$result = [System.Windows.MessageBox]::Show("The selected applications have been removed from the list. Do you want to save these changes to the configured BYO app list now?", "Save Changes", [System.Windows.MessageBoxButton]::YesNo, [System.Windows.MessageBoxImage]::Question)
|
||||
|
||||
if ($result -eq 'Yes') {
|
||||
$userAppListPath = Join-Path -Path $State.Controls.txtApplicationPath.Text -ChildPath 'UserAppList.json'
|
||||
$userAppListPath = Get-BYOApplicationListPath -State $State
|
||||
Save-BYOApplicationList -Path $userAppListPath -State $State
|
||||
}
|
||||
}
|
||||
@@ -391,18 +407,24 @@ function Invoke-CopyBYOApps {
|
||||
)
|
||||
|
||||
$localAppsPath = $State.Controls.txtApplicationPath.Text
|
||||
$userAppListPath = Join-Path -Path $localAppsPath -ChildPath 'UserAppList.json'
|
||||
$userAppListPath = Get-BYOApplicationListPath -State $State
|
||||
$listView = $State.Controls.lstApplications
|
||||
|
||||
try {
|
||||
# Ensure items are sorted by current priority before saving
|
||||
# Exclude CopyStatus when saving and ensure Priority is an integer; include AdditionalExitCodes and IgnoreNonZeroExitCodes for parity with Save-BYOApplicationList
|
||||
# Ensure the configured BYO app list folder exists before writing the manifest.
|
||||
$userAppListDirectory = Split-Path -Path $userAppListPath -Parent
|
||||
if (-not [string]::IsNullOrWhiteSpace($userAppListDirectory) -and -not (Test-Path -Path $userAppListDirectory -PathType Container)) {
|
||||
New-Item -Path $userAppListDirectory -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
# Ensure items are sorted by current priority before saving.
|
||||
# Exclude CopyStatus when saving and ensure Priority is an integer; include AdditionalExitCodes and IgnoreNonZeroExitCodes for parity with Save-BYOApplicationList.
|
||||
$applications = $listView.Items | Sort-Object Priority | Select-Object @{N = 'Priority'; E = { [int]$_.Priority } }, Name, CommandLine, Arguments, Source, AdditionalExitCodes, IgnoreNonZeroExitCodes
|
||||
$applications | ConvertTo-Json -Depth 5 | Set-Content -Path $userAppListPath -Force -Encoding UTF8
|
||||
WriteLog "Successfully updated UserAppList.json with all applications from the UI."
|
||||
WriteLog "Successfully updated BYO app list at $userAppListPath with all applications from the UI."
|
||||
}
|
||||
catch {
|
||||
$errorMessage = "Failed to update UserAppList.json: $_"
|
||||
$errorMessage = "Failed to update BYO app list at $($userAppListPath): $_"
|
||||
WriteLog $errorMessage
|
||||
[System.Windows.MessageBox]::Show($errorMessage, "Error", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Error)
|
||||
return
|
||||
|
||||
@@ -93,7 +93,7 @@ function Get-UIConfig {
|
||||
UpdateLatestNet = $State.Controls.chkUpdateLatestNet.IsChecked
|
||||
UpdateOneDrive = $State.Controls.chkUpdateOneDrive.IsChecked
|
||||
UpdatePreviewCU = $State.Controls.chkUpdatePreviewCU.IsChecked
|
||||
UserAppListPath = "$($State.Controls.txtApplicationPath.Text)\UserAppList.json"
|
||||
UserAppListPath = $State.Controls.txtUserAppListPath.Text
|
||||
USBDriveList = @{}
|
||||
Username = $State.Controls.txtUsername.Text
|
||||
Threads = [int]$State.Controls.txtThreads.Text
|
||||
@@ -585,6 +585,7 @@ function Update-UIFromConfig {
|
||||
Set-UIValue -ControlName 'chkBringYourOwnApps' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'BringYourOwnApps' -State $State
|
||||
Set-UIValue -ControlName 'txtApplicationPath' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'AppsPath' -State $State
|
||||
Set-UIValue -ControlName 'txtAppListJsonPath' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'AppListPath' -State $State
|
||||
Set-UIValue -ControlName 'txtUserAppListPath' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'UserAppListPath' -State $State
|
||||
|
||||
# Handle AppsScriptVariables
|
||||
$appsScriptVarsKeyExists = $false
|
||||
|
||||
@@ -435,10 +435,18 @@ function Register-EventHandlers {
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
$selectedPath = Invoke-BrowseAction -Type 'OpenFile' -Title "Select AppList.json File" -Filter "JSON files (*.json)|*.json" -AllowNewFile
|
||||
$selectedPath = Invoke-BrowseAction -Type 'OpenFile' -Title "Select Winget AppList File" -Filter "JSON files (*.json)|*.json" -AllowNewFile
|
||||
if ($selectedPath) { $localState.Controls.txtAppListJsonPath.Text = $selectedPath }
|
||||
})
|
||||
|
||||
$State.Controls.btnBrowseUserAppListPath.Add_Click({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
$selectedPath = Invoke-BrowseAction -Type 'OpenFile' -Title "Select BYO AppList File" -Filter "JSON files (*.json)|*.json" -AllowNewFile
|
||||
if ($selectedPath) { $localState.Controls.txtUserAppListPath.Text = $selectedPath }
|
||||
})
|
||||
|
||||
$State.Controls.btnBrowseAppSource.Add_Click({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
@@ -466,17 +474,23 @@ function Register-EventHandlers {
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
|
||||
$initialDir = $localState.Controls.txtApplicationPath.Text
|
||||
# Default the save dialog to the configured BYO app list path.
|
||||
$currentPath = $localState.Controls.txtUserAppListPath.Text
|
||||
$initialDir = if (-not [string]::IsNullOrWhiteSpace($currentPath)) { Split-Path -Path $currentPath -Parent } else { $localState.Controls.txtApplicationPath.Text }
|
||||
if ([string]::IsNullOrWhiteSpace($initialDir) -or -not (Test-Path $initialDir)) { $initialDir = $localState.FFUDevelopmentPath }
|
||||
$fileName = if (-not [string]::IsNullOrWhiteSpace($currentPath)) { Split-Path -Path $currentPath -Leaf } else { "UserAppList.json" }
|
||||
|
||||
$savePath = Invoke-BrowseAction -Type 'SaveFile' `
|
||||
-Title "Save Application List" `
|
||||
-Title "Save BYO App List" `
|
||||
-Filter "JSON files (*.json)|*.json|All files (*.*)|*.*" `
|
||||
-InitialDirectory $initialDir `
|
||||
-FileName "UserAppList.json" `
|
||||
-FileName $fileName `
|
||||
-DefaultExt ".json"
|
||||
|
||||
if ($savePath) { Save-BYOApplicationList -Path $savePath -State $localState }
|
||||
if ($savePath) {
|
||||
$localState.Controls.txtUserAppListPath.Text = $savePath
|
||||
Save-BYOApplicationList -Path $savePath -State $localState
|
||||
}
|
||||
})
|
||||
|
||||
$State.Controls.btnLoadBYOApplications.Add_Click({
|
||||
@@ -484,15 +498,18 @@ function Register-EventHandlers {
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
|
||||
$initialDir = $localState.Controls.txtApplicationPath.Text
|
||||
# Default the import dialog to the configured BYO app list path.
|
||||
$currentPath = $localState.Controls.txtUserAppListPath.Text
|
||||
$initialDir = if (-not [string]::IsNullOrWhiteSpace($currentPath)) { Split-Path -Path $currentPath -Parent } else { $localState.Controls.txtApplicationPath.Text }
|
||||
if ([string]::IsNullOrWhiteSpace($initialDir) -or -not (Test-Path $initialDir)) { $initialDir = $localState.FFUDevelopmentPath }
|
||||
|
||||
$loadPath = Invoke-BrowseAction -Type 'OpenFile' `
|
||||
-Title "Import Application List" `
|
||||
-Title "Import BYO App List" `
|
||||
-Filter "JSON files (*.json)|*.json|All files (*.*)|*.*" `
|
||||
-InitialDirectory $initialDir
|
||||
|
||||
if ($loadPath) {
|
||||
if ($loadPath) {
|
||||
$localState.Controls.txtUserAppListPath.Text = $loadPath
|
||||
Import-BYOApplicationList -Path $loadPath -State $localState
|
||||
Update-CopyButtonState -State $localState
|
||||
}
|
||||
|
||||
@@ -70,8 +70,10 @@ function Initialize-UIControls {
|
||||
$State.Controls.txtWingetModuleVersion = $window.FindName('txtWingetModuleVersion')
|
||||
$State.Controls.applicationPathPanel = $window.FindName('applicationPathPanel')
|
||||
$State.Controls.appListJsonPathPanel = $window.FindName('appListJsonPathPanel')
|
||||
$State.Controls.userAppListPathPanel = $window.FindName('userAppListPathPanel')
|
||||
$State.Controls.btnBrowseApplicationPath = $window.FindName('btnBrowseApplicationPath')
|
||||
$State.Controls.btnBrowseAppListJsonPath = $window.FindName('btnBrowseAppListJsonPath')
|
||||
$State.Controls.btnBrowseUserAppListPath = $window.FindName('btnBrowseUserAppListPath')
|
||||
$State.Controls.chkBringYourOwnApps = $window.FindName('chkBringYourOwnApps')
|
||||
$State.Controls.byoApplicationPanel = $window.FindName('byoApplicationPanel')
|
||||
$State.Controls.wingetSearchPanel = $window.FindName('wingetSearchPanel')
|
||||
@@ -159,6 +161,7 @@ function Initialize-UIControls {
|
||||
$State.Controls.chkUpdatePreviewCU = $window.FindName('chkUpdatePreviewCU')
|
||||
$State.Controls.txtApplicationPath = $window.FindName('txtApplicationPath')
|
||||
$State.Controls.txtAppListJsonPath = $window.FindName('txtAppListJsonPath')
|
||||
$State.Controls.txtUserAppListPath = $window.FindName('txtUserAppListPath')
|
||||
$State.Controls.chkInstallDrivers = $window.FindName('chkInstallDrivers')
|
||||
$State.Controls.chkCopyDrivers = $window.FindName('chkCopyDrivers')
|
||||
$State.Controls.chkCompressDriversToWIM = $window.FindName('chkCompressDriversToWIM')
|
||||
@@ -305,6 +308,7 @@ function Initialize-UIDefaults {
|
||||
$State.Controls.chkInstallApps.IsChecked = $State.Defaults.generalDefaults.InstallApps
|
||||
$State.Controls.txtApplicationPath.Text = $State.Defaults.generalDefaults.ApplicationPath
|
||||
$State.Controls.txtAppListJsonPath.Text = $State.Defaults.generalDefaults.AppListJsonPath
|
||||
$State.Controls.txtUserAppListPath.Text = $State.Defaults.generalDefaults.UserAppListPath
|
||||
$State.Controls.chkInstallWingetApps.IsChecked = $State.Defaults.generalDefaults.InstallWingetApps
|
||||
$State.Controls.chkBringYourOwnApps.IsChecked = $State.Defaults.generalDefaults.BringYourOwnApps
|
||||
|
||||
|
||||
@@ -108,20 +108,28 @@ function Save-WingetList {
|
||||
})
|
||||
}
|
||||
|
||||
# Default the save dialog to the configured Winget app list path.
|
||||
$currentPath = $State.Controls.txtAppListJsonPath.Text
|
||||
$initialDirectory = if (-not [string]::IsNullOrWhiteSpace($currentPath)) { Split-Path -Path $currentPath -Parent } else { $State.Controls.txtApplicationPath.Text }
|
||||
if ([string]::IsNullOrWhiteSpace($initialDirectory) -or -not (Test-Path -Path $initialDirectory -PathType Container)) {
|
||||
$initialDirectory = $State.Controls.txtApplicationPath.Text
|
||||
}
|
||||
$fileName = if (-not [string]::IsNullOrWhiteSpace($currentPath)) { Split-Path -Path $currentPath -Leaf } else { "AppList.json" }
|
||||
|
||||
$sfd = New-Object System.Windows.Forms.SaveFileDialog
|
||||
$sfd.Filter = "JSON files (*.json)|*.json"
|
||||
$sfd.Title = "Save App List"
|
||||
# Correctly get the path from the UI control via the State object
|
||||
$sfd.InitialDirectory = $State.Controls.txtApplicationPath.Text
|
||||
$sfd.FileName = "AppList.json"
|
||||
$sfd.Title = "Save Winget App List"
|
||||
$sfd.InitialDirectory = $initialDirectory
|
||||
$sfd.FileName = $fileName
|
||||
|
||||
if ($sfd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
||||
$appList | ConvertTo-Json -Depth 10 | Set-Content $sfd.FileName -Encoding UTF8
|
||||
[System.Windows.MessageBox]::Show("App list saved successfully.", "Success", "OK", "Information")
|
||||
$State.Controls.txtAppListJsonPath.Text = $sfd.FileName
|
||||
[System.Windows.MessageBox]::Show("Winget app list saved successfully.", "Success", "OK", "Information")
|
||||
}
|
||||
}
|
||||
catch {
|
||||
[System.Windows.MessageBox]::Show("Error saving app list: $_", "Error", "OK", "Error")
|
||||
[System.Windows.MessageBox]::Show("Error saving Winget app list: $_", "Error", "OK", "Error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,11 +140,17 @@ function Import-WingetList {
|
||||
[psobject]$State
|
||||
)
|
||||
try {
|
||||
# Default the import dialog to the configured Winget app list path.
|
||||
$currentPath = $State.Controls.txtAppListJsonPath.Text
|
||||
$initialDirectory = if (-not [string]::IsNullOrWhiteSpace($currentPath)) { Split-Path -Path $currentPath -Parent } else { $State.Controls.txtApplicationPath.Text }
|
||||
if ([string]::IsNullOrWhiteSpace($initialDirectory) -or -not (Test-Path -Path $initialDirectory -PathType Container)) {
|
||||
$initialDirectory = $State.Controls.txtApplicationPath.Text
|
||||
}
|
||||
|
||||
$ofd = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$ofd.Filter = "JSON files (*.json)|*.json"
|
||||
$ofd.Title = "Import App List"
|
||||
# Correctly get the path from the UI control via the State object
|
||||
$ofd.InitialDirectory = $State.Controls.txtApplicationPath.Text
|
||||
$ofd.Title = "Import Winget App List"
|
||||
$ofd.InitialDirectory = $initialDirectory
|
||||
|
||||
if ($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
||||
$importedAppsData = Get-Content $ofd.FileName -Raw | ConvertFrom-Json
|
||||
@@ -144,16 +158,16 @@ function Import-WingetList {
|
||||
$newAppListForItemsSource = [System.Collections.Generic.List[object]]::new()
|
||||
|
||||
if ($null -ne $importedAppsData.apps) {
|
||||
# Get default architecture from the UI for fallback
|
||||
# Get default architecture from the UI for fallback.
|
||||
$defaultArch = $State.Controls.cmbWindowsArch.SelectedItem
|
||||
|
||||
foreach ($appInfo in $importedAppsData.apps) {
|
||||
$arch = if ($appInfo.source -eq 'msstore') { 'NA' } else { if ($appInfo.PSObject.Properties['architecture']) { $appInfo.architecture } else { $defaultArch } }
|
||||
$newAppListForItemsSource.Add([PSCustomObject]@{
|
||||
IsSelected = $true # Imported apps are marked as selected
|
||||
IsSelected = $true
|
||||
Name = $appInfo.name
|
||||
Id = $appInfo.id
|
||||
Version = "" # Will be populated when searching or if data exists
|
||||
Version = ""
|
||||
Source = $appInfo.source
|
||||
Architecture = $arch
|
||||
AdditionalExitCodes = if ($appInfo.PSObject.Properties['AdditionalExitCodes']) { $appInfo.AdditionalExitCodes } else { "" }
|
||||
@@ -164,12 +178,13 @@ function Import-WingetList {
|
||||
}
|
||||
|
||||
$State.Controls.lstWingetResults.ItemsSource = $newAppListForItemsSource.ToArray()
|
||||
$State.Controls.txtAppListJsonPath.Text = $ofd.FileName
|
||||
|
||||
[System.Windows.MessageBox]::Show("App list imported successfully.", "Success", "OK", "Information")
|
||||
[System.Windows.MessageBox]::Show("Winget app list imported successfully.", "Success", "OK", "Information")
|
||||
}
|
||||
}
|
||||
catch {
|
||||
[System.Windows.MessageBox]::Show("Error importing app list: $_", "Error", "OK", "Error")
|
||||
[System.Windows.MessageBox]::Show("Error importing Winget app list: $_", "Error", "OK", "Error")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ function Get-GeneralDefaults {
|
||||
$ffuCapturePath = Join-Path -Path $FFUDevelopmentPath -ChildPath "FFU"
|
||||
$officePath = Join-Path -Path $appsPath -ChildPath "Office"
|
||||
$appListJsonPath = Join-Path -Path $appsPath -ChildPath "AppList.json"
|
||||
$userAppListPath = Join-Path -Path $appsPath -ChildPath "UserAppList.json"
|
||||
$driversJsonPath = Join-Path -Path $driversPath -ChildPath "Drivers.json"
|
||||
|
||||
return [PSCustomObject]@{
|
||||
@@ -163,6 +164,7 @@ function Get-GeneralDefaults {
|
||||
InstallApps = $false
|
||||
ApplicationPath = $appsPath
|
||||
AppListJsonPath = $appListJsonPath
|
||||
UserAppListPath = $userAppListPath
|
||||
InstallWingetApps = $false
|
||||
BringYourOwnApps = $false
|
||||
# M365 Apps/Office Tab Defaults
|
||||
@@ -305,6 +307,7 @@ function Update-ApplicationPanelVisibility {
|
||||
$subOptionVisibility = if ($installAppsChecked) { 'Visible' } else { 'Collapsed' }
|
||||
$State.Controls.applicationPathPanel.Visibility = $subOptionVisibility
|
||||
$State.Controls.appListJsonPathPanel.Visibility = $subOptionVisibility
|
||||
$State.Controls.userAppListPathPanel.Visibility = $subOptionVisibility
|
||||
$State.Controls.chkInstallWingetApps.Visibility = $subOptionVisibility
|
||||
$State.Controls.chkBringYourOwnApps.Visibility = $subOptionVisibility
|
||||
$State.Controls.chkDefineAppsScriptVariables.Visibility = $subOptionVisibility
|
||||
|
||||
Reference in New Issue
Block a user