Public/Get-SCOMHealthCheckOpsConfig.ps1
|
Function Get-SCOMHealthCheckOpsConfig { <# .Synopsis .DESCRIPTION Tim Culham of www.culham.net wrote an awesome lightweight Daily Health Check Script which can be found here: http://www.culham.net/powershell/scom-2012-scom-2012-r2-daily-check-powershell-script-html-report/ I have been meaning to write something similiar for awhile so decided to take his wonderfully written script/structure and extend it by adding in a number of the more in depth Database Health Checks/KH Useful SQL Queries that I frequently find myself asking customers to run when they are experiencing performance issues with the Ops & DW DB's. I will cleanup my code in a later version and add additional functionality but for now Kudo's to Tim! MBullwin 11/3/2014 www.OpsConfig.com [ https://gallery.technet.microsoft.com/SCOM-Health-Check-fd2272ec ] As with all scripts I post, this is provided AS-IS without warrenty so please test first and use at your own risk. What this version of the script will give you:(Some of these are just features which are carried over from the original, many are added) 01. Version/Service Pack/Edition of SQL for each SCOM DB Server 02. Disk Space Info for Ops DB, DW DB, and associated Temp DB's 03. Database Backup Status for all DB's except Temp. 04. Top 25 Largest Tables for Ops DB and DW DB 05. Number of Events Generated Per Day (Ops DB) 06. Top 10 Event Generating Computers (Ops DB) 07. Top 25 Events by Publisher (Ops DB) 08. Number of Perf Insertions Per Day (Ops DB) 09. Top 25 Perf Insertions by Object/Counter Name (Ops DB) 10. Top 25 Alerts by Alert Count 11. Alerts with a Repeat Count higher than 200 12. Stale State Change Data 13. Top 25 Monitors Changing State in the last 7 Days 14. Top 25 Monitors Changing State By Object 15. Ops DB Grooming History 16. Snapshot of DW Staging Tables 17. DW Grooming Retention 18. Management Server checks (Works well on prem, seems to have some issues with gateways due to remote calls-if you see some errors flash by have no fear though I wouldn't necessarily trust the results coming back from a Gateway server in the report depending on firewall settings) 19. Daily KPI 20. MP's Modified in the Last 24 hours 21. Overrides in Default MP Check 22. Unintialized Agents 23. Agent Stats (Healthy, Warning, Critical, Unintialized, Total) 24. Agent Pending Management Summary 25. Alert Summary 26. Servers in Maintenance Mode For more details on this version checkout: www.OpsConfig.com .NOTES Author: MBullwin Blog: www.OpsConfig.com Version: v1.1 Date: 11/3/2014 Original Script: https://gallery.technet.microsoft.com/SCOM-Health-Check-fd2272ec Modifications by Tyson: 23020.08.11 - Added ManagementServer param. It's not fancy, but better than hardcoded localhost. #> Param ( [Parameter(Mandatory=$false, ValueFromPipeline=$false, Position=2, ParameterSetName='Parameter Set 1')] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [string]$ManagementServer='Localhost' ) ################################################################################################################### # # Tim Culham of www.culham.net wrote an awesome lightweight Daily Health Check Script # which can be found here: # # http://www.culham.net/powershell/scom-2012-scom-2012-r2-daily-check-powershell-script-html-report/ # # I have been meaning to write something similiar for awhile so decided to take his wonderfully written # script/structure and extend it by adding in a number of the more in depth Database Health Checks/KH Useful # SQL Queries that I frequently find myself asking customers to run when they are experiencing performance # issues with the Ops & DW DB's. # # I will cleanup my code in a later version and add additional functionality but for now Kudo's to Tim! # # # MBullwin 11/3/2014 # www.OpsConfig.com # # As with all scripts I post, this is provided AS-IS without warrenty so please test first and use at your own risk. ###################################################################################################################### $StartTime=Get-Date . Import-SCOMPowerShellModule # Connect to localhost when running on the management server or define a server to connect to. $connect = New-SCManagementGroupConnection -ComputerName $ManagementServer # The Name and Location of are we going to save this Report $CreateReportLocation = [System.IO.Directory]::CreateDirectory("c:\SCOM_Health_Check") $ReportLocation = "c:\SCOM_Health_Check" $ReportName = "$(get-date -format "yyyy-M-dd")-SCOM-HealthCheck.html" $ReportPath = "$ReportLocation\$ReportName" # Create header for HTML Report $Head = "<style>" $Head +="BODY{background-color:#CCCCCC;font-family:Calibri,sans-serif; font-size: small;}" $Head +="TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse; width: 98%;}" $Head +="TH{border-width: 1px;padding: 0px;border-style: solid;border-color: black;background-color:#293956;color:white;padding: 5px; font-weight: bold;text-align:left;}" $Head +="TD{border-width: 1px;padding: 0px;border-style: solid;border-color: black;background-color:#F0F0F0; padding: 2px;}" $Head +="</style>" # Retrieve the name of the Operational Database and Data WareHouse Servers from the registry. $reg = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Setup\" $OperationsManagerDBServer = $reg.DatabaseServerName $OperationsManagerDWServer = $reg.DataWarehouseDBServerName # If the value is empty in this key, then we'll use the Get-SCOMDataWarehouseSetting cmdlet. If (!($OperationsManagerDWServer)) {$OperationsManagerDWServer = Get-SCOMDataWarehouseSetting | Select-Object -expandProperty DataWarehouseServerName} $OperationsManagerDBServer = $OperationsManagerDBServer.ToUpper() $OperationsManagerDWServer = $OperationsManagerDWServer.ToUpper() $ReportingURL = Get-SCOMReportingSetting | Select-Object -ExpandProperty ReportingServerUrl $WebConsoleURL = Get-SCOMWebAddressSetting | Select-Object -ExpandProperty WebConsoleUrl <# # The number of days before Database Grooming # These are my settings, I use this to determine if someone has changed something # Feel free to comment this part out if you aren't interested $AlertDaysToKeep = 2 $AvailabilityHistoryDaysToKeep = 2 $EventDaysToKeep = 1 $JobStatusDaysToKeep = 1 $MaintenanceModeHistoryDaysToKeep = 2 $MonitoringJobDaysToKeep = 2 $PerformanceDataDaysToKeep = 2 $StateChangeEventDaysToKeep = 2 # SCOM Agent Heartbeat Settings $AgentHeartbeatInterval = 180 $MissingHeartbeatThreshold = 3 #> # SQL Server Function to query the Operational Database Server function Run-OpDBSQLQuery { Param($sqlquery) $SqlConnection = New-Object System.Data.SqlClient.SqlConnection $SqlConnection.ConnectionString = "Server=$OperationsManagerDBServer;Database=OperationsManager;Integrated Security=True" $SqlCmd = New-Object System.Data.SqlClient.SqlCommand $SqlCmd.CommandText = $sqlquery $SqlCmd.Connection = $SqlConnection $SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter $SqlAdapter.SelectCommand = $SqlCmd $DataSet = New-Object System.Data.DataSet $SqlAdapter.Fill($DataSet) | Out-Null $SqlConnection.Close() $DataSet.Tables[0] } # SQL Server Function to query the Data Warehouse Database Server function Run-OpDWSQLQuery { Param($sqlquery) $SqlConnection = New-Object System.Data.SqlClient.SqlConnection $SqlConnection.ConnectionString = "Server=$OperationsManagerDWServer;Database=OperationsManagerDW;Integrated Security=True" $SqlCmd = New-Object System.Data.SqlClient.SqlCommand $SqlCmd.CommandText = $sqlquery $SqlCmd.Connection = $SqlConnection $SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter $SqlAdapter.SelectCommand = $SqlCmd $DataSet = New-Object System.Data.DataSet $SqlAdapter.Fill($DataSet) | Out-Null $SqlConnection.Close() $DataSet.Tables[0] } # Retrieve the Data for the Majority of the Report # Truth is we probably don't need all of this data, but even on a busy environment it only takes a couple of mins to run. Write-Host "Retrieving Agents" $Agents = Get-SCOMAgent Write-Host "Retrieving Alerts" $Alerts = Get-SCOMAlert Write-Host "Retrieving Groups" $Groups = Get-SCOMGroup Write-Host "Retrieving Management Group" $ManagementGroup = Get-SCOMManagementGroup Write-Host "Retrieving Management Packs" $ManagementPacks = Get-SCManagementPack Write-Host "Retrieving Management Servers" $ManagementServers = Get-SCOMManagementServer Write-Host "Retrieving Monitors" $Monitors = Get-SCOMMonitor Write-Host "Retrieving Rules" $Rules = Get-SCOMRule # Check to see if the Reporting Server Site is OK $ReportingServerSite = New-Object System.Net.WebClient $ReportingServerSite = [net.WebRequest]::Create($ReportingURL) $ReportingServerSite.UseDefaultCredentials = $true $ReportingServerStatus = $ReportingServerSite.GetResponse() | Select-Object -expandProperty statusCode # This code can convert the "OK" Result to an Integer, like 200 # (($web.GetResponse()).Statuscode) -as [int] # Check to see if the Web Server Site is OK $WebConsoleSite = New-Object System.Net.WebClient $WebConsoleSite = [net.WebRequest]::Create($WebConsoleURL) $WebConsoleSite.UseDefaultCredentials = $true $WebConsoleStatus = $WebConsoleSite.GetResponse() | Select-Object -expandProperty statusCode # SQL Server Function to query Size of the Database Server $DatabaseSize = @" select a.FILEID, [FILE_SIZE_MB]=convert(decimal(12,2),round(a.size/128.000,2)), [SPACE_USED_MB]=convert(decimal(12,2),round(fileproperty(a.name,'SpaceUsed')/128.000,2)), [FREE_SPACE_MB]=convert(decimal(12,2),round((a.size-fileproperty(a.name,'SpaceUsed'))/128.000,2)) , [GROWTH_MB]=convert(decimal(12,2),round(a.growth/128.000,2)), NAME=left(a.NAME,15), FILENAME=left(a.FILENAME,60) from dbo.sysfiles a "@ #SQL Server Function to query Size of the TempDB $TempDBSize =@" USE tempdb select a.FILEID, [FILE_SIZE_MB]=convert(decimal(12,2),round(a.size/128.000,2)), [SPACE_USED_MB]=convert(decimal(12,2),round(fileproperty(a.name,'SpaceUsed')/128.000,2)), [FREE_SPACE_MB]=convert(decimal(12,2),round((a.size-fileproperty(a.name,'SpaceUsed'))/128.000,2)) , [GROWTH_MB]=convert(decimal(12,2),round(a.growth/128.000,2)), NAME=left(a.NAME,15), FILENAME=left(a.FILENAME,60) from dbo.sysfiles a "@ #SQL Server Function to query the version of SQL $SQLVersion =@" SELECT SERVERPROPERTY('productversion') AS "Product Version", SERVERPROPERTY('productlevel') AS "Service Pack", SERVERPROPERTY ('edition') AS "Edition" "@ # Run the Size Query against the Operational Database and Data Warehouse Database Servers $OPDBSize = Run-OpDBSQLQuery $DatabaseSize $DWDBSize = Run-OpDWSQLQuery $DatabaseSize $OPTPSize = Run-OpDBSQLQuery $TempDBSize $DWTPSize = Run-OpDWSQLQuery $TempDBSize $OPSQLVER = Run-OpDBSQLQuery $SQLVersion $DWSQLVER = Run-OpDWSQLQuery $SQLVersion # Insert the Database Server details into the Report $ReportOutput += "<h2>Database Servers</h2>" $ReportOutput += "<p>Operational Database Server : $OperationsManagerDBServer</p>" $ReportOutput += $OPSQLVER | Select-Object "Product Version", "Service Pack", Edition | ConvertTo-Html -Fragment $ReportOutput += "<p>Data Warehouse Database Server : $OperationsManagerDWServer</p>" $ReportOutput += $DWSQLVER | Select-Object "Product Version", "Service Pack", Edition | ConvertTo-Html -Fragment # Insert the Size Results for the Operational Database into the Report $ReportOutput += "<h3>$OperationsManagerDBServer Operations Manager DB</h4>" $ReportOutput += $OPDBSize | Select-Object Name, FILE_SIZE_MB, SPACE_USED_MB, FREE_SPACE_MB, FILENAME | ConvertTo-HTML -fragment $ReportOutput += "<br></br>" $ReportOutput += "<h3>Operations Temp DB</h4>" $ReportOutput += $OPTPSize | Select-Object Name, FILE_SIZE_MB, SPACE_USED_MB, FREE_SPACE_MB, FILENAME | ConvertTo-HTML -fragment # Insert the Size Results for the Data Warehouse Database and TempDB into the Report $ReportOutput += "<br>" $ReportOutput += "<h3>$OperationsManagerDWServer Data Warehouse DB</h4>" $ReportOutput += $DWDBSize | Select-Object Name, FILE_SIZE_MB, SPACE_USED_MB, FREE_SPACE_MB, FILENAME | ConvertTo-HTML -fragment $ReportOutput += "<br></br>" $ReportOutput += "<h3>Data Warehouse Temp DB</h4>" $ReportOutput += $DWTPSize | Select-Object Name, FILE_SIZE_MB, SPACE_USED_MB, FREE_SPACE_MB, FILENAME | ConvertTo-HTML -fragment # SQL Query to find out how many State Changes there were yesterday $StateChangesYesterday = @" -- How Many State Changes Yesterday?: select count(*) from StateChangeEvent where cast(TimeGenerated as date) = cast(getdate()-1 as date) "@ $StateChanges = Run-OpDBSQLQuery $StateChangesYesterday | Select-Object -ExpandProperty Column1 | Out-String $AllStats = @() $StatSummary = New-Object psobject $StatSummary | Add-Member noteproperty "Open Alerts" (($Alerts | Where-Object {$_.ResolutionState -ne 255}).count) $StatSummary | Add-Member noteproperty "Groups" ($Groups.Count) $StatSummary | Add-Member noteproperty "Monitors" ($Monitors.Count) $StatSummary | Add-Member noteproperty "Rules" ($Rules.Count) $StatSummary | Add-Member noteproperty "State Changes Yesterday" ($StateChanges | ForEach-Object {$_.TrimStart()} | ForEach-Object {$_.TrimEnd()}) $AllStats += $StatSummary #SQL Query Top 10 Event generating computers $TopEventGeneratingComputers = @" SELECT top 10 LoggingComputer, COUNT(*) AS TotalEvents FROM EventallView GROUP BY LoggingComputer ORDER BY TotalEvents DESC "@ #SQL Query number of Events Generated per day $NumberOfEventsPerDay = @" SELECT CASE WHEN(GROUPING(CONVERT(VARCHAR(20), TimeAdded, 101)) = 1) THEN 'All Days' ELSE CONVERT(VARCHAR(20), TimeAdded, 101) END AS DayAdded, COUNT(*) AS NumEventsPerDay FROM EventAllView GROUP BY CONVERT(VARCHAR(20), TimeAdded, 101) WITH ROLLUP ORDER BY DayAdded DESC "@ #SQL Query Most Common Events by Publishername $MostCommonEventsByPub = @" SELECT top 25 Number AS "Event Number", Publishername, COUNT(*) AS TotalEvents FROM EventAllView GROUP BY Number, Publishername ORDER BY TotalEvents DESC "@ #SQL Query the Number of Performance Insertions per Day $NumberofPerInsertsPerDay = @" SELECT CASE WHEN(GROUPING(CONVERT(VARCHAR(20), TimeSampled, 101)) = 1) THEN 'All Days' ELSE CONVERT(VARCHAR(20), TimeSampled, 101) END AS DaySampled, COUNT(*) AS NumPerfPerDay FROM PerformanceDataAllView GROUP BY CONVERT(VARCHAR(20), TimeSampled, 101) WITH ROLLUP ORDER BY DaySampled DESC "@ #SQL Query the Most common perf insertions by perf counter name $MostCommonPerfByN = @" select top 25 pcv.objectname, pcv.countername, count (pcv.countername) as total from performancedataallview as pdv, performancecounterview as pcv where (pdv.performancesourceinternalid = pcv.performancesourceinternalid) group by pcv.objectname, pcv.countername order by count (pcv.countername) desc "@ #SQL Query the Top 25 Alerts by Alert Count $MostCommonAByAC = @" SELECT Top 25 AlertStringName, Name, SUM(1) AS AlertCount, SUM(RepeatCount+1) AS AlertCountWithRepeatCount FROM Alertview WITH (NOLOCK) GROUP BY AlertStringName, Name ORDER BY AlertCount DESC "@ #SQL Query for Stale State Change Data $StaleStateChangeData = @" declare @statedaystokeep INT SELECT @statedaystokeep = DaysToKeep from PartitionAndGroomingSettings WHERE ObjectName = 'StateChangeEvent' SELECT COUNT(*) as 'Total StateChanges', count(CASE WHEN sce.TimeGenerated > dateadd(dd,-@statedaystokeep,getutcdate()) THEN sce.TimeGenerated ELSE NULL END) as 'within grooming retention', count(CASE WHEN sce.TimeGenerated < dateadd(dd,-@statedaystokeep,getutcdate()) THEN sce.TimeGenerated ELSE NULL END) as '> grooming retention', count(CASE WHEN sce.TimeGenerated < dateadd(dd,-30,getutcdate()) THEN sce.TimeGenerated ELSE NULL END) as '> 30 days', count(CASE WHEN sce.TimeGenerated < dateadd(dd,-90,getutcdate()) THEN sce.TimeGenerated ELSE NULL END) as '> 90 days', count(CASE WHEN sce.TimeGenerated < dateadd(dd,-365,getutcdate()) THEN sce.TimeGenerated ELSE NULL END) as '> 365 days' from StateChangeEvent sce "@ #SQL Query Noisest monitors changing state in the past 7 days $NoisyMonitorData = @" select distinct top 25 m.DisplayName as MonitorDisplayName, m.Name as MonitorIdName, mt.typename AS TargetClass, count(sce.StateId) as NumStateChanges from StateChangeEvent sce with (nolock) join state s with (nolock) on sce.StateId = s.StateId join monitorview m with (nolock) on s.MonitorId = m.Id join managedtype mt with (nolock) on m.TargetMonitoringClassId = mt.ManagedTypeId where m.IsUnitMonitor = 1 -- Scoped to within last 7 days AND sce.TimeGenerated > dateadd(dd,-7,getutcdate()) group by m.DisplayName, m.Name,mt.typename order by NumStateChanges desc "@ #SQL Query Top 25 Monitors changing state by Object $NoisyMonitorByObject =@" select distinct top 25 bme.DisplayName AS ObjectName, bme.Path, m.DisplayName as MonitorDisplayName, m.Name as MonitorIdName, mt.typename AS TargetClass, count(sce.StateId) as NumStateChanges from StateChangeEvent sce with (nolock) join state s with (nolock) on sce.StateId = s.StateId join BaseManagedEntity bme with (nolock) on s.BasemanagedEntityId = bme.BasemanagedEntityId join MonitorView m with (nolock) on s.MonitorId = m.Id join managedtype mt with (nolock) on m.TargetMonitoringClassId = mt.ManagedTypeId where m.IsUnitMonitor = 1 -- Scoped to specific Monitor (remove the "--" below): -- AND m.MonitorName like ('%HealthService%') -- Scoped to specific Computer (remove the "--" below): -- AND bme.Path like ('%sql%') -- Scoped to within last 7 days AND sce.TimeGenerated > dateadd(dd,-7,getutcdate()) group by s.BasemanagedEntityId,bme.DisplayName,bme.Path,m.DisplayName,m.Name,mt.typename order by NumStateChanges desc "@ #SQL Query Grooming Settings for the Operational Database $OpsDBGrooming =@" SELECT ObjectName, GroomingSproc, DaysToKeep, GroomingRunTime, DataGroomedMaxTime FROM PartitionAndGroomingSettings WITH (NOLOCK) "@ #SQL Query DW DB Staging Tables $DWDBStagingTables = @" select count(*) AS "Alert Staging Table" from Alert.AlertStage "@ $DWDBStagingTablesEvent =@" select count (*) AS "Event Staging Table" from Event.eventstage "@ $DWDBStagingTablesPerf =@" select count (*) AS "Perf Staging Table" from Perf.PerformanceStage "@ $DWDBStagingTablesState =@" select count (*) AS "State Staging Table" from state.statestage "@ #SQL Query DW Grooming Retention $DWDBGroomingRetention =@" select ds.datasetDefaultName AS 'Dataset Name', sda.AggregationTypeId AS 'Agg Type 0=raw, 20=Hourly, 30=Daily', sda.MaxDataAgeDays AS 'Retention Time in Days' from dataset ds, StandardDatasetAggregation sda WHERE ds.datasetid = sda.datasetid ORDER by "Retention Time in Days" desc "@ #SQL function to Query the Top 25 largest tables in a database $DWDBLargestTables =@" SELECT TOP 25 a2.name AS [tablename], (a1.reserved + ISNULL(a4.reserved,0))* 8 AS reserved, a1.rows as row_count, a1.data * 8 AS data, (CASE WHEN (a1.used + ISNULL(a4.used,0)) > a1.data THEN (a1.used + ISNULL(a4.used,0)) - a1.data ELSE 0 END) * 8 AS index_size, (CASE WHEN (a1.reserved + ISNULL(a4.reserved,0)) > a1.used THEN (a1.reserved + ISNULL(a4.reserved,0)) - a1.used ELSE 0 END) * 8 AS unused, (row_number() over(order by (a1.reserved + ISNULL(a4.reserved,0)) desc))%2 as l1, a3.name AS [schemaname] FROM (SELECT ps.object_id, SUM (CASE WHEN (ps.index_id < 2) THEN row_count ELSE 0 END) AS [rows], SUM (ps.reserved_page_count) AS reserved, SUM (CASE WHEN (ps.index_id < 2) THEN (ps.in_row_data_page_count + ps.lob_used_page_count + ps.row_overflow_used_page_count) ELSE (ps.lob_used_page_count + ps.row_overflow_used_page_count) END ) AS data, SUM (ps.used_page_count) AS used FROM sys.dm_db_partition_stats ps GROUP BY ps.object_id) AS a1 LEFT OUTER JOIN (SELECT it.parent_id, SUM(ps.reserved_page_count) AS reserved, SUM(ps.used_page_count) AS used FROM sys.dm_db_partition_stats ps INNER JOIN sys.internal_tables it ON (it.object_id = ps.object_id) WHERE it.internal_type IN (202,204) GROUP BY it.parent_id) AS a4 ON (a4.parent_id = a1.object_id) INNER JOIN sys.all_objects a2 ON ( a1.object_id = a2.object_id ) INNER JOIN sys.schemas a3 ON (a2.schema_id = a3.schema_id) WHERE a2.type <> N'S' and a2.type <> N'IT' "@ #SQL Function to query and check backup status of SQL Databases $SQLBackupStatus =@" SELECT d.name, DATEDIFF(Day, COALESCE(MAX(b.backup_finish_date), d.create_date), GETDATE()) AS [DaysSinceBackup] FROM sys.databases d LEFT OUTER JOIN msdb.dbo.backupset b ON d.name = b.database_name WHERE d.is_in_standby = 0 AND source_database_id is null AND d.name NOT LIKE 'tempdb' AND (b.[type] IN ('D', 'I') OR b.[type] IS NULL) GROUP BY d.name, d.create_date "@ # Run additional SQL Queries against the Operational Database $OPTOPALERT = Run-OpDBSQLQuery $TopEventGeneratingComputers $OPNUMEPERDAY = Run-OpDBSQLQuery $NumberOfEventsPerDay $OPMOSTCOMEVENT = Run-OpDBSQLQuery $MostCommonEventsByPub $OPPERFIPERD = Run-OpDBSQLQuery $NumberofPerInsertsPerDay $OPPERFIBYN = Run-OpDBSQLQuery $MostCommonPerfByN $OPTOPALERTC = Run-OpDBSQLQuery $MostCommonAByAC $OPSTALESTD = Run-OpDBSQLQuery $StaleStateChangeData $OPNOISYMON = Run-OpDBSQLQuery $NoisyMonitorData $OPNOISYMONOBJ = Run-OpDBSQLQuery $NoisyMonitorByObject $OPDBGROOM = Run-OpDBSQLQuery $OpsDBGrooming $OPLARGTAB = Run-OpDBSQLQuery $DWDBLargestTables $OPDBBACKUP = Run-OpDBSQLQuery $SQLBackupStatus #Run additional SQL Queries against the DW DB $DWDBSGTB = Run-OpDWSQLQuery $DWDBStagingTables $DWDBSGTBEV = Run-OpDWSQLQuery $DWDBStagingTablesEvent $DWDBSGTBPE = Run-OpDWSQLQuery $DWDBStagingTablesPerf $DWDBSGTBST = Run-OpDWSQLQuery $DWDBStagingTablesState $DWDBGRET = Run-OpDWSQLQuery $DWDBGroomingRetention $DWDBLARGETAB = Run-OpDWSQLQuery $DWDBLargestTables $DWDBBACKUP = Run-OpDWSQLQuery $SQLBackupStatus #Output to HTML Report $ReportOutput += "<h2>Operational Database Health</h2>" $ReportOutput += "<h3>Operations Database Backup Status</h3>" $ReportOutput += $OPDBBACKUP | Select-Object name, DaysSinceBackup | ConvertTo-HTML -Fragment $ReportOutput += "<h3>Operations Database Top 25 Largest Tables</h3>" $ReportOutput += $OPLARGTAB | Select-Object tablename, reserved, row_count, data, index_size, unused |ConvertTo-Html -Fragment $ReportOutput += "<h3>Number of Events Generated Per Day</h3>" $ReportOutput += $OPNUMEPERDAY | Select-Object NumEventsPerDay, DayAdded | ConvertTo-HTML -Fragment $ReportOutput += "<h3>Top 10 Event Generating Computers</h3>" $ReportOutput += $OPTOPALERT | Select-Object LoggingComputer, TotalEvents | ConvertTo-HTML -Fragment $ReportOutput += "<h3>Top 25 Events By Publisher</h3>" $ReportOutput += $OPMOSTCOMEVENT | Select-Object "Event Number", Publishername, TotalEvents | ConvertTo-Html -Fragment $ReportOutput += "<h3>Number of Perf Insertions Per Day</h3>" $ReportOutput += $OPPERFIPERD | Select-Object DaySampled, NumPerfPerDay | ConvertTo-Html -Fragment $ReportOutput += "<h3>Top 25 Perf Insertions by Object/Counter Name</h3>" $ReportOutput += $OPPERFIBYN | Select-Object objectname, countername, total | ConvertTo-Html -Fragment $ReportOutput += "<h3>Top 25 Alerts by Alert Count</h3>" $ReportOutput += $OPTOPALERTC | Select-Object AlertStringName, Name, AlertCount, AlertCountWithRepeatCount | ConvertTo-Html -Fragment # Get the alerts with a repeat count higher than the variable $RepeatCount $RepeatCount = 200 $ReportOutput += "<br>" $ReportOutput += "<h3>Alerts with a Repeat Count higher than $RepeatCount</h3>" # Produce a table of all Open Alerts above the repeatcount and add it to the Report $ReportOutput += $Alerts | Where-Object {$_.RepeatCount -ge $RepeatCount -and $_.ResolutionState -ne 255} | Select-Object Name, Category, NetBIOSComputerName, RepeatCount | Sort-Object repeatcount -desc | ConvertTo-HTML -fragment #Output to HTML report $ReportOutput += "<h3>Stale State Change Data</h3>" $ReportOutput += $OPSTALESTD | Select-Object "Total StateChanges", "within grooming retention", "> grooming retention","> 30 days","> 90 days","> 365 days"| ConvertTo-Html -Fragment $ReportOutput += "<h3>Top 25 Monitors Changing State in the last 7 Days</h3>" $ReportOutput += $OPNOISYMON | Select-Object MonitorDisplayName, MonitorIdName, TargetClass, NumStateChanges | ConvertTo-Html -Fragment $ReportOutput += "<h3>Top 25 Monitors Changing State By Object</h3>" $ReportOutput += $OPNOISYMONOBJ | Select-Object ObjectName, Path, MonitorDisplayName, MonitorIdName,TargetClass, NumStateChanges | ConvertTo-Html -Fragment $ReportOutput += "<h3>Operations Database Grooming History</h3>" $ReportOutput += $OPDBGROOM | Select-Object ObjectName, GroomingSproc, DaysToKeep, GroomingRunTime,DataGroomedMaxTime | ConvertTo-HTML -Fragment # SQL Query to find out what Grooming Jobs have run in the last 24 hours $DidGroomingRun = @" -- Did Grooming Run?: SELECT [InternalJobHistoryId] ,[TimeStarted] ,[TimeFinished] ,[StatusCode] ,[Command] ,[Comment] FROM [dbo].[InternalJobHistory] WHERE [TimeStarted] >= DATEADD(day, -2, GETDATE()) Order by [TimeStarted] "@ # Produce a table of Grooming History and add it to the Report $ReportOutput += "<h3>Grooming History From The Past 48 Hours</h3>" $ReportOutput += Run-OpDBSQLQuery $DidGroomingRun InternalJobHistoryId, TimeStarted, TimeFinished, StatusCode, Command, Comment | Select-Object | ConvertTo-HTML -fragment #Produce Table of DW DB Health $ReportOutput +="<h2>Data Warehouse Database Health</h2>" $ReportOutput +="<h3>Data Warehouse DB Backup Status</h3>" $ReportOutput +=$DWDBBACKUP | Select-Object name, DaysSinceBackup | ConvertTo-Html -Fragment $ReportOutput +="<h3>Data Warehouse Top 25 Largest Tables</h3>" $ReportOutput +=$DWDBLARGETAB | Select-Object tablename, reserved, row_count, data, index_size, unused |ConvertTo-Html -fragment $ReportOutput +="<h3>Data Warehouse Staging Tables</h3>" $ReportOutput +=$DWDBSGTB | Select-Object "Alert Staging Table", Table | ConvertTo-Html -Fragment $ReportOutput +=$DWDBSGTBEV | Select-Object "Event Staging Table", Table | ConvertTo-Html -Fragment $ReportOutput +=$DWDBSGTBPE | Select-Object "Perf Staging Table", Table | ConvertTo-Html -Fragment $ReportOutput +=$DWDBSGTBST | Select-Object "State Staging Table", Table| ConvertTo-Html -Fragment $ReportOutput +="<h3>Data Warhouse Grooming Retention</h3>" $ReportOutput +=$DWDBGRET | Select-Object "Dataset Name", "Agg Type 0=raw, 20=Hourly, 30=Daily","Retention Time in Days"| ConvertTo-Html -Fragment # Insert the Results for the Number of Management Servers into the Report $ReportOutput += "<p>Number of Management Servers : $($ManagementServers.count)</p>" # Retrieve the data for the Management Servers $ReportOutput += "<br>" $ReportOutput += "<h2>Management Servers</h2>" $AllManagementServers = @() ForEach ($ManagementServer in $ManagementServers) { # Find out the Server Uptime for each of the Management Servers #Original query referenced -computer $ManagementServer.Name this was an error I modified to .Displayname to fix #TPaul: updated this to use CIM instead of WMI $lastboottime = (Get-CimInstance -ClassName win32_operatingsystem ).LastBootUpTime $sysuptime =New-TimeSpan $lastboottime (Get-Date) $totaluptime = "" + $sysuptime.days + " days " + $sysuptime.hours + " hours " + $sysuptime.minutes + " minutes " + $sysuptime.seconds + " seconds" # Find out the Number of WorkFlows Running on each of the Management Servers $perfWorkflows = Get-Counter -ComputerName $ManagementServer.DisplayName -Counter "\Health Service\Workflow Count" -SampleInterval 1 -MaxSamples 1 # The Performance Counter seems to return a lot of other characters and spaces...I only want the number of workflows, let's dump the rest [int]$totalWorkflows = $perfWorkflows.readings.Split(':')[-1] | ForEach-Object {$_.TrimStart()} | ForEach-Object {$_.TrimEnd()} $ManagementServerProperty = New-Object psobject $ManagementServerProperty | Add-Member noteproperty "Management Server" ($ManagementServer.DisplayName) $ManagementServerProperty | Add-Member noteproperty "Health State" ($ManagementServer.HealthState) $ManagementServerProperty | Add-Member noteproperty "Version" ($ManagementServer.Version) $ManagementServerProperty | Add-Member noteproperty "Action Account" ($ManagementServer.ActionAccountIdentity) $ManagementServerProperty | Add-Member noteproperty "System Uptime" ($totaluptime) $ManagementServerProperty | Add-Member noteproperty "Workflows" ($totalWorkflows) $AllManagementServers += $ManagementServerProperty } # Insert the Results for the Management Servers into the Report $ReportOutput += $AllManagementServers | Select-Object "Management Server", "Health State", "Version", "Action Account", "System Uptime", "Workflows" | Sort-Object "Management Server" | ConvertTo-HTML -fragment # Insert the Results for the Stats and State Changes into the Report $ReportOutput += "<br>" $ReportOutput += "<h2>Daily KPI</h2>" $ReportOutput += $AllStats | Select-Object "Open Alerts", "Groups", "Monitors", "Rules", "State Changes Yesterday" | ConvertTo-HTML -fragment # Retrieve and Insert the Results for the Management Packs that have been modified into the Report Write-Host "Checking for Management Packs that have been modified in the last 24 hours" $ReportOutput += "<br>" $ReportOutput += "<h2>Management Packs Modified in the Last 24 Hours</h2>" If (!($ManagementPacks | Where-Object {$_.LastModified -ge (Get-Date).addhours(-24)})) { $ReportOutput += "<p>No Management Packs have been updated in the last 24 hours</p>" } Else { $ReportOutput += $ManagementPacks | Where-Object {$_.LastModified -ge (Get-Date).addhours(-24)} | Select-Object Name, LastModified | ConvertTo-HTML -fragment } # Retrieve and Insert the Results for the Default Management Pack into the Report # This 'should be empty'...don't store stuff here! Write-Host "Checking for the Default Management Pack for Overrides" $ReportOutput += "<br>" $ReportOutput += "<h2>The Default Management Pack</h2>" # Excluding these 2 ID's as they are part of the default MP for DefaultUser and Language Code Overrides $excludedID = "5a67584f-6f63-99fc-0d7a-55587d47619d", "e358a914-c851-efaf-dda9-6ca5ef1b3eb7" $defaultMP = $ManagementPacks | Where-Object {$_.Name -match "Microsoft.SystemCenter.OperationsManager.DefaultUser"} ##Changed below line for compat with PowerShell 2.0 ##If (!($defaultMP.GetOverrides() | ? {$_.ID -notin $excludedID})) If (!($defaultMP.GetOverrides() | Where-Object {$excludedID -NotContains $_.ID})) { $ReportOutput += "<p>There are no Overrides being Stored in the Default Management Pack</p>" } Else { ##Changed below line for compat with PowerShell 2.0 #$foundOverride = Get-SCOMClassInstance -id ($defaultMP.GetOverrides() | ? {$_.ID -notin $excludedID -AND $_.ContextInstance -ne $guid} | select -expandproperty ContextInstance -ea SilentlyContinue) $foundOverride = Get-SCOMClassInstance -id ($defaultMP.GetOverrides() | Where-Object {$excludedID -NotContains $_.ID -AND $_.ContextInstance -ne $guid} | Select-Object -expandproperty ContextInstance -ea SilentlyContinue) $ReportOutput += "<p>Found overrides against the following targets: $foundOverride.Displayname</p>" ##PowerShell 2.0 Compat ##$ReportOutput += $($defaultMP.GetOverrides() | ? {$_.ID -notin $excludedID} | Select Name, Property, Value, LastModified, TimeAdded) | ConvertTo-HTML -fragment $ReportOutput += $($defaultMP.GetOverrides() | Where-Object {$excludedID -NotContains $_.ID} | Select-Object -Property Name, Property, Value, LastModified, TimeAdded) | ConvertTo-HTML -fragment } # Show all Agents that are in an Uninitialized State Write-Host "Checking for Uninitialized Agents" $ReportOutput += "<br>" $ReportOutput += "<h2>Uninitialized Agents</h2>" If (!($Agents | Where-Object -FilterScript {$_.HealthState -eq "Uninitialized"} | Select-Object -Property Name)) { $ReportOutput += "<p>No Agents are in the Uninitialized State</p>" } Else { $ReportOutput += $Agents | Where-Object -FilterScript {$_.HealthState -eq "Uninitialized"} | Select-Object -Property Name | ConvertTo-HTML -fragment } # Show a Summary of all Agents States $healthy = $uninitialized = $warning = $critical = 0 Write-Host "Checking Agent States" $ReportOutput += "<br>" $ReportOutput += "<h3>Agent Stats</h3>" switch ($Agents | Select-Object HealthState ) { {$_.HealthState -like "Success"} {$healthy++} {$_.HealthState -like "Uninitialized"} {$uninitialized++} {$_.HealthState -like "Warning"} {$warning++} {$_.HealthState -like "Error"} {$critical++} } $totalagents = ($healthy + $warning + $critical + $uninitialized) $AllAgents = @() $iAgent = New-Object psobject $iAgent | Add-Member noteproperty -Name "Agents Healthy" -Value $healthy $iAgent | Add-Member noteproperty -Name "Agents Warning" -Value $warning $iAgent | Add-Member noteproperty -Name "Agents Critical" -Value $critical $iAgent | Add-Member noteproperty -Name "Agents Uninitialized" -Value $uninitialized $iAgent | Add-Member noteproperty -Name "Total Agents" -Value $totalagents $AllAgents += $iAgent $ReportOutput += $AllAgents | Select-Object -Property "Agents Healthy", "Agents Warning", "Agents Critical", "Agents Uninitialized", "Total Agents" | ConvertTo-HTML -fragment # Agent Pending Management States Write-Host "Checking Agent Pending Management States" $ReportOutput += "<br>" $ReportOutput += "<h3>Agent Pending Management Summary</h3>" $pushInstall = $PushInstallFailed = $ManualApproval = $RepairAgent = $RepairFailed = $UpdateFailed = 0 $agentpending = Get-SCOMPendingManagement switch ($agentpending | Select-Object AgentPendingActionType ) { {$_.AgentPendingActionType -like "PushInstall"} {$pushInstall++} {$_.AgentPendingActionType -like "PushInstallFailed"} {$PushInstallFailed++} {$_.AgentPendingActionType -like "ManualApproval"} {$ManualApproval++} {$_.AgentPendingActionType -like "RepairAgent"} {$RepairAgent++} {$_.AgentPendingActionType -like "RepairFailed"} {$RepairFailed++} {$_.AgentPendingActionType -like "UpdateFailed"} {$UpdateFailed++} } $AgentsPending = @() $AgentSummary = New-Object psobject $AgentSummary | Add-Member noteproperty "Push Install" ($pushInstall) $AgentSummary | Add-Member noteproperty "Push Install Failed" ($PushInstallFailed) $AgentSummary | Add-Member noteproperty "Manual Approval" ($ManualApproval) $AgentSummary | Add-Member noteproperty "Repair Agent" ($RepairAgent) $AgentSummary | Add-Member noteproperty "Repair Failed" ($RepairFailed) $AgentSummary | Add-Member noteproperty "Update Failed" ($UpdateFailed) $AgentsPending += $AgentSummary # Produce a table of all Agent Pending Management States and add it to the Report $ReportOutput += $AgentsPending | Select-Object "Push Install", "Push Install Failed", "Manual Approval", "Repair Agent", "Repair Failed", "Update Failed" | ConvertTo-HTML -fragment $ReportOutput += "<br>" $ReportOutput += "<h2>Alerts</h2>" $AlertsAll = ($Alerts | Where-Object {$_.ResolutionState -ne 255}).Count $AlertsWarning = ($Alerts | Where-Object {$_.Severity -eq "Warning" -AND $_.ResolutionState -ne 255}).Count $AlertsError = ($Alerts | Where-Object {$_.Severity -eq "Error" -AND $_.ResolutionState -ne 255}).Count $AlertsInformation = ($Alerts | Where-Object {$_.Severity -eq "Information" -AND $_.ResolutionState -ne 255}).Count $Alerts24hours = ($Alerts | Where-Object {$_.TimeRaised -ge (Get-Date).addhours(-24) -AND $_.ResolutionState -ne 255}).count $AllAlerts = @() $AlertSeverity = New-Object psobject $AlertSeverity | Add-Member noteproperty "Warning" ($AlertsWarning) $AlertSeverity | Add-Member noteproperty "Error" ($AlertsError) $AlertSeverity | Add-Member noteproperty "Information" ($AlertsInformation) $AlertSeverity | Add-Member noteproperty "Last 24 Hours" ($Alerts24hours) $AlertSeverity | Add-Member noteproperty "Total Open Alerts" ($AlertsAll) $AllAlerts += $AlertSeverity # Produce a table of all alert counts for warning, error, information, Last 24 hours and Total Alerts and add it to the Report $ReportOutput += $AllAlerts | Select-Object "Warning", "Error", "Information", "Last 24 Hours", "Total Open Alerts" | ConvertTo-HTML -fragment <# # Check if the Action Account is a Local Administrator on Each Management Server # This will only work if the account is a member of the Local Administrators Group directly. # If it has access by Group Membership, you can look for that Group instead # $ActionAccount = "YourGrouptoCheck" # Then replace all 5 occurrences below of $ManagementServer.ActionAccountIdentity with $ActionAccount Write-Host "Checking if the Action Account is a member of the Local Administrators Group for each Management Server" $ReportOutput += "<br>" $ReportOutput += "<h2>SCOM Action Account</h2>" ForEach ($ms in $ManagementServers.DisplayName | sort DisplayName) { $admins = @() $group =[ADSI]"WinNT://$ms/Administrators" $members = @($group.psbase.Invoke("Members")) $members | foreach { $obj = new-object psobject -Property @{ Server = $Server Admin = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) } $admins += $obj } If ($admins.admin -match $ManagementServer.ActionAccountIdentity) { # Write-Host "The user $($ManagementServer.ActionAccountIdentity) is a Local Administrator on $ms" $ReportOutput += "<p>The user $($ManagementServer.ActionAccountIdentity) is a Local Administrator on $ms</p>" } Else { # Write-Host "The user $($ManagementServer.ActionAccountIdentity) is NOT a Local Administrator on $ms" $ReportOutput += "<p><span style=`"color: `#CC0000;`">The user $($ManagementServer.ActionAccountIdentity) is NOT a Local Administrator on $ms</span></p>" } } #> # Objects in Maintenance Mode #SQL Query Servers in MMode $ServersInMM =@" select DisplayName from dbo.MaintenanceMode mm join dbo.BaseManagedEntity bm on mm.BaseManagedEntityId = bm.BaseManagedEntityId where Path is NULL and IsInMaintenanceMode = 1 "@ $OpsDBSIMM = Run-OpDBSQLQuery $ServersInMM $ReportOutput += "<br>" $ReportOutput += "<h2>Servers in Maintenance Mode</h2>" If (!($OpsDBSIMM)) { $ReportOutput += "<p>There are no objects in Maintenance Mode</p>" } Else { $ReportOutput += $OpsDBSIMM | Select-Object DisplayName | ConvertTo-HTML -fragment } <# # Global Grooming Settings # Simple comparisons against the values set at the beginning of this script # I use this to see if anyone has changed the settings. So set the values at the top of this script to match the values that your environment 'should' be set to. $ReportOutput += "<br>" $ReportOutput += "<h2>SCOM Global Settings</h2>" $SCOMDatabaseGroomingSettings = Get-SCOMDatabaseGroomingSetting If ($SCOMDatabaseGroomingSettings.AlertDaysToKeep -ne $AlertDaysToKeep) {$ReportOutput += "<p><span style=`"color: `#CC0000;`">Alert Days to Keep has been changed! Reset back to $AlertDaysToKeep</span></p>"} Else {$ReportOutput += "<p>Alert Days is correctly set to $AlertDaysToKeep</p>"} If ($SCOMDatabaseGroomingSettings.AvailabilityHistoryDaysToKeep -ne $AvailabilityHistoryDaysToKeep) {$ReportOutput += "<p><span style=`"color: `#CC0000;`">Availability History Days has been changed! Reset back to $AvailabilityHistoryDaysToKeep</span></p>"} Else {$ReportOutput += "<p>Availability History Days is correctly set to $AvailabilityHistoryDaysToKeep</p>"} If ($SCOMDatabaseGroomingSettings.EventDaysToKeep -ne $EventDaysToKeep) {$ReportOutput += "<p><span style=`"color: `#CC0000;`">Event Days has been changed! Reset back to $EventDaysToKeep</span></p>"} Else {$ReportOutput += "<p>Event Days is correctly set to $EventDaysToKeep</p>"} If ($SCOMDatabaseGroomingSettings.JobStatusDaysToKeep -ne $JobStatusDaysToKeep) {$ReportOutput += "<p><span style=`"color: `#CC0000;`">Job Days (Task History) has been changed! Reset back to $JobStatusDaysToKeep</span></p>"} Else {$ReportOutput += "<p>Job Days (Task History) is correctly set to $JobStatusDaysToKeep</p>"} If ($SCOMDatabaseGroomingSettings.MaintenanceModeHistoryDaysToKeep -ne $MaintenanceModeHistoryDaysToKeep) {$ReportOutput += "<p><span style=`"color: `#CC0000;`">Maintenance Mode History has been changed! Reset back to $MaintenanceModeHistoryDaysToKeep</span></p>"} Else {$ReportOutput += "<p>Maintenance Mode History is correctly set to $MaintenanceModeHistoryDaysToKeep</p>"} If ($SCOMDatabaseGroomingSettings.MonitoringJobDaysToKeep -ne $MonitoringJobDaysToKeep) {$ReportOutput += "<p><span style=`"color: `#CC0000;`">Monitoring Job Data has been changed! Reset back to $MonitoringJobDaysToKeep</span></p>"} Else {$ReportOutput += "<p>Monitoring Job Data is correctly set to $MonitoringJobDaysToKeep</p>"} If ($SCOMDatabaseGroomingSettings.PerformanceDataDaysToKeep -ne $PerformanceDataDaysToKeep) {$ReportOutput += "<p><span style=`"color: `#CC0000;`">Performance Data has been changed! Reset back to $PerformanceDataDaysToKeep</span></p>"} Else {$ReportOutput += "<p>Performance Data is correctly set to $PerformanceDataDaysToKeep</p>"} If ($SCOMDatabaseGroomingSettings.StateChangeEventDaysToKeep -ne $StateChangeEventDaysToKeep) {$ReportOutput += "<p><span style=`"color: `#CC0000;`">State Change Data has been changed! Reset back to $StateChangeEventDaysToKeep</span></p>"} Else {$ReportOutput += "<p>State Change Data is correctly set to $StateChangeEventDaysToKeep</p>"} # SCOM Agent Heartbeat Settings $HeartBeatSetting = Get-SCOMHeartbeatSetting If ($HeartBeatSetting.AgentHeartbeatInterval -ne 180 -AND $HeartBeatSetting.MissingHeartbeatThreshold -ne 3) {$ReportOutput += "<p><span style=`"color: `#CC0000;`">The HeartBeat Settings have been changed! Reset back to $AgentHeartbeatInterval and $MissingHeartbeatThreshold</span></p>"} Else {$ReportOutput += "<p>The HeartBeat Settings are correctly set to Interval: $AgentHeartbeatInterval and Missing Threshold: $MissingHeartbeatThreshold</p>"} #> # How long did this script take to run? $EndTime=Get-Date $TotalRunTime=$EndTime-$StartTime # Add the time to the Report $ReportOutput += "<br>" $ReportOutput += "<p>Total Script Run Time: $($TotalRunTime.hours) hrs $($TotalRunTime.minutes) min $($TotalRunTime.seconds) sec</p>" # Close the Body of the Report $ReportOutput += "</body>" Write-Host "Saving HTML Report to $ReportPath" # Save the Final Report to a File ConvertTo-HTML -head $Head -body "$ReportOutput" | Out-File $ReportPath Invoke-Item "$ReportPath" <# # Send Final Report by email... Write-Host "Emailing Report" $SMTPServer ="your.smtpserver.com" $Body = ConvertTo-HTML -head $Head -body "$ReportOutput" $SmtpClient = New-Object Net.Mail.SmtpClient($smtpServer); $mailmessage = New-Object system.net.mail.mailmessage $mailmessage.from = "sender@company.com" $mailmessage.To.add("recipient@company.com") # Want more recipient's? Just add a new line $mailmessage.To.add("anotherrecipient@company.com") $mailmessage.Subject = "Tims SCOM Healthcheck Report" $MailMessage.IsBodyHtml = $true $mailmessage.Body = $Body $smtpclient.Send($mailmessage) #> <# # Insert the Results for the Reporting Server URL into the Report $ReportOutput += "<h2>Reporting Server</h2>" $ReportOutput += "<p>Reporting Server URL : <a href=`"$ReportingURL/`">$ReportingURL</a></p>" $ReportOutput += "<p>The Reporting Server URL Status : $ReportingServerStatus</p>" # Insert the Results for the Web Console URL into the Report $ReportOutput += "<h2>Web Console Servers</h2>" $ReportOutput += "<p>Web Console URL : <a href=`"$WebConsoleURL/`">$WebConsoleURL</a></p>" $ReportOutput += "<p>The Web Console URL Status : $WebConsoleStatus</p>" #> # A bit of cleanup #Clear-Variable Agents, Alerts, Groups, ManagementGroup, ManagementPacks, ManagementServer, Monitors, Rules, ReportOutput, StartTime, EndTime, TotalRunTime, SCOMDatabaseGroomingSettings } |