Get-SCOMPerformanceData.ps1

. 'C:\Users\typaul\OneDrive - Microsoft\SCOM\MP\MPDev\MPProjects\SCOMAgentHelper\SCOMHelper\Resources\vX.XX_ModuleFiles\SCOMHelper\Private.ps1'

Function Get-SCOMPerformanceData {
<#
.SYNOPSIS
    Retrieves performance data from SCOM using the Operations Manager PowerShell SDK.
 
.DESCRIPTION
    The Get-SCOMPerformanceData function retrieves performance counter data from System Center Operations Manager (SCOM)
    using the native PowerShell SDK. It can query performance data by monitoring object GUID or computer name,
    with flexible filtering options for specific performance counters and time ranges.
     
    The function retrieves all related monitoring objects for the specified target and collects performance
    data from all available performance counters. It calculates statistical values (last, maximum, average)
    for each counter over the specified time period.
     
    This function requires an active SCOM management group connection established via
    New-SCOMManagementGroupConnection.
 
.PARAMETER Id
    The GUID of the monitoring object to retrieve performance data for. This parameter is mandatory when using
    the 'ById' parameter set. Must be a valid GUID format. Use the ObjectId alias for convenience.
 
.PARAMETER ComputerName
    The display name of the computer to retrieve performance data for. This parameter is mandatory when using
    the 'ByComputerName' parameter set. The function will automatically resolve the computer name to its
    corresponding monitoring object GUID.
 
.PARAMETER HoursLookback
    The number of hours to look back for performance data samples. Default is 72 hours (3 days).
    This parameter is ignored when PerfObjectsOnly is specified.
 
.PARAMETER CounterNames
    An array of specific performance counter names to filter results. If not specified or empty,
    all available performance counters will be returned. Counter names are case-sensitive and should
    match exactly (e.g., "% Processor Time", "Free Megabytes").
 
.PARAMETER PerfObjectsOnly
    When specified, returns only the performance counter objects without retrieving actual performance
    data samples. This is useful for discovering available counters or when you only need metadata.
 
.INPUTS
    None. This function does not accept pipeline input.
 
.OUTPUTS
    [PSCustomObject[]] or [Microsoft.EnterpriseManagement.Monitoring.PerformanceData[]]
     
    When PerfObjectsOnly is false (default), returns PSCustomObject array with properties:
    - MonitoringRuleId: GUID of the monitoring rule collecting this data
    - MonitoringRuleDisplayName: Display name of the monitoring rule
    - MonitoringRuleDescription: Description of the monitoring rule
    - MonitoringObjectDisplayName: Display name of the target object
    - MonitoringObjectId: GUID of the target monitoring object
    - MonitoringObjectPath: Full path of the monitoring object
    - MonitoringObjectFullName: Full name including class information
    - MonitoringClassId: GUID of the monitoring class
    - ObjectName: Performance object name (e.g., "Processor", "Memory")
    - CounterName: Performance counter name (e.g., "% Processor Time")
    - InstanceName: Counter instance name (e.g., "_Total", "C:")
    - ScaleFactor: Scaling factor applied to counter values
    - LastValue: Most recent sample value
    - MaximumValue: Maximum value in the time range
    - AverageValue: Average value in the time range
     
    When PerfObjectsOnly is true, returns native SCOM PerformanceData objects.
 
.EXAMPLE
    # Connect to SCOM and get basic performance data for a server
    New-SCOMManagementGroupConnection -ComputerName "scom.domain.com"
    $PerfData = Get-SCOMPerformanceData -ComputerName "SQL01.domain.com" -HoursLookback 96
    $PerfData | Select-Object ObjectName, CounterName, InstanceName, LastValue, AverageValue | Format-Table
 
    This example retrieves all performance data for the last 96 hours for SQL01 and displays
    key metrics in a table format.
 
.EXAMPLE
    # Get specific performance counters for a monitoring object
    $ObjectId = "c5a9c43c-84d9-a3c2-6e84-c9d68bf79aa1"
    $Counters = @("% Processor Time", "Free Megabytes", "% Free Space")
    $PerfData = Get-SCOMPerformanceData -Id $ObjectId -CounterNames $Counters -HoursLookback 12 -Verbose
     
    # Analyze CPU utilization
    $CPUData = $PerfData | Where-Object { $_.CounterName -eq "% Processor Time" }
    Write-Host "Average CPU: $([math]::Round($CPUData.AverageValue, 2))%"
    Write-Host "Peak CPU: $([math]::Round($CPUData.MaximumValue, 2))%"
 
    This example retrieves specific performance counters and analyzes CPU utilization statistics.
 
.EXAMPLE
    # Discover available performance counters without retrieving data
    $PerfObjects = Get-SCOMPerformanceData -ComputerName "WEB01.domain.com" -PerfObjectsOnly
    $UniqueCounters = $PerfObjects | Select-Object ObjectName, CounterName, InstanceName | Sort-Object ObjectName, CounterName
    $UniqueCounters | Group-Object ObjectName | ForEach-Object {
        Write-Host "Performance Object: $($_.Name)" -ForegroundColor Green
        $_.Group | ForEach-Object { Write-Host " $($_.CounterName) [$($_.InstanceName)]" }
    }
 
    This example discovers all available performance counters for a web server without
    retrieving actual performance data.
 
.EXAMPLE
    # Monitor disk space across multiple time periods
    $DiskCounters = @("Free Megabytes", "% Free Space")
    $Last96Hours = Get-SCOMPerformanceData -ComputerName "FILE01.domain.com" -CounterNames $DiskCounters -HoursLookback 96
    $Last7Days = Get-SCOMPerformanceData -ComputerName "FILE01.domain.com" -CounterNames $DiskCounters -HoursLookback 168
     
    # Compare disk usage trends
    $DiskComparison = $Last96Hours | ForEach-Object {
        $Counter = $_
        $SevenDayAvg = ($Last7Days | Where-Object {
            $_.ObjectName -eq $Counter.ObjectName -and
            $_.CounterName -eq $Counter.CounterName -and
            $_.InstanceName -eq $Counter.InstanceName
        }).AverageValue
         
        [PSCustomObject]@{
            Drive = $Counter.InstanceName
            Counter = $Counter.CounterName
            Last96HrAvg = [math]::Round($Counter.AverageValue, 2)
            Last7DayAvg = [math]::Round($SevenDayAvg, 2)
            Trend = if ($Counter.AverageValue -lt $SevenDayAvg) { "Decreasing" } else { "Increasing" }
        }
    }
    $DiskComparison | Format-Table
 
    This example compares disk usage trends between the last 96 hours and last 7 days.
 
.EXAMPLE
    # Export performance data to CSV for analysis
    $Servers = @("SQL01.domain.com", "WEB01.domain.com", "APP01.domain.com")
    $AllPerfData = @()
     
    foreach ($Server in $Servers) {
        Write-Host "Collecting data from $Server..."
        $ServerData = Get-SCOMPerformanceData -ComputerName $Server -HoursLookback 48
        $ServerData | ForEach-Object {
            $_ | Add-Member -NotePropertyName "ServerName" -NotePropertyValue $Server
        }
        $AllPerfData += $ServerData
    }
     
    $AllPerfData | Export-Csv -Path "C:\Reports\PerformanceData.csv" -NoTypeInformation
    Write-Host "Performance data exported to C:\Reports\PerformanceData.csv"
 
    This example collects performance data from multiple servers and exports it to CSV
    for further analysis in Excel or other tools.
 
.EXAMPLE
    # Find performance bottlenecks across different object types
    $BottleneckCounters = @("% Processor Time", "Available MBytes", "Current Disk Queue Length", "% Disk Time")
    $PerfData = Get-SCOMPerformanceData -Id "c5a9c43c-84d9-a3c2-6e84-c9d68bf79aa1" -CounterNames $BottleneckCounters -HoursLookback 6
     
    # Identify potential issues
    $Issues = $PerfData | Where-Object {
        ($_.CounterName -eq "% Processor Time" -and $_.AverageValue -gt 80) -or
        ($_.CounterName -eq "Available MBytes" -and $_.AverageValue -lt 1024) -or
        ($_.CounterName -eq "Current Disk Queue Length" -and $_.AverageValue -gt 2) -or
        ($_.CounterName -eq "% Disk Time" -and $_.AverageValue -gt 85)
    }
     
    if ($Issues) {
        Write-Host "Performance Issues Detected:" -ForegroundColor Red
        $Issues | ForEach-Object {
            Write-Host "- $($_.ObjectName)\$($_.CounterName)[$($_.InstanceName)]: Avg=$($_.AverageValue), Max=$($_.MaximumValue)" -ForegroundColor Yellow
        }
    } else {
        Write-Host "No performance issues detected in the last 6 hours." -ForegroundColor Green
    }
 
    This example identifies potential performance bottlenecks by checking key system counters
    against known thresholds.
 
    .EXAMPLE
    $PerfObjects = Get-SCOMPerformanceData -ComputerName "sql23.contoso.com" -PerfObjectsOnly
 
    # Hand pick individual counters
    $Counters = $PerfObjects | Out-GridView -PassThru
 
    # Example 1: use counter names that are stored in $Counters, previous 7 days
    # $PerfData = Get-SCOMPerformanceData -ComputerName "SQL23.contoso.com" -HoursLookback 168-CounterNames $Counters.CounterName
 
    # Example 2: hard code the counter names, previous 7 days
    # $PerfData = Get-SCOMPerformanceData -ComputerName "SQL23.contoso.com" -HoursLookback 168 -CounterNames '% Processor Time','% Free Space','Available MBytes'
 
    # Filter example 1, Time range, use 2nd element in the $PerfData array
    $PerfData[1].RawData | ? {$_.TimeSampled -lt (Get-Date).AddDays(-1) -and $_.TimeSampled -gt (Get-Date).AddDays(-2) }
 
 
    # Filter example 2, use Get-Date with (-) negative days offset
    $PerfData | Where-Object {$_.CounterName -eq '% Processor Time' -and $_.InstanceName -eq '_Total'} | Select-Object RawData -ExpandProperty RawData | Where-Object {$_.TimeSampled -lt (Get-Date).AddDays(-1) -and $_.TimeSampled -gt (Get-Date).AddDays(-2) }
 
    # Filter example 3, use specific countername, specific datestamps
    $procRawData = $PerfData | Where-Object {$_.CounterName -eq '% Processor Time' -and $_.InstanceName -eq '_Total'} | Select-Object RawData -ExpandProperty RawData
    $procRawData | Where-Object {$_.TimeSampled -gt '2025-07-06 12:00:00 AM' -and $_.TimeSampled -lt '2025-07-07 12:00:00 PM' }
 
.NOTES
    Author: Tyson Paul (https://monitoringguys.com/2019/11/12/scomhelper/)
    Version History:
    2025.06.27 - 1.0: Initial version
     
    Requirements:
    - SCOM 2012 or later with PowerShell SDK installed
    - Active SCOM management group connection (New-SCOMManagementGroupConnection)
    - Appropriate SCOM permissions to read monitoring objects and performance data
    - PowerShell 3.0 or later
     
    Performance Considerations:
    - Large time ranges (HoursLookback) can result in significant memory usage and longer execution times
    - Consider filtering by CounterNames when you only need specific metrics
    - Use PerfObjectsOnly for discovery scenarios to avoid unnecessary data retrieval
    - The function processes all related monitoring objects, which can be extensive for complex systems
     
    Troubleshooting:
    - Ensure SCOM management group connection is established before calling this function
    - Verify the computer name exists in SCOM and is properly monitored
    - Check that the monitoring object GUID is valid and accessible
    - Use -Verbose for detailed execution information
     
    Common Counter Names:
    - CPU: "% Processor Time"
    - Memory: "Available MBytes", "% Committed Bytes In Use"
    - Disk: "Free Megabytes", "% Free Space", "Current Disk Queue Length", "% Disk Time"
    - Network: "Bytes Total/sec", "Packets/sec"
 
.LINK
    New-SCOMManagementGroupConnection
    Get-SCOMClassInstance
    Get-SCOMClass
 
.FUNCTIONALITY
    SCOM SDK, Performance Monitoring, System Center Operations Manager
#>


    [CmdletBinding(DefaultParameterSetName = "ByComputerNameWithCounters")]
    Param (
        [Parameter(Mandatory = $true, ParameterSetName = "ByComputerNameWithCounters")]
        [Parameter(Mandatory = $true, ParameterSetName = "ByComputerNameObjectsOnly")]
        [string]$ComputerName,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = "ByIdWithCounters",
            HelpMessage = "The GUID of the entity for which to retrieve performance counters")]
        [Parameter(
            Mandatory = $true,
            ParameterSetName = "ByIdObjectsOnly",
            HelpMessage = "The GUID of the entity for which to retrieve performance counters")]
        [Alias('ObjectId')]
        [ValidateScript({
            if ([Guid]::TryParse($_, [ref][Guid]::Empty)) {
                $true
            } else {
                throw "Invalid GUID format. Please provide a valid GUID string."
            }
        })]
        [Object]$Id,

        [Parameter(Mandatory = $false, ParameterSetName = "ByComputerNameWithCounters")]
        [Parameter(Mandatory = $false, ParameterSetName = "ByIdWithCounters")]
        [int]$HoursLookback = 72,

        [Parameter(Mandatory = $true, ParameterSetName = "ByComputerNameWithCounters")]
        [Parameter(Mandatory = $true, ParameterSetName = "ByIdWithCounters")]
        [ValidateNotNullOrEmpty()]
        [string[]]$CounterNames,

        [Parameter(Mandatory = $true, ParameterSetName = "ByComputerNameObjectsOnly")]
        [Parameter(Mandatory = $true, ParameterSetName = "ByIdObjectsOnly")]
        [switch]$PerfObjectsOnly
    )


    ###########################################
    Function Get-RMORecursive {
        [CmdletBinding()]
        Param (
            [Parameter(Mandatory = $true)]
            $ClassInstance
        )
        $arr = @()
        $arr += $ClassInstance
        # Get all related monitoring objects recursively
        $relatedObjects = $ClassInstance.GetRelatedMonitoringObjects()
        ForEach ($relatedObject in $relatedObjects) {
                # Recursively get related monitoring objects for this object
                $arr += (Get-RMORecursive -ClassInstance $relatedObject)
        }
        Return $arr
    }#end Function Get-RMORecursive
    ###########################################

    If ($ComputerName.Length -gt 0) {
        # If we are using the ComputerName parameter, we need to get the Id of the MonitoringObject
        try {
            # Only supports Microsoft.Windows.Computer class at this time.
            $Id = Get-SCOMIdFromHostName $ComputerName
        }
        Catch {
            Write-Error "Failed to get MonitoringObjectId for ComputerName '$ComputerName'. Error: $_"
            return $false
        }
    }

    Write-Verbose "Getting Performance Data for MonitoringObjectId: $Id"
    If (-NOT $PerfObjectsOnly) {
        Write-Verbose "Looking back $HoursLookback hours for performance data."
    }
    Else {
        Write-Verbose "Retrieving performance objects only, no data samples will be returned."
    }
    Write-Verbose "Counter Names Provided: $CounterNames"

    $Instance = Get-SCOMClassInstance -Id $Id
    # Get related monitoring objects recursively because, sadly, the standard method only returns the direct instances, not nested ones.
    $allRMO = Get-RMORecursive -ClassInstance $Instance

    ForEach ($obj in $allRMO) {
        #$obj.GetMonitoringPerformanceData() | ForEach-Object {
        ForEach ($objPerf in $obj.GetMonitoringPerformanceData()) {
            $perfDataRaw = $null
            if ($objPerf.CounterName -in $CounterNames -OR $CounterNames.Count -eq 0) {
                Write-Verbose "Selected Counter: $($objPerf.ObjectName)/$($objPerf.CounterName)/$($objPerf.InstanceName)"
                If (-NOT $PerfObjectsOnly) {
                    $SampleStartTime = (Get-Date).AddHours(-$HoursLookback)
                    $SampleEndTime = (Get-Date)
                    $perfDataRaw = $objPerf | ForEach-Object {$objPerf.GetValues($SampleStartTime,$SampleEndTime)}
                }
                Else {
                    # If we only want the performance objects, we can just return the object itself
                    $objPerf
                }
            }
            Else {
                #TODO Test this case
                Continue
            }
            
            if ($perfDataRaw -and $perfDataRaw.Count -gt 0) {
                [double]$LastValue = $perfDataRaw[-1].SampleValue
                [double]$MaximumValue = ($perfDataRaw | Measure-Object -Property SampleValue -Maximum).Maximum
                [double]$AverageValue = ($perfDataRaw | Measure-Object -Property SampleValue -Average).Average
                $tmpObj = [PSCustomObject]@{
                    MonitoringRuleId = $objPerf.MonitoringRuleId
                    MonitoringRuleDisplayName = $objPerf.MonitoringRuleDisplayName
                    MonitoringRuleDescription = $objPerf.MonitoringRuleDescription
                    MonitoringObjectDisplayName = $objPerf.MonitoringObjectDisplayName
                    MonitoringObjectId = $objPerf.MonitoringObjectId
                    MonitoringObjectPath = $objPerf.MonitoringObjectPath
                    MonitoringObjectFullName = $objPerf.MonitoringObjectFullName
                    MonitoringClassId = $objPerf.MonitoringClassId
                    ObjectName = $objPerf.ObjectName
                    CounterName = $objPerf.CounterName
                    InstanceName = $objPerf.InstanceName
                    ScaleFactor = $objPerf.ScaleFactor
                    LastValue = $LastValue
                    MaximumValue = $MaximumValue
                    AverageValue = $AverageValue
                    RawData = @($perfDataRaw)
                }
                $tmpObj
            }
        }
    }
}#end Function Get-SCOMPerformanceData

