Coffee Break: Deep dive into a SCOM Administrator’s toolbox

In this Coffee Break, Stoyan Chalakov, Microsoft MVP and SCOM consultant, joined Bruce Cullen, Director of Products at Cookdown, for a deep dive look at the free tools that he uses most when helping customers be successful with SCOM.

There are endless SCOM Admin tools and several were already covered in Stoyan’s SCOMathon session, so check that out if you can’t find one here that you want more information about. But for this Coffee Break, Stoyan has whittled down the list to those he uses most frequently. He revealed where to look for and exposed his full toolkit for the first time. Here’s are the tool categories Stoyan covered:

Let’s dive into his toolbox.  

PowerShell based Tools and Scripts

You can do almost anything with PowerShell and that can be applied within SCOM too. So here are some of the PowerShell scripts Stoyan uses regularly as a SCOM Admin.

SCOMHelper PowerShell Module

Labelled as “A SCOM Admin’s best friend” by The Monitoring Guys, this module has multiple functions. Check out The Monitoring Guys’ blog for lots of helpful information about this script.

Here is a quick rundown of some of its key functions.

New-SCOMClassGraph

This cmdlet lets you generate a graphical representation of a class in SCOM so you can see its taxonomy, properties, hosting and discovery relationships.

Start-SCOMOverrideTool

Deploy-SCOMAgent

This has a graphical user interface that lets you enter data, like a management group or server name, and conduct an agent deployment. Stoyan often uses this for automation purposes.

Export-SCOMEffectiveMonitoringConfigurationReport

This is a super helpful function that Stoyan gets asked about nearly every week. This lets you see which workflows, like rules, monitors, and discoveries, are applied to a particular server or group of servers.

Cleanup

Effective Monitoring Configuration Script

This script part of the SCOMHelper Module and it allows you to use the cmdlet from the module or it is also available as a script.

This script helps you answer: “What is monitored on my server?” and “Which are the applied monitoring settings?” You can run the script for a server group or an individual server and the CSV output will also show you the overrides and their values – an important piece of information so you know what you’ve inherited from a SCOM management group.

The neat CSV report allows you to filter based on set parameters for the information you need and present a clean report to management or customers.

See a demo of this script in action at 11:04 in the Coffee Break recording.

Reset alerted Monitors (with closed Alerts)

This is one for those using SCOM 2016 and earlier versions. It used to be possible to close alerts from monitors, even if the monitor was still unhealthy. Usually, this wouldn’t be a problem if you are the one doing the alert management. But if this is left to the end user, maybe your Active Directory Team, they may be closing alerts without considering whether it comes from a monitor or a rule. But if you close an alerts from a monitor, if it doesn’t change state, it will stay in the same state and not notify the user with an alert again.

There are plenty of blogs that offer solutions to this challenge, but Stoyan’s favourite is this Scomurr’s Blog post.

So, to fix this you need a script. Some don’t reset all the monitors from all the classes, but this one is tested thoroughly. You can use the ‘execute’ mode which directly gets all the monitors that have generated alerts, which have been closed by an operator rather than by the system.

Or you can use the ‘report’ function to report on the number of monitors and flag how big an issue is. This is helpful if you’re integrating SCOM with an ITSM tool or ticketing system.

Reset Single Monitor (multiple alerts)

This tool is a couple of PowerShell lines that you can use to reset a single monitor that has alerted several times. It helps you clear up all the refiring of alerts.  

This PowerShell script gets the DisplayName and also the targeted class and does a reset of all the alerts.  

#Get the monitor that needs to be reset by its DisplayName  

$Monitor = Get-SCOMMonitor | where 

#Get the monitor that needs to be reset by its DisplayName  

$MonitoringClass = Get-SCOMClass -DisplayName H {$_.DisplayName -eq 'Health Service Heartbeat Failure'} 

Health Service Watcher' 

#Get all the unhealthy instances of the targeted class and reset Monitor 

