Weekend Scripter: Tea Time!

Weekend Scripter: Tea Time!

  • Comments 2
  • Likes

 

Microsoft Scripting Guy Ed Wilson here. It has been a great week with some excellent guest bloggers. It is not that I took the week off; rather, I have spent the week working on some special projects I wanted to complete. The great thing about working with guest bloggers is that I get to tap into world-class writers who are experts in their field. This week we have had some excellent SharePoint articles and SQL server articles. In addition to reading and trying out the commands from the articles, I decided to take some time to work on a really cool script I have been wanting to write for some time.

The New-TeaTimer.ps1 script falls into the really cool category. The complete code is seen here.

New-TeaTimer.ps1

function new-teatimer
{
Param([int]$seconds = 240)
Start-Sleep -Seconds $seconds
} #end function new-teatimer
function New-BalloonTip
{
Param(
  [string]$Title = "tea is ready",
  [string]$Text = "get your tea",
  [int]$Timeout = 4000,
  [int]$eventTimeout = 5
)
Add-Type -AssemblyName system.windows.forms
$toast = New-Object system.windows.forms.notifyicon
$toast.icon = [system.drawing.systemicons]::information
$toast.balloonTipTitle = $Title
$toast.visible = $true
$toast.BalloonTipText = $Text
$toast.ShowBalloonTip($Timeout)
Register-ObjectEvent -InputObject $toast -EventName BalloonTipClicked `
-SourceIdentifier ballon_clicked
if(Wait-Event -Timeout $eventTimeout -SourceIdentifier ballon_clicked)
{
  Remove-Event -SourceIdentifier ballon_clicked
  Unregister-Event -SourceIdentifier ballon_clicked
  $toast.Dispose()
}
else
{
  Unregister-Event -SourceIdentifier ballon_clicked
  $toast.Dispose()
}
} #end function new-balloontip
# *** Entry Point to script ***
$st = Get-Date
new-teatimer
$et = Get-Date
New-BalloonTip
(New-TimeSpan -Start $st -End $et).totalSeconds

The New-TeaTimer.ps1 script begins with a function called new-teatimer. This function basically calls the Start-Sleep cmdlet. The reason for creating a separate function is because most of the time I let my tea steep for four minutes. Therefore, what I have is a timer that defaults to four minutes. To time my tea, all I need to do is to call the new-teatimer function. I do not need to calculate how many seconds are in four minutes; I just call the function and leave it at that. The new-teatimer function is shown here:

function new-teatimer
{
Param([int]$seconds = 240)
Start-Sleep -Seconds $seconds
} #end function new-teatimer

The first thing that is done in the New-BalloonTip function is to define a number of parameters. The title is the bold portion of the balloon, and the test parameter governs the text that is displayed in the body of the balloon. The timeout governs how long the balloon displays before disappearing, and the eventtimeout controls how long the script will wait for the event to timeout. This portion of the script is shown here:

function New-BalloonTip
{
Param(
  [string]$Title = "tea is ready",
  [string]$Text = "get your tea",
  [int]$Timeout = 4000,
  [int]$eventTimeout = 5
)

The forms assembly from the system.windows .NET Framework namespace is not loaded by default. The easiest way to load the assembly is to use the Add-Type cmdlet, and specify the assembly name. The New-Object cmdlet is used to create a new instance of the notifyicon class—the class that makes the popup toast that appears in the message area. The icon used by the notifyicon .NET Framework class is selected from the systemicons class that is found in the system.drawing .NET Framework namespace. Each icon in the systemicons class is a static property from this class. This is shown here:

PS C:\> Add-Type -AssemblyName system.windows.forms
PS C:\> [system.drawing.systemicons] | Get-Member -MemberType property –Static

   TypeName: System.Drawing.SystemIcons

Name             MemberType      Definition
----                 ----------              ----------
Application     Property             static System.Drawing.Icon Application {get;}
Asterisk          Property             static System.Drawing.Icon Asterisk {get;}
Error               Property             static System.Drawing.Icon Error {get;}
Exclamation    Property             static System.Drawing.Icon Exclamation {get;}
Hand              Property             static System.Drawing.Icon Hand {get;}
Information    Property             static System.Drawing.Icon Information {get;}
Question        Property             static System.Drawing.Icon Question {get;}
Shield             Property             static System.Drawing.Icon Shield {get;}
Warning         Property             static System.Drawing.Icon Warning {get;}
WinLogo        Property              static System.Drawing.Icon WinLogo {get;}

PS C:\>

The remaining lines of code set properties that have names that are relatively self-explanatory. This portion of the function is shown here:

Add-Type -AssemblyName system.windows.forms
$toast = New-Object system.windows.forms.notifyicon
$toast.icon = [system.drawing.systemicons]::information
$toast.balloonTipTitle = $Title
$toast.visible = $true
$toast.BalloonTipText = $Text
$toast.ShowBalloonTip($Timeout)

To receive an event when the balloon is clicked, use the Register-ObjectEvent cmdlet. The BalloonTipClicked event occurs when the user clicks the balloon. Other NotifyIcon events are available and would be wired up in the same manner as the BalloonTipClicked event. The sourceidentifier property value is used to track the event, and is a string that can be anything I wish to use. The input object is the new notifyicon instance that is stored in the $toast variable. This portion of the script is shown here:

Register-ObjectEvent -InputObject $toast -EventName BalloonTipClicked `
-SourceIdentifier ballon_clicked

If an event is generated by clicking the balloon, I want to remove the event from the event queue and unregister the event. When I have completed those two actions, I wish to call the dispose method of the notifyicon class to cause the balloon to disappear. This portion of the code is shown here:

if(Wait-Event -Timeout $eventTimeout -SourceIdentifier ballon_clicked)

{

  Remove-Event -SourceIdentifier ballon_clicked

  Unregister-Event -SourceIdentifier ballon_clicked

  $toast.Dispose()

}

On the other hand, it is entirely possible that the event may time out, or the balloon could be closed out by clicking the “x” in the upper right hand corner of the balloon. If this should happen, the close BalloonTipClosed event is generated, and not the BalloonTipClicked event. Therefore, to get rid of the balloon, it is necessary to unregister the event and call the dispose method. This portion of the function is shown here:

else

{

  Unregister-Event -SourceIdentifier ballon_clicked

  $toast.Dispose()

}

} #end function new-balloontip

