If you are doing Powershell GUI ( WPF or Form) you probably know about the System.Windows.Forms.FolderBrowserDialog class that leverage a builtin folder selector like this:
If the tool is good enough in most cases, it is far from perfection and has some limitations that we will try to exceed in this blog post.
There are two area where we can bring improvement:
- FolderBrowserDialog can only be started from a few dedicated locations (you can start browsing from c:\ but not from c:\temp...). I will show you how to start from anywhere.
- FolderBrowserDialog can’t show hidden folders. This is something that we will also change.
Start from anywhere!
As described in the link above, the browse folder dialog can only display folders from special locations. the available location can be enumerated with this command:[Enum]::GetNames([System.Environment+SpecialFolder])
You can follow this link if you need translation to real paths: Windows: known folders (renenyffenegger.ch).
So if we can only use special folders, let’s redirect one to a location of our choice. What do you think about something like CDBurning (yes, this is one of the available locations)?
Using Powershell, you can do it like this:
$Path = "c:\Temp"
$Signature = @'
[DllImport("shell32.dll")]
public extern static int SHSetKnownFolderPath(ref Guid folderId, uint flags, IntPtr token, [MarshalAs(UnmanagedType.LPWStr)] string path);
'@
Now, to use it with FolderBrowser Dialog, we just need to tell it to open to CDBurning location (known as ID 9E52AB10-F80D-49DF-ACB8-4330F5687855) like this:
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property @{ShowNewFolderButton = $false ; Description = "Select a directory" ; RootFolder = 'CDBurning'}
$result = $FolderBrowser.ShowDialog((New-Object System.Windows.Forms.Form -Property @{TopMost = $true}))
Unfortunately, the change won’t happen until the explorer is refreshed (F5). How to do this in Powershell? I have no idea, but cool guys Farag & Oz-Zo responsible for the Sofia project on GitHub found out! Believe me, those few lines are the holy grail for a lot of scripting guys and the code is smart enough to refresh anything from explorer, to desktop, to cache icon, to taskbar… enjoy, but use carefully, it sometimes crashes ISE:
$UpdateExplorer = @{
Namespace = "WinAPI"
Name = "UpdateExplorer"
Language = "CSharp"
MemberDefinition = @"
private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);
private const int WM_SETTINGCHANGE = 0x1a;
private const int SMTO_ABORTIFHUNG = 0x0002;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern bool SendNotifyMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, int fuFlags, int uTimeout, IntPtr lpdwResult);
[DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
public static void Refresh()
{
// Update desktop icons
// Обновить иконки рабочего стола
SHChangeNotify(0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero);
// Update environment variables
// Обновить переменные среды
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, null, SMTO_ABORTIFHUNG, 100, IntPtr.Zero);
// Update taskbar
// Обновить панель задач
SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, "TraySettings");
}
private static readonly IntPtr hWnd = new IntPtr(65535);
private const int Msg = 273;
// Virtual key ID of the F5 in File Explorer
// Виртуальный код клавиши F5 в проводнике
private static readonly UIntPtr UIntPtr = new UIntPtr(41504);
[DllImport("user32.dll", SetLastError=true)]
public static extern int PostMessageW(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam);
public static void PostMessage()
{
// Simulate pressing F5 to refresh the desktop
// Симулировать нажатие F5 для обновления рабочего стола
PostMessageW(hWnd, Msg, UIntPtr, IntPtr.Zero);
}
"@
}
if (-not ("WinAPI.UpdateExplorer" -as [type]))
{
Add-Type @UpdateExplorer
}
# Simulate pressing F5 to refresh the desktop
# Симулировать нажатие F5 для обновления рабочего стола
[WinAPI.UpdateExplorer]::PostMessage()
# Refresh desktop icons, environment variables, taskbar
# Обновить иконки рабочего стола, переменные среды, панель задач
[WinAPI.UpdateExplorer]::Refresh()
# Restart the Start menu
# Перезапустить меню "Пуск"
Stop-Process -Name StartMenuExperienceHost -Force -ErrorAction Ignore
After the code has run, we can safely launch the FolderDialog, hidden folders will show up as expected!
![]()
$Path = "c:\Temp"
$Signature = @'
[DllImport("shell32.dll")]
public extern static int SHSetKnownFolderPath(ref Guid folderId, uint flags, IntPtr token, [MarshalAs(UnmanagedType.LPWStr)] string path);
'@
$Type = Add-Type -MemberDefinition $Signature -Name 'KnownFolders' -Namespace 'SHSetKnownFolderPath' -PassThru
$Type::SHSetKnownFolderPath([ref]'9E52AB10-F80D-49DF-ACB8-4330F5687855', 0, 0, $Path)|Out-Null
the $Path variable can of course be redirected to whatever location you want.
$Type::SHSetKnownFolderPath([ref]'9E52AB10-F80D-49DF-ACB8-4330F5687855', 0, 0, $Path)|Out-Null
the $Path variable can of course be redirected to whatever location you want.
Now, to use it with FolderBrowser Dialog, we just need to tell it to open to CDBurning location (known as ID 9E52AB10-F80D-49DF-ACB8-4330F5687855) like this:
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property @{ShowNewFolderButton = $false ; Description = "Select a directory" ; RootFolder = 'CDBurning'}
$result = $FolderBrowser.ShowDialog((New-Object System.Windows.Forms.Form -Property @{TopMost = $true}))
...and yes, it just opens where we want it to (sorry my temp folder is almost empty):
![]()
First, we must disable the hidden folders option, this can be done by changing explorer settings for the current user.
Showing hidden files
Now imagine you want to allow folder selection within your profile folder but you want the selector to also show the AppData folder. Here is how to proceed:First, we must disable the hidden folders option, this can be done by changing explorer settings for the current user.
Set-ItemProperty -path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -name 'Hidden' -value 1
Unfortunately, the change won’t happen until the explorer is refreshed (F5). How to do this in Powershell? I have no idea, but cool guys Farag & Oz-Zo responsible for the Sofia project on GitHub found out! Believe me, those few lines are the holy grail for a lot of scripting guys and the code is smart enough to refresh anything from explorer, to desktop, to cache icon, to taskbar… enjoy, but use carefully, it sometimes crashes ISE:
$UpdateExplorer = @{
Namespace = "WinAPI"
Name = "UpdateExplorer"
Language = "CSharp"
MemberDefinition = @"
private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);
private const int WM_SETTINGCHANGE = 0x1a;
private const int SMTO_ABORTIFHUNG = 0x0002;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern bool SendNotifyMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, int fuFlags, int uTimeout, IntPtr lpdwResult);
[DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
public static void Refresh()
{
// Update desktop icons
// Обновить иконки рабочего стола
SHChangeNotify(0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero);
// Update environment variables
// Обновить переменные среды
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, null, SMTO_ABORTIFHUNG, 100, IntPtr.Zero);
// Update taskbar
// Обновить панель задач
SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, "TraySettings");
}
private static readonly IntPtr hWnd = new IntPtr(65535);
private const int Msg = 273;
// Virtual key ID of the F5 in File Explorer
// Виртуальный код клавиши F5 в проводнике
private static readonly UIntPtr UIntPtr = new UIntPtr(41504);
[DllImport("user32.dll", SetLastError=true)]
public static extern int PostMessageW(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam);
public static void PostMessage()
{
// Simulate pressing F5 to refresh the desktop
// Симулировать нажатие F5 для обновления рабочего стола
PostMessageW(hWnd, Msg, UIntPtr, IntPtr.Zero);
}
"@
}
if (-not ("WinAPI.UpdateExplorer" -as [type]))
{
Add-Type @UpdateExplorer
}
# Simulate pressing F5 to refresh the desktop
# Симулировать нажатие F5 для обновления рабочего стола
[WinAPI.UpdateExplorer]::PostMessage()
# Refresh desktop icons, environment variables, taskbar
# Обновить иконки рабочего стола, переменные среды, панель задач
[WinAPI.UpdateExplorer]::Refresh()
# Restart the Start menu
# Перезапустить меню "Пуск"
Stop-Process -Name StartMenuExperienceHost -Force -ErrorAction Ignore
After the code has run, we can safely launch the FolderDialog, hidden folders will show up as expected!