$MonitoringClass | Get-SCOMClassInstance |
where {$_.HealthState -eq 'Error'} |
foreach {$_.ResetMonitoringState($Monitor)} 

Force SCOM Discovery

If you want to force a SCOM discovery, perhaps when you’re doing some overriding, you can use this script to force one so you don’t have to wait for the full interval before the next discovery is scheduled. Often this is used when you want to disable a particular SQL instances or whole servers. All you need is the Display Name of the discovery.

Close Old Alerts from Rules

This small PowerShell script was created to close old alerts coming from rules in SCOM. Alerts from rules do not auto-close so you will have to manually close them. This script looks for those alerts and specifically also checks for the last modified date (and not the alert age). If it was not modified since the last X hours (you define X first in this script, else it is 96 hours by default) it will close those alerts for you and insert a comment. This can help to clean up some environments of a lot of old alerts which are still open. Or you could just schedule this script to run.

Check Gateway/Agent Certificate

This PowerShell script gathers all your agent certificates from your personal store and presents you with a view of whether they are meeting the criteria you need to get authentication.

This is helpful if you want to connect an agent or gateway that is not from the same domain or Kerberos realm. If you install your certificates and import them with the MomCertImport.exe but find that your system isn’t green, this certificate check can help you troubleshoot why. It highlights all the key usage, key spec, expiration date, etc. in green or red to show good or bad/missing data respectively.

With it visualized, you won’t forget what you need to check for.

GUI based Tools

These GUI based tools for SCOM that have been around for years and you may well know them already.  

MP Viewer (Current Version 2.3.3) 

Here are just a few of the great features of MP Viewer: 

This MP Viewer lets you export into a really sleek looking HTML format that you can share with a customer. It will show you all the descriptions, names of monitors within the MP, aggregate monitors, rules, and more.  

Override Explorer 

Override Creator 

Data Warehouse Grooming and Retention 

A couple more helpful tools include: 

DWDataRP (not exactly GUI based) 

This allows you to modify the data sets in your Data Warehouse and how long the data stays there. Many of the data sets have a default retention of around 400 days but if you don’t need data to be retained for that long, you will be paying for and using   unnecessary data warehouse capacity. With the DWDataRP tool, you just need to go through and change the retention period for each data set.  

Read more about it here: https://kevinholman.com/2010/01/05/understanding-and-modifying-data-warehouse-retention-and-grooming/ 

Data Warehouse Grooming Settings Tool 

Another, more graphical option that is GUI based, is the Data Warehouse Grooming Settings Tool. This lets you very quickly modify all the settings. This is the most convenient answer to the need to reset your data retention timeframes.  

Management Packs  

Back in the SCOMathon session, Stoyan covered a lot of management packs, but here is his list of MPs that he uses very frequently and are used in at least 90% of his environments.  

The BIG List #1 

SCOM Management – MP – Making a SCOM Admin’s life a little easier (Kevin Holman).

It adds useful discovered properties for your agents and adds a bunch of tasks to allow you to delegate common SCOM administration tasks to end users.

See Kevin’s blog on this one and you’ll realise why this is top of the list 

SQL MP Run As Accounts – NO LONGER REQUIRED – Run As Addendum MP (Kevin Holman) 

A key challenge in SQL monitoring is that the SCOM agent runs as Local System for the default agent action account but doesn’t have full rights to the SQL server. Moreover, this should never be given the SysAdmin role in SQL because the Local System account is easy to impersonate by anyone who already has admin rights to the operating system. This can be solved with Service Security Identifiers (SIDs). 

Community Catalog MP – One Management Pack to rule them all (Cookdown) 

This management pack is like an app store for SCOM, all you need an internet connection and it gives a clear overview of all the community management packs that are available.  

Cookdown’s Easy Tune – Set overrides en-mass & tune SCOM with no coding required (Cookdown) 

