Powershell Template

Topics: User Forum
Feb 23, 2009 at 7:33 PM


I have been toying with ways to make my Powershell tests work both in and out of Polymon. I am posting my last attempt below.

During this quest, I decided the best solution would be if Polymon could do the following:

 1) Append any calls to Write-Host to the Status Text.
 2) Append any calls to Write-Warning to the StatusText and set the StatusID code to 2.
 3) Append any calls to Write-Error to StatusText and set the StatusID to 3.
 4) Append any Hash Table objects that are output with a numerical value to the StatusCounters hash table.

I have plans to create a Powershell wrapper to mimic this behavior, but thought I would post this first to see what others thought about this subject.


###########################################################
# Monitor Name:
###########################################################

function main {
    # To add an error to the error collection:
    #   $ErrorList += "Error text."
    # To add a warning to the warning collection:
    #   $WarningList += "Warning text."
    # To add a message to the message collection:
    #   $MessageList += "Message text."
    # To add or modify a status counter:
    #   $StatusCounters["StatusName"] = $NumericStatusValue
    
    # Testing code goes here.
    
    }


#trap
#{
#    $ErrorList += "Exception: " + $_
#    exit
#}

$MessageList = @()
$WarningList = @()
$ErrorList = @()
$StatusCounters = @{}
$StatusID = 1
$StatusText = ""

$TotalTestSeconds = Measure-Command { . Main }
$StatusCounters["TotalTestSeconds"] = $TotalTestSeconds.TotalSeconds

if ($ErrorList.Count)
{
    if ($StatusID -lt 3) { $StatusID = 3 }
    $StatusText += "$($ErrorList.count) Errors: `r`n"
    $StatusText += $ErrorList | %{" $_`r`n"}
}
if ($WarningList.Count)
{
    if ($StatusID -lt 2) { $StatusID = 2 }
    $StatusText += "$($WarningList.count) Warnings: `r`n"
    $StatusText += $WarningList | %{ " $_`r`n" }
}
if ($MessageList.Count)
{
    $StatusText += "$($MessageList.count) Messages: `r`n"
    $StatusText += $MessageList | %{ " $_`r`n" }
}

if ($Status.StatusID) # Update Polymon if this was ran in Polymon
{
    $Status.StatusID = $StatusID
    $Status.StatusText = $StatusText
    $StatusCounters.Keys | %{ $Counters.Add($_, $StatusCounters[$_]) }
}
else
{
    "StatusID      : {0}" -f $StatusID
    "StatusText    : "
    $StatusText
    "StatusCounters: "
    $StatusCounters
}
Feb 23, 2009 at 10:13 PM
Just completed my proof of concept, tomorrow I'll work on the real thing.


# Make any Powershell script a Polymon test proof of concept
#OUTPUT
# -Start-
# Counter: TestCounter1 = 1
# Not a Hashtable: Quoted Text
# Not a Hashtable: Called Write-Host: Host text
# Not a Hashtable: Called Write-Warning: Warning text
# Not a Hashtable: Called Write-Error: Error text
# Counter: TestCounter2 = 2
# Not a Counter: TestCounter3 = This is not a number
# -End-

[ScriptBlock]$testScriptBlock = {
    @{"TestCounter1" = 1}
    "Quoted Text"
    Write-Host "Host text"
    Write-Debug "Debug text"
    Write-Verbose "Verbose text"
    Write-Warning "Warning text"
    Write-Error "Error text"
    $Counter2 = @{}
    $Counter2["TestCounter2"]=2
    $Counter2["TestCounter3"]="This is not a number"
    $Counter2
    }

function Write-Host {
    "Called Write-Host: $args"
    }
function Write-Warning {
    "Called Write-Warning: $args"
    }
function Write-Error {
    "Called Write-Error: $args"
    }

"-Start-"
. $TestScriptBlock | ForEach {
    if(($_ -is [System.Collections.Hashtable]) ) {
        $hashtable = $_
        ForEach ($items in $hashtable) {
            ForEach ($key in $items.Keys) {
                [Decimal]$value = 0
                if ([Decimal]::TryParse([string]$items[$key],[ref]$value)) {
                    "Counter: $key = $($items[$key])"
                    }
                else {
                    "Not a Counter: $key = $($items[$key])"
                    }
                }
            }
        }
    else { "Not a Hashtable: $_" }
    }
"-End-"

Feb 24, 2009 at 6:29 PM
And finally....


[ScriptBlock]$testScriptBlock = {
    # . & "\\server\share\testScript.ps1"
    @{"TestCounter1" = 1}
    Write-Host "Host text"
    Write-Warning "Warning text"
    Write-Error "Error text"
    $Counter2 = @{}
    $Counter2["TestCounter2"]=2
    $Counter2
    }