The entry point to the script gets a datetime object and stores it in a variable named $st. It then calls the new-teatimer function. It then calls the Get-Date cmdlet once again and stores a datetime object in the $et variable. These two timestamps are called in order to verify that the new-teatimer is working properly. Because tea gets bitter if it is allowed to steep too long, it is important to see how well the new-teatimer function is working. On my computer, the value between the desired timer and the actual timer was milliseconds, which is good enough for a cup of Darjeeling tea. The New-BalloonTip function is called, and finally the total number of seconds from the timespan object that is used to compare the two datetime values is queried from the New-TimeSpan cmdlet. This portion of the script is shown here:

$st = Get-Date
new-teatimer
$et = Get-Date
New-BalloonTip
(New-TimeSpan -Start $st -End $et).totalSeconds

When the script runs, the balloon shown in the following image appears in the notification area.

Image of notification balloon shown when script runs

Join me tomorrow for another Weekend Scripter article. I would love for you to follow us on Twitter and Facebook. If you have any questions, send email to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys


Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Why use sleep? PowerShell can do this asynchronously. Aren't you guys supposed to promote PowerShell's features and take a leap forward from old style polling?

  • @Robert -

    I saw your comment on twitter the other day as well as the link to your blog. The reason I used sleep here, is that for my application it was just fine. For most people, there is an aweful lot of new information in this article, and I try not to introduce too many new concepts at once ... in other words, I like to avoid information overload. I am planning on updating the post in the next few weeks, and will incorporate the async operation in that post. As for being a shell hog? I do not find that an issue as I routinely have the ISE, and several Windows PowerShell consoles open at the same time.

    Thanks for your comment,

    ed