NOTICE:
This script is depracated and so is Technet Gallery apparently. Try the new "Export-SCOMEffectiveMonitoringConfigurationReport" in the SCOMHelper PowerShell module instead. The module is full of tasty treats for the SCOM admin!

https://monitoringguys.com/2019/11/12/scomhelper/

 

 

v1.15

2018.03.23: Deleted previous submission (2137 downloads). Replaced with this one.
(If I'm being completely honest, I accidentally deleted the previous version. That's what I get for rushing.)

 

I was working on a support case recently when it was brought to my attention that this Operations Manager cmdlet:

Export-SCOMEffectiveMonitoringConfiguration 

 does not output ALL contained instance configurations even when the "-RecurseContainedObjects"  parameter is used. Yes, really!  It outputs configuration data for only one instance of each particular type, be it a logical disk or Ethernet adapter etc., you only get monitor and rule configuration output for one of a kind in the resulting .csv file. If you have three logical disks (i.e. C:, D:, E:) you will only see configuration data for one of those, whichever one gets enumerated first by the cmdlet. 

To overcome this limitation I've written a script that accepts a group name, computer name, or ID. It "recurses" all contained objects, outputs each configuration set to a separate file, then merges all of the files together to create one complete .CSV. (Thanks to Matt Hofacker for the idea) Finally, it outputs the complete dataset to GridView (great idea StefanRoth!) for easy viewing and filtering. The merged CSV file is available to open in Excel if needed.

Test with Operations Manager 2012 R2, 2016.

 

 

 

 

 

History:

2018.04.06:  Added "Rule/Monitor DisplayName" column to output.

 

 

PowerShell
Edit|Remove
<#  
.SYNOPSIS 
    Operations Manager Powershell script to ouput the effective monitoring configurations for a specified object ID, group, or Computer name. 
.DESCRIPTION 
    Will recursively find contained instances of an object (or group of objects) and ouput the effective monitoring configurations/settings to individual output files 
    Will merge all resulting files into one .csv file, delimited by pipes '|', then output all data to Grid View 
.EXAMPLE 
    .\ExportEffectiveMonitoringConfiguration.ps1 -SCOMGroupName "All Windows Computers" -TargetFolder "C:\Export\MyConfigFiles" -OutputFileName "AllWindowsComputers_MonitoringConfigs.CSV" 
.EXAMPLE 
    .\ExportEffectiveMonitoringConfiguration.ps1 -ComputerName "SQL01.contoso.com" -TargetFolder "C:\Export" -OutputFileName "Output.csv" 
.EXAMPLE 
    The following example returns the Monitoring object ID of a Windows Computer instance with name: "db01.contoso.com". The ID is then used as a parameter.  
 
    PS C:\> $ComputerID = (Get-SCOMClass -name "Microsoft.windows.computer" | Get-SCOMClassInstance | ? {$_.DisplayName -like "db01.contoso.com"}).Id 
    PS C:\> .\ExportEffectiveMonitoringConfiguration.ps1 -ID $ComputerID -TargetFolder "C:\Export" -OutputFileName "Output.csv" 
.EXAMPLE 
    The following example will output all monitoring configuration info for a specific computer to a csv file. There will be no confirmation prompt to proceed.  
    Any previously stored  DisplayName file will be removed and recreated. This will increase run time of script as it will have to retrieve all of the workflow Displaynames and  
    this is very expensive on the SQL database. It will then display that information in GridView. The -PassThru parameter 
    will allow the user to filter the view and then export any selected GridView line items to a new csv file named "FilteredOutput.csv" 
     
    PS C:\> .\ExportEffectiveMonitoringConfiguration.ps1 -ComputerName "win01.contoso.com" -TargetFolder "C:\Export" -OutputFileName "WIN01_Output.csv" -NoConfirm -ClearCache -NoGridView 
    PS C:\> Import-Csv 'C:\Export\Merged_WIN01_Output.csv' -Delimiter '|' | Out-GridView -PassThru | Export-Csv -Path 'C:\Export\FilteredOutput.csv' -NoTypeInformation -Force 