# This wrapper simplifies Polymon Powershell Test creation and testing in and out of Polymon.
#
# This is accomplished by:
# 1) Appending any calls to Write-Host to the Status Text.
# 2) Appending any calls to Write-Warning to the StatusText and setting the StatusID code to 2 (Warning).
# 3) Appending any calls to Write-Error to StatusText and setting the StatusID to 3 (Error).
# 4) Appending any Hashtable objects that are output with a numerical value to the StatusCounters hash table.

function Invoke-Test ([scriptblock]$Expression) {
    # If this was ran in Polymon set StatusID to Warning just in case the script aborts
    if ($Status.StatusID) {
        $Status.StatusID = 2
        $Status.StatusText = "Test Aborted Abnormally"
        }
    $MessageList = @()
    $WarningList = @()
    $ErrorList = @()
    $StatusCounters = @{}
    $StatusID = 1
    $StatusText = ""

    function Write-Host {
        $script:MessageList += $Args[0]
        }
    function Write-Warning {
        $script:WarningList += $Args[0]
        }
    function Write-Error {
        $script:ErrorList += $Args[0]
        }

    $TotalTestSeconds = Measure-Command {
        . $Expression | ForEach {
            $_
            if(($_ -is [System.Collections.Hashtable]) ) {
                $hashtable = $_
                ForEach ($items in $hashtable) {
                    ForEach ($key in $items.Keys) {
                        [Decimal]$value = 0
                        if ([Decimal]::TryParse([string]$items[$key],[ref]$value)) {
                            "Counter: $key = $($items[$key])"
                            $StatusCounters[$key] = $items[$key]
                            }
                        }
                    }
                }
            }
        }

    $StatusCounters["TotalTestSeconds"] = $TotalTestSeconds.TotalSeconds

    if ($ErrorList.Count) {
        if ($StatusID -lt 3) { $StatusID = 3 }
        $StatusText += "$($ErrorList.count) Errors: `r`n"
        $StatusText += $ErrorList | %{" $_`r`n"}
        }
    if ($WarningList.Count) {
        if ($StatusID -lt 2) { $StatusID = 2 }
        $StatusText += "$($WarningList.count) Warnings: `r`n"
        $StatusText += $WarningList | %{ " $_`r`n" }
        }
    if ($MessageList.Count) {
        $StatusText += "$($MessageList.count) Messages: `r`n"
        $StatusText += $MessageList | %{ " $_`r`n" }
        }

    # Update Polymon if this was ran in Polymon
    if ($Status.StatusID) {
        $Status.StatusID = $StatusID
        $Status.StatusText = $StatusText
        $StatusCounters.Keys | %{ $Counters.Add($_, $StatusCounters[$_]) }
        }
    else {
        "StatusID      : {0}" -f $StatusID
        "StatusText    : "
        $StatusText
        "StatusCounters: "
        $StatusCounters
        }
    }
. Invoke-Test $testScriptBlock


Developer
Mar 1, 2009 at 12:54 PM
(I'm cross posting the script below from another thread because it's related to both.)

I'm looking forward to trying your approach. I really like the idea of being able to use normal PowerShell commands and having the data appear in PolyMon.

However, I've got a poor man's approach to letting a script run inside and outside of PolyMon. It creates objects to hold the $status properties $counters perf counters and then displays them at the end.

# ---------------  Script Starts Here ---------------------
# The invoke-SQLCommand is from here:
# http://www.leeholmes.com/blog/InteractingWithSQLDatabasesInPowerShellInvokeSqlCommand.aspx

# $PolyMon is set so the script can behave differently when it's not run in PolyMon
$PolyMon = $true
# 'Default MSH Host' is the name of the PolyMon host
if ($Host.name -ne "Default MSH Host"){$PolyMon = $false}
# if not in PolyMon create an object to simulate the $status object in PolyMon
if (!$PolyMon){
  $status = "" | Select-Object StatusID, StatusText
  $counters = @{}
}

# run a SQL stored procedure to get some data to analyze
# The data could be anything, in this case, the SP returns the following data
# Name                        CurrentValue DesiredValue State
# ----                        ------------ ------------ -----
# AcceptRequests                         1            1 Success
# BounceDaemonCount                      4            2 Success
# MailerDaemonCount                      4            4 Success
# ProcessEmailAll                        1            1 Success
# ProcessingMailerDaemonCount            4            4 Success
# ThrottledMailerDaemonCount             1            1 Success
# UnlimitedMailerDaemonCount             3            3 Success

$results = invoke-sqlcommand -sqlCommand "spCheckEmailConfig_PowerShell" -dataSource 'YOURSERVER' -database 'YOURDATABASE'
$errors   = @($results | ?{$_.State -eq "Error"}).count
$warnings = @($results | ?{$_.State -eq "Warning"}).count

