diff --git a/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1 b/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1
index 6d4ab63..0316bb0 100644
--- a/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1
+++ b/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1
@@ -1,3 +1,11 @@
+# Allow Orchestrator.ps1 to override the app list file paths while preserving legacy defaults.
+param(
+ [Parameter()]
+ [string]$wingetAppsJsonFile = (Join-Path -Path $PSScriptRoot -ChildPath "WinGetWin32Apps.json"),
+ [Parameter()]
+ [string]$userAppsJsonFile = (Join-Path -Path (Split-Path -Parent $PSScriptRoot) -ChildPath "UserAppList.json")
+)
+
function Invoke-Process {
[CmdletBinding(SupportsShouldProcess)]
param
@@ -247,11 +255,6 @@ function Install-Applications {
}
}
-# Define paths for the JSON files
-$wingetAppsJsonFile = "$PSScriptRoot\WinGetWin32Apps.json"
-# Look for UserAppList.json one directory level up from the script's location. This keeps the user specific json files (AppList.json and UserAppList.json in the Apps dir)
-$userAppsJsonFile = Join-Path -Path (Split-Path -Parent $PSScriptRoot) -ChildPath "UserAppList.json"
-
# Initialize empty arrays for apps from each source
$wingetApps = @()
$userApps = @()
@@ -286,9 +289,9 @@ if ($wingetApps.Count -gt 0) {
Install-Applications -apps $wingetApps
}
-# Read the UserAppList.json file if it exists
+# Read the configured BYO app list file if it exists
if (Test-Path -Path $userAppsJsonFile) {
- Write-Host "Processing UserAppList.json..."
+ Write-Host "Processing $(Split-Path -Path $userAppsJsonFile -Leaf)..."
try {
$userContent = Get-Content -Path $userAppsJsonFile -Raw -ErrorAction Stop | ConvertFrom-Json
if ($userContent -is [array]) {
@@ -296,19 +299,19 @@ if (Test-Path -Path $userAppsJsonFile) {
Write-Host "Found $(($userApps | Measure-Object).Count) user-defined apps."
}
elseif ($userContent) {
- $userApps = @($userContent) # Ensure it's an array
+ $userApps = @($userContent)
Write-Host "Found 1 user-defined app."
}
else {
- Write-Host "UserAppList.json is empty or invalid."
+ Write-Host "$(Split-Path -Path $userAppsJsonFile -Leaf) is empty or invalid."
}
}
catch {
- Write-Error "Failed to read or parse UserAppList.json file: $_"
+ Write-Error "Failed to read or parse BYO app list file '$userAppsJsonFile': $_"
}
}
else {
- Write-Host "UserAppList.json file not found. Skipping."
+ Write-Host "BYO app list file not found at $userAppsJsonFile. Skipping."
}
# Install User apps if any were found
diff --git a/FFUDevelopment/Apps/Orchestration/Orchestrator.ps1 b/FFUDevelopment/Apps/Orchestration/Orchestrator.ps1
index 9f6bc68..88c23f1 100644
--- a/FFUDevelopment/Apps/Orchestration/Orchestrator.ps1
+++ b/FFUDevelopment/Apps/Orchestration/Orchestrator.ps1
@@ -28,6 +28,23 @@ Write-Host "---------------------------------------------------" -ForegroundColo
# Define the path to the scripts
$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
+# Resolve the configured BYO app list path for runtime orchestration.
+$appInstallConfigPath = Join-Path -Path $scriptPath -ChildPath "AppInstallConfig.json"
+$userAppsJsonFile = Join-Path -Path (Split-Path -Parent $scriptPath) -ChildPath "UserAppList.json"
+
+if (Test-Path -Path $appInstallConfigPath) {
+ try {
+ $appInstallConfig = Get-Content -Path $appInstallConfigPath -Raw | ConvertFrom-Json
+ if ($null -ne $appInstallConfig -and $appInstallConfig.PSObject.Properties.Match('UserAppListPath').Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($appInstallConfig.UserAppListPath)) {
+ $userAppsJsonFile = $appInstallConfig.UserAppListPath
+ Write-Host "Using BYO app list path from AppInstallConfig.json: $userAppsJsonFile"
+ }
+ }
+ catch {
+ Write-Host "Failed to parse AppInstallConfig.json. Falling back to default BYO app list path."
+ }
+}
+
# Define the list of scripts to run
$scriptList = @(
"Install-LTSCUpdate.ps1",
@@ -51,7 +68,6 @@ foreach ($script in $scriptList) {
switch ($script) {
"Install-Win32Apps.ps1" {
$wingetAppsJsonFile = Join-Path -Path $scriptPath -ChildPath "WinGetWin32Apps.json"
- $userAppsJsonFile = Join-Path -Path (Split-Path -Parent $scriptPath) -ChildPath "UserAppList.json"
if (-not (Test-Path -Path $wingetAppsJsonFile) -and -not (Test-Path -Path $userAppsJsonFile)) {
$shouldRun = $false
}
@@ -69,8 +85,13 @@ foreach ($script in $scriptList) {
Write-Host "---------------------------------------------------" -ForegroundColor Yellow
Write-Host " Running script: $script " -ForegroundColor Yellow
Write-Host "---------------------------------------------------" -ForegroundColor Yellow
- # Run script and wait for it to finish
- & $scriptFile
+ # Run script and wait for it to finish.
+ if ($script -eq "Install-Win32Apps.ps1") {
+ & $scriptFile -UserAppsJsonFile $userAppsJsonFile
+ }
+ else {
+ & $scriptFile
+ }
}
}
diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1
index 6a197fb..1554d87 100644
--- a/FFUDevelopment/BuildFFUVM.ps1
+++ b/FFUDevelopment/BuildFFUVM.ps1
@@ -633,6 +633,7 @@ if (-not $AppsPath) { $AppsPath = "$FFUDevelopmentPath\Apps" }
if (-not $AppListPath) { $AppListPath = "$AppsPath\AppList.json" }
if (-not $UserAppListPath) { $UserAppListPath = "$AppsPath\UserAppList.json" }
if (-not $OrchestrationPath) { $OrchestrationPath = "$AppsPath\Orchestration" }
+if (-not $appInstallConfigPath) { $appInstallConfigPath = "$OrchestrationPath\AppInstallConfig.json" }
if (-not $wingetWin32jsonFile) { $wingetWin32jsonFile = "$OrchestrationPath\WinGetWin32Apps.json" }
if (-not $InstallOfficePath) { $InstallOfficePath = "$OrchestrationPath\Install-Office.ps1" }
if (-not $InstallDefenderPath) { $InstallDefenderPath = "$OrchestrationPath\Update-Defender.ps1" }
@@ -2437,6 +2438,54 @@ function Save-KB {
return $fileName
}
+function Sync-UserAppListForOrchestration {
+ param(
+ [Parameter(Mandatory)]
+ [string]$SourcePath,
+ [Parameter(Mandatory)]
+ [string]$AppsPath,
+ [Parameter(Mandatory)]
+ [string]$OrchestrationPath,
+ [Parameter(Mandatory)]
+ [string]$AppInstallConfigPath
+ )
+
+ # Ensure the orchestration folder exists before writing runtime configuration.
+ if (-not (Test-Path -Path $OrchestrationPath -PathType Container)) {
+ New-Item -Path $OrchestrationPath -ItemType Directory -Force | Out-Null
+ }
+
+ # Persist the runtime BYO app list path so Orchestrator can honor custom file names.
+ $appInstallConfig = [ordered]@{
+ UserAppListPath = $null
+ }
+
+ if (-not [string]::IsNullOrWhiteSpace($SourcePath) -and (Test-Path -Path $SourcePath -PathType Leaf)) {
+ $stagedUserAppListName = Split-Path -Path $SourcePath -Leaf
+ $stagedUserAppListPath = Join-Path -Path $AppsPath -ChildPath $stagedUserAppListName
+
+ if (-not [string]::Equals([System.IO.Path]::GetFullPath($SourcePath), [System.IO.Path]::GetFullPath($stagedUserAppListPath), [System.StringComparison]::OrdinalIgnoreCase)) {
+ Copy-Item -Path $SourcePath -Destination $stagedUserAppListPath -Force | Out-Null
+ WriteLog "Staged BYO app list for orchestration: $SourcePath -> $stagedUserAppListPath"
+ }
+ else {
+ WriteLog "Using BYO app list already staged at $stagedUserAppListPath"
+ }
+
+ $appInstallConfig.UserAppListPath = "D:\$stagedUserAppListName"
+ }
+ elseif (Test-Path -Path (Join-Path -Path $AppsPath -ChildPath 'UserAppList.json') -PathType Leaf) {
+ $appInstallConfig.UserAppListPath = "D:\UserAppList.json"
+ WriteLog "Using default BYO app list path for orchestration."
+ }
+ else {
+ WriteLog "No BYO app list found at configured path '$SourcePath'."
+ }
+
+ $appInstallConfig | ConvertTo-Json | Set-Content -Path $AppInstallConfigPath -Encoding UTF8
+ WriteLog "Wrote app install config to $AppInstallConfigPath"
+}
+
function New-AppsISO {
#Create Apps ISO file
$OSCDIMG = "$adkpath`Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe"
@@ -6082,7 +6131,19 @@ if ($InstallApps) {
Set-Progress -Percentage 6 -Message "Downloading and preparing applications..."
if (Test-Path -Path $AppsISO) {
WriteLog "Apps ISO exists at: $AppsISO"
- WriteLog "Will use existing ISO"
+
+ # Refresh the Apps ISO when a BYO app list is present so the staged manifest
+ # and AppInstallConfig.json stay in sync with the current build inputs.
+ if (Test-Path -Path $UserAppListPath) {
+ WriteLog "Configured BYO app list detected. Refreshing Apps ISO to include the latest BYO app list data."
+ Sync-UserAppListForOrchestration -SourcePath $UserAppListPath -AppsPath $AppsPath -OrchestrationPath $OrchestrationPath -AppInstallConfigPath $appInstallConfigPath
+ Remove-Item -Path $AppsISO -Force -ErrorAction SilentlyContinue
+ New-AppsISO
+ WriteLog "Apps ISO refreshed to include the latest BYO app list data."
+ }
+ else {
+ WriteLog "Will use existing ISO"
+ }
}
else {
try {
@@ -6125,7 +6186,7 @@ if ($InstallApps) {
# If there are no existing apps, use the original AppList.json directly
if (-not $hasExistingApps) {
WriteLog "No existing applications found. Using original AppList.json for all apps."
- Get-Apps -AppList $AppListPath -AppsPath $AppsPath -WindowsArch $WindowsArch -OrchestrationPath $OrchestrationPath -LogFilePath $LogFile -ThrottleLimit $Threads
+ Get-Apps -AppList $AppListPath -AppsPath $AppsPath -UserAppListPath $UserAppListPath -WindowsArch $WindowsArch -OrchestrationPath $OrchestrationPath -LogFilePath $LogFile -ThrottleLimit $Threads
}
else {
# Compare apps in AppList.json with existing installations
@@ -6197,7 +6258,7 @@ if ($InstallApps) {
# Download missing apps
WriteLog "Downloading missing applications"
- Get-Apps -AppList $modifiedAppListPath -AppsPath $AppsPath -WindowsArch $WindowsArch -OrchestrationPath $OrchestrationPath -LogFilePath $LogFile -ThrottleLimit $Threads
+ Get-Apps -AppList $modifiedAppListPath -AppsPath $AppsPath -UserAppListPath $UserAppListPath -WindowsArch $WindowsArch -OrchestrationPath $OrchestrationPath -LogFilePath $LogFile -ThrottleLimit $Threads
# Cleanup modified app list
Remove-Item -Path $modifiedAppListPath -Force
@@ -6207,11 +6268,11 @@ if ($InstallApps) {
}
}
}
- # Check is UserAppList.json exists and output to the user which apps will be installed
- # It's expected that the user will have already copied the applications and created the UserAppList.json file
+ # Check if the configured BYO app list exists and output which apps will be installed.
+ # It is expected that the user will have already copied the applications and created the BYO app list file.
if (Test-Path -Path $UserAppListPath) {
$userAppList = Get-Content -Path $UserAppListPath -Raw | ConvertFrom-Json
- WriteLog "UserAppList.json found, the following apps will be installed:"
+ WriteLog "$(Split-Path -Path $UserAppListPath -Leaf) found, the following apps will be installed:"
foreach ($app in $userAppList) {
WriteLog "$($app.name)"
}
@@ -6525,6 +6586,9 @@ if ($InstallApps) {
WriteLog "InjectUnattend is true but source file missing: $unattendSource. Skipping unattend injection."
}
}
+ # Stage the configured BYO app list and runtime config before creating the Apps ISO.
+ Sync-UserAppListForOrchestration -SourcePath $UserAppListPath -AppsPath $AppsPath -OrchestrationPath $OrchestrationPath -AppInstallConfigPath $appInstallConfigPath
+
Set-Progress -Percentage 10 -Message "Creating Apps ISO..."
WriteLog "Creating $AppsISO file"
New-AppsISO
@@ -7434,6 +7498,7 @@ if ($InstallApps) {
Remove-Item -Path $AppsISO -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
+ Sync-UserAppListForOrchestration -SourcePath $UserAppListPath -AppsPath $AppsPath -OrchestrationPath $OrchestrationPath -AppInstallConfigPath $appInstallConfigPath
New-AppsISO
WriteLog "Apps ISO refreshed with LTSC CU assets"
}
@@ -7621,6 +7686,13 @@ if (Test-Path -Path $wingetWin32jsonFile -PathType Leaf) {
Remove-Item -Path $wingetWin32jsonFile -Force -ErrorAction SilentlyContinue
WriteLog "Removal complete"
}
+
+# Remove AppInstallConfig.json so it is always rebuilt next run
+if (Test-Path -Path $appInstallConfigPath -PathType Leaf) {
+ WriteLog "Removing $appInstallConfigPath"
+ Remove-Item -Path $appInstallConfigPath -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
diff --git a/FFUDevelopment/BuildFFUVM_UI.xaml b/FFUDevelopment/BuildFFUVM_UI.xaml
index b12a5b6..ae13f82 100644
--- a/FFUDevelopment/BuildFFUVM_UI.xaml
+++ b/FFUDevelopment/BuildFFUVM_UI.xaml
@@ -270,19 +270,32 @@
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -336,8 +349,8 @@
-
-
+
+
@@ -405,8 +418,8 @@
-
-
+
+
diff --git a/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1 b/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1
index dd982a8..ce2f475 100644
--- a/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1
+++ b/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1
@@ -161,6 +161,7 @@ function Invoke-ParallelProcessing {
ApplicationItemData = $currentItem
AppListJsonPath = $localJobArgs['AppListJsonPath']
AppsPath = $localJobArgs['AppsPath']
+ UserAppListPath = $localJobArgs['UserAppListPath']
OrchestrationPath = $localJobArgs['OrchestrationPath']
ProgressQueue = $localProgressQueue
WindowsArch = $localJobArgs['WindowsArch']
diff --git a/FFUDevelopment/FFU.Common/FFU.Common.Winget.psm1 b/FFUDevelopment/FFU.Common/FFU.Common.Winget.psm1
index 32a0e42..1e9aecb 100644
--- a/FFUDevelopment/FFU.Common/FFU.Common.Winget.psm1
+++ b/FFUDevelopment/FFU.Common/FFU.Common.Winget.psm1
@@ -358,6 +358,8 @@ function Start-WingetAppDownloadTask {
[string]$AppListJsonPath,
[Parameter(Mandatory = $true)]
[string]$AppsPath,
+ [Parameter()]
+ [string]$UserAppListPath,
[Parameter(Mandatory = $true)]
[string]$OrchestrationPath,
[Parameter(Mandatory = $true)]
@@ -379,11 +381,11 @@ function Start-WingetAppDownloadTask {
WriteLog "Starting download task for $($appName) with ID $($appId) from source $($source)."
try {
- # Define paths
- $userAppListPath = Join-Path -Path $AppsPath -ChildPath "UserAppList.json"
+ # Resolve the BYO app list path so duplicate checks honor custom file names.
+ $userAppListPath = if (-not [string]::IsNullOrWhiteSpace($UserAppListPath)) { $UserAppListPath } else { Join-Path -Path $AppsPath -ChildPath "UserAppList.json" }
$appFound = $false
- # 1. Check UserAppList.json and content
+ # 1. Check the configured BYO app list and content
if (Test-Path -Path $userAppListPath) {
try {
$userAppListContent = Get-Content -Path $userAppListPath -Raw | ConvertFrom-Json
@@ -724,6 +726,8 @@ function Get-Apps {
[string]$AppList,
[Parameter(Mandatory = $true)]
[string]$AppsPath,
+ [Parameter()]
+ [string]$UserAppListPath,
[Parameter(Mandatory = $true)]
[string]$WindowsArch,
[Parameter(Mandatory = $true)]
@@ -787,6 +791,7 @@ function Get-Apps {
# CLI builds should create WinGetWin32Apps.json, so SkipWin32Json is false
$taskArguments = @{
AppsPath = $AppsPath
+ UserAppListPath = $UserAppListPath
AppListJsonPath = $AppList
OrchestrationPath = $OrchestrationPath
WindowsArch = $WindowsArch
diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1
index a85a320..15fbdaa 100644
--- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1
+++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1
@@ -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
diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1
index befaed3..f3d7098 100644
--- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1
+++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1
@@ -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
diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1
index 1881c40..5446e7a 100644
--- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1
+++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1
@@ -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
}
diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1
index fc84a6c..5cd41fa 100644
--- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1
+++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1
@@ -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
diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1
index 7a8c8f9..3adc7af 100644
--- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1
+++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1
@@ -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")
}
}
diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1
index a01c312..c9c2e10 100644
--- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1
+++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1
@@ -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