.NOTES 
    File Name  : ExportEffectiveMonitoringConfiguration.ps1   
    Author     : Author: Tyson Paul  ( https://blogs.msdn.microsoft.com/tysonpaul/ ) 
    Requires   : Operations Manager Powershell Console 
    Version    : 1.15 
    Original Date: 7-25-2014 
    History 
        2018.04.06: Added column to output: Rule/Monitor DisplayName 
        2017.11.28: Fixed paramter types.  
        2017.10.11: Added ability to specify object ID or single computer name. Improved Help content. Improved output formatting. 
        2014.8.6:     Fixed output file name/path problem. 
.PARAMETER    -ComputerName 
        Accepts a single FQDN (Fully Qualified Domain Name) name of an monitored computer. 
.PARAMETER    -ID 
        Accepts a single guid. Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). 
.PARAMETER    -SCOMGroupName 
        Accepts a single group name. 
.PARAMETER    -TargetFolder 
        The full path to where the script should output the configuration files. 
.PARAMETER    -OutputFileName 
        The name of the complete output file in which all other configuration files will be compiled. 
.PARAMETER  -NoConfirm 
        Switch. Will not prompt user for confirmation to proceed. (Caution should be taken when targeting large groups.) 
.PARAMETER  -NoGridView 
        Switch. Will not display configuration in PowerShell GridView 
.PARAMETER  -ClearCache 
        Switch. Will delete any existing DisplayName file. This is a file that is saved in the $ENV:TEMP path. It will save a list of  
        workflow Names and DisplayNames from previous executions of this script. This will significantly speed up the runtime of the script.  
        This option is only useful if management packs have been updated and DisplayNames of workflows have been modified since the script 
        was last run. When enabled, this parameter will force the script to retrieve all of the DisplayNames from the SDK instead of the locally stored file. 
.LINK 
        Tyson's Blog:     https://blogs.msdn.microsoft.com/tysonpaul 
#> 
 
################################################################################################### 
    [CmdletBinding(DefaultParameterSetName='P1',  
                  SupportsShouldProcess=$true,  
                  PositionalBinding=$false)] 
 
    Param 
    ( 
        # 1 
        [Parameter(Mandatory=$true,  
                   ValueFromPipeline=$false, 
                   ValueFromPipelineByPropertyName=$false,  
                   ValueFromRemainingArguments=$false,  
                   Position=0, 
                   ParameterSetName='P1')] 
        [ValidateNotNull()] 
        [ValidateNotNullOrEmpty()] 
        [string]$ComputerName, 
 
        #2 
        [Parameter(Mandatory=$true,  
                   ValueFromPipeline=$false, 
                   ValueFromPipelineByPropertyName=$false,  
                   ValueFromRemainingArguments=$false,  
                   Position=0, 
                   ParameterSetName='P2')] 
        [ValidateNotNull()] 
        [ValidateNotNullOrEmpty()] 
        [string]$SCOMGroupName, 
 
        #3 
        [Parameter(Mandatory=$true,  
                   ValueFromPipeline=$false, 
                   ValueFromPipelineByPropertyName=$false,  
                   ValueFromRemainingArguments=$false,  
                   Position=0, 
                   ParameterSetName='P3')] 
        [ValidateNotNull()] 
        [ValidateNotNullOrEmpty()] 
        # Validate GUID pattern 
        [ValidatePattern("[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]-[a-f0-9][a-f0-9][a-f0-9][a-f0-9]-[a-f0-9][a-f0-9][a-f0-9][a-f0-9]-[a-f0-9][a-f0-9][a-f0-9][a-f0-9]-[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]")] 
        [System.Guid]$ID, 
 
        #4 
        [Parameter(Mandatory=$false,  
            ValueFromPipeline=$false, 
            ValueFromPipelineByPropertyName=$false,  
            ValueFromRemainingArguments=$false,  
            Position=1)] 
        [string]$TargetFolder = "C:\SCOM_Export", 
 
        #5 
        [Parameter(Mandatory=$false,  
            ValueFromPipeline=$false, 
            ValueFromPipelineByPropertyName=$false,  
            ValueFromRemainingArguments=$false,  
            Position=2 )] 
        [string]$OutputFileName, 
 
        #6 
        [Parameter(Mandatory=$false,  
            ValueFromPipeline=$false, 
            ValueFromPipelineByPropertyName=$false,  
            ValueFromRemainingArguments=$false,  
            Position=3 )] 
        [switch]$NoConfirm, 
 
        #6 
        [Parameter(Mandatory=$false,  
            ValueFromPipeline=$false, 
            ValueFromPipelineByPropertyName=$false,  
            ValueFromRemainingArguments=$false,  
            Position=4 )] 
        [switch]$NoGridview=$false, 
 
        #7 
        [Parameter(Mandatory=$false,  
            ValueFromPipeline=$false, 
            ValueFromPipelineByPropertyName=$false,  
            ValueFromRemainingArguments=$false,  
            Position=5 )] 
        [switch]$ClearCache=$false 
         
) 
 
