Asynchronously Save Images in the Clipboard to File

PowerShell v5.0 introduces us with two cmdlets, Get-Clipboard and Set-Clipboard which, just as their names suggest, allow us to obtain and set the contents of the clipboard. What’s not immediately apparent though is that these cmdlets can process more than just text in the clipboard.

In this blog, we’ll focus on using Get-Clipboard, and create a script which is used in combination with Register-EvengineEvent to save an image to file any time that one is detected in the clipboard during the current session. A repo containing the script is available at GitHub

Get-Clipboard supports the processing of several types of data that is in the clipboard, one of which is that of an image type. Examples of these would be print screens or copying an image created in Paint into the clipboard.

When an image is in the clipboard, and we use Get-Clipboard -Format Image, an object of Bitmap type (inherited from System.Drawing.Image) is returned. One of the methods this class provides is Save. Save has several overloads, one of which writes to a file and a variety of formats of your choice.

The script below requires use of the ISE, and uses Register-EngineEvent to register a scriptblock, which runs when a PowerShell.OnIdle event is raised. In the scriptblock, we check to see if there is any content in the clipboard of image type. If there is, we take the details of the current file open in ISE the editor, and use it to generate a unique filename. Then, the content of the clipboard is saved to this filename using the method mentioned above. Finally, we clear the clipboard to ensure that there we don’t end up in a continuous looping operation.

