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"/> + + + + + + + + + + + + + + + + + + + + + +