Summary: Microsoft Scripting Guy, Ed Wilson, revisits the LoveOMatic, a Windows PowerShell Script that sends random email messages at random times.
Microsoft Scripting Guy, Ed Wilson, is here. Several months ago, I wrote a Windows PowerShell script I called the LoveOMatic (See Weekend Scripter: Introducing the Scripting Guys PowerShell LoveOMatic). It was named after the Scripting Guys famous ‘omatic tools, such as the ScriptOMatic. The point of the script was to illustrate sending email by using the Windows PowerShell cmdlet Send-MailMessage. This is an extremely important technique from a monitoring perspective. I just thought I would have a bit of fun with it. It also illustrated using the Get-Random cmdlet, which is important from a scripting load balancing perspective. But the script could stand a bit of improvement. So, in honor of Valentine’s Day, I am introducing the LoveOMatic V2 script.
Note Even if you never intend to spam a loved one, the lesson today is a good one—how to improve a previously existing script. In fact, in past Scripting Games, I have engineered an event similar to this. All the time, we, as scripters, find a script that does basically what we need to do, and we revise the script. So this is, in effect, a real-life scenario.
Not everything needs to be in a function
It has become nearly a truism that when improving a script, the first thing that happens is to move “stuff” into a function.
Note Refer back to the original blog post to see the script under discussion.
But there are some scripts that do not necessarily gain much by moving things around into a function—this is one of them. Normally, I use a function to encapsulate code that will be reused or to separate out the logic of the script. This script is pretty much a straight-line script. I could move the For loop into a function, but that would not really gain me very much. Instead, I think I will keep the straight line execution in a script, and not move things to a function. There are three main improvements I will make.
The first is to add support for command-line parameters. Things that make sense are the number of emails to send, the minimum and the maximum time values between emails, and the messages themselves. The second improvement is to move the messages from hard-coded string literals stored in variables to a single text file. The third improvement uses the PSDefaultParameterValues feature of Windows PowerShell 3.0.
Adding command-line parameter support
When I run the script, I might like to be able to specify certain things from the command line, instead of opening up the script in the Windows PowerShell ISE and editing the text of the script. To do this, I use the Param statement. Because I also want the script to run like the other script ran, I assign default values for each parameter. If I want to shorten the min time, I can supply that from the command line, or if I specify no new value for the command-line parameter, then the script runs with default values. The parameter section is shown here.
Param(
$numberOfEmails = 6,
$minHour = 3600,
$maxHours = 10800,
$message = (Get-Content c:\fso\messages.txt))
Adding support for the text file
Because the original script used an array of messages, the only change I needed to do was to move the messages to a text file. Each message appears on its own individual line. When I use Get-Content to read the text of the file, it stores the results in the $message array. This is a major improvement in the script. The original code is shown here.
$message1 = "I am thinking of you."
$message2 = "You are the sunshine of my life."
$message3 = "You walk in beauty like the night."
$message4 = "I am glad I met you."
$message5 = "My life is richer with you a part of it."
The new code is shown here (it is part of the Param section):
$message = (Get-Content c:\fso\messages.txt))
Not only is the new code cleaner, but it also means the script is more flexible. I can change, for example, the entire contents of the file, or add new messages to the bottom of the file, or even change files. So, for example, if I want to send out Earth Day email messages, I simply create a new file named EarthDay.Txt and supply it at the message line, or from the command line when calling the script. Cool.
Creating PSDefaultParameterValues
Of the three big improvements I made to the original LoveOMatic, for me, the best improvement is the one where I create a new set of PSDefaultParameterValues. Keep in mind, I do not need to do this in my script—in fact, I normally would not. The big payoff comes when you do this IN YOUR Windows PowerShell PROFILE! Then, you do not even need to mess with the values. For example, useful defaults would be the From and the SMTPServer, because they would not change very often. If I am configuring something for a monitoring application, I might go ahead and specify the Subject and the To fields as well—as I have done here.
$PSDefaultParameterValues = @{
"Send-MailMessage:From"="Ed@Nwtraders.msft"
"Send-MailMessage:To"="ScriptingWife@Nwtraders.msft"
"Send-MailMessage:Subject"="Happy Valentine’s Day"
"Send-MailMessage:SMTPServer"="smtphost.nwtraders.msft"}
Note Keep in mind that I am here overwriting any previously stored default parameter values. If I need to ADD to the default values, I use the ADD method as shown here.
$PsDefaultParameterValues.add(“cmdlet:parameter,NewValue)
Advantage
By specifying the major parameters for my Send-MailMessage cmdlet, I have a very short command now to send email, as shown here.
Send-MailMessage -body $message[$rnd]
The LoveOMaticV2.ps1 script appears here.
LoveOMaticV2.ps1
# Requires -version 3.0
Param(
$numberOfEmails = 6,
$minHour = 3600,
$maxHours = 10800,
$message = (Get-Content c:\fso\messages.txt))
$PSDefaultParameterValues = @{
"Send-MailMessage:From"="Ed@Nwtraders.msft"
"Send-MailMessage:To"="ScriptingWife@Nwtraders.msft"
"Send-MailMessage:Subject"="Happy Valentine's Day"
"Send-MailMessage:SMTPServer"="smtphost.nwtraders.msft"}
For($i=0; $i -le $numberOfEmails; $i++)
{
$rnd = Get-Random -Minimum 0 -Maximum 5
Send-MailMessage -body $message[$rnd]
"message: $($message[$rnd]) sent at $((get-date).tostring())"
Start-Sleep -Seconds (Get-Random -Minimum $minHour -Maximum $maxHours)
}
Join me tomorrow when I will begin a series of guest blog articles written by two extremely talented Microsoft PFEs—it is something you do NOT want to miss.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy