Monday, January 27, 2014

Creating A Message Box Using PowerShell

The other day, a student asked me how he could display a message box as part of a script. There is no, out of the box, cmdlet that does this, she pointed out. What she wanted to do was to display a window, with a window title and some contents, like this:
image

Now if you are a .NET programmer, this is pretty much a no brainer – just call the Windows.Forms.Messagebox’s Show method, and provide the text and the heading. Easy – but what if you are using PowerShell? As a Lync Admin – you might have a script that, say, provisioned users and you want to display a nice friendly message box to tell the user some bit of information such as how many users were provisioned. So how do you do that?

PowerShell, as you all know (or probably should know) is based on .NET – many cmdlets are just a PowerShell wrapper around an existing .NET Class/method. For example, Get-Process just invokes the System.Diagnostic.Process class’s GetProcesses() method. Now the cmdlet does a little more than just a GetProcess call, such as processing wild cards, enabling the cmdlet to run against a different machine, etc. But essentially, the base cmdlets just enable IT Pros simple access into .NET functions and Features. Application specific cmdlets, such as those supported by Lync, provide access into application specific functions, which are implemented as .NET Classes too. You can think of the application specific classes as just an extension to .NET.

The .NET Framework, and the Application specific ‘extensions’, are vast. There are a very large number of classes in total, and many of them have no relevance at all to the tasks that Lync admins carry out. To load all the relevant classes at runtime each time you open PowerShell would be very wasteful – both in terms of how long it would take to load them all, plus the runtime overhead (i.e. memory) wasted on classes never used. Thus, PowerShell only loads a core set of classes by default. But there is nothing stopping you from first loading the using any other .NET class.

Each .NET class is delivered via a DLL. If you look at http://msdn.microsoft.com/en-us/library/system.diagnostics.process(v=vs.110).aspx, you can see the Systems.Diagnostics.Process class is implemented in the System.DLL (See ‘Assembly:’). System.DLL holds the core .NET functions and is always loaded by PowerShell. If you go to the http://msdn.microsoft.com/en-us/library/system.diagnostics.process(v=vs.110).aspx page, which documents the MessageBox class, you’ll see this class is contained in System.Windows.Forms DLL, which is NOT loaded by default.
So to use the class in the first place, we need to first load the DLLs. This is pretty easy and looks like this:
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
This load the DLL from .NET’s global assembly cache and it remains loaded in the current PowerShell  window and memory space till you close that instance of PowerShell. If you have other assemblies either part of the .NET Framework (e.g. System.Numerics) or a privately developed assembly, you can load them in a similar fashion.

Developers and PowerShell Gurus will probably point out that the LoadWithPartialName method has been deprecated. This is true but the recommended approach is more complex. Whatever,  it remains the case that the LoadWithPartialName method works, and is likely to continue to work for some time! You can read a good explanation of this in Lee Home’s blog (http://www.leeholmes.com/blog/2006/01/17/how-do-i-easily-load-assemblies-when-loadwithpartialname-has-been-deprecated/) although you should note that that article was written in the Monad days, and is just slightly out of date (e.g. it uses .MSH as an extension). But for now, using this deprecated method does work and as I say, is likely to continue to work for a while!

So once you have the DLL loaded, you can just use the normal .NET calling feature to invoke a method in a .NET class directly. The core PowerShell line of code to do this is:
[Windows.Forms.MessageBox]::Show($Message, $BoxTitle, 
   
[Windows.Forms.MessageBoxButtons]::OK , 
    [Windows.Forms.MessageBoxIcon]::Information)
Ok, so it’s three lines long but it is quite simple. But rather than have to load an assembly and call that nasty looking method, you can wrap all of this inside a simple Advanced Function, and load that in your Profile or as part of a script or suite of scripts. I’ve written a simple script that implements a Show-Message function that displays a message box as you see above – see my PowerShell scripts blog for the script.
The bottom line is that you can easily display a message box from your script if and where you need. Just leverage my script as part of your profile, a personal module or directly inside a longer script and go for it.

1 comment:

jlrd said...

I've got a couple scripts that pop up a message box for basic user interface purposes.

My understanding is that the Add-Type cmdlet is what took over for a lot of the "LoadWithPartialName"/Reflection maneuvers.

I've been using something like the following without issue since at least v3, not sure if this works on v2.

Add-Type -AssemblyName System.Windows.Forms

$Message = 'Message here...'
$Title = 'Title is This'
$Button = [Windows.Forms.MessageBoxButtons]::OK
$Icon = [Windows.Forms.MessageBoxIcon]::Information

[Windows.Forms.MessageBox]::Show($Message,$Title,$Button,$Icon)

I've had some frustration with Add-Type for custom .NET stuff but never core .NET framework classes that I can recall.