PureStorage.FlashArray.AlertsRuleModules.Powershell.PA (ProbeActionModuleType)

Element properties:

TypeProbeActionModuleType
IsolationAny
AccessibilityInternal
RunAsDefault
OutputTypeSystem.PropertyBagData

Member Modules:

ID Module Type TypeId RunAs 
Probe ProbeAction Microsoft.Windows.PowerShellPropertyBagTriggerOnlyProbe Default
WarningFilter ConditionDetection System.ExpressionFilter Default

Overrideable Parameters:

IDParameterTypeSelector
TimeoutSecondsint$Config/TimeoutSeconds$
LogToArraybool$Config/LogToArray$
IgnoreCertificateErrorsbool$Config/IgnoreCertificateErrors$

Source Code:

<ProbeActionModuleType ID="PureStorage.FlashArray.AlertsRuleModules.Powershell.PA" Accessibility="Internal" Batching="false" PassThrough="false">
<Configuration>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" type="xsd:string" name="ArrayInstanceId"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" type="xsd:integer" name="TimeoutSeconds"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" type="xsd:boolean" name="LogToArray" minOccurs="0" maxOccurs="1"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" type="xsd:boolean" name="IgnoreCertificateErrors" minOccurs="0" maxOccurs="1"/>
</Configuration>
<OverrideableParameters>
<OverrideableParameter ID="TimeoutSeconds" Selector="$Config/TimeoutSeconds$" ParameterType="int"/>
<OverrideableParameter ID="LogToArray" Selector="$Config/LogToArray$" ParameterType="bool" Comment="Enable/Disable additional detailed logging to the array"/>
<OverrideableParameter ID="IgnoreCertificateErrors" Selector="$Config/IgnoreCertificateErrors$" ParameterType="bool"/>
</OverrideableParameters>
<ModuleImplementation Isolation="Any">
<Composite>
<MemberModules>
<ProbeAction ID="Probe" TypeID="Windows!Microsoft.Windows.PowerShellPropertyBagTriggerOnlyProbe">
<ScriptName>AlertsRule.ps1</ScriptName>
<ScriptBody><Script>param($Endpoint, $Username, $Password, $ArrayInstanceId, $KnowledgePath, $LogToArray, $IgnoreCertificateErrors, $newtonsoft_path, $renci_path, $rest_shared_path, $rest_path, $sdk_path)

#Import-Module OperationsManager -Verbose
#=================================================================================
# This file gets Alerts from the Pure Array command line
# If you want to test it, remember to generate the alerts with severity either warning or critical,
# because in the XML file, I use severity to separate alerts.
# In our real alerts, the severity is either warning or critical or info (no alerts raised in the console).
#=================================================================================

function AlertsRule {
# Import the SCOM PowerShell module
$ScriptName = "AlertsRule.ps1"
$NUMBER_ALERT_FIELDS = 11
$disableLoggingToArray = -not $LogToArray

LoadOperationsManagerModule $ScriptName

# Gather script start time
$StartTime = Get-Date
# Gather who the script is running as
$WhoAmI = whoami

# Load MomScript API
$momapi = new-object -comObject 'MOM.ScriptAPI'
Log $ScriptName $GLOBAL:INFO_LEVEL "Starting script $Endpoint"
LoadPowerShellSDK $ScriptName

# Connect to FlashArray and get alert messages
$Password = $Password | ConvertTo-SecureString -AsPlainText -Force
try {
$IgnoreCertErrors = [bool]$IgnoreCertificateErrors
$FlashArray = New-PfaArray -EndPoint $Endpoint -UserName $Username -Password $Password -ClientName $GLOBAL:clientName -ClientVersion $GLOBAL:mpVersion -IgnoreCertificateError:$IgnoreCertErrors -HttpTimeOutInMilliSeconds 60000 -DisableLoggingToArray:$disableLoggingToArray
}
catch {
Log $ScriptName $GLOBAL:ERROR_LEVEL "Connection to array failed on these credentials. $Username. Check that the PurePowershell cmdlet is installed, and your credentials are correct. Error: $_"
exit
}

$arrayHost = $Endpoint
$loc = $arrayHost.IndexOf("//")
if ($loc -ge 0) {
$arrayHost = $arrayHost.Substring($loc + 2)
}
# $ArrayAlertMessages is the String output of CLI. Only get all the open alerts, if there is no open messages, only output ""
$ArrayAlertMessages = ""
Log $ScriptName $GLOBAL:VERBOSE_LEVEL "Calling New-PfaCLICommand - ProceesID ($PID) for Endpoint ($Endpoint)"
try {
$ArrayAlertMessages = New-PfaCLICommand -EndPoint $arrayHost -UserName $Username -Password $Password -CommandText "puremessage list --open --csv --notitle" -DisableLoggingToArray:$disableLoggingToArray
$ArrayAlertMessages = $ArrayAlertMessages.trim()

$NewAlertMessages = $ArrayAlertMessages.Split("`n") | foreach { $_.trim() }
$ArrayAlerts = foreach ($AlertMessage in $NewAlertMessages) {
if ($AlertMessage.Length -eq 0) {
continue
}
$parsedAlert = Parse-ArrayAlert -AlertMessage $AlertMessage
$parsedAlert
}
}
catch {
Log $ScriptName $GLOBAL:ERROR_LEVEL "Failed to get alert messages. Error: $_"
}

Log $ScriptName $Global:VERBOSE_LEVEL "CLI message output: $ArrayAlertMessages"
$Warning = "No"

# Get the instances of this particular array
$ArrayInstance = Get-SCOMClassInstance -Id $ArrayInstanceId

# Get Alerts sorted with most recent first
$SCOMAlerts = Get-SCOMAlert -Instance $ArrayInstance -ResolutionState 0 | where { $_.name -like "*REST API*" }
Log $ScriptName $GLOBAL:INFO_LEVEL "SCOM Alerts: $SCOMAlerts for Endpoint $Endpoint"

$ParsedSCOMAlerts = @()
# If the alerts are null, we don't need to do anything or else we need to resolve the CLOSED alerts in the console
if ($null -ne $SCOMAlerts) {
foreach ($SCOMAlert in $SCOMAlerts) {
$ParsedSCOMALert = Parse-SCOMAlert -AlertMessage $SCOMAlert
$ParsedSCOMAlerts += $ParsedSCOMALert
$matchingArrayAlert = $ArrayAlerts | Where-Object { ($_.ID -eq $ParsedSCOMALert.ID) -and ($_.Code -eq $ParsedSCOMALert.Code) }
if ($null -eq $matchingArrayAlert) {
# Alert is no longer active, resolve it
Log $ScriptName $GLOBAL:INFO_LEVEL "Resolving alert: $SCOMAlert for Endpoint ($Endpoint)"
Resolve-SCOMAlert -Alert $SCOMAlert
}
}
}

# If $ArrayAlertMessages are null, just return bag with no warnings
if ($ArrayAlertMessages -eq $null -or $ArrayAlertMessages.Length -eq 0) {
Log $ScriptName $GLOBAL:INFO_LEVEL "No alert messages"
$Warning = "No"
$bag = $momapi.CreatePropertyBag()
$bag.AddValue('Warning', $Warning)
$bag
}
else {
# Parse the output of the command line. The first line is attributes names, the last line is "" (empty string).
# The other lines in the $MessageArrays are open alert messages that are separated by commas
# All the fields(11): ID, Time, Severity, Category, Code, Component, Name, Event, Expected, Actual, Details
$length = @($ArrayAlerts).Length
Log $ScriptName $GLOBAL:WARNING_LEVEL "`nFound $length open alert messages"

# Read JSON file.
$knowledge = Get-Content $KnowledgePath | ConvertFrom-Json

# Adding new alerts
$Warning = "Yes"
foreach ($ArrayAlert in $ArrayAlerts) {
# Check if an alert already exists before adding it
$matchingSCOMAlert = $ParsedSCOMAlerts | Where-Object { ($_.ID -eq $ArrayAlert.Id) -and ($_.Code -eq $ArrayAlert.Code) }
if ($null -eq $matchingSCOMAlert) {
# It is a new Alert add it, otherwise ignore it
$bag = $momapi.CreatePropertyBag()

# Add the necessary properties to the object and add the object to the return bag
Add-AlertKnowledge $ArrayAlert $knowledge
Generate-AlertDescription $ArrayAlert

$bag.AddValue('Warning', $Warning)
$bag.AddValue('ID', $ArrayAlert.Id)
$bag.AddValue('TimeOpened', $ArrayAlert.TimeOpened)
$bag.AddValue('Severity', $ArrayAlert.Severity)
$bag.AddValue('Category', $ArrayAlert.Category)
$bag.AddValue('Code', $ArrayAlert.Code)
$bag.AddValue('Component', $ArrayAlert.Component)
$bag.AddValue('Name', $ArrayAlert.Name)
$bag.AddValue('Event', $ArrayAlert.Event)
$bag.AddValue('Expected', $ArrayAlert.Expected)
$bag.AddValue('Actual', $ArrayAlert.Actual)
$bag.AddValue('Details', $ArrayAlert.Details)
$bag.AddValue('Description', $ArrayAlert.Description)
$bag.AddValue('Triggers', $ArrayAlert.Triggers)
$bag.AddValue('URL', $ArrayAlert.URL)
$bag.AddValue('Notes', $ArrayAlert.Notes)
$bag.AddValue('Message', $ArrayAlert.Message)

Log $ScriptName $GLOBAL:WARNING_LEVEL "`nNew alert: $($ArrayAlert.Code)"

$bag
}
}
}

$EndTime = Get-Date
$ScriptTime = ($EndTime - $StartTime).TotalSeconds
Log $ScriptName $GLOBAL:INFO_LEVEL "Script has completed at $Endpoint. Runtime was ($ScriptTime) seconds."

Disconnect-PfaArray -Array $FlashArray
}

