Wednesday 27 June 2012

Scripting Balloon Messages in Windows

Do you know those balloons that pop-up in the notification area in Windows to tell you something? I've always thought it would be nice to be able to use those in certain situations when doing something to an end user computer in the corporate environments I work in. Several months ago I figured this out and then today I was reminded of it when I was shown a script at a new customer site that uses the same idea. With that thought in my head I figured it would make a good topic to write about here. I'm going to show two examples.

The first example is the script I've 'borrowed' from a colleague today. In the environment this script was written for, the available Configuration Manager notification options after software updates have been installed doesn't quiet meet the business requirements so a script has been created to notify the user a restart is required and the Configuration Manager software update deployments have been configured to neither notify the end user nor restart the computer. The script is triggered whenever an end user logs on or unlocks their workstation. I’ll explain how that has been configured later.

The script is very simple in that it queries the operating system to see if it is waiting for a restart after patching. If Windows Update is waiting for a restart the script pops a balloon message to the end user.  Here is the script:


#
# 
# Purpose:  Prompts end user to restart if Windows is waiting for a restart after
#           patching
# 
# Usage : powershell.exe -file .\PatchingRestartNagBalloon.ps1
# 
# Version 1.0
# 
# Maintenance History
# ----------------------------------------------------------------------
# Version   -    Date      -    Change           -       Author
# ----------------------------------------------------------------------
# 1.0            25/06/12       Script Created           justanothertechnicalblog
# 
# 


$SoftwareUpdateRestartStatus = New-Object -ComObject "Microsoft.Update.SystemInfo"
$IsRebootRequired = $SoftwareUpdateRestartStatus.RebootRequired

If ($IsRebootRequired -eq $True)
{
[void] [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”)

$notification = New-Object System.Windows.Forms.NotifyIcon 

#Define the icon for the system tray
$notification.Icon = [System.Drawing.SystemIcons]::Information

#Display title of balloon window
$notification.BalloonTipTitle = “Software Updates”

#Type of balloon icon
$notification.BalloonTipIcon = “Info”

#Notification message
$notification.BalloonTipText = “Software updates have been installed on your computer. Please save your work and restart your computer as soon as possible”

#Make balloon tip visible when called
$notification.Visible = $True

#Call the balloon notification
$notification.ShowBalloonTip(0)
start-sleep -s 10

#Make balloon tip invisible when called
$notification.Visible = $False
 Exit-PSSession
}
Else
{
 Exit-PSSession
}

The second example is the script I wrote some months ago for another customer. When we built new machines for that customer, new computer objects were created in a 'Provisioning' OU in Active Directory and it was up to the technical staff who built machines to then move the new computer object to the correct Active Directory OU. Like busy first level support teams the world over, these guys were busy and often forgot this step, especially when the rest to of the workstation provisioning process was fully automated. Trying to dynamically determin the correct OU for a new machine was not possible for several reasons, so instead I wrote this script to notify the end user to take action if the machine was still in the OU where the Configuration Manager task sequence put it. I wanted a notification balloon and if the user clicked the balloon I wanted Ineternet Explorer to open to the Service Delivery team's web site so a ticket could be logged. This is what I came up with after stealing ideas from a number of places and asking for a bit of help from the resident Exchange guru who knew his PowerShell better than me. I'd reference where I got the ideas from, but it was months ago and I don't recall them all.  I also don't have enough comments in the script to remind me why I've done certain things, but that just makes reading the code more fun right?

#
# 
# Purpose:  Prompts end user to contact the Service Desk and opens IE to the
#           Service Desk home page
# 
# Usage : powershell.exe -f .\ActiveDirectoryOUNagBalloon.ps1
# 
# Version 1.0
# 
# Maintenance History
# ----------------------------------------------------------------------
# Version   -    Date      -    Change           -       Author
# ----------------------------------------------------------------------
# 1.0            25/06/12       Script Created           justanothertechnicalblog
# 
# 