This MP lets you manage overwriting SCOM from CSV files, so you don’t need to understand in the ins and outs of SCOM. You can set up the tuning, send it to your domain experts, and check the tuning is correct.  

Cookdown’s PowerShell MP – Bye-bye VBScript. Hello PowerShell. (Cookdown) 

This lets you create PowerShell based monitors and write your own monitoring logic. 

Log File Monitoring MP – Supercharges the log file analytic capabilities for your Windows servers (NiCE) 

This is the tool that Stoyan uses to monitor all his log files. He said, “I think it’s the best log file monitoring solution that is out there.” 

Security Monitoring MP – SCOM as Security Monitoring Tools (Nathan Gau) 

This MP lets you do security monitoring in SCOM with helpful rules and monitors for different security aspects in your environment. You can find out more about using SCOM as a security tool in Nathan Gau’s SCOMathon session recording

The BIG List #2 

Ping Monitoring Management Pack – Free extension that verifies device connections (OpsLogix)  

This is what to use when you want to check availability of your systems with basic up and down statuses. 

PKI Certificate Validation MP – PKI Certificate and Certificate Revocation Lists Monitoring (Raphael Burri, Bob Cornelissen) 

This is the only MP that monitors the expiry data and the validity of your certificates in the stores of your agents.  

OpsMgr Self Maintenance Management Pack – useful tools for the busy SCOM admin to automate away common SCOM administration (Tao Yang, Cookdown) 

Check out the blog to find out what exactly this MP can do for you and your management group.

URL Genie: Bulk Website Monitoring Management Pack – An Easy, Powerful Solution for Bulk Website Monitoring (Tyson Paul, The Monitoring Guys) 

This bulk website monitoring MP monitors all your URLs and is intuitive to use. 

Management Pack Authoring 

If you are authoring management packs, you will be familiar with the SCOM Console. It lets you do SCOM GUI authoring the easy way. You can do process monitoring, URL monitoring, service monitoring and more through a neat template.  

Resources 

Stoyan also shared his favourite resources on MP authoring so you can deep dive into the topic. 

Visual Studio + VSAE 

Microsoft Visual Studio and its authoring extension VSAE is an add-in for Visual Studio which provides Lifecycle Management Tools to support Management Pack authoring. 

Notepad++/Visual Studio Code 

When it comes to writing management packs, Stoyan uses Visual Studio Code or Notepad++. 

Support Platforms  

If you’re looking for help, these are the very best forums to find answers. 

And if you’re looking for related support platforms and sources to get help, look here: 

So there you have it, the core toolbox of a SCOM Admin!

Scripts 

Effective Monitoring Configuration Script 

<#  
.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}" -f $elapsed_enumeration.Elapsed.TotalSeconds 

$elapsed_merge = [System.Diagnostics.Stopwatch]::StartNew() 

 

#    ------ Merge Operation  ------ 

MergeFiles -strPath $TargetFolder -strOutputFileName $OutputFileName 

#    ------ Merge Operation  ------ 

 

$Merge_TimeSeconds = "{0:N4}" -f $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}" -f $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 :"  

} 

 

Page Break
 

Check Gateway/Agent CertificateScript 

 

# OMv3CertCheck.ps1 

# 

# Original Publish Date 1/2009 

#    (Lincoln Atkinson?, https://blogs.technet.microsoft.com/momteam/author/latkin/ ) 

# 

# Update 2017.11.17 (Tyson Paul, https://blogs.msdn.microsoft.com/tysonpaul/ ) 

#    Fixed certificate SerialNumber parsing error.  

# 

# Update 2/2009 

#    Fixes for subjectname validation 

#    Typos 

#    Modification for CA chain validation 

#    Adds needed check for MachineKeyStore property on the private key 

# 

# Update 7/2009 

#    Fix for workgroup machine subjectname validation 

# 

 

# Consider all certificates in the Local Machine "Personal" store 

$certs = [Array] (dir cert:\LocalMachine\my\) 

 