function Add-AlertKnowledge {
param ($alertData, $knowledge)

$article = $knowledge | Where-Object -Property Id -eq $alertData.Code | Select-Object -First 1

$description = $article.Description

if ($description) {
$description = [regex]::Replace($description, '\{(\w+)\}', {
param($match)
$key = $match.Groups[1].Value;
if ($alertData.ContainsKey($key)) { return $alertData[$key] }
else { return $key }
})
}

$alertData.Add('Description', $description)
$alertData.Add('Triggers', $article.Triggers)
$alertData.Add('URL', $article.{Knowledge Base URL})
$alertData.Add('Notes', $article.Notes)
}

function Generate-AlertDescription {
param ($alertData)

$ds = [ordered]@{
'ID' = 'Alert ID';
'TimeOpened' = 'Time Opened';
'Severity' = 'Severity';
'Category' = 'Category';
'Code' = 'Code';
'Component' = 'Component';
'Name' = 'Name';
'Description' = 'Description';
'Event' = 'Event';
'Triggers' = 'Triggers';
'Expected' = 'Expected';
'Actual' = 'Actual';
'URL' = 'Knowledge Base';
'Details' = 'Details';
'Notes' = 'Notes'}

$message = ""

foreach ($key in $ds.Keys) {
$part = [string]$alertData[$key]
if (![string]::IsNullOrEmpty($part)) {
$message += "$($ds[$key]): $part`n"
}
}

$alertData.Add('Message', $message.Trim("`n"))
}

function Parse-ArrayAlert {
param ($AlertMessage)
$dict = @{}
if (!$AlertMessage) {
Log $ScriptName $Global:ERROR_LEVEL "Alert message is null or empty";
return $dict
}
$Fields = $AlertMessage.Split(",")
if ($Fields.length -ne $NUMBER_ALERT_FIELDS) {
Log $ScriptName $Global:ERROR_LEVEL "Alert message has incorrect number of fields. Failed message: $AlertMessage"
}
else {
$dict.Add('Id', [int32]$Fields[0])
$dict.Add('TimeOpened', [datetime]$Fields[1])
$dict.Add('Severity', $Fields[2])
$dict.Add('Category', $Fields[3])
$dict.Add('Code', $Fields[4])
$dict.Add('Component', $Fields[5])
$dict.Add('Name', $Fields[6])
$dict.Add('Event', $Fields[7])
$dict.Add('Expected', $Fields[8])
$dict.Add('Actual', $Fields[9])
$dict.Add('Details', $Fields[10])
}
return $dict
}

