Skip to content

Instantly share code, notes, and snippets.

@IMJLA
Last active January 14, 2024 05:43
Show Gist options
  • Save IMJLA/1d570aa2bb5c30215c222e7a5e5078fd to your computer and use it in GitHub Desktop.
Save IMJLA/1d570aa2bb5c30215c222e7a5e5078fd to your computer and use it in GitHub Desktop.
A folder browser dialog with an address bar in native PowerShell
$AssemblyFullName = 'System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
$Assembly = [System.Reflection.Assembly]::Load($AssemblyFullName)
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.AddExtension = $false
$OpenFileDialog.CheckFileExists = $false
$OpenFileDialog.DereferenceLinks = $true
$OpenFileDialog.Filter = "Folders|`n"
$OpenFileDialog.Multiselect = $false
$OpenFileDialog.Title = "Select folder"
$OpenFileDialogType = $OpenFileDialog.GetType()
$FileDialogInterfaceType = $Assembly.GetType('System.Windows.Forms.FileDialogNative+IFileDialog')
$IFileDialog = $OpenFileDialogType.GetMethod('CreateVistaDialog',@('NonPublic','Public','Static','Instance')).Invoke($OpenFileDialog,$null)
$null = $OpenFileDialogType.GetMethod('OnBeforeVistaDialog',@('NonPublic','Public','Static','Instance')).Invoke($OpenFileDialog,$IFileDialog)
[uint32]$PickFoldersOption = $Assembly.GetType('System.Windows.Forms.FileDialogNative+FOS').GetField('FOS_PICKFOLDERS').GetValue($null)
$FolderOptions = $OpenFileDialogType.GetMethod('get_Options',@('NonPublic','Public','Static','Instance')).Invoke($OpenFileDialog,$null) -bor $PickFoldersOption
$null = $FileDialogInterfaceType.GetMethod('SetOptions',@('NonPublic','Public','Static','Instance')).Invoke($IFileDialog,$FolderOptions)
$VistaDialogEvent = [System.Activator]::CreateInstance($AssemblyFullName,'System.Windows.Forms.FileDialog+VistaDialogEvents',$false,0,$null,$OpenFileDialog,$null,$null).Unwrap()
[uint32]$AdviceCookie = 0
$AdvisoryParameters = @($VistaDialogEvent,$AdviceCookie)
$AdviseResult = $FileDialogInterfaceType.GetMethod('Advise',@('NonPublic','Public','Static','Instance')).Invoke($IFileDialog,$AdvisoryParameters)
$AdviceCookie = $AdvisoryParameters[1]
$Result = $FileDialogInterfaceType.GetMethod('Show',@('NonPublic','Public','Static','Instance')).Invoke($IFileDialog,[System.IntPtr]::Zero)
$null = $FileDialogInterfaceType.GetMethod('Unadvise',@('NonPublic','Public','Static','Instance')).Invoke($IFileDialog,$AdviceCookie)
if ($Result -eq [System.Windows.Forms.DialogResult]::OK) {
$FileDialogInterfaceType.GetMethod('GetResult',@('NonPublic','Public','Static','Instance')).Invoke($IFileDialog,$null)
}
Write-Output $OpenFileDialog.FileName
@IMJLA
Copy link
Author

IMJLA commented Feb 5, 2020

You are absolutely right, thank you so much! Sorry for sending garbage downstream when you were expecting a folder path :)

All fixed now, with the caveat that I prefer $null = because Out-Null is quite a bit slower. The difference is insignificant in this case but it's habitually what I use. I'm told that Out-Null performs better and takes the lead in PS 6.0+ but obviously this script is pretty Windows-specific since it uses Windows Forms.

@theM94
Copy link

theM94 commented Feb 5, 2020

You are absolutely right, thank you so much! Sorry for sending garbage downstream when you were expecting a folder path :)

All fixed now, with the caveat that I prefer $null = because Out-Null is quite a bit slower. The difference is insignificant in this case but it's habitually what I use. I'm told that Out-Null performs better and takes the lead in PS 6.0+ but obviously this script is pretty Windows-specific since it uses Windows Forms.

All good! Your Folderbrowser is exactly what I wanted and needed. At the time, the easiest fix was just to ' -join "" ' on the resulting variable that catches the output and then it would work.

But I ended up wanting to find the culprits, when I had the time to test it out.

Thanks for your efforts!

@Jasonthurston
Copy link

It's opening behind my window, how to set "Topmost"?
I suspect this is happening b/c I have topmost set in my form that this is getting called from via a click event. I had to set the containing form to topmost b/c it was opening behind ISE.

@IMJLA
Copy link
Author

IMJLA commented Apr 13, 2020

@Jasonthurston Initially my research led me to complicated workarounds like this: https://stackoverflow.com/questions/4666580/how-can-i-set-topmost-at-the-savefiledialog-using-c

I found the solution to be MUCH simpler. After your other window is loaded (you have achieved the desired behavior), just set TopMost to false so it can behave normally again. Then the folder browser dialog opens in front the way it should.

@L4kyn
Copy link

L4kyn commented Aug 29, 2020

Awesome! Thanks for sharing :) 👍

@bcollier001
Copy link

I am very new to powershell so I apologize in advanced. I have been looking all over for something like this and it has worked wonders for me, just one question, is it possible to get the folder browser to open a specific path. I am creating a batch program that uses this powershell script for obvious reasons. Is there a way that batch could change where this script opens? Preferably a UNC path? Thank you!

@GoshaRus
Copy link

$OpenFileDialog.InitialDirectory=$InitialDirectory

@afterdarky
Copy link

