I'm using PowerShell to enable the user to browse for file/folder paths for a Node.js app (because I haven't found a better light-weight alternative so far), and I'm having the age old trouble of dealing with the horrible, poor usability FolderBrowserDialog
that doesn't support:
- pasting paths
- accessing Quick Access items
- changing the view
- Sorting or filtering items
- etc...
The standard script looks like this:
Function Select-FolderDialog($Description="Select Folder", $RootFolder="MyComputer"){
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$objForm = New-Object System.Windows.Forms.FolderBrowserDialog
$objForm.RootFolder = $RootFolder
$objForm.ShowNewFolderButton = $true
$objForm.Description = "Please choose a folder"
$Show = $objForm.ShowDialog()
If ($Show -eq "OK")
{
Return $objForm.SelectedPath
}
Else
{
Write-Error "Operation cancelled by user."
}
}
$folder = Select-FolderDialog
write-host $folder
I've used the Windows API CodePack for C# Windows Forms apps in the past to create a CommonOpenFileDialog
with IsFolderPicker = true
, giving me the features and accessibility of the OpenFileDialog
with the ease of use of a managed folder browser.
in my search of a way to use something like this here as well, I learned that the regular FolderBrowserDialog got an upgrade, at least in .Net Core.
Huh, that's neat.
Is there any way I can access the upgraded version from a PowerShell script?
Adding $objForm.AutoUpgradeEnabled = $true
to the above code doesn't change anything (and is indeed the default)
(Also, if somebody has a good idea how to provide a decent Folder Browser Dialog to a Node.js app in a more direct way, drop me a comment ^^)
Workarounds so far:
1: Abuse an OpenFileDialog
Function Select-FolderDialog($Description="Select Folder", $RootFolder="MyComputer"){
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$objForm = New-Object System.Windows.Forms.OpenFileDialog
$objForm.DereferenceLinks = $true
$objForm.CheckPathExists = $true
$objForm.FileName = "[Select this folder]"
$objForm.Filter = "Folders|`n"
$objForm.AddExtension = $false
$objForm.ValidateNames = $false
$objForm.CheckFileExists = $false
$Show = $objForm.ShowDialog()
If ($Show -eq "OK")
{
Return $objForm.FileName
}
Else
{
Write-Error "Operation cancelled by user."
}
}
$folder = Select-FolderDialog
write-host $folder
This creates a dialog inheriting from the much nicer FileDialog Class, which displays only folders and enables you to return a path like "C:\Some dir\Dir I want\[Select this folder]", even when it doesn't exist, which I can then trim back to "C:\Some dir\Dir I want".
Pros:
- Full featured browser, as desired
Cons:
- The file name field can't be empty. Trying to use something like a
newline character results in an error dialog complaining about an
invalid filename and the
FileOpenDialog
refusing to return the filename, even thoughValidateNames
isfalse
. - The user could enter anything in the filename field, which could lead to confusion.
- When you navigate up a folder tree, clicking the accept button ("Open") simply browses back to the previously selected child directory, even if you deselect it
- You have to trim away the last part of the returned path and hope that no further weirdness happened as a result of selecting a non-existent file
2: Give a standard dialog an Edit control
Using the Shell.BrowseForFolder method
# Included the options values from https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-browseinfoa
$BIF_RETURNONLYFSDIRS = [uint32]"0x00000001"
$BIF_DONTGOBELOWDOMAIN = [uint32]"0x00000002"
$BIF_STATUSTEXT = [uint32]"0x00000004"
$BIF_RETURNFSANCESTORS = [uint32]"0x00000008"
$BIF_EDITBOX = [uint32]"0x00000010" # <-- this is the important one
$BIF_VALIDATE = [uint32]"0x00000020"
$BIF_NEWDIALOGSTYLE = [uint32]"0x00000040" # <-- this sounds nice, but somehow changes nothing
$BIF_BROWSEINCLUDEURLS = [uint32]"0x00000080"
$BIF_USENEWUI = $BIF_NEWDIALOGSTYLE
$BIF_UAHINT = [uint32]"0x00000100"
$BIF_NONEWFOLDERBUTTON = [uint32]"0x00000200"
$BIF_NOTRANSLATETARGETS = [uint32]"0x00000400"
$BIF_BROWSEFORCOMPUTER = [uint32]"0x00001000"
$BIF_BROWSEFORPRINTER = [uint32]"0x00002000"
$BIF_BROWSEINCLUDEFILES = [uint32]"0x00004000"
$BIF_SHAREABLE = [uint32]"0x00008000"
$BIF_BROWSEFILEJUNCTIONS = [uint32]"0x00010000"
$options = 0
$options += $BIF_STATUSTEXT
$options += $BIF_EDITBOX
$options += $BIF_VALIDATE
$options += $BIF_NEWDIALOGSTYLE
$options += $BIF_BROWSEINCLUDEURLS
$options += $BIF_SHAREABLE
$options += $BIF_BROWSEFILEJUNCTIONS
$shell = new-object -comobject Shell.Application
$folder = $shell.BrowseForFolder(0, "Select a folder", $options)
if($folder){
write-host $folder.Self.Path()
}
I included the options for clarity, but you could hard-code all of the the above into $folder = $shell.BrowseForFolder(0, "Select a folder", 98548)
, which is neat.
Pros:
- Using the folder browser dialog as intended
- Robust UX
- Can paste a path
- Supports UNC paths
- Supports auto-complete
Cons:
- No side panel with Quick Access Items, etc
- Can't change view, sort, etc
- No previews/thumbnails