function Get-PropertyFromContext {
Param ([xml] $Context,
[string] $Name)

$val = $null
try {
$val = ($context.DataItem.Property | Where-Object { $_.Name -eq $Name })."#text"
}
catch {
Log $ScriptName $Global:ERROR_LEVEL "Failed to parse context ($context, $Name) with error: $_"
}
return $val
}

function Parse-SCOMAlert {
Param ($AlertMessage)

$dict = @{}
if (!$AlertMessage) {
Log $ScriptName $Global:ERROR_LEVEL "Alert message is null or empty"
return $dict
}
[xml]$context = $AlertMessage.Context

$dict.Add('Id', [int32](Get-PropertyFromContext -Context $context -Name 'ID'))
$dict.Add('Code', (Get-PropertyFromContext -Context $context -Name 'Code'))

return $dict
}function GetLogLevel ($default){
if (Test-Path HKLM:\SOFTWARE\PureStorage\SCOM\LogLevel){
$item = Get-ItemProperty "HKLM:\SOFTWARE\PureStorage\SCOM\LogLevel"
$log_level = $item.'(default)'
$log_level
}
else
{
$default
}
}

function InitLogging () {
$GLOBAL:MOMAPI = New-Object -comObject "Mom.ScriptAPI"
$GLOBAL:mpVersion = (Get-SCOMManagementPack -name "PureStorageFlashArray").Version.ToString()
if(!$GLOBAL:mpVersion) {$GLOBAL:mpVersion = 'unknown'}
$GLOBAL:clientName = "SCOM"
##### LOG LEVEL ####
# 1 Errors
# 2 Warnings
# 3 Information
# 4 Verbose
$GLOBAL:ERROR_LEVEL = 1
$GLOBAL:WARNING_LEVEL = 2
$GLOBAL:INFO_LEVEL = 3
$GLOBAL:VERBOSE_LEVEL = 4

$GLOBAL:CURRENT_LOG_LEVEL = GetLogLevel $GLOBAL:VERBOSE_LEVEL

$GLOBAL:LOG_CODES_MAP = @{}
$GLOBAL:DEFAULT_LOG_CODE = 5555
################## Discoveries ##################
$GLOBAL:LOG_CODES_MAP.Add("PureVolumeDiscovery.ps1", 2001)
$GLOBAL:LOG_CODES_MAP.Add("PurePortDiscovery.ps1", 2002)
$GLOBAL:LOG_CODES_MAP.Add("PureHostDiscovery.ps1", 2003)
$GLOBAL:LOG_CODES_MAP.Add("PureControllerDiscovery.ps1", 2004)
$GLOBAL:LOG_CODES_MAP.Add("PureArrayDiscovery.ps1", 2005)
$GLOBAL:LOG_CODES_MAP.Add("InitialArrayDiscovery.ps1", 2006)
##################### Rules #######################
$GLOBAL:LOG_CODES_MAP.Add("AlertsRule.ps1", 2101)
$GLOBAL:LOG_CODES_MAP.Add("ArrayPerformanceRule.ps1", 2102)
$GLOBAL:LOG_CODES_MAP.Add("HostgroupPerformanceRule.ps1", 2103)
$GLOBAL:LOG_CODES_MAP.Add("HostPerformanceRule.ps1", 2104)
$GLOBAL:LOG_CODES_MAP.Add("VolumePerformanceRule.ps1", 2105)
#################### Monitors ######################
$GLOBAL:LOG_CODES_MAP.Add("AlertsRuleMonitor.ps1", 2201)
$GLOBAL:LOG_CODES_MAP.Add("ConnectionHealthMonitor.ps1", 2203)
$GLOBAL:LOG_CODES_MAP.Add("ControllerHealthMonitor.ps1", 2204)
$GLOBAL:LOG_CODES_MAP.Add("VolumeIOMonitor.ps1", 2205)
$GLOBAL:LOG_CODES_MAP.Add("HostIOMonitor.ps1", 2206)
$GLOBAL:LOG_CODES_MAP.Add("PortHealthMonitor.ps1", 2207)
$GLOBAL:LOG_CODES_MAP.Add("PodMediatorStatus.ps1", 2208)
##################### Tasks #######################
$GLOBAL:LOG_CODES_MAP.Add("NewHostTask.ps1", 2301)
$GLOBAL:LOG_CODES_MAP.Add("NewVolumeTask.ps1", 2302)
$GLOBAL:LOG_CODES_MAP.Add("VerifyEndpointTask.ps1", 2303)
################### Dashboards ####################
$GLOBAL:LOG_CODES_MAP.Add("PureVolumeState.ps1", 2401)
$GLOBAL:LOG_CODES_MAP.Add("PureArrayDashboardIOPSData.ps1", 2402)
$GLOBAL:LOG_CODES_MAP.Add("PureArrayDashboardLatencyData.ps1", 2403)
$GLOBAL:LOG_CODES_MAP.Add("PureArrayDashboardBandwidthData.ps1", 2404)
$GLOBAL:LOG_CODES_MAP.Add("PureHostState.ps1", 2405)
$GLOBAL:LOG_CODES_MAP.Add("PureArrayDashboardSpaceMetricsData.ps1", 2406)
}

