-
-
Save IMJLA/1d570aa2bb5c30215c222e7a5e5078fd to your computer and use it in GitHub Desktop.
$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 |
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.
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 =
becauseOut-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!
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.
@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.
Awesome! Thanks for sharing :) 👍
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!
$OpenFileDialog.InitialDirectory=$InitialDirectory
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
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!
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
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}
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!
@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.
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.
@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.
Add " | Out-Null" on Lines 13, 16, 23
-> Otherwise the Output, when returned through function returns an array with count of 4.