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:
rbalsleyMSFT
2026-03-18 18:59:28 -07:00
parent b388eae439
commit eac8be3d31
12 changed files with 237 additions and 60 deletions
@@ -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
@@ -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
}
}
}
+78 -6
View File
@@ -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
+20 -7
View File
@@ -270,19 +270,32 @@
</Grid>
</StackPanel>
<!-- AppList.json Path - Shows only when Install Applications is checked -->
<!-- Winget AppList Path - Shows only when Install Applications is checked -->
<StackPanel x:Name="appListJsonPathPanel" Visibility="Collapsed" Margin="25,5,5,10">
<TextBlock Text="AppList.json Path:" Margin="0,0,0,5" ToolTip="Path to the AppList.json file"/>
<TextBlock Text="Winget AppList Path:" Margin="0,0,0,5" ToolTip="Path to the Winget AppList JSON file"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtAppListJsonPath" Grid.Column="0" VerticalAlignment="Center" ToolTip="Path to the AppList.json file"/>
<TextBox x:Name="txtAppListJsonPath" Grid.Column="0" VerticalAlignment="Center" ToolTip="Path to the Winget AppList JSON file (AppList.json)"/>
<Button x:Name="btnBrowseAppListJsonPath" Grid.Column="1" Content="Browse..." Width="80" Margin="5,0,0,0" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
<!-- BYO AppList Path - Shows only when Install Applications is checked -->
<StackPanel x:Name="userAppListPathPanel" Visibility="Collapsed" Margin="25,5,5,10">
<TextBlock Text="BYO AppList Path:" Margin="0,0,0,5" ToolTip="Path to the Bring Your Own applications JSON file (UserAppList.json)"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtUserAppListPath" Grid.Column="0" VerticalAlignment="Center" ToolTip="Path to the Bring Your Own applications JSON file (UserAppList.json)"/>
<Button x:Name="btnBrowseUserAppListPath" Grid.Column="1" Content="Browse..." Width="80" Margin="5,0,0,0" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
<!-- Winget Applications Section - Indented under Install Applications -->
<CheckBox x:Name="chkInstallWingetApps" Content="Install Winget Applications" Margin="5" ToolTip="Enable to install applications using Windows Package Manager (winget)"/>
@@ -336,8 +349,8 @@
<!-- Save/Import/Clear Buttons -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Button x:Name="btnSaveWingetList" Content="Save AppList.json" Padding="15,5" Margin="0,0,10,0" ToolTip="Save selected applications to a JSON file"/>
<Button x:Name="btnImportWingetList" Content="Import AppList.json" Padding="15,5" Margin="0,0,10,0" ToolTip="Import applications from a JSON file"/>
<Button x:Name="btnSaveWingetList" Content="Save Winget AppList" Padding="15,5" Margin="0,0,10,0" ToolTip="Save selected applications to a JSON file"/>
<Button x:Name="btnImportWingetList" Content="Import Winget AppList" Padding="15,5" Margin="0,0,10,0" ToolTip="Import applications from a JSON file"/>
<Button x:Name="btnDownloadSelected" Content="Download Selected" Padding="15,5" Margin="0,0,10,0" ToolTip="Download all selected applications"/>
<Button x:Name="btnClearWingetList" Content="Clear List" Padding="15,5" ToolTip="Clear all applications from the list"/>
</StackPanel>
@@ -405,8 +418,8 @@
<!-- Save/Import/Clear Buttons -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,10">
<Button x:Name="btnSaveBYOApplications" Content="Save UserAppList.json" Margin="0,0,10,0" Padding="10,5" ToolTip="Save application list to JSON file"/>
<Button x:Name="btnLoadBYOApplications" Content="Import UserAppList.json" Margin="0,0,10,0" Padding="10,5" ToolTip="Import application list from JSON file"/>
<Button x:Name="btnSaveBYOApplications" Content="Save BYO AppList" Margin="0,0,10,0" Padding="10,5" ToolTip="Save application list to JSON file"/>
<Button x:Name="btnLoadBYOApplications" Content="Import BYO AppList" Margin="0,0,10,0" Padding="10,5" ToolTip="Import application list from JSON file"/>
<Button x:Name="btnEditApplication" Content="Edit Application" IsEnabled="False" Margin="0,0,10,0" Padding="10,5" ToolTip="Edit the selected application's details"/>
<Button x:Name="btnCopyBYOApps" Content="Copy Apps" IsEnabled="False" Margin="0,0,10,0" Padding="10,5" ToolTip="Copy applications with a specified source path to the AppsPath\Win32 folder"/>
<Button x:Name="btnRemoveSelectedBYOApps" Content="Remove Selected" IsEnabled="False" Margin="0,0,10,0" Padding="10,5" ToolTip="Remove selected applications from the list"/>
@@ -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']
@@ -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
@@ -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) {
$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