function GetLogCode ($filename) {
$code = $GLOBAL:LOG_CODES_MAP[$filename]
if ($code -eq $null) {
$code = $DEFAULT_LOG_CODE
}
return $code
}

function GetSCOMLogLevel ($level) {
switch ($level){
$GLOBAL:ERROR_LEVEL {1}
$GLOBAL:WARNING_LEVEL {2}
$GLOBAL:INFO_LEVEL {0}
$GLOBAL:VERBOSE_LEVEL {0}
}
}

function Log ($script_name, $level, $message) {
if ($level -le $GLOBAL:CURRENT_LOG_LEVEL){
$code = GetLogCode $script_name
$level = GetSCOMLogLevel $level
if($GLOBAL:mpVersion -eq 'unknown') {$GLOBAL:mpVersion = (Get-SCOMManagementPack -name "PureStorageFlashArray").Version.ToString()}
$fullScriptName = "$script_name" + " [$GLOBAL:mpVersion]"
$GLOBAL:MOMAPI.LogScriptEvent($fullScriptName, $code, $level, $message)
}
}

function LoadPowerShellSDK ($script_name){
Log $script_name $GLOBAL:VERBOSE_LEVEL "Importing PowerShell SDK in $script_name"

try {
Log $script_name $GLOBAL:VERBOSE_LEVEL "Loading PowerShell SDK dependencies"
Add-Type -Path $newtonsoft_path, $renci_path, $rest_shared_path, $rest_path

Log $script_name $GLOBAL:VERBOSE_LEVEL "Importing PowerShell SDK"
Import-Module $sdk_path -Verbose
}
catch {
Log $script_name $GLOBAL:ERROR_LEVEL "Failed to import PowerShell SDK"
}
}

function LoadOperationsManagerModule ($script_name)
{
Log $script_name $GLOBAL:INFO_LEVEL "Importing Operations Manager module in $script_name"
try
{
# https://blogs.technet.microsoft.com/cchamp/2015/09/30/loading-the-scom-powershell-module-for-real-this-time/
$OMCmdletsTest = (Get-Module|% {$_.Name}) -Join ' '
If (!$OMCmdletsTest.Contains('OperationsManager')) {
$ModuleFound = $false
$SetupKeys = @('HKLM:\Software\Microsoft\Microsoft Operations Manager\3.0\Setup',
'HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup')
foreach($setupKey in $SetupKeys) {
If ((Test-Path $setupKey) -and ($ModuleFound -eq $false)) {
$setupKey = Get-Item -Path $setupKey
$installDirectory = $setupKey.GetValue('InstallDirectory')
$psmPath = $installdirectory + '\Powershell\OperationsManager\OperationsManager.psm1'
If (Test-Path $psmPath) {
$ModuleFound = $true
}
}
}
If ($ModuleFound) {
Import-Module $psmPath
} else {
Import-Module OperationsManager
}
}
else { Log $ScriptName $GLOBAL:INFO_LEVEL "Operations Manager module is already present" }
}
catch
{
Log $script_name $GLOBAL:INFO_LEVEL "Failed to import Operations Manager: $_"
}
}