function Show-BalloonTip
{
    # Requires -Version 2.0
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNull()]
        [String]$BalloonTipText,
        [Parameter(Position = 1)]
        [String]$BalloonTipTitle = 'PowerShell Event Notificaton',
        [Parameter(Position = 2)]
        [ValidateSet('Error', 'Info', 'None', 'Warning')]
        [String]$BalloonTipIcon = 'Info'
    )
    end 
    {
        Add-Type -AssemblyName System.Windows.Forms
        Add-Type -AssemblyName System.Drawing
        [Windows.Forms.ToolTipIcon]$BalloonTipIcon = $BalloonTipIcon
        $NotifyIcon = New-Object Windows.Forms.NotifyIcon -Property @{
            BalloonTipIcon = $BalloonTipIcon
            BalloonTipText = $BalloonTipText
            BalloonTipTitle = $BalloonTipTitle
            Icon = [Drawing.Icon]::ExtractAssociatedIcon((Get-Command powershell).Path)
            Text = -join $BalloonTipText[0..62]
            Visible = $true
        }
        switch ($BalloonTipIcon) 
        {
            Error {[Media.SystemSounds]::Hand.Play()}
            Info {[Media.SystemSounds]::Asterisk.Play()}
            None {[Media.SystemSounds]::Beep.Play()}
            Warning {[Media.SystemSounds]::Exclamation.Play()}
        }
        $NotifyIcon.ShowBalloonTip(0)
        switch ($Host.Runspace.ApartmentState) 
        {
            STA 
            {
                $null = Register-ObjectEvent -InputObject $NotifyIcon -SourceIdentifier "BalloonTipClicked_event" -EventName BalloonTipClicked -Action{
                    #Write-Host "Starting IE"
                    Start-Process 'c:\Program Files\Internet Explorer\iexplore.exe' -ArgumentList 'http://justanothertechnicalblog.blogspot.com.au/' -WindowStyle Maximized -Verb Open
                    $Sender.Dispose()
                    $global:clicked = $true
                    Unregister-Event $EventSubscriber.SourceIdentifier
                    Remove-Job $EventSubscriber.Action
                }
            }
            default 
            {
                continue
            }
        }
     }
 }
 
Get-EventSubscriber | Unregister-Event | Out-Null
#Write-Host $Host.Runspace.ApartmentState
Show-BalloonTip "Some location information for this computer has not been configured correctly.  Please contact the Service Desk resolve this issue.  This is a minor issue and should not impact your use of this machine" "Computer Configuration Warning" "Warning"

$global:clicked = $false
$timeOut=20 #20 Seconds
for($timeOut;$timeOut -gt 0;$timeOut--)
{
    if($global:clicked)
    {
        break;
    }
    #Write-Host "Sleeping 1 second"
    Start-Sleep -Milliseconds 1000
    
}


NOTE: This script doesn't exit properly when run from the PowerShell ISE. It runs without issue if run from the command prompt.

Update: After doing some reading today I found this article from Microsoft. I definately used this when I originaly wrote my script above:

Displaying a Message in the Notification Area

How and when do I run these scripts?  Well in both instanaces at both customers the scripts are run via a scheduled task.  Group Policy Preferences lets you schedule tasks across your desktop fleet and that is what has been used.  The task scheduler can fire a scrript on an event and two very useful events are a logon event and a screen unlock event.  I'm not going to detail how to set up scheduled tasks using Group Policy Preferences today, that sounds like a nice topic for another post, but take a look at GPP and you'll figure it out.

The other thing to note about the scripts above is that they display a command prompt when run.  Yuk.  This is no good when you have gone to the work to implement a nice neat balloon pop-up solution to notify your end users of something.  How do we stop PowerShell displaying the command prompt window?  Well, that is also a topic for another post, but I've written it along with this one.  Click here to view two solutions I've used.

No comments:

Post a Comment