diff --git a/FFUDevelopment/BuildFFUVM_UI.ps1 b/FFUDevelopment/BuildFFUVM_UI.ps1
index bbb7a38..bf01bee 100644
--- a/FFUDevelopment/BuildFFUVM_UI.ps1
+++ b/FFUDevelopment/BuildFFUVM_UI.ps1
@@ -3,7 +3,7 @@
param()
# --------------------------------------------------------------------------
-# SECTION 1: Variables & Constants
+# SECTION: Variables & Constants
# --------------------------------------------------------------------------
$FFUDevelopmentPath = $PSScriptRoot
$AppsPath = Join-Path $FFUDevelopmentPath "Apps"
@@ -24,6 +24,172 @@ function Get-USBDrives {
}
}
}
+# --------------------------------------------------------------------------
+# SECTION: Modern folder dialog
+# --------------------------------------------------------------------------
+
+# 1) Define a C# class that uses the correct GUIDs for IFileDialog, IFileOpenDialog, and FileOpenDialog,
+# while omitting conflicting "GetResults/GetSelectedItems" from IFileDialog.
+Add-Type -TypeDefinition @"
+using System;
+using System.Runtime.InteropServices;
+
+public static class ModernFolderBrowser
+{
+ // Flags for IFileDialog
+ [Flags]
+ private enum FileDialogOptions : uint
+ {
+ OverwritePrompt = 0x00000002,
+ StrictFileTypes = 0x00000004,
+ NoChangeDir = 0x00000008,
+ PickFolders = 0x00000020,
+ ForceFileSystem = 0x00000040,
+ AllNonStorageItems = 0x00000080,
+ NoValidate = 0x00000100,
+ AllowMultiSelect = 0x00000200,
+ PathMustExist = 0x00000800,
+ FileMustExist = 0x00001000,
+ CreatePrompt = 0x00002000,
+ ShareAware = 0x00004000,
+ NoReadOnlyReturn = 0x00008000,
+ NoTestFileCreate = 0x00010000,
+ DontAddToRecent = 0x02000000,
+ ForceShowHidden = 0x10000000
+ }
+
+ // IFileDialog (GUID from Windows SDK)
+ // - Omitting GetResults / GetSelectedItems to avoid overshadow.
+ [ComImport]
+ [Guid("42F85136-DB7E-439C-85F1-E4075D135FC8")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ private interface IFileDialog
+ {
+ [PreserveSig]
+ int Show(IntPtr parent);
+
+ void SetFileTypes(uint cFileTypes, IntPtr rgFilterSpec);
+ void SetFileTypeIndex(uint iFileType);
+ void GetFileTypeIndex(out uint piFileType);
+ void Advise(IntPtr pfde, out uint pdwCookie);
+ void Unadvise(uint dwCookie);
+ void SetOptions(FileDialogOptions fos);
+ void GetOptions(out FileDialogOptions pfos);
+ void SetDefaultFolder(IShellItem psi);
+ void SetFolder(IShellItem psi);
+ void GetFolder(out IShellItem ppsi);
+ void GetCurrentSelection(out IShellItem ppsi);
+ void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
+ void GetFileName(out IntPtr pszName);
+ void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
+ void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
+ void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
+ void GetResult(out IShellItem ppsi);
+ void AddPlace(IShellItem psi, int fdap);
+ void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
+ void Close(int hr);
+ void SetClientGuid(ref Guid guid);
+ void ClearClientData();
+ void SetFilter(IntPtr pFilter);
+
+ // NOTE: We intentionally do NOT define GetResults and GetSelectedItems here,
+ // because they cause overshadow warnings in IFileOpenDialog.
+ }
+
+ // IFileOpenDialog extends IFileDialog by adding 2 new methods with the same name,
+ // which otherwise cause overshadow warnings. We'll define them only here.
+ [ComImport]
+ [Guid("D57C7288-D4AD-4768-BE02-9D969532D960")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ private interface IFileOpenDialog : IFileDialog
+ {
+ // These two come after the parent's vtable:
+ void GetResults(out IntPtr ppenum);
+ void GetSelectedItems(out IntPtr ppsai);
+ }
+
+ // The coclass for creating an IFileOpenDialog
+ [ComImport]
+ [Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")]
+ private class FileOpenDialog
+ {
+ }
+
+ // IShellItem
+ [ComImport]
+ [Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ private interface IShellItem
+ {
+ void BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv);
+ void GetParent(out IShellItem ppsi);
+ void GetDisplayName(uint sigdnName, out IntPtr ppszName);
+ void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
+ void Compare(IShellItem psi, uint hint, out int piOrder);
+ }
+
+ private const uint SIGDN_FILESYSPATH = 0x80058000;
+
+ public static string ShowDialog(string title, IntPtr parentHandle)
+ {
+ // Create COM dialog instance
+ IFileOpenDialog dialog = (IFileOpenDialog)(new FileOpenDialog());
+
+ // Get current options
+ FileDialogOptions opts;
+ dialog.GetOptions(out opts);
+
+ // Add flags for picking folders
+ opts |= FileDialogOptions.PickFolders | FileDialogOptions.PathMustExist | FileDialogOptions.ForceFileSystem;
+ dialog.SetOptions(opts);
+
+ // Set title
+ if (!string.IsNullOrEmpty(title))
+ {
+ dialog.SetTitle(title);
+ }
+
+ // Show the dialog
+ int hr = dialog.Show(parentHandle);
+ // 0 = S_OK. 1 or 0x800704C7 often means user canceled. Return null if so.
+ if (hr != 0)
+ {
+ if ((uint)hr == 0x800704C7 || hr == 1)
+ {
+ return null; // Canceled
+ }
+ else
+ {
+ Marshal.ThrowExceptionForHR(hr);
+ }
+ }
+
+ // Retrieve the selection (IShellItem)
+ IShellItem shellItem;
+ dialog.GetResult(out shellItem);
+ if (shellItem == null) return null;
+
+ // Convert to file system path
+ IntPtr pszPath = IntPtr.Zero;
+ shellItem.GetDisplayName(SIGDN_FILESYSPATH, out pszPath);
+ if (pszPath == IntPtr.Zero) return null;
+
+ string folderPath = Marshal.PtrToStringAuto(pszPath);
+ Marshal.FreeCoTaskMem(pszPath);
+
+ return folderPath;
+ }
+}
+"@ -Language CSharp
+
+# 2) Define a PowerShell function that invokes our C# wrapper
+function Show-ModernFolderPicker {
+ param(
+ [string]$Title = "Select a folder"
+ )
+ # For a simple test, pass IntPtr.Zero as the parent window handle
+ return [ModernFolderBrowser]::ShowDialog($Title, [IntPtr]::Zero)
+}
# --------------------------------------------------------------------------
# SECTION: Winget Management Functions
@@ -850,7 +1016,7 @@ $window.Add_Loaded({
# Add keyboard handler
$script:lstUSBDrives.Add_KeyDown({
- param($eventSrc, $keyEvent)
+ param($eventSource, $keyEvent)
if ($keyEvent.Key -eq 'Space') {
$selectedItem = $script:lstUSBDrives.SelectedItem
if ($selectedItem) {
@@ -865,7 +1031,7 @@ $window.Add_Loaded({
# Add selection change handler
$script:lstUSBDrives.Add_SelectionChanged({
- param($eventSrc, $selChangeEvent)
+ param($eventSource, $selChangeEvent)
# Update Select All checkbox state
$allSelected = -not ($script:lstUSBDrives.Items | Where-Object { -not $_.IsSelected })
$script:chkSelectAllUSBDrives.IsChecked = $allSelected
@@ -953,6 +1119,38 @@ $window.Add_Loaded({
$script:chkBringYourOwnApps.Visibility = 'Collapsed'
})
+ # Bring Your Own Applications checkbox should only show if Install Applications is checked
+ $script:chkBringYourOwnApps = $window.FindName('chkBringYourOwnApps')
+ $script:byoApplicationPanel = $window.FindName('byoApplicationPanel')
+ $script:chkBringYourOwnApps.Visibility = if ($script:chkInstallApps.IsChecked) { 'Visible' } else { 'Collapsed' }
+
+ # Show/Hide Bring Your Own Applications based on Install Apps state
+ $script:chkInstallApps.Add_Checked({
+ $script:chkBringYourOwnApps.Visibility = 'Visible'
+ })
+ $script:chkInstallApps.Add_Unchecked({
+ $script:chkBringYourOwnApps.IsChecked = $false
+ $script:chkBringYourOwnApps.Visibility = 'Collapsed'
+ $script:byoApplicationPanel.Visibility = 'Collapsed'
+ })
+
+ # Show/Hide Application Information section based on Bring Your Own Applications state
+ $script:chkBringYourOwnApps.Add_Checked({
+ $script:byoApplicationPanel.Visibility = 'Visible'
+ })
+ $script:chkBringYourOwnApps.Add_Unchecked({
+ $script:byoApplicationPanel.Visibility = 'Collapsed'
+ $window.FindName('txtAppName').Text = ''
+ $window.FindName('txtAppCommandLine').Text = ''
+ $window.FindName('txtAppSource').Text = ''
+ })
+
+ # Show/Hide Winget panel based on checkbox state
+ $script:chkInstallWingetApps.Add_Checked({
+ $script:wingetPanel.Visibility = 'Visible'
+ # Don't show search panel here - it should only show after validation
+ })
+
# Show/Hide Winget panel based on checkbox state
$script:chkInstallWingetApps.Add_Checked({
$script:wingetPanel.Visibility = 'Visible'
@@ -1028,7 +1226,7 @@ $window.Add_Loaded({
$script:lstWingetResults.AddHandler(
[System.Windows.Controls.GridViewColumnHeader]::ClickEvent,
[System.Windows.RoutedEventHandler] {
- param($sender, $e)
+ param($eventSource, $e)
$header = $e.OriginalSource
if ($header -is [System.Windows.Controls.GridViewColumnHeader] -and $header.Tag) {
Invoke-ListViewSort -listView $script:lstWingetResults -property $header.Tag
@@ -1090,6 +1288,60 @@ $window.Add_Loaded({
$script:txtStatus.Text = "Cleared all applications from the list"
}
})
+
+ # Add Browse button handler for App Source
+ $script:btnBrowseAppSource = $window.FindName('btnBrowseAppSource')
+ $script:btnBrowseAppSource.Add_Click({
+ $selectedPath = Show-ModernFolderPicker -Title "Select Application Source Folder"
+ if ($selectedPath) {
+ $window.FindName('txtAppSource').Text = $selectedPath
+ }
+ })
+
+ # Add Browse button handler for FFU Development Path
+ $script:btnBrowseFFUDevPath = $window.FindName('btnBrowseFFUDevPath')
+ $script:btnBrowseFFUDevPath.Add_Click({
+ $selectedPath = Show-ModernFolderPicker -Title "Select FFU Development Path"
+ if ($selectedPath) {
+ $window.FindName('txtFFUDevPath').Text = $selectedPath
+ }
+ })
+
+ # Add Browse button handler for FFU Capture Location
+ $script:btnBrowseFFUCaptureLocation = $window.FindName('btnBrowseFFUCaptureLocation')
+ $script:btnBrowseFFUCaptureLocation.Add_Click({
+ $selectedPath = Show-ModernFolderPicker -Title "Select FFU Capture Location"
+ if ($selectedPath) {
+ $window.FindName('txtFFUCaptureLocation').Text = $selectedPath
+ }
+ })
+
+ # Add Browse button handler for Office Path
+ $script:btnBrowseOfficePath = $window.FindName('btnBrowseOfficePath')
+ $script:btnBrowseOfficePath.Add_Click({
+ $selectedPath = Show-ModernFolderPicker -Title "Select Office Path"
+ if ($selectedPath) {
+ $window.FindName('txtOfficePath').Text = $selectedPath
+ }
+ })
+
+ # Add Browse button handler for Drivers Folder
+ $script:btnBrowseDriversFolder = $window.FindName('btnBrowseDriversFolder')
+ $script:btnBrowseDriversFolder.Add_Click({
+ $selectedPath = Show-ModernFolderPicker -Title "Select Drivers Folder"
+ if ($selectedPath) {
+ $window.FindName('txtDriversFolder').Text = $selectedPath
+ }
+ })
+
+ # Add Browse button handler for PE Drivers Folder
+ $script:btnBrowsePEDriversFolder = $window.FindName('btnBrowsePEDriversFolder')
+ $script:btnBrowsePEDriversFolder.Add_Click({
+ $selectedPath = Show-ModernFolderPicker -Title "Select PE Drivers Folder"
+ if ($selectedPath) {
+ $window.FindName('txtPEDriversFolder').Text = $selectedPath
+ }
+ })
})
# Function to search for Winget apps
@@ -1336,6 +1588,7 @@ $btnLoadConfig.Add_Click({
# Applications tab
$window.FindName('chkInstallApps').IsChecked = $configContent.InstallApps
$window.FindName('chkInstallWingetApps').IsChecked = $configContent.InstallWingetApps
+ $window.FindName('chkBringYourOwnApps').IsChecked = $configContent.BringYourOwnApps
# Update USB Drive selection if present in config
if ($configContent.USBDriveList) {
diff --git a/FFUDevelopment/BuildFFUVM_UI.xaml b/FFUDevelopment/BuildFFUVM_UI.xaml
index bc5740a..aa3e89c 100644
--- a/FFUDevelopment/BuildFFUVM_UI.xaml
+++ b/FFUDevelopment/BuildFFUVM_UI.xaml
@@ -660,6 +660,49 @@
Content="Bring Your Own Applications"
Margin="5"
ToolTip="Enable to bring your own applications during the build process"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -776,7 +819,7 @@
-
+