function Format-Number($value, $digits, $unit)
{
return [string][Math]::Round($value, $digits) + $unit
}

function Format-ByteNumber($value, $suffix)
{
if ($null -eq $value) {
return '-'
} elseif ($value -ge 1PB){
$v = $value/1PB
$unit =' PB'
} elseif ($value -ge 1TB){
$v = $value/1TB
$unit = ' TB'
} elseif ($value -ge 1GB){
$v = $value/1GB
$unit = ' GB'
} elseif ($value -ge 1MB){
$v = $value/1MB
$unit = ' MB'
} elseif ($value -ge 1kB){
$v = $value/1kB
$unit = ' kB'
} else {
$v = $value
$unit =' B'
}

return Format-Number $v 2 ($unit + $suffix)
}

function Get-PerfCounterValue($instance, $counterName, $startTime, $endTime)
{
$criteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringPerformanceDataCriteria("CounterName='$counterName'")
$performanceDataCollection = $instance.GetMonitoringPerformanceData($criteria)
Log $ScriptName $GLOBAL:VERBOSE_LEVEL $performanceDataCollection
$valueReader = $performanceDataCollection.GetValueReader($startTime, $endTime)
while ($valueReader.Read()) {
Write-Output $valueReader.GetMonitoringPerformanceDataValue().SampleValue
}
}


InitLogging


AlertsRule
</Script></ScriptBody>
<Parameters>
<Parameter>
<Name>Endpoint</Name>
<Value>$Target/Property[Type="PureStorage.FlashArray.PureArray"]/Endpoint$</Value>
</Parameter>
<Parameter>
<Name>Username</Name>
<Value>$RunAs[Name="PureStorage.FlashArray.FlashArrayAdminAccount"]/UserName$</Value>
</Parameter>
<Parameter>
<Name>Password</Name>
<Value>$RunAs[Name="PureStorage.FlashArray.FlashArrayAdminAccount"]/Password$</Value>
</Parameter>
<Parameter>
<Name>ArrayInstanceId</Name>
<Value>$Config/ArrayInstanceId$</Value>
</Parameter>
<Parameter>
<Name>KnowledgePath</Name>
<Value>$FileResource[Name="AlertsCustomerClean"]/Path$</Value>
</Parameter>
<Parameter>
<Name>LogToArray</Name>
<Value>$Config/LogToArray$</Value>
</Parameter>
<Parameter>
<Name>IgnoreCertificateErrors</Name>
<Value>$Config/IgnoreCertificateErrors$</Value>
</Parameter>
<Parameter>
<Name>newtonsoft_path</Name>
<Value>$FileResource[Name="Newtonsoft.Json"]/Path$</Value>
</Parameter>
<Parameter>
<Name>renci_path</Name>
<Value>$FileResource[Name="Renci.SshNet"]/Path$</Value>
</Parameter>
<Parameter>
<Name>rest_shared_path</Name>
<Value>$FileResource[Name="PureStorage.Rest.Shared"]/Path$</Value>
</Parameter>
<Parameter>
<Name>rest_path</Name>
<Value>$FileResource[Name="PureStorage.Rest"]/Path$</Value>
</Parameter>
<Parameter>
<Name>sdk_path</Name>
<Value>$FileResource[Name="PureStoragePowerShellSDK"]/Path$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>$Config/TimeoutSeconds$</TimeoutSeconds>
</ProbeAction>
<ConditionDetection ID="WarningFilter" TypeID="System!System.ExpressionFilter">
<Expression>
<SimpleExpression>
<ValueExpression>
<XPathQuery Type="String">Property[@Name='Warning']</XPathQuery>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="String">Yes</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
</ConditionDetection>
</MemberModules>
<Composition>
<Node ID="Probe"/>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>System!System.PropertyBagData</OutputType>
<TriggerOnly>true</TriggerOnly>
</ProbeActionModuleType>