Wednesday, October 04, 2006 12:43 PM bart

A graphical MessageBox confirmation cmdlet in PowerShell

Introduction

I've been posting about Windows PowerShell programmability quite a bit lately. A few interesting posts you might want to check out before continuing include:

Especially the last one got attention from the community, even our colleagues and friends of the IT Pro world :-). I can imagine they never thought of some kind of GUI popping up out of a dark shell environment. One of my blog readers, Karl Prosser, had the good idea to wrap the MessageBox functionality in a cmdlet, so that's what I've done in this post.

Ow yes, to attract Visual Basic folks too, I've written this one in VB 2005.

The code

Let's start by looking at the cmdlet itself. The basics are easy: inherit from PSCmdlet and annotate with Cmdlet, all from the System.Management.Automation namespace. Furthermore, add some parameters and override ProcessRecord:

Imports System.ComponentModel
Imports
System.Management.Automation
Imports
System.Management.Automation.Runspaces
Imports
System.Windows.Forms

<Cmdlet(
"show", "confirm")> Public Class
ShowConfirmCmdlet
    Inherits
PSCmdlet

    Private _confirmationScript As
ScriptBlock
    Private _inputObject As
PSObject
    Private _title As String =
""

    <Parameter(Mandatory:=True, Position:=1)> Property ConfirmationScript() As
ScriptBlock
        Get
            Return
_confirmationScript
        End
Get

        Set(ByVal value As
ScriptBlock)
            _confirmationScript = value
        End
Set
    End
Property

    <Parameter(Mandatory:=True, Position:=2, ValueFromPipeline:=True)> Property InputObject() As
PSObject
        Get
            Return
_inputObject
        End
Get

        Set(ByVal value As
PSObject)
            _inputObject = value
        End
Set
    End
Property

    <Parameter(Mandatory:=False)> Property Title() As
String
        Get
            Return
_title
        End
Get

        Set(ByVal value As String
)
            _title = value
        End
Set
    End
Property

    Protected Overrides Sub
ProcessRecord()
        Dim o As Object
= _confirmationScript.InvokeReturnAsIs(_inputObject)
        Dim res As String =
""
        If LanguagePrimitives.TryConvertTo(o, res)
Then
            If MessageBox.Show(res, _title, MessageBoxButtons.YesNo, MessageBoxIcon.Question) = DialogResult.Yes
Then
                WriteObject(_inputObject)
            End
If
        End
If
    End
Sub
End
Class

I won't repeat all of the basis over here once more. Just check out other PowerShell posts for more information.

Next, we need to build a snap-in wrapper to make it easy to use the cmdlet. Here it is:

<RunInstaller(True)> Public Class FindStringPSSnapIn
    Inherits
CustomPSSnapIn

    Private _cmdlets As New System.Collections.ObjectModel.Collection(Of
CmdletConfigurationEntry)
    Private _providers As New System.Collections.ObjectModel.Collection(Of
ProviderConfigurationEntry)
    Private _types As New System.Collections.ObjectModel.Collection(Of
TypeConfigurationEntry)
    Private _formats As New System.Collections.ObjectModel.Collection(Of
FormatConfigurationEntry)

    Public Sub New
()
        MyBase
.New()
        Cmdlets.Add(
New CmdletConfigurationEntry("show-confirm", GetType(ShowConfirmCmdlet), Nothing
))
    End
Sub

    Public Overrides ReadOnly Property Name() As
String
        Get
            Return
"DemoPSSnapIn"
        End
Get
    End
Property

    Public Overrides ReadOnly Property Vendor() As
String
        Get
            Return
"BdsSoft"
        End
Get
    End
Property

    Public Overrides ReadOnly Property Description() As
String
        Get
            Return
"This snap-in contains a set of demonstration cmdlets."
        End
Get
    End
Property

   
Public Overrides ReadOnly Property Cmdlets() As System.Collections.ObjectModel.Collection(Of
CmdletConfigurationEntry)
        Get
            Return
_cmdlets
        End
Get
    End
Property

    Public Overrides ReadOnly Property Providers() As System.Collections.ObjectModel.Collection(Of
ProviderConfigurationEntry)
        Get
            Return
_providers
        End
Get
    End
Property

    Public Overrides ReadOnly Property Types() As System.Collections.ObjectModel.Collection(Of
TypeConfigurationEntry)
        Get
            Return
_types
       
End
Get
   
End
Property

    Public Overrides ReadOnly Property Formats() As System.Collections.ObjectModel.Collection(Of
FormatConfigurationEntry)
        Get
            Return
_formats
        End
Get
    End
Property
End
Class

This principle was explained in a previous post. So, compile the whole thing and install it as explained below.

Install it

For the IT pros I've uploaded the compiled library to my site over here. Extract the file (results in a single .dll file) and install it as shown below:

installutil ShowConfirmCmdlet.dll

Installutil.exe can be found in the %windir%\Microsoft.NET\Framework\v2.0.50727 folder, so change the PATH environment variable accordingly (set PATH=%PATH%;%windir%\Microsoft.NET\Framework\v2.0.50727).

Run it

Now open Windows PowerShell. The first thing to do is to register the snap-in we made:

add-pssnapin demopssnapin

Next, we'll try it out with a classic example: get-process (gps). First notice I've three instances of Notepad up and running:

PS C:\Users\Bart> gps n*

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
     59       3     1496       6708    52     0,13   2916 notepad
     59       3     1496       6736    52     0,13   4392 notepad
     59       3     1544       6916    52     0,48   4932 notepad

Now, we'll ask for confirmation as follows:

gps n* | show-confirm { "Kill " + $args[0].ProcessName + "?" } -title "Please confirm" | stop-process -whatif

Assuming you choose Yes twice and No once, the output should look like this:

PS C:\Users\Bart> gps n* | show-confirm { "Kill " + $args[0].ProcessName + "?" } -title "Please confirm" | stop-process -whatif
What if: Performing operation "Stop-Process" on Target "notepad (2916)".
What if: Performing operation "Stop-Process" on Target "notepad (4392)".
PS C:\Users\Bart>

I've used whatif just to simulate what would happen; of course you can omit it and kill the processes right away.

Conclusion

There is overlap with -confirm, I know, and the pipeline order is different:

gather | confirm | perform

instead of

gather | perform -confirm

It's just a matter of taste. Personally the first one - that we've been building in this post - reads more user-friendly to me. One caveat however: no "Yes to all" or "No to all" option yet. This would require the use of BeginProcess and EndProcess overrides as Lee pointed out but isn't covered in this post (notice we'd need a more complex custom dialog other than MessageBox.Show to do this - or Vista might help out with this).

Anyway, enjoy.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# re: A graphical MessageBox confirmation cmdlet in PowerShell

Thursday, October 05, 2006 7:52 AM by Lee

Actually, it wouldn't require any session state between cmdlet calls.  PowerShell instantiates your class and calls your BeginProcessing() override before the first object comes down the pipeline, calls ProcessRecord() for each object as it comes down the pipeline, and EndProcessing() after the last object has been processed.

Your cmdlet instance persists for that whole time, so you could simply store the "Yes to All" / "No to All" response in a private variable.

# re: A graphical MessageBox confirmation cmdlet in PowerShell

Thursday, October 05, 2006 12:30 PM by bart

Hi Lee,

Indeed forgot about that :$. The danger of the last sentence I guess :). I'll update the post accordingly.

-Bart