Hi @IMJLA, I wanted to use this code on windows server 2012 you commented in a post on reddit that it was not possible, I changed a line in the code and now it is working very well. Just change $OpenFileDialog = [System.Windows.Forms.OpenFileDialog]::new() for $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog

@IMJLA
Copy link
Author

IMJLA commented Apr 26, 2021

Huh, I could have sworn there was something else to it but won't pretend to remember. This was another performance habit, as New-Object is terribly slow. But it shouldn't matter in this case! I updated the Gist since compatibility is more important than performance here. Thank you so much!

@GoshaRus
Copy link

I probably wanted to say that the user should be able to select a folder. In any case, the previous version worked for me, but as far as I remember there was some problem that I solved.
Anyway, look at this

function OpenFileDialog ($InitialDirectory){
$AssemblyFullName = 'System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
$Assembly = [System.Reflection.Assembly]::Load($AssemblyFullName)
$OFD = [System.Windows.Forms.OpenFileDialog]::new()
$OFD.AddExtension = $false
$OFD.CheckFileExists = $false
$OFD.DereferenceLinks = $true
$OFD.Filter = "Folders|`n"
$OFD.InitialDirectory=$InitialDirectory
$OFD.Multiselect = $false
$OFD.Title = "Выберите папку"
$OFDType = $OFD.GetType()
$OFDInterfaceType = $Assembly.GetType('System.Windows.Forms.FileDialogNative+IFileDialog')
$IFD = $OFDType.GetMethod('CreateVistaDialog',@('NonPublic','Public','Static','Instance')).Invoke($OFD,$null)
$null = $OFDType.GetMethod('OnBeforeVistaDialog',@('NonPublic','Public','Static','Instance')).Invoke($OFD,$IFD)
[uint32]$PickFoldersOption = $Assembly.GetType('System.Windows.Forms.FileDialogNative+FOS').GetField('FOS_PICKFOLDERS').GetValue($null)
$FolderOptions = $OFDType.GetMethod('get_Options',@('NonPublic','Public','Static','Instance')).Invoke($OFD,$null) -bor $PickFoldersOption
$null = $OFDInterfaceType.GetMethod('SetOptions',@('NonPublic','Public','Static','Instance')).Invoke($IFD,$FolderOptions)
$VistaDialogEvent = [System.Activator]::CreateInstance($AssemblyFullName,'System.Windows.Forms.FileDialog+VistaDialogEvents',$false,0,$null,$OFD,$null,$null).Unwrap()
[uint32]$AdviceCookie = 0
$AdvisoryParameters = @($VistaDialogEvent,$AdviceCookie)
$AdviseResult = $OFDInterfaceType.GetMethod('Advise',@('NonPublic','Public','Static','Instance')).Invoke($IFD,$AdvisoryParameters)
$AdviceCookie = $AdvisoryParameters[1]
$Result = $OFDInterfaceType.GetMethod('Show',@('NonPublic','Public','Static','Instance')).Invoke($IFD,[System.IntPtr]::Zero)
$null = $OFDInterfaceType.GetMethod('Unadvise',@('NonPublic','Public','Static','Instance')).Invoke($IFD,$AdviceCookie)
if ($Result -eq [System.Windows.Forms.DialogResult]::OK) {
$OFDInterfaceType.GetMethod('GetResult',@('NonPublic','Public','Static','Instance')).Invoke($IFD,$null)
}
return $OFD.FileName
}
$result=OpenFileDialog ([Environment]::GetFolderPath("Desktop"))
echo $result

@afterdarky
Copy link

Powershell 4.0 does not recognize the class ":: new ()" so the change was necessary to have more compatibility, which you can do and add a condition to determine which one to use based on the version of your powershell. Here is an example:
If($PSVersionTable.PSVersion.Major -ge 5){$OpenFileDialog =[System.Windows.Forms.OpenFileDialog]::new()}else{$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog}

@FVZGmbH
Copy link

FVZGmbH commented Aug 23, 2022

Hi, nice code!
My calling form is set Topmost. That causes the box will always stay behind my form. Is there a chance to fix that without to disable Topmost?
A "normal" initiated OpenFileDialog will act as it should.
Thanks!

@IMJLA
Copy link
Author

IMJLA commented Aug 24, 2022

@FVZGmbH for now this is all I've got on that https://gist.github.com/IMJLA/1d570aa2bb5c30215c222e7a5e5078fd?permalink_comment_id=3251927#gistcomment-3251927

But if you find a better solution let us know! It might be possible to solve this if your other dialogs are behaving properly.

@DanGough
Copy link

DanGough commented Oct 27, 2023

Thanks for posting! Found a few issues:

  • $Result is 0 when I press OK, so if ($Result -eq [System.Windows.Forms.DialogResult]::OK) never equals true, so line 25 never runs
  • $AdviseResult is set but never used in line 20
  • It fails at line 13 in PowerShell 7 as $OpenFileDialogType.GetMethod('OnBeforeVistaDialog',@('NonPublic','Public','Static','Instance')) returns null, which generates an error when trying to call .Invoke() on it.

Due to the PS7 issue I am using the Show-FolderDialog cmdlet from the PSShowFunctions module instead for now, which looks identical when presented.

@IMJLA
Copy link
Author

IMJLA commented Jan 14, 2024

@DanGough thank you!

Interesting, I can't reproduce $Result being 0 on my Windows 10 machine but I suspect we have an environment difference; I certainly didn't do thorough testing. (also why PS7 doesn't work, thank you for posting a modern alternative)

I did need to invoke the Advise method, although I'm pretty sure I could have dumped $AdviseResult to $null instead since it is unused, and that would make PSScriptAnalyzer happy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment