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.
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.
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
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.
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.
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)}
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.
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.
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.
These GUI based tools for SCOM that have been around for years and you may well know them already.
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.
A couple more helpful tools include:
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/
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.
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.
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.
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.
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.
Stoyan also shared his favourite resources on MP authoring so you can deep dive into the topic.
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.
When it comes to writing management packs, Stoyan uses Visual Studio Code or Notepad++.
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!
<#
.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.***" }
}