Register-EngineEvent -SourceIdentifier PowerShell.OnIdle -Action {
    $content = Get-Clipboard -Format Image
    If ($content) 
        $path = $psISE.CurrentFile.FullPath | Split-Path
        $filename = "$path\ImageCap - " + (Get-date -Format 'hh-mm-ss') + '.png'
        Set-Clipboard -Value $Null

Thanks for reading!



Sharing Events Handlers in PowerShell GUIs

When you setup an event handler and its code, it is helpful to know it can be shared amongst multiple controls whilst still allowing access to the specific control which raised the event.

The first apparent benefit of this is that it instantly reduces the amount of code and code replication in your scripts, but it also gives an insight into what information is received by an event handler.

Although there are some exceptions, a typical event will provide two sets of information that the hander can process

  • The calling object (referenced in your code by use of the variable $this), also known as the Sender
  • The event arguments, passed in as a pipelined object $_

To illustrate how we can use this, we’re going to create a form with two buttons, which share the same event handler, which change the background color to white when the mouse hovers over it, and then back to normal when the mouse leaves the button’s area. You can find a copy of the PowerShell Studio form, and exported .ps1 file (for looking at the pure PowerShell code) at my GitHub repository

  • Create a form with two buttons
  • Select the first button
  • For the MouseHover event, call its handler buttonHover
  • For the MouseLeave event, call its handler buttonLeave
  • Repeat the button setting for the second button
Sharing Events - Force and Event Design

The first buttons configuration

Sharing Events - Force and Event Design Button 2

The second buttons configuration

  For our the event code, use the following:

$buttonHover = {
    $script:oldColor = $this.BackColor
    $this.BackColor = 'White'

	$this.BackColor = $script:oldColor

Shared events in action :


Introducing SMAPLEX, an SMA PowerShell Drive Powered by Simplex

How I learned to stop worrying about writing an SMA PowerShell provider and love the drive

In addition to handling storage, distribution, queuing, and execution of runbooks, Service Management Automation (aka SMA) also performs the storage of resources (referred to in the Windows Azure Pack as ‘assets’). SMA resources consist of connections, credentials, variables (though purists will rightly tell you that they’re constants), schedules, runbooks, and jobs. Access to these elements can be carried out via the cmdlets provided with SMA, from its web services, or, if you’re using it, the Windows Azure Pack admin portal itself.

I thought I’d set myself the challenge of seeing if I could write a PowerShell Provider for SMA so that I could review all my resources from a single drive. This prove to be too much of a challenge for me. Fortunately I found out that MVP Jim Christopher (@beefarino on Twitter), has written a PowerShell module called Simplex.


Written for implementation with PowerShell in a module, Simplex deals with the parts that you would normally need to write a provider for. All that’s really required is to define the structure and PowerShell code in a domain specific language (DSL) style template for the provider you wish to create.

NOTE : Simplex creates a navigation provider. This means that whilst you are able to list and access content, writing new and updating content is not possible.

As mentioned, the contents of this file use a DSL type syntax, of which there are only three keywords.

Root is at the top of your provider code and defines what you will see if you are at the root of the providers drive. e.g. SMA:\. Nested under there, using braces to indicate the blocks can be the two other keyboards.

Folder indicates a container within our tree structure. The text immediately after this is the name given to the folder itself. The main thing to keep  in mind with folder keywords in a configuration script is that they represent a static item whose contents will not change.

Script is similar to a folder, but with the exception that when a PS Drive compatible command, such as Get-ChildItem, is used to list contents, the code within braces is dynamically run.

Configuring the SMA PowerShell Provider

Ensuring that you’re running on a system with the SMA cmdlets already installed, there’s four things we need to do in order to setup our SMA drive (or indeed any provider) :

  • Download the latest Simplex module
  • Install the module
  • Create our provider template
  • Register the PS Drive

Download and Install the Simplex Module

         smaplex - jims repo

  • Click Download Module Zip

         smaplex - jims appveyor build

  • Download Simplex
  • Create a folder called Simplex in one of your module paths
  • Extract the contents of the ZIP file to this folder

         smaplex - installing the module

Provider Configuration Document

Copy the content below into the clipboard, and save the content to a location on your hard disk.

(Note that there is no code for Connections, simply because it is not a feature I use at all within my Runbooks)

root {
    script Connections {

    script Credentials{
        $defaultDisplaySet = 'Name', 'LastModifiedTime'
        $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet', [string[]]$defaultDisplaySet)
        $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
        $credentials = Get-SmaCredential @smaParameters
            $hash = @{
                CredentialID     = $psitem.CredentialID
                TenantID         = $psitem.TenantID
                Name             = $psitem.Name
                CreationTime     = $psitem.CreationTime
                LastModifiedTime = $psitem.LastModifiedTime
                Password         = $psitem.Password
                UserName         = $psitem.UserName
                Description      = $psitem.Description
            $psObject = New-Object -TypeName PSObject -Property $hash
            $psObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers
    script Schedules{
        $defaultDisplaySet = 'Name', 'LastModifiedTime'
        $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet', [string[]]$defaultDisplaySet)
        $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
        $schedules = Get-SmaSchedule @smaParameters
            $hash = @{
                DayInterval      = $psitem.DayInterval
                ScheduleID       = $psitem.ScheduleID
                TenantID         = $psitem.TenantID
                Name             = $psitem.Name
                Description      = $psitem.Description
                StartTime        = $psitem.StartTime
                ExpiryTime       = $psitem.ExpiryTime
                CreationTime     = $psitem.CreationTime
                LastModifiedTime = $psitem.LastModifiedTime
                IsEnabled        = $psitem.IsEnabled
                NextRun          = $psitem.NextRun
                JobContexts      = $psitem.JobContexts
                Runbooks         = $psitem.Runbooks
            $psObject = New-Object -TypeName PSObject -Property $hash
            $psObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers
    script Variables{
        $defaultDisplaySet = 'Name', 'Value', 'LastModifiedTime'
        $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet', [string[]]$defaultDisplaySet)
        $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
        $variables = Get-SmaVariable @smaParameters
            $hash = @{
                VariableId       = $psitem.VariableId
                TenantID         = $psitem.TenantID
                Name             = $psitem.Name
                IsEncrypted      = $psitem.IsEncrypted
                CreationTime     = $psitem.CreationTime
                LastModifiedTime = $psitem.LastModifiedTime
                Value            = $psitem.Value
                Description      = $psitem.Description
            $psObject = New-Object -TypeName PSObject -Property $hash
            $psObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers
    script Jobs {

    script Runbook {
        $defaultDisplaySet = 'RunbookName', 'LastModifiedTime'
        $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet', [string[]]$defaultDisplaySet)
        $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
        $runbooks = Get-SmaRunbook @smaParameters
        ForEach ($Runbook in $runbooks) 
            <#  Try {
                    $DraftContent = Get-SmaRunbookDefinition -Name $Runbook.RunbookName -Type Draft @smaParameters
                    $PublishedContent = Get-SmaRunbookDefinition -Name $Runbook.RunbookName -Type Published @smaParameters
                    Catch {
            $hash = @{
                TenantID                  = $Runbook.TenantID
                RunbookID                 = $Runbook.RunbookID
                RunbookName               = $Runbook.RunbookName
                CreationTime              = $Runbook.CreationTime
                LastModifiedTime          = $Runbook.LastModifiedTime
                LastModifiedBy            = $Runbook.LastModifiedBy
                Description               = $Runbook.Description
                IsApiOnly                 = $Runbook.IsApiOnly
                IsGlobal                  = $Runbook.IsGlobal
                PublishedRunbookVersionID = $Runbook.PublishedRunbookVersionID
                DraftRunbookVersionID     = $Runbook.DraftRunbookVersionID
                Tags                      = $Runbook.Tags
                LogDebug                  = $Runbook.LogDebug
                LogVerbose                = $Runbook.LogVerbose
                LogProgress               = $Runbook.LogProgress
                Statistics                = $Runbook.Statistics
                Schedules                 = $Runbook.Schedules
            $psObject = New-Object -TypeName PSObject -Property $hash
            $psObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers

Configuration Document Structure

The document content for our SMA PS Drive above will generate a structure of the following type.

  • ROOTDRIVE: (We’ll be calling the drive SMA in this blog, so it will be SMA:)
  • Connections
    • Result of Connections scriptblock
  • Credentials
    • Result of Credentials scriptblock
  • Schedules
    • Result of Schedules scriptblock
  • Variables
    • Result of Variables scriptblock
  • Jobs
    • Result of Jobs scriptblock
  • Runbooks
    • Result of Runbooks scriptblock

A Quick Look at a Script section

Let’s take a look at the code within the Credentials scriptblock above.

The first thing to notice is that is now pure PowerShell code. The tasks it performs is to define the default parameters that will be returned, execute the command Get-SMACredential, an SMA cmdlet, and then output this object. You’ve probably noticed that we’re using a splat for it’s parameters. This is defined in our intialisation script, which is covered later in this article.

The rest of the script sections use a similar methodology to obtain the associated information from SMA. However, in the Runbook section, you’ll see that the runbook definition section is commented out. The reason for this is becaue it can result in substantially long waits before the information is returned. Because i am using about 50 runbooks, this is an option i’m currently not . It may be possible to speed up this operation by a direct SQL query or using multiple jobs. I’ve yet to test this though.

Initialising the PS Drive

Now that we’ve installed Simplex and have written our initial configuration document, all this is left to do is initialise the drive. After importing the module in a PowerShell session we use the New-PSDrive command with custom PSProvider and Root parameter settings. The value for Root is set to the path of the configuration document we earlier created.

In this same script we create a credential object, which is included along with the WebServiceEndpoint in our hashtable, used for paramter splatting in the configuration document.

Import-Module Simplex
New-PSDrive -Name SMA -PSProvider Simplex -Root "d:\temp\config.ps1"

$username = "contoso\administrator"
$secpasswd = ConvertTo-SecureString "mypassword" -AsPlainText -Force 
$credential = New-Object System.Management.Automation.PSCredential ($username, $secpasswd)

$smaParameters = @{
Credential = $Credential
WebserviceEndpoint = 'https://sma01'

SMA PSDrive in Action

Run the code above. All being well, you will shortly see confirmation that the drive has been initialised.


From this point, you can navigate and use navigational and filtering cmdlets, such as Get-ChildItem, and Where to customise your output.


  • Set-Location SMA:
  • Get-ChildItem
sma - get-childitem

Change to SMA drive and list contents


  • Set-Location -Path .\Runbook
  • Get-ChildItem | Sort RunbookName
sma - list runbooks

List Runbooks,  sorting on the RunBookName property


  • [datetime] $before = ‘1/5/2016’
  • Get-ChildItem | Where StartTime -LT $before
sma - get and filter jobs

Only list jobs which started before a certain date


Combining the use of SMA cmdlet’s with Jim’s module to make a PS Drive has made the retrieval of information from its database a relatively simple process, and makes it easy for extension at a later date. The data abstraction achieved by implementing a PS-Provider is also something for me personally that is desirable for writing other scripts that can use this for data retrieval.

Thanks for reading, and you can find the files for SmaPlex at my GitHub repo.