September 1st, 2009

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

 

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:WINDOWSassemblyGAC_MSILSystem.Windows.Forms2.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

 

Author

0 comments

Discussion are closed.