#
$PerfObjects = Get-SCOMPerformanceData -ComputerName "sql23.contoso.com" -PerfObjectsOnly
$Counters = $PerfObjects | Out-GridView -PassThru

# $PerfData = Get-SCOMPerformanceData -ComputerName "SQL23.contoso.com" -HoursLookback 168-CounterNames $Counters.CounterName
# $PerfData = Get-SCOMPerformanceData -ComputerName "SQL23.contoso.com" -HoursLookback 96 -CounterNames '% Processor Time','% Free Space','Available MBytes'

#TODO Add filtering example
$PerfData[1].RawData | ? {$_.TimeSampled -lt (Get-Date).AddDays(-1) -and $_.TimeSampled -gt (Get-Date).AddDays(-2) }

# Example of filtering performance data for a specific counter and time range
# Note, the time range is only applicable to the raw data in the OperationsManager DB
$PerfData | Where-Object {$_.CounterName -eq '% Processor Time' -and $_.InstanceName -eq '_Total'} | Select-Object RawData -ExpandProperty RawData | Where-Object {$_.TimeSampled -lt (Get-Date).AddDays(-1) -and $_.TimeSampled -gt (Get-Date).AddDays(-2) }

$procRawData = $PerfData | Where-Object {$_.CounterName -eq '% Processor Time' -and $_.InstanceName -eq '_Total'} | Select-Object RawData -ExpandProperty RawData
$procRawData | Where-Object {$_.TimeSampled -gt '2025-07-06 12:00:00 AM' -and $_.TimeSampled -lt '2025-07-07 12:00:00 PM' }


$procRawData | Where-Object {$_.TimeSampled -gt '2025-07-04 12:00:00 AM'  } | select timesampled




#TODO add raw data to Get-SCOMObjectAvailability


"SQL23.contoso.com","SQL24.contoso.com" | % {Get-SCOMPerformanceData -ComputerName $_ -HoursLookback 96 -CounterNames $counters.countername}