write-host "Checking that there are certs in the Local Machine Personal store..." 

if ($certs -eq $null) 

{ 

    Write-Host "There are no certs in the Local Machine `"Personal`" store." 

    Write-Host "This is where the client authentication certificate should be imported." 

    Write-Host "Check if certificates were mistakenly imported to the Current User" 

    Write-Host "`"Personal`" store or the `"Operations Manager`" store." 

    exit 

} 

 

write-host "Verifying each cert..." 

foreach ($cert in $certs) 

{ 

    write-host "`nExamining cert - Serial number $($cert.SerialNumber)" 

    write-host "---------------------------------------------------" 

 

    $pass = $true 

       

    # Check subjectname 

           

    $pass = &{ 

        $fqdn = $env:ComputerName 

        $fqdn += "." + [DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name 

        trap [DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] 

        { 

            # Not part of a domain 

            continue; 

        } 

             

        $fqdnRegexPattern = "CN=" + $fqdn.Replace(".","\.") + '(,.*)?$' 

             

        if (!( $cert.SubjectName.Name -match $fqdnRegexPattern )) 

        { 

            Write-Host "Cert subjectname" -BackgroundColor Red -ForegroundColor Black 

            Write-Host "`tThe SubjectName of this cert does not match the FQDN of this machine." 

            Write-Host "`tActual - $($cert.SubjectName.Name)" 

            Write-Host "`tExpected (case insensitive)- CN=$fqdn" 

            $false 

        } else { $true; Write-Host "Cert subjectname" -BackgroundColor Green -ForegroundColor Black } 

    } 

       

    # Verify private key 

             

    if (!( $cert.HasPrivateKey )) 

    { 

        Write-Host "Private key" -BackgroundColor Red -ForegroundColor Black 

        Write-Host "`tThis certificate does not have a private key." 

        Write-Host "`tVerify that proper steps were taken when installing this cert." 

        $pass = $false 

    } elseif (!($cert.PrivateKey.CspKeyContainerInfo.MachineKeyStore)) 

    { 

        Write-Host "Private key" -BackgroundColor Red -ForegroundColor Black 

        Write-Host "`tThis certificate's private key is not issued to a machine account." 

        Write-Host "`tOne possible cause of this is that the certificate" 

        Write-Host "`twas issued to a user account rather than the machine," 

        Write-Host "`tthen copy/pasted from the Current User store to the Local" 

        Write-Host "`tMachine store.  A full export/import is required to switch" 

        Write-Host "`tbetween these stores." 

        $pass = $false 

    } 

    else { Write-Host "Private key" -BackgroundColor Green -ForegroundColor Black } 

 

    # Check expiration dates 

             

    if (($cert.NotBefore -gt [DateTime]::Now) -or ($cert.NotAfter -lt [DateTime]::Now)) 

    { 

        Write-Host "Expiration" -BackgroundColor Red -ForegroundColor Black 

        Write-Host "`tThis certificate is not currently valid." 

        Write-Host "`tIt will be valid between $($cert.NotBefore) and $($cert.NotAfter)" 

        $pass = $false 

    } else { Write-Host "Expiration" -BackgroundColor Green -ForegroundColor Black } 

       

       

    # Enhanced key usage extension 

             

    $enhancedKeyUsageExtension = $cert.Extensions |? {$_.ToString() -match "X509EnhancedKeyUsageExtension"} 

    if ($enhancedKeyUsageExtension -eq $null) 

    { 

        Write-Host "Enhanced Key Usage Extension" -BackgroundColor Red -ForegroundColor Black 

        Write-Host "`tNo enhanced key usage extension found.`n" 

        $pass = $false 

    } 

    else 

    { 

        $usages = $enhancedKeyUsageExtension.EnhancedKeyUsages 

        if ($usages -eq $null) 

        { 

            Write-Host "Enhanced Key Usage Extension" -BackgroundColor Red -ForegroundColor Black 

            Write-Host "`tNo enhanced key usages found.`n" 

            $pass = $false 

        } 

        else 

        { 

            $srvAuth = $cliAuth = $false 

            foreach ($usage in $usages) 

            { 

                if ($usage.Value -eq "1.3.6.1.5.5.7.3.1") { $srvAuth = $true} 

                if ($usage.Value -eq "1.3.6.1.5.5.7.3.2") { $cliAuth = $true} 

            } 

            if ((!$srvAuth) -or (!$cliAuth)) 

            { 

                Write-Host "Enhanced Key Usage Extension" -BackgroundColor Red -ForegroundColor Black 

                Write-Host "`tEnhanced key usage extension does not meet requirements." 

                Write-Host "`tRequired EKUs are 1.3.6.1.5.5.7.3.1 and 1.3.6.1.5.5.7.3.2" 

                Write-Host "`tEKUs found on this cert are:" 

                $usages |%{ Write-Host "`t$($_.Value)" } 

                $pass = $false 

            } 

            else { Write-Host "Enhanced Key Usage Extension" -BackgroundColor Green -ForegroundColor Black } 

        } 

    } 

       

    # KeyUsage extension 

       

    $keyUsageExtension = $cert.Extensions |? {$_.ToString() -match "X509KeyUsageExtension"} 

    if ($keyUsageExtension -eq $null) 

    { 

        Write-Host "Key Usage Extensions" -BackgroundColor Red -ForegroundColor Black 

        Write-Host "`tNo key usage extension found." 

        Write-Host "`tA KeyUsage extension matching 0xA0 (Digital Signature, Key Encipherment)" 

        Write-Host "`tor better is required." 

        $pass = $false 

    } 

    else 

    { 

        $usages = $keyUsageExtension.KeyUsages 

        if ($usages -eq $null) 

        { 

            Write-Host "Key Usage Extensions" -BackgroundColor Red -ForegroundColor Black 

            Write-Host "`tNo key usages found." 

            Write-Host "`tA KeyUsage extension matching 0xA0 (DigitalSignature, KeyEncipherment)" 

            Write-Host "`tor better is required." 

            $pass = $false 

        } 

        else 

        { 

            if (($usages.value__ -band 0xA0) -ne 0xA0) 

            { 

                Write-Host "Key Usage Extensions" -BackgroundColor Red -ForegroundColor Black 

                Write-Host "`tKey usage extension exists but does not meet requirements." 

                Write-Host "`tA KeyUsage extension matching 0xA0 (Digital Signature, Key Encipherment)" 

                Write-Host "`tor better is required." 

                Write-Host "`tKeyUsage found on this cert matches:" 

                Write-Host "`t$usages" 

                $pass = $false 

            } else { Write-Host "Key Usage Extensions" -BackgroundColor Green -ForegroundColor Black } 

        } 

    } 

       

    # KeySpec 

             

    $keySpec = $cert.PrivateKey.CspKeyContainerInfo.KeyNumber 

    if ($keySpec -eq $null) 

    { 

        Write-Host "KeySpec" -BackgroundColor Red -ForegroundColor Black 

        Write-Host "`tKeyspec not found.  A KeySpec of 1 is required" 

        $pass = $false 

    } 

    elseif ($keySpec.value__ -ne 1) 

    { 

        Write-Host "KeySpec" -BackgroundColor Red -ForegroundColor Black 

        Write-Host "`tKeyspec exists but does not meet requirements." 

        Write-Host "`tA KeySpec of 1 is required." 

        Write-Host "`tKeySpec for this cert: $($keySpec.value__)" 

        $pass = $false 

    } else {Write-Host "KeySpec" -BackgroundColor Green -ForegroundColor Black} 

       

       

    # Check that serial is written to proper reg 

             

    $certSerial = $cert.SerialNumber 

    $certSerialReversed = "" 

    -1..-19 |% {$certSerialReversed += $certSerial[2*$_] + $certSerial[2*$_ + 1]} 

   

    if (! (Test-Path "HKLM:\SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Machine Settings")) 

    { 

        Write-Host "Serial number written to registry" -BackgroundColor Red -ForegroundColor Black 

        Write-Host "`tThe cert serial number is not written to registry." 

        Write-Host "`tNeed to run MomCertImport.exe" 

        $pass = $false 

    } 

    else 

    { 

        $regKeys = get-itemproperty -path "HKLM:\SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Machine Settings" 

        if ($regKeys.ChannelCertificateSerialNumber -eq $null) 

        { 

            Write-Host "Serial number written to registry" -BackgroundColor Red -ForegroundColor Black 

            Write-Host "`tThe cert serial number is not written to registry." 

            Write-Host "`tNeed to run MomCertImport.exe" 

            $pass = $false 

        } 

        else 

        { 

            $regSerial = "" 

            $regKeys.ChannelCertificateSerialNumber |% {$regSerial += $_.ToString("X2")} 

                   

            if ($regSerial -ne $certSerialReversed) 

            { 

                Write-Host "Serial number written to registry" -BackgroundColor Red -ForegroundColor Black 

                Write-Host "`tThe serial number written to the registry does not match this certificate" 

                Write-Host "`tExpected registry entry: $certSerialReversed" 

                Write-Host "`tActual registry entry:   $regSerial" 

                $pass = $false 

            } else { Write-Host "Serial number written to registry" -BackgroundColor Green -ForegroundColor Black } 

        } 

    } 

 

 

    # Check that the cert's issuing CA is trusted (This is not technically required 

    # as it is the remote machine cert's CA that must be trusted.  Most users leverage 

    # the same CA for all machines, though, so it's worth checking 

 

    $chain = new-object Security.Cryptography.X509Certificates.X509Chain 

    $chain.ChainPolicy.RevocationMode = 0 

    if ($chain.Build($cert) -eq $false ) 

    { 

        Write-Host "Certification chain" -BackgroundColor Yellow -ForegroundColor Black 

        Write-Host "`tThe following error occurred building a certification chain with this cert:" 

        Write-Host "`t$($chain.ChainStatus[0].StatusInformation)" 

        write-host "`tThis is an error if the certificates on the remote machines are issued" 

        write-host "`tfrom this same CA - $($cert.Issuer)" 

        write-host "`tPlease ensure the certificates for the CAs which issued the certificates configured" 

        write-host "`ton the remote machines is installed to the Local Machine Trusted Root Authorities" 

        write-host "`tstore on this machine." 

    } 

    else 

    { 

        $rootCaCert = $chain.ChainElements | select -property Certificate -last 1 

        $localMachineRootCert = dir cert:\LocalMachine\Root |? {$_ -eq $rootCaCert.Certificate} 

        if ($localMachineRootCert -eq $null) 

        { 

            Write-Host "Certification chain" -BackgroundColor Yellow -ForegroundColor Black 

            Write-Host "`tThis certificate has a valid certification chain installed, but" 

            Write-Host "`ta root CA certificate verifying the issuer $($cert.Issuer)" 

            Write-Host "`twas not found in the Local Machine Trusted Root Authorities store." 

            Write-Host "`tMake sure the proper root CA certificate is installed there, and not in" 

            Write-Host "`tthe Current User Trusted Root Authorities store." 

        } 

        else 

        { 

            Write-Host "Certification chain" -BackgroundColor Green -ForegroundColor Black 

            Write-Host "`tThere is a valid certification chain installed for this cert," 

            Write-Host "`tbut the remote machines' certificates could potentially be issued from" 

            Write-Host "`tdifferent CAs.  Make sure the proper CA certificates are installed" 

            Write-Host "`tfor these CAs." 

        } 

 

    } 

 

 

    if ($pass) { Write-Host "`n***This certificate is properly configured and imported for Ops Manager use.***" } 

}