Creating dashboard using the PowerShell Grid widget can offer a lot of flexibility in the way you want to consume the data out of Operations Manager. In my experience, however, it can be much more difficult to configure than the other widgets.
This post will detail the steps for configuring the dashboard to suit your environment. I will also explain each section of the script, and what it is used for.
The first step is to initialize a table variable in the following manner. The table name does not matter and is not used. The object type has to be the one listed. This table will be where we will add any Alerts we wish to display in the dashboard.

$OutputTable = New-Object System.Data.DataTable "Total Alerts"

Once the table variable is created, any script can be utilized to insert data into it. For example, in the script below we are creating an Alert Count for three specific monitors, targeted at a specific group. The group we use for this example is called “Dashboard Target Group”.

## Define the variable which will hold the group class instances
$Groups = Get-SCOMGroup -DisplayName 'Dashboard Target Group'

#Names of the desired Alerts
$LogicalDiskAlertName = 'Logical Disk Free Space is low'
$MemoryAlertName = 'Available Megabytes of Memory is too low'
$CPUAlertName = 'Total CPU Utilization Percentage is too high'

## Retrieve the objects nested in the selected groups
$ClassInstances = $Groups.GetRelatedMonitoringObjects()
$OutputTable = New-Object System.Data.DataTable "Total Alerts"

## Loop through the nested objects and check whether or not they are groups
## If they are groups, grab their contained items.
Do {
    $CIGroups = @()
    $FilteredClassInstances = @()
    Foreach ($CI in $ClassInstances) {
        If ($CI.GetType().Name -eq "MonitoringObjectGroup") {
            $CIGroups += $CI
            $FilteredClassInstances += $CI.GetRelatedMonitoringObjects()
        }
        Else {
            $FilteredClassInstances += $CI
        }
    }
    $ClassInstances = $FilteredClassInstances | Select-Object -Unique
}
Until ($CIGroups.Count -eq 0)

## Get and process the active SCOM alerts to identify ones generated by the selected groups' contents
$SCOMAlerts = @()
$ActiveAlerts = Get-SCOMAlert -ResolutionState 0
foreach ($Alert in $ActiveAlerts) {
    #$i++
    If ($ClassInstances.Id -contains $Alert.MonitoringObjectId) {
        $SCOMAlerts += $Alert
    }
    ElseIf ($Alert.MonitoringObjectPath -notmatch '^$') {
        If ($ClassInstances.DisplayName -contains ($Alert.MonitoringObjectPath.Split(';') | Select-Object -First 1)) {
            $SCOMAlerts += $Alert
        }
    }
}

$DiskAlerts = $SCOMAlerts | ?{$_.Name -eq $LogicalDiskAlertName}
$MemoryAlerts = @($SCOMAlerts | ?{$_.Name -eq $MemoryAlertName})
$CPUAlerts = $SCOMAlerts | ?{$_.Name -eq $CPUAlertName}

Once the script is working as desired we can begin formatting the output table using the Alert variables (In the example using $DiskAlerts, $MemoryAlerts, and $CPUAlerts). In our example, we will be adding the name of the Alert and the Alert Count into the Output Table.

$DiskTable = New-Object System.Object
$DiskTable | Add-Member -type NoteProperty -Name 'count' -Value $DiskAlertCount
$DiskTable | Add-Member -type NoteProperty -Name 'name' -Value "Logical Disk Free Space is low"

$MemoryTable = New-Object System.Object
$MemoryTable | Add-Member -type NoteProperty -Name 'count' -Value $MemoryAlertCount
$MemoryTable | Add-Member -type NoteProperty -Name 'name' -Value "Available Megabytes of Memory is too low"

$CPUTable = New-Object System.Object
$CPUTable | Add-Member -type NoteProperty -Name 'count' -Value $CPUAlertCount
$CPUTable | Add-Member -type NoteProperty -Name 'name' -Value "Total CPU Utilization Percentage is too high"

$OutputTable += $DiskTable
$OutputTable += $MemoryTable
$OutputTable += $CPUTable

Now that the $OutputTable variable contains all data that we want on the dashboard, the next step is to format the data into a form that the SCOM console will accept, which the following portion of the script will perform.
Some notes on this portion of the script. The $ScriptContext variable is a global variable using in SCOM, which will prevent the script from running successfully outside of a PowerShell Grid Widget. The xsd://foo!bar/baz definition does not matter and can be named anything after the XSD:// declaration.

## Output the alert counts
    foreach ($Table in $OutputTable) {
        $i++
        $dataObject = $ScriptContext.CreateInstance("xsd://foo!bar/baz")
        $dataobject["Id"] = $i.tostring()
        $dataObject["Alert Count"] = ($Table.Count).ToString()
        $dataObject["Alert Name"] = ($Table.Name).ToString()
        $ScriptContext.ReturnCollection.Add($dataObject)
    }