# Turn the results into a nicely formatted table that will appear in the email notifications
$errorMsg = @("There are [$errors] errors and [$warnings] warnings.","--------------------------",($results |
  format-table -auto | out-string).trim()) | out-string
 
# Add the perf counters
$Counters.Add("Email Configuration Errors",$errors)
$Counters.Add("Email Configuration Warnings",$warnings)

# set the properties of the $status object
if ($errors -gt 0) {
  $status.StatusID = 3   # 3=Fail
  $status.StatusText = $errorMsg
}
elseif ($warnings -gt 0) {
  $status.StatusID = 2   # 2=Warning
  $status.StatusText = $errorMsg
}
else {
  $status.StatusID = 1   # 1=OK
  $status.StatusText = $errorMsg
}

# If not in PolyMon return the $status and $counters objects
if (!$PolyMon){
  $status | fl
  $counters | ft -auto
}
# ---------------  Script Ends Here ---------------------

Mar 2, 2009 at 2:54 PM
Your script looks similar to where I started. Just so you know, I have wrote a couple of tests using my latest template and it seems to work great. Very easy to build and debug new tests because you do not have to do anything special and when you are done it is a simple matter to point a copy of the above Powershell Stub (as I have been referring to it) to run the test script.

I did find one minor bug. My suggested line to run an external script should not have an ampersand in it. In other words....

 # . & "\\server\share\testScript.ps1"

 ... should read ...

 # . "\\server\share\testScript.ps1"




Jul 20, 2009 at 10:03 PM

Minor update to my Powershell Wrapper that greatly simplifies Powershell test creation and debugging. It now records the total run time as milliseconds.

[ScriptBlock]$testScriptBlock = {
	. "c:\Program Files\PolyMon\MyTest.ps1"
   Write-Host "status"
   Write-Warning "warning"
   Write-Error "error"
   @{ counter1 = 123 }
	}

# This wrapper simplifies Polymon Powershell Test creation.
#
# This is accomplished by: 
# 1) Appending any calls to Write-Host to the Status Text.
#    Write-Host "status"
# 2) Appending any calls to Write-Warning to the StatusText and setting the StatusID code to 2 (Warning).
#    Write-Warning "this is a warning"
# 3) Appending any calls to Write-Error to StatusText and setting the StatusID to 3 (Error).
#    Write-Error "error"
# 4) Appending any Hashtable objects that are output with a numerical value to the StatusCounters hash table.
#    @{ counter1 = 123 }

function Invoke-Test ([scriptblock]$Expression) {
	# If this was ran in Polymon set StatusID to Warning just in case the script aborts
	if ($Status.StatusID) { 
		$Status.StatusID = 2
		$Status.StatusText = "Test Aborted Abnormally"
		} 
	$MessageList = @()
	$WarningList = @()
	$ErrorList = @()
	$StatusCounters = @{}
	$StatusID = 1
	$StatusText = ""

	function Write-Host {
		$script:MessageList += $Args[0]
		}
	function Write-Warning {
		$script:WarningList += $Args[0]
		}
	function Write-Error {
		$script:ErrorList += $Args[0]
		}

	$TotalTestTime = Measure-Command { 
		. $Expression | ForEach {
			$_
			if(($_ -is [System.Collections.Hashtable]) ) {
				$hashtable = $_
				ForEach ($items in $hashtable) {
					ForEach ($key in $items.Keys) {
						[Decimal]$value = 0
						if ([Decimal]::TryParse([string]$items[$key],[ref]$value)) {
							"Counter: $key = $($items[$key])"
							$StatusCounters[$key] = $items[$key]
							}
						}
					}
				}
			}
		}

	$StatusCounters["TotalTime_ms"] = $TotalTestTime.TotalMilliseconds

	if ($ErrorList.Count) {
		if ($StatusID -lt 3) { $StatusID = 3 }
		$StatusText += "$($ErrorList.count) Errors: `r`n"
		$StatusText += $ErrorList | %{" $_`r`n"}
		}
	if ($WarningList.Count) {
		if ($StatusID -lt 2) { $StatusID = 2 }
		$StatusText += "$($WarningList.count) Warnings: `r`n"
		$StatusText += $WarningList | %{ " $_`r`n" }
		}
	if ($MessageList.Count) {
		$StatusText += "$($MessageList.count) Messages: `r`n"
		$StatusText += $MessageList | %{ " $_`r`n" }
		}

	# Update Polymon if this was ran in Polymon
	if ($Status.StatusID) { 
		$Status.StatusID = $StatusID
		$Status.StatusText = $StatusText
		$StatusCounters.Keys | %{ $Counters.Add($_, $StatusCounters[$_]) }
		}
	else {
		"StatusID      : {0}" -f $StatusID
		"StatusText    : "
		$StatusText 
		"StatusCounters: "
		$StatusCounters
		}
	}
. Invoke-Test $testScriptBlock