I write a lot of PowerShell scripts. Like a lot, a lot. I write them for any manner of things, but recently I’ve been tasked to help out with a few Server & Application Monitor templates. These are some of the most interesting things that SAM has to offer.
Looking at each of the scripts, I decided it was worth revisiting based on some of my newly acquired PowerShell superpowers (cape not included).
SAM script templates (Windows Scripting, PowerShell, Perl, Python, whatever) require two things – a statistic and an exit code.
Exit Codes
These exit codes are defined within the Orion system itself. The details on this (and other things) can be found on Windows PowerShell monitor. The ones that apply here are:
Exit Code | State |
---|---|
0 | Up |
1 | Down |
2 | Warning |
3 | Critical |
* | Unknown |
Because these never change I set them up an a hash table at the top of each script.
#region Define Exit Codes
$ExitCode = @{ "Up" = 0;
"Down" = 1;
"Warning" = 2;
"Critical" = 3;
"Unknown" = 4 }
#endregion Define Exit Codes
This allows me to forget the codes themselves and instead think about the status.
$ExitCode["Up"]
0
$ExitCode["Critical"]
3
I do most of my work within the PowerShell ISE (for a number of reasons), but the problem with the exit command is that it closes the ISE. It’s required for the Script Monitor, but I don’t want to complicate my copy & paste job.
Protecting the PowerShell ISE
So the first thing that actually does anything except assign static values is determine if I’m running within the Orion console or anything else. I can do this by checking for the existence of variables that only exist in that environment. The one that I’ve chosen was ${IP} because it’s defined for every single script in Orion.
#region Check to see if this is running from within Orion
if ( -not '${IP}' )
{
$TargetServer = "10.10.10.10"
$IsOrion = $false
}
else
{
$TargetServer = '${IP}'
$IsOrion = $true
}
#endregion Check to see if this is running from within Orion
Now I have a variable $IsOrion to determine if this script is executing within Orion. If it’s is, then use the passed in ${IP} variable as the server to operate against. If not, then use the one that I’m defining elsewhere (10.10.10.10) and set the $IsOrion variable as false.
Building Blocks: try…catch…finally
As part of writing good scripts, I’ve taken to using the try…catch…finally formula. For those unaware, this is the way they work.
- You try the stuff in the try block
- If you encounter an error, you catch that error.
- Finally you run the finally block.
try
{
# Let's try this out
}
catch
{
# hit an error in the try block
}
finally
{
# Do this if you get an error or not
}
It’s simple.
Giving the script what it wants
As stated earlier, the script only needs a statistic, but I like to include a message about the statistic. There are a few reason for this, but the biggest for me is including this message in any alerts. It’s much easier to understand an alert that says, “3 user(s) locked out: Sparenberg, Kevin; Smith, Joe; Beeblebrox, Zaphod” than “You have 3 locked out users.”
I can take immediate action on the first, but the second require more work to even understand where to start.
I think of each returned statistic as two lines – a message and the statistic itself. Scripting this out, it becomes something like this:
Write-Host "Message: 3 user(s) locked out: Beeblebrox, Zaphod; Smith, Joe; Sparenberg, Kevin"
Write-Host "Statistic: 3"
Message: 3 user(s) locked out: Beeblebrox, Zaphod; Smith, Joe; Sparenberg, Kevin
Statistic: 3
Of course, you would use variables for the values and names, but this should provide you an understanding.
Applying the Logic
Let’s take a script I wrote and show you the breakdown.
Header
Exit Codes & Orion Check
#region Define Exit Codes
$ExitCode = @{ "Up" = 0;
"Down" = 1;
"Warning" = 2;
"Critical" = 3;
"Unknown" = 4
}
#endregion Define Exit Codes
#region Check to see if this is running from within Orion
if ( -not ${IP} )
{
$TargetServer = "domain.controller.ip"
$IsOrion = $false
}
else
{
$TargetServer = ${IP}
$IsOrion = $true
}
#endregion Check to see if this is running from within Orion
Try Block
Try to find a list of all locked out users on a specific active directory server. Assume that you encounter no errors.
try
{
$LockedOutUsers = Search-ADAccount -LockedOut -Server $TargetServer | Sort-Object -Property Name
if ( $LockedOutUsers )
{
Write-Host "Message: User(s) locked out: $( ( $LockedOutUsers | Select-Object -ExpandProperty Name ) -Join "; " )"
Write-Host "Statistic: $( @( $LockedOutUsers ).Count )"
$ExitState = "Warning"
}
else
{
Write-Host "Message: No users locked out."
Write-Host "Statistic: 0"
$ExitState = "Up"
}
}
Catch Block
In the (unlikely) event that you catch an error, give some suggestions and continue on.
catch
{
# Most likely, the Search-ADAccount is missing"
if ( -not ( Get-Command -Name Search-ADAccount -ErrorAction SilentlyContinue ) )
{
Write-Host "Message: ERROR: The Search-ADAccount function is missing. Please install it with `"Add-WindowsFeature -Name RSAT-AD-PowerShell`""
Write-Host "Statistic: 0"
}
else
{
Write-Host "Message: ERROR: $($Error[0])"
Write-Host "Statistic: 0"
}
$ExitState = "Down"
}
Finally Block
You’ve reached the end. If this is on an Orion Server, then go ahead and use exit codes, otherwise just display them.
finally
{
if ( $IsOrion )
{
exit $ExitCode[$ExitState]
}
else
{
Write-Host "Not running on an Orion Server - do not exit" -ForegroundColor Yellow
Write-Host "Exit Code: $( $ExitCode[$ExitState] )" -ForegroundColor Red
}
}
That’s it. That’s my simple recipe for how I build my SAM Custom PowerShell Script Monitors. If you have any feedback, please don’t hesitate to let me know. I’m always interested in hearing from the community.
Until next time, keep on rambling!
Great post. It’s been very helpful as my team has gotten started leveraging the benefits of SolarWinds and Powershell. I’m wondering if you have any suggestions on how you could source control with a Git repo the powershell that you are using in your Application Monitor.
Unfortunately, I’m only just getting started using Git for repo management, so I’m a very individual to answer that question. Personally, I’ve always just used a “Scripts” folder that I replicate to each of my Orion servers (when running with Additional Polling Engines).