Hey, Scripting Guy! Can I Open a File Dialog Box with Windows PowerShell?

Hey, Scripting Guy! Can I Open a File Dialog Box with Windows PowerShell?

  • Comments 20
  • Likes

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am a faithful reader of the Hey, Scripting Guy! articles and have been a Dr. Scripto fan for years. I love your new TechNet Script Center Gallery too by the way. It is much easier to use and to locate scripts than what you had before. I had no idea you had so many scripts up there. WOW! Anyway, I have this script that I have used for a while that I got off the old Script Repository that creates a common dialog box to open a file explorer window. The problem is the script does not work on Windows Vista, and I have also tried it on Windows 7 and it does not work there either. Is there any easy way to open a file dialog box so that I can use a file picker to select a file path instead of having to type in a long file name and location?

-- PG

Hey, Scripting Guy! Answer

Hello PG, Microsoft Scripting Guy Ed Wilson here. Ah, the old UserAccounts.CommonDialog problem. There was an article posted way back in January 2005 called, How Can I Show Users a Dialog Box for Selecting Files? Along with the script you found on the Script Repository, that is about it for documentation. As far as I can recall, I never saw any documentation about the UserAccounts.CommonDialog object on MSDN, so it was an undocumented object. (I actually had another question a while back that I answered during Quick-Hits Friday.) Using undocumented objects is always an iffy proposition at best, but it is also fununtil your scripts start to break. The UserAccounts.CommonDialog object was removed from Windows Vista because of security concerns.

You can accomplish the same thing, in a very similar manner as a matter of fact, by using Windows PowerShell. I have taken the liberty of writing a pretty cool function that is called Get-FileName that you will be able to use to accomplish your file-picking needs. The good thing is that the Get-FileName function uses a standard .NET Framework class that is documented on MSDN, so it will be easy for you to add additional capabilities to the function if you desire to do so. I have placed the function in a script that I call Get-FileNameFunction.ps1, so you will be able to easily play around with the function, get familiar with it, and do some exploration with it. After you get it tuned the way you like it, you may want to consider placing it in a function library or even putting it in your Windows PowerShell profile. The complete Get-FileNameFunction.ps1 script is seen here.

Get-FileNameFunction.ps1