The full script is listed below:


# Define the variable which will hold the group class instances
$Groups = Get-SCOMGroup -DisplayName 'Dashboard Target Group'

#Names of the desired Alerts
$LogicalDiskAlertName = 'Logical Disk Free Space is low'
$MemoryAlertName = 'Available Megabytes of Memory is too low'
$CPUAlertName = 'Total CPU Utilization Percentage is too high'

# Retrieve the objects nested in the selected groups
$ClassInstances = $Groups.GetRelatedMonitoringObjects()
$OutputTable = New-Object System.Data.DataTable "Total Alerts"
# Retrieve the objects nested in the selected groups
$ClassInstances = $Groups.GetRelatedMonitoringObjects()
$OutputTable = New-Object System.Data.DataTable "Total Alerts"

# Loop through the nested objects and check whether or not they are groups
# If they are groups, grab their contained items.
Do {
    $CIGroups = @()
    $FilteredClassInstances = @()
    Foreach ($CI in $ClassInstances) {
        If ($CI.GetType().Name -eq "MonitoringObjectGroup") {
            $CIGroups += $CI
            $FilteredClassInstances += $CI.GetRelatedMonitoringObjects()
        }
        Else {
            $FilteredClassInstances += $CI
        }
    }
    $ClassInstances = $FilteredClassInstances | Select-Object -Unique
}
Until ($CIGroups.Count -eq 0)

# Get and process the active SCOM alerts to identify ones generated by the selected groups' contents
$SCOMAlerts = @()
$ActiveAlerts = Get-SCOMAlert -ResolutionState 0
foreach ($Alert in $ActiveAlerts) {
    #$i++
    If ($ClassInstances.Id -contains $Alert.MonitoringObjectId) {
        $SCOMAlerts += $Alert
    }
    ElseIf ($Alert.MonitoringObjectPath -notmatch '^$') {
        If ($ClassInstances.DisplayName -contains ($Alert.MonitoringObjectPath.Split(';') | Select-Object -First 1)) {
            $SCOMAlerts += $Alert
        }
    }
}

$DiskAlerts = $SCOMAlerts | ?{$_.Name -eq $LogicalDiskAlertName}
$MemoryAlerts = @($SCOMAlerts | ?{$_.Name -eq $MemoryAlertName})
$CPUAlerts = $SCOMAlerts | ?{$_.Name -eq $CPUAlertName}


# Save the output into a table
#$OutputTable = $SCOMAlerts | Group-Object Name | Sort-Object Count -Descending | Select-Object -Property Count,Name

$DiskAlertCount = $DiskAlerts | Measure-Object | Select-Object -ExpandProperty Count
$MemoryAlertCount = $MemoryAlerts.Count
$CPUAlertCount = $CPUAlerts.Count

$DiskTable = New-Object System.Object
$DiskTable | Add-Member -type NoteProperty -Name 'count' -Value $DiskAlertCount
$DiskTable | Add-Member -type NoteProperty -Name 'name' -Value "Logical Disk Free Space is low"

$MemoryTable = New-Object System.Object
$MemoryTable | Add-Member -type NoteProperty -Name 'count' -Value $MemoryAlertCount
$MemoryTable | Add-Member -type NoteProperty -Name 'name' -Value "Available Megabytes of Memory is too low"

$CPUTable = New-Object System.Object
$CPUTable | Add-Member -type NoteProperty -Name 'count' -Value $CPUAlertCount
$CPUTable | Add-Member -type NoteProperty -Name 'name' -Value "Total CPU Utilization Percentage is too high"


$OutputTable += $DiskTable
$OutputTable += $MemoryTable
$OutputTable += $CPUTable

# Return the results
If ($OutputTable.count -eq 0) {
    # If there are no alerts to output, say so
    $i++
    $Zero = "0"
    $dataObject = $ScriptContext.CreateInstance("xsd://foo!bar/baz")
    $dataobject["Id"] = $i.tostring()
    $dataObject["Alert Count"] = $Zero
    $dataObject["Alert Name"] = "No alerts present for selected objects"
    $ScriptContext.ReturnCollection.Add($dataObject)
}
Else {
    # Output the alert counts
    foreach ($Table in $OutputTable) {
        $i++
        $dataObject = $ScriptContext.CreateInstance("xsd://foo!bar/baz")
        $dataobject["Id"] = $i.tostring()
        $dataObject["Alert Count"] = ($Table.Count).ToString()
        $dataObject["Alert Name"] = ($Table.Name).ToString()
        $ScriptContext.ReturnCollection.Add($dataObject)
    }
}