New-Variable -Name StartupVariables -Force -Value (Get-Variable -Scope Global | Select -ExpandProperty Name) 
     
##################################################################################################### 
Function Cleanup(){ 
    $ErrorActionPreference = "SilentlyContinue"    #Depending on when this is called, some variables may not be initialized and clearing could throw benign error. Supress. 
    Write-Host "`nPerforming cleanup..." -ForegroundColor Cyan 
    #Cleanup 
    Get-Variable | Where-Object { $StartupVariables -notcontains $_.Name } | % { Remove-Variable -Name "$($_.Name)" -Force -Scope 1 } 
} 
 
########################################################################################################     
#   Will clean up names/strings with special characters (like URLs and Network paths) 
Function CleanName { 
Param( 
    [string]$uglyString 
) 
    # Remove problematic characters and leading/trailing spaces 
    $prettyString = (($uglyString.Replace(':','_')).Replace('/','_')).Replace('\','_').Trim() 
 
    # If the string has been modified, output that info 
    If ($uglyString -ne $prettyString) { 
        Write-Verbose "There was a small problem with the characters in this parameter: [$($uglyString)]..." 
        Write-Verbose "Original Name:`t`t$($uglyString)" 
        Write-Verbose "Modified Name:`t`t$($prettyString)"  
    } 
     
    Return $prettyString 
    #> 
} 
########################################################################################################     
#    Function MergeFiles 
#    Will find .csv files and merge them together.  
Function MergeFiles{ 
    Param( 
        [string]$strPath, 
        [string]$strOutputFileName 
    ) 
     
 
    $strOutputFilePath = (Join-Path $strPath $strOutputFileName) 
    # If output file already exists, remove it.  
    If (Test-Path $strOutputFilePath -PathType Leaf) {  
        Write-Verbose "Output file [ $($strOutputFilePath) ] already exists. Removing..." 
        Remove-Item -Path $strOutputFilePath -Force  
    } 
 
    If (Test-Path $strOutputFilePath) {  
        Write-Error "Cannot remove $strOutputFilePath and therefore cannot generate merged output file." -ForegroundColor Yellow -BackgroundColor Black 
        Write-Error "Remove this file first: [ $($strOutputFilePath) ]" 
        Write-Error "Exiting ! " 
        Cleanup 
        Exit 
    } 
 
    Get-ChildItem -Path $strPath -File -Filter *.csv -Exclude $strOutputFileName -Recurse | ForEach { 
        $intThisHeaderLength = (Get-Content -LiteralPath $_.FullName)[0].Length  
        If ($intThisHeaderLength -gt $intLongestHeaderLength) { 
            $objLongestHeaderFile = $_ 
            $intLongestHeaderLength = $intThisHeaderLength 
        } 
    } 
 
    Write-Host "Has largest set of file headers: [ $($objLongestHeaderFile.FullName) ] " 
    # Create the master merge file seeded by the data from the existing CSV file with the most headers out of the entire set of CSVs. 
    Try{ 
        Get-Content $objLongestHeaderFile.FullName | Out-File -LiteralPath $strOutputFilePath -Force -Encoding UTF8 
    }Catch{ 
        Write-Error $error[0] 
        Write-Host "Something is wrong with this path [$($strOutputFilePath)]." -ForegroundColor Red -BackgroundColor Yellow 
        Write-Host "Exiting..."  
        Exit 
    } 
    # Iterate through all of the CSVs, append all of them into the master (except for the one already used as the seed above and except for the master merge file itself.) 
    $i=0 
    $tempArray = @() 
    Get-ChildItem -Path $strPath -File -Filter *.csv -Exclude "merged*" -Recurse | ForEach {  
        If( ( $_.FullName -eq $objLongestHeaderFile.FullName ) -or ($_.FullName -like (Get-Item $strOutputFilePath).FullName) ){ 
            Write-Host "Skip this file: `t" -NoNewline; Write-Host "$($_.FullName)" -ForegroundColor Red -BackgroundColor Yellow 
        } 
        Else { 
            Write-Host "Merge this file: `t" -NoNewline; Write-Host "$($_.FullName)" -BackgroundColor Green -ForegroundColor Black 
            $tempArray += ((((Get-Content -Raw -Path $_.FullName) -Replace "\n"," " ) -Split "\r"| Select -Skip 1 ) 
            $i++ 
        } 
    } 
    $tempArray | Out-File -LiteralPath $strOutputFilePath -Append -Encoding UTF8 
    "" # Cheap formatting 
    Write-Host "Total files merged: `t" -NoNewline; Write-Host "$i" -BackgroundColor Black -ForegroundColor Green 
    Write-Host "Master output file: `t" -NoNewline; Write-Host "$strOutputFilePath" -BackgroundColor black -ForegroundColor Green 
} # EndFunction 
################################################################################################### 
 
Function MakeObject { 
Param( 
    [string]$strMergedFilePath 
) 
    $mainArray = @() 
    [string]$rootFolder = Split-Path $strMergedFilePath -Parent 
     
    $tmpFileName = "ExportEffectiveMonitoringConfiguration.ps1_DisplayNamesCSV.tmp" 
    Try { 
        [string]$savedDNs = (Join-Path $env:Temp $tmpFileName )         
        New-Item -ItemType File -Path $savedDNs -ErrorAction SilentlyContinue 
    }Catch{ 
        [string]$savedDNs = (Join-Path $rootFolder $tmpFileName ) 
    } 
    If ($ClearCache){ 
        Write-Host "Removing saved DisplayNames file: [$($savedDNs)]" -F Gray 
        Remove-Item -Path $savedDNs -Force 
    } 
 
    If (!($strMergedFilePath)) { 
        Write-Host "Cannot find [ $($strMergedFilePath) ] and therefore cannot compile object for Grid View." -ForegroundColor Yellow -BackgroundColor Black 
        Write-Host "Exiting..." 
        Cleanup 
        Exit 
    } 
     
    $Headers = @() 
    $Headers= (Get-Content -LiteralPath $strMergedFilePath | Select -First 1).Split('|') 
    $FileContents = (Get-Content -LiteralPath $strMergedFilePath | Select -Skip 1 ).Replace("`0",'') 
    $r=1 
 
    <# The Export-SCOMEffectiveMonitoringConfiguration cmdlet does not include DisplayName in it's default output set. 
        Querying the SDK for the workflow DisplayName is expensive.  In the code below we try to benefit from a saved  
        list of Name->DisplayName pairs. If the list does not already exist, we will create one. If it does already  
        exist, we will import it into a hash table for fast DisplayName lookup while building the rows of the master file.  
    #> 
    $DNHash = @{} 
    Try { 
        [System.Object[]]$arrDN = (Import-Csv -Path $savedDNs -ErrorAction SilentlyContinue) 
    } Catch { 
        $arrDN = @() 
    } 
    # If a previous list of Name/DisplayName pairs exists, let's use it to build our fast hash table. 
    If ([bool]$arrDN ){ 
        ForEach ($item in $arrDN) { 
            $DNHash.Add($item.'Rule/Monitor Name',$item.'Rule/Monitor DisplayName') 
        } 
    } 
    $arrTmpDN = @() 
    ForEach ($Row in $FileContents) { 
        $percent = [math]::Round(($r / $FileContents.count*100),0)  
         Write-Progress -Activity "** What's happening? **" -status "Formatting your data! [Percent: $($percent)]" -percentComplete $percent 
        If ($Row.Length -le 1) { Continue; } 
        $c=0 
        $arrRow = @() 
        $arrRow = $Row.Split('|') 
 
        # If the ForEach has already executed one iteration and thus the full object template has already been created,  
        # duplicate the template instead of building a new object and adding members to it for each column. This is about 3x faster than building the object every iteration. 
        If ([bool]($templateObject)) { 
            $object = $templateObject.PsObject.Copy() 
            $object.Index = $r.ToString("0000") 
        } 
        Else { 
            $object = New-Object -TypeName PSObject 
            $object | Add-Member -MemberType NoteProperty -Name "Index" -Value $r.ToString("0000") 
        }         
        ForEach ($Column in $Headers) { 
            If ( ($arrRow[$c-eq ''-or ($arrRow[$c-eq ' ') ) { $arrRow[$c] = 'N/A' } 
            # Some header values repeat. If header already exists, give it a unique name 
            [int]$Position=1 
            $tempColumn = $Column 
            # The first 10 columns are unique. However, beyond 10, the column names repeat: 
            #  Parameter Name, Default Value, Effective Value 
            # A clever way to assign each set of repeats a unique name is to append an incremental instance number.  
            # Each set (of 3 column names) gets an occurance number provided by the clever math below. 
            # Example: Parameter Name1, Default Value1, Effective Value1, Parameter Name2, Default Value2, Effective Value2 
            If ($c -ge 10) { 
                $Position = [System.Math]::Ceiling(($c / 3)-3) 
                $tempColumn = $Column + "$Position" 
            } 
            If ([bool]($templateObject)) { 
                $object.$tempColumn = $arrRow[$c] 
            } 
            Else { $object | Add-Member -MemberType NoteProperty -Name $tempColumn -Value "$($arrRow[$c])" } 
 
            If ($Column -eq 'Rule/Monitor Name') { 
                # If DisplayName (DN) does not already exist in set 
                If (-not [bool]($DN = $DNHash.($arrRow[$c])) ) { 
                    # Find the DisplayName 
                    switch ($arrRow[7]) #Assuming this column header is consistently "Type" 
                    { 
                        'Monitor' { 
                            $DN = (Get-SCOMMonitor -Name $arrRow[$c]).DisplayName  
                        } 
                        'Rule' { 
                            $DN = (Get-SCOMRule -Name $arrRow[$c]).DisplayName 
                        } 
                        Default {Write-Host "SWITCH DEFAULT IN FUNCTION: 'MAKEOBJECT', SOMETHING IS WRONG." -F Red -B Yellow} 
                    } 
                 
                    # If no DN exists for the workflow, set a default 
                    If (-Not([bool]$DN)) { 
                        $DN = "N/A" 
                    } 
                    Else{ 
                        $DNHash.Add($arrRow[$c],$DN)  
                    } 
                } 
                # DN Exists, add it to the hash table for fast lookup. Also add it to the catalog/array of known DNs so it can be saved and used again 
                #  next time for fast lookup. 
                 
                If ([bool]($templateObject)) { 
                    $object.'Rule/Monitor DisplayName' = $DN 
                } 
                Else { $object | Add-Member -MemberType NoteProperty -Name "Rule/Monitor DisplayName" -Value $DN } 
            } 
            $c++ 
        } 
        $r++ 
        $mainArray +$object 
        If (-not [bool]($templateObject)) { 
            $templateObject = $object.PsObject.Copy() 
        } 
        Remove-Variable -name object,tmpObj,DN -ErrorAction SilentlyContinue 
    } 
    # Build a simple array to hold unique Name,DisplayName values so that it can be exported easily to a CSV file.  
    # This cached csv file will significantly speed up the script next time it runs. 
    ForEach ($Key in $DNHash.Keys){ 
        $tmpObj = New-Object -TypeName PSObject 
        $tmpObj | Add-Member -MemberType NoteProperty -Name "Rule/Monitor Name" -Value $Key 
        $tmpObj | Add-Member -MemberType NoteProperty -Name "Rule/Monitor DisplayName" -Value $DNHash.$Key 
        $arrTmpDN +$tmpObj 
    } 
    $mainArray | Export-Csv -Path $strMergedFilePath -Force -Encoding UTF8 -Delimiter '|' -NoTypeInformation 
    $arrTmpDN | Export-Csv -Path $savedDNs -Force -Encoding UTF8 -NoTypeInformation 
    Return $mainArray 
} 
################################################################################################### 
 
# The export cmdlet (Export-SCOMEffectiveMonitoringConfiguration) seems to include rogue LF linefeeds which causes problems. These LF characters need to be removed.  
# This will affect LF and CRLF so that only CR remain, which is fine for later use of Get-Content. 
Function FixLineFeeds { 
Param ( 
    [String]$TargetFolder 
) 
 
} 
# --------------------------------------------------------------------------------------------------------------------------------------------------- 
 
If (!(Test-Path $TargetFolder)) {  
    Write-Verbose "TargetFolder [ $($TargetFolder) ] does not exist. Creating it now..." 
    new-item -ItemType Directory -Path $TargetFolder 
    If (!(Test-Path $TargetFolder)) { 
        Write-Error "Unable to create TargetFolder: $TargetFolder. Exiting." 
        Cleanup 
        Exit 
    } 
    Else {  
        Write-Verbose "Created TargetFolder successfully. " 
    } 
 } 
 
$elapsed_enumeration = [System.Diagnostics.Stopwatch]::StartNew() 
 
# If group name is provided... 
If ($SCOMGroupName) { 
    $choice='group' 
    $objects = @(Get-SCOMGroup -DisplayName $SCOMGroupName | Get-SCOMClassInstance)  
    If (-not($objects)) {Write-Error "Unable to get group: [ $($SCOMGroupName) ]." 
        Write-Verbose "To troubleshoot, run this command:`n`n  Get-SCOMGroup -DisplayName '$SCOMGroupName' | Get-SCOMClassInstance `n"  
        Write-Error "Exiting...";  
        Cleanup 
        Exit  
    } 
    Else { 
        Write-Verbose "Success getting group: [ $($SCOMGroupName) ]." 
    } 
    $TempName = $SCOMGroupName 
    $count = $objects.GetRelatedMonitoringObjects().Count 
    "" # Cheap formatting 
    "" 
    Write-Host "This will output ALL monitoring configuration for group: " -ForegroundColor Cyan -BackgroundColor Black -NoNewline; ` 
    Write-Host "[" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
    Write-Host "$($SCOMGroupName)" -ForegroundColor Yellow -BackgroundColor Black -NoNewline; ` 
    Write-Host "]" -ForegroundColor Red -BackgroundColor Black  
 
    Write-Host "There are: " -ForegroundColor Green -BackgroundColor Black -NoNewline; ` 
    Write-Host "[" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
    Write-Host "$($count)" -ForegroundColor Yellow -BackgroundColor Black -NoNewline; ` 
    Write-Host "]" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
    Write-Host " nested objects in that group." -ForegroundColor Green -BackgroundColor Black 
    Write-Host "This might take a little while depending on how large the group is and how many hosted objects exist!"  -ForegroundColor Green -BackgroundColor Black 
    "" # Cheap formatting 
} 
 
# If ID is provided... 
ElseIf ($ID) { 
    $choice='ID' 
    Write-Verbose "Getting class instance with ID: [ $($ID) ] " 
    $objects = (Get-SCOMClassInstance -Id $ID) 
    If (-not($objects)) { 
        Write-Error "Unable to get class instance for ID: [ $($ID) ] " 
        Write-Verbose "To troubleshoot, use this command:`n`n  Get-SCOMClassInstance -Id '$ID' `n" 
        Write-Error "Exiting...";  
        Cleanup 
        Exit  
    } 
    Else { 
        Write-Verbose "Success getting class instance with ID: [ $($ID) ], DisplayName: [ $($ID.DisplayName) ]." 
    } 
    $TempName = $ID 
    $count = $objects.GetRelatedMonitoringObjects().Count 
    "" # Cheap formatting 
    "" 
    Write-Host "This will output ALL monitoring configuration for object: " -ForegroundColor Cyan -BackgroundColor Black -NoNewline; ` 
    Write-Host "[" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
    Write-Host "$($objects.DisplayName) , " -ForegroundColor Yellow -BackgroundColor Black -NoNewline; ` 
    Write-Host "ID: $ID " -ForegroundColor Gray -BackgroundColor Black -NoNewline; ` 
    Write-Host "]" -ForegroundColor Red -BackgroundColor Black  
    Write-Host "There are: " -ForegroundColor Green -BackgroundColor Black -NoNewline; ` 
    Write-Host "[" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
    Write-Host "$($count)" -ForegroundColor Yellow -BackgroundColor Black -NoNewline; ` 
    Write-Host "]" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
    Write-Host " related monitoring objects."  -ForegroundColor Green -BackgroundColor Black 
    Write-Host "This might take a little while depending on how hosted objects exist !" -ForegroundColor Green -BackgroundColor Black 
    "" # Cheap formatting 
} 
# Assume individul computer name is provided... 
ElseIf ($ComputerName){  
    $choice='ComputerName' 
    # $objects = @(Get-SCOMClass -Name "Microsoft.Windows.Computer" | Get-SCOMClassInstance | Where-Object {$ComputerName -contains $_.DisplayName } )  
    # This approach should prove to be more efficient for environments with more than 40-ish Computers/agents. 
    $ClassName = 'Microsoft.Windows.Computer' 
    $ComputerClass = (Get-SCOMClass -Name $ClassName) 
    If (-not($ComputerClass)) { 
        Write-Error "Unable to get class: [ $ClassName ]."  
        Write-Verbose "To troubleshoot, use this command:`n`n  Get-SCOMClass -Name '$ClassName' `n" 
        Write-Error "Exiting...";  
        Cleanup 
        Exit  
    } 
    Else { 
        Write-Verbose "Success getting class object with name: [ $($ClassName) ]." 
    } 
 
    Write-Verbose "Getting class instance of [ $($ClassName) ] with DisplayName of [ $($ComputerName) ]..." 
    $objects = @(Get-SCOMClassInstance -DisplayName $ComputerName | Where-Object {$_.LeastDerivedNonAbstractManagementPackClassId -like $ComputerClass.Id.Guid} ) 
    If (-not($objects)) { 
        Write-Error "Unable to get class instance for DisplayName: [ $($ComputerName) ] " 
        Write-Verbose "To troubleshoot, use this command:`n`n   `$ComputerClass = (Get-SCOMClass -Name '$ClassName') " 
        Write-Verbose "   Get-SCOMClassInstance -DisplayName '$ComputerName' | Where-Object {`$_.LeastDerivedNonAbstractManagementPackClassId -like `$ComputerClass.Id.Guid} `n" 
        Write-Error "Exiting...";  
        Cleanup 
        Exit  
    } 
    Else { 
        Write-Verbose "Success getting class instance for DisplayName: [ $($ComputerName) ] " 
    } 
    $TempName = $ComputerName 
    $count = $objects.GetRelatedMonitoringObjects().Count 
    "" # Cheap formatting 
    "" 
    Write-Host "This will output ALL monitoring configuration for Computer: " -ForegroundColor Cyan -BackgroundColor Black -NoNewline; ` 
    Write-Host "[" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
    Write-Host "$($objects.DisplayName)" -ForegroundColor Yellow -BackgroundColor Black -NoNewline; ` 
    Write-Host "]" -ForegroundColor Red -BackgroundColor Black  
    Write-Host "There are: " -ForegroundColor Green -BackgroundColor Black -NoNewline; ` 
    Write-Host "[" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
    Write-Host "$($count)" -ForegroundColor Yellow -BackgroundColor Black -NoNewline; ` 
    Write-Host "]" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
    Write-Host " related monitoring objects."  -ForegroundColor Green -BackgroundColor Black 
    Write-Host "This might take a little while depending on how many hosted objects exist !"  -ForegroundColor Green -BackgroundColor Black 
    "" # Cheap formatting 
} 
Else{ 
    #This should never happen because of parameter validation 
    Write-Host "No input provided. Exiting..." 
    Cleanup 
    Exit 
} 
 
 
# If no OutputFileName exists, then simply use the DisplayName of the class instance. 
If (-not($OutputFileName)) { 
    $OutputFileName = "Merged_"+$TempName+".csv" 
} 
Else { 
    $tempIndex = $OutputFileName.LastIndexOf('.') 
    If ($tempIndex -gt 1) { $temp = $OutputFileName.Substring(0, $tempIndex) } 
    Else { $temp = $OutputFileName } 
    $OutputFileName = "Merged_"+$temp+".csv" 
} 
 
# If output directory already contains file, this will notify the user. You may not want the merge operation to include other/older/foreign CSV files. 
$existingFiles = Get-ChildItem -LiteralPath $TargetFolder 
If ($existingFiles){ 
    Write-Host "CAUTION: Files already exist in the output directory! You probably want to remove them first." -ForegroundColor Red -BackgroundColor Yellow 
    If (-not ($NoConfirm)){ Read-Host "Press any key to continue..."} 
 
} 
 
If (-not($NoConfirm)){  
    # Force user to acknowledge prompt. 
    While (-not($readin)){ 
        $readin = Read-Host -Prompt  "Continue? (y/n) `n" 
        Switch ($readin) 
        { 
            "y" {Write-Host "Proceed..." -BackgroundColor Black -ForegroundColor Cyan } 
            "n" {Write-Host "Exiting..." -BackgroundColor Yellow -ForegroundColor Red; Exit; } 
            Default {Write-Host "Must select 'y' to proceed or 'n' to exit." -BackgroundColor Yellow -ForegroundColor Red; $readin ="" ;} 
        } 
    } 
} 
 
# Iterators used for nicely formatted output. 
$o=1 
$i=1 
# Iterate through the objects (including hosted instances) and dig out all related configs for rules/monitors. 
$objects | % ` 
    { 
        $DN = (CleanName -uglyString $_.DisplayName) 
        $path = (Join-Path $TargetFolder "($( CleanName -uglyString $_.Path ))_$($DN).csv" ) 
        Export-SCOMEffectiveMonitoringConfiguration -Instance $_ -Path $path 
        Write-Host "$($i): " -ForegroundColor Cyan -NoNewline; ` 
        Write-Host "[" -ForegroundColor Red -NoNewline; ` 
        Write-Host "$($_.Path)" -ForegroundColor Yellow -NoNewline; ` 
        Write-Host "]" -ForegroundColor Red -NoNewline; ` 
        Write-Host " $($_.FullName)"  -ForegroundColor Green 
        $r=1   #for progress bar calculation below 
         
        $related = @($_.GetRelatedMonitoringObjects()) 
        Write-Verbose "There are $($related.Count) 'related' monitoring objects for $($_.DisplayName)." 
        $related | foreach ` 
        { 
            $percent = [math]::Round((($r / $related.Count) *100),0) 
            Write-Progress -Activity "** What's happening? **" -status "Getting your data. Be patient! [Percent: $($percent)]" -PercentComplete $percent 
            $DN = (($($_.DisplayName).Replace(':','_')).Replace('/','_')).Replace('\','_') 
            $path= (Join-Path $TargetFolder "($($_.Path))_$($DN).csv" ) 
            Export-SCOMEffectiveMonitoringConfiguration -Instance $_ -Path $path 
            Write-Host "$($i): " -ForegroundColor Cyan -NoNewline; ` 
            Write-Host "[" -ForegroundColor Red -NoNewline; ` 
            Write-Host "$($_.Path)" -ForegroundColor Yellow -NoNewline; ` 
            Write-Host "]" -ForegroundColor Red -NoNewline; ` 
            Write-Host " $($_.FullName)"  -ForegroundColor Green 
            $i++   # formatting, total line numbers 
            $r++   # this object's hosted items, for progress bar calculation above 
        } 
         
        #$o++ 
                      
    } 
     
$Enumeration_TimeSeconds = "{0:N4}" -$elapsed_enumeration.Elapsed.TotalSeconds 
$elapsed_merge = [System.Diagnostics.Stopwatch]::StartNew() 
 
#    ------ Merge Operation  ------ 
MergeFiles -strPath $TargetFolder -strOutputFileName $OutputFileName 
#    ------ Merge Operation  ------ 
 
$Merge_TimeSeconds = "{0:N4}" -$elapsed_merge.Elapsed.TotalSeconds 
Write-Host "Enumeration Duration: `t" -ForegroundColor Green -BackgroundColor Black -NoNewline; ` 
Write-Host "[" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
Write-Host "$($Enumeration_TimeSeconds)" -ForegroundColor Yellow -BackgroundColor Black -NoNewline; ` 
Write-Host "]" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
Write-Host " seconds."  -ForegroundColor Green -BackgroundColor Black 
 
Write-Host "Merge Duration: `t" -ForegroundColor Green -BackgroundColor Black -NoNewline; ` 
Write-Host "[" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
Write-Host "$($Merge_TimeSeconds)" -ForegroundColor Yellow -BackgroundColor Black -NoNewline; ` 
Write-Host "]" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
Write-Host " seconds."  -ForegroundColor Green -BackgroundColor Black 
 
Write-Host "Formatting output for Grid View. This might take a minute..."  -ForegroundColor Cyan -BackgroundColor Black 
$elapsed_makeobject = [System.Diagnostics.Stopwatch]::StartNew() 
[string]$strMergedFilePath = (Join-Path $TargetFolder $OutputFileName$objBlob = MakeObject -strMergedFilePath $strMergedFilePath 
$MakeObject_TimeSeconds = "{0:N4}" -$elapsed_makeobject.Elapsed.TotalSeconds 
 
Write-Host "Grid View Format Duration: " -ForegroundColor Green -BackgroundColor Black -NoNewline; ` 
Write-Host "[" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
Write-Host "$($MakeObject_TimeSeconds)" -ForegroundColor Yellow -BackgroundColor Black -NoNewline; ` 
Write-Host "]" -ForegroundColor Red -BackgroundColor Black -NoNewline; ` 
Write-Host " seconds."  -ForegroundColor Green -BackgroundColor Black 
 
If (-not($NoGridview)){ 
    $objBlob  | Out-Gridview -Title "Your Effective Configuration for $choice :"  
} 
 
Cleanup