Function Get-FileName($initialDirectory)
{  
 [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
 Out-Null

 $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
 $OpenFileDialog.initialDirectory = $initialDirectory
 $OpenFileDialog.filter = "All files (*.*)| *.*"
 $OpenFileDialog.ShowDialog() | Out-Null
 $OpenFileDialog.filename
} #end function Get-FileName

# *** Entry Point to Script ***

Get-FileName -initialDirectory "c:\fso"

The Get-FileName function begins by using the Function keyword to define the function, followed by the function name, and then the input parameter. The function name uses the two part Verb-Noun naming standard that is important for Windows PowerShell 2.0. The input parameter is defined inside the parentheses by the variable $initialDirectory. This variable will have a scope inside the Get-FileName function. The function then opens with a script block, which is delimited by a pair of curly brackets. The opening curly bracket immediately follows the function declaration. The function declaration, input variable, and opening curly bracket are seen here.

Function Get-FileName($initialDirectory)
{  

Next we will need to load the .NET Framework assembly that contains the ability to create Windows forms. To do this in Windows PowerShell 1.0, you must use the LoadWithPartialName static method from the System.Reflection.Assembly .NET Framework class.

(Actually there is another method to load the assembly, but it is worse than this one, and I will not go over it right now. To be fully transparent, the LoadWithPartialName static method is deprecated. However, it has been deprecated for several versions of the .NET Framework, and has not been made obsolete yet, I suspect partially because the other method is so much worse. )

The LoadWithPartialName static method takes the name of the assembly that contains the .NET Framework classes you want to use. This is important because you will not always know the name of the assembly, unless you look it up on MSDN. Today we will be using the System.Windows.Forms.OpenFileDialog .NET Framework class. When we look at the class reference information on MSDN at the top of the class page, it tells you the namespace and the assembly. In this example, the OpenFileDialog class resides in the System.Windows.Forms namespace. The assembly is also named System.Windows.Forms. (Sometimes, the assembly name and the namespace name are different. These are occasions when you must get familiar with the MSDN documentation.) This is seen here:

Image of the OpenFileDialog Class

 

Because I always refer to .NET Framework classes by their full name, I always know the namespace. This is because the System.Windows.Forms.OpenFileDialog class resides in the System.Windows.Forms namespace. Only the last part of the name is the actual class name. The first portion (System.Windows.Forms) points to the namespace in which the class resides. Knowing about the .NET Framework class namespaces is a great way to find other classes that are related to a common topic. Because this week we are looking at creating Windows PowerShell scripts that use graphical components, it makes sense to open MSDN and peruse the System.Windows.Forms .NET Framework namespace. This is kind of like going to the library, finding a single book on a topic such as big band music in the United States, going to the open stacks, and looking around in that general area for other books about big band music. It is a great way to find interesting books, and by extraction, the technique is a great way to find interesting .NET Framework classes.

When you call the LoadWithPartialName static method, it provides feedback when the assembly is loaded. If the assembly is not loaded, because it is not found or it is spelled incorrectly, no error is generated. Because of this, you will want to insure that the code is working properly. I like to test this inside a Windows PowerShell console. This is seen here:

PS C:\> [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms"
)

GAC    Version        Location
---    -------        --------
True   v2.0.50727     C:\WINDOWS\assembly\GAC_MSIL\System.Windows.Forms\2.0....


PS C:\>

Once I know it is working properly, I pipe the returned information to the Out-Null cmdlet to remove the distracting feedback mechanism. This is seen here:

 [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
 Out-Null

It is time to create an instance of the System.Windows.Forms.OpenFileDialog .NET Framework class. To do this you will want to use the New-Object cmdlet and give it the full name of the .NET Framework class you are working with. Store the resulting instance of an OpenFileDialog class in a variable named $OpenFileDialog. This is seen here:

 $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog

After you have an instance of the OpenFileDialog class, you can assign values for a couple of properties. The initialDirectory property is used to control where the file dialog box will open. This is something that is best left up to the script to decide, so I decided to use a variable here (the same one that is passed to the function) to control the initial location. A cool thing to do is to store the location in the registry and to read the value from the registry each time the function is called. In this way, the file dialog box will always return to the last used location. The Filter property is used to configure which files are displayed in the dialog box. The filter string contains a description of the filter, followed by the vertical bar (|), and the filter pattern. You are allowed to have multiple filters as long as you separate each filter by a semicolon. The filter pattern I have defined is a simple one that shows all files. This is seen here:

 $OpenFileDialog.initialDirectory = $initialDirectory
 $OpenFileDialog.filter = "All files (*.*)| *.*"

To show the dialog box, you call the ShowDialog method. Because the method will return status information to the Windows PowerShell console, I pipe the return to the Out-Null cmdlet as seen here:

 $OpenFileDialog.ShowDialog() | Out-Null

Windows PowerShell functions always return a value. There is little need to use the Return statement to return the value. When the filename property is neither captured in a variable nor written out, it will be returned to the calling code. This makes it easy to use the returned filename that was selected in the dialog box for other purposes. After we have returned the file name and closed the curly brackets, the function is complete. When closing curly brackets to close out a function, I consider it to be a best practice to always include an ending comment character that states the curly bracket is ending the function and the name of the function. This practice makes your code much easier to read. This is seen here:

 $OpenFileDialog.filename

 

} #end function Get-FileName

The entry point to the script is simple; I pass a path to the initialDirectory parameter when I call the Get-FileName function. This is seen here:

Get-FileName -initialDirectory "c:\fso"

When the script runs, the Open dialog box that is seen here appears:

Image of the Open file dialog box

 

PG, thank you for your kind words, and thank you for asking us to look at Open file dialog boxes. Join us tomorrow as graphical Windows PowerShell week continues. If you want to know what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to scripter@microsoft.com or post it on the Official Scripting Guys Forum. See you tomorrow. Until then, take care and happy scripting. 
 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • When I run the Get-FileName function from the PowerShell ISE, the function works fine, but when I run it from the console, it never shows the OpenFileDialog and just hangs.

    If I add

       $OpenFileDialog.ShowHelp = $true

    before ShowDialog(), the dialog is displayed and I can choose a file.

    Why is there this difference in behavior between the ISE and console?

  • The same issue exists for FolderBrowserDialog except that there isn't a ShowHelp property so there isn't this work around. But it works in ISE. Here is my code:

    Function Get-FolderName()

    {  

    [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null

    [System.Reflection.Assembly]::LoadWithPartialName("System") | Out-Null

    $OpenFolderDialog = New-Object System.Windows.Forms.FolderBrowserDialog

    $OpenFolderDialog.SelectedPath = "C:\"

    # must be true for OpenFileDialog, otherwise it hangs

    $OpenFolderDialog.ShowHelp = $true;

    $OpenFolderDialog.ShowDialog() | Out-Null

    $OpenFolderDialog.filename

    } #end function Get-FolderName

    # *** Entry Point to Script ***

    Get-FolderName

  • I found that these scripts run fine in Single-Threaded Apartment mode. i.e powershell -sta <your script>

  • (sorry. i can understand english vary hardly.)

    more simple this code.varname is not need.

    Function Get-FileName($initialDirectory)

    {  

    [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null

    (New-Object System.Windows.Forms.OpenFileDialog)|ForEach-object {

        $_ = $initialDirectory

        $_.filter = "All files (*.*)| *.*"

        $_.ShowDialog() | Out-Null

        $_.filename

    } #end function Get-FileName

  • This script runs fine, however the dialog box opens behind other windows - is there a function to get it to stay on top?

  • I find that the dialog often appears behind the PoSH or ISE windows.

    Putting "$OpenFileDialog.ShowHelp = $true" AFTER "$OpenFileDialog.filename" seems to have solved that for me.

    I.e.

    Function Get-FileName($initialDirectory)

    {  

    [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |

    Out-Null

    $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog

    $OpenFileDialog.initialDirectory = $initialDirectory

    $OpenFileDialog.filter = "All files (*.*)| *.*"

    $OpenFileDialog.ShowDialog() | Out-Null

    $OpenFileDialog.filename

    $OpenFileDialog.ShowHelp = $true

    } #end function Get-FileName

  • @Mike W-C Thank you for pointing this out ... I think you are right, it will help with some of the timing issues going on here. It is amazing how different systems treat the same script sometimes ...

  • Thanks HLO ! This works perfectly: powershell.exe -sta <your script>

  • I'm using this code to open a file dialog box, but how do I retain the file selection made by the user in my vbscript?

    vbscript looks like this so far:

    Set objShell = CreateObject("WSCript.shell")

    strCommand = "powershell.exe -sta C:\Users\*****\Desktop\Scripts\test.ps1"

    objShell.Run strCommand, 0, True

    powershell script:

    Function Get-FileName($initialDirectory)

    {  

    [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |

    Out-Null

    $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog

    $OpenFileDialog.initialDirectory = $initialDirectory

    $OpenFileDialog.filter = "All files (*.*)| *.*"

    $OpenFileDialog.ShowHelp = $true

    $OpenFileDialog.ShowDialog() | Out-Null

    $OpenFileDialog.filename

    } #end function Get-FileName

    # *** Entry Point to Script ***

    Get-FileName -initialDirectory "C:\Users\jtedmo1\Desktop\Scripts"

  • brochacho - you could write the selected filename and path to a temp text file(or reg value), then have vbscript read it (as far as I know there isn't a way to share variables between the two scripting languages)

    so at the end of your powershell script instead of:

            $OpenFileDialog.filename

    change this to

            Set-Contents c:\myfile.txt $OpenFileDialog.filename

    Then read the txt file in your vbscript, and assign to a variable

  • Thanks for this

  • Thanks for this. Like others have mentioned I had to add $OpenFileDialog.ShowHelp = $true to get it to work properly.  I've added this to my list of powershell functions for getting input from the user via a GUI (blog.danskingdom.com/powershell-multi-line-input-box-dialog-open-file-dialog-folder-browser-dialog-input-box-and-message-box).

  • Is it possible to disable parts of the open dialog window such as create new folder?

  • Is there any way to get this to show a taskbar icon when it's invoked? Bit too easy for it to get lost behind other windows.

  • To get it to open with focus (on top) add $OpenFileDialog.Topmost = $true