HTML Report for SCCM Site Component Warnings and Errors

Just a quick one 🙂

If you’re like me you are too lazy busy to regularly check the component status of an SCCM Site Server for any issues, so why not get PowerShell to do it for you?

The code below will email an html-formatted report of any site components that are currently in an error or warning status, together with the last few error or warning status messages for each component. Run it as a scheduled task or with your favorite automation tool to keep your eye on any current issues. Whether you get annoyed because you now created more work for yourself, or get happy because you can stay on top of issues in your SCCM environment, I leave to you!

The report will display the components that are marked as either critical or warning with the current number of messages:

It will then display the last x status messages for each component for a quick view of what the current issue/s are:

Run the script either on the site server or somewhere where the SCCM console is installed, and set the required parameters in the script.

#####################################################################################################
## ##
## This script checks for any SCCM Site Server components currently in an error or warning ##
## state and emails it as an html report, including the latest status messages for each component. ##
## ##
#####################################################################################################
################
## PARAMETERS ##
################
# Site server FQDN
$SiteServer = "SCCMServer.Contoso.com"
# Site code
$SiteCode = "ABC"
# Location of the resource dlls in the SCCM admin console path
$script:SMSMSGSLocation =$env:SMS_ADMIN_UI_PATH\00000409
# SCCM SQL Server / instance
$script:dataSource = 'SCCMServer'
# SCCM SQL database
$script:database = 'CM_ABC'
# Number of Status messages to report
$SMCount = 5
# Tally interval – see https://docs.microsoft.com/en-us/sccm/develop/core/servers/manage/about-configuration-manager-tally-intervals
$TallyInterval = '0001128000100008'
# Email params
$EmailParams = @{
To = 'joe.bloggs@contoso.com'
From = 'SCCMReports@contoso.com'
Smtpserver = 'contoso-com.mail.protection.outlook.com'
Port = 25
Subject = "SCCM Site Server Component Status Report | $SiteServer | $SiteCode | $(Get-Date Format ddMMMyyyy)"
}
# Html CSS style
$Style = @"
<style>
table {
border-collapse: collapse;
}
td, th {
border: 1px solid #ddd;
padding: 8px;
}
th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #4286f4;
color: white;
}
h2 {
color: red;
}
</style>
"@
###############
## FUNCTIONS ##
###############
# Function to get data from SQL server
function Get-SQLData {
param($Query)
$connectionString = "Server=$dataSource;Database=$database;Integrated Security=SSPI;"
$connection = New-Object TypeName System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = $Query
$reader = $command.ExecuteReader()
$table = New-Object TypeName 'System.Data.DataTable'
$table.Load($reader)
# Close the connection
$connection.Close()
return $Table
}
# Function to get the status message description
function Get-StatusMessage {
param (
$MessageID,
[ValidateSet("srvmsgs.dll","provmsgs.dll","climsgs.dll")]$DLL,
[ValidateSet("Informational","Warning","Error")]$Severity,
$InsString1,
$InsString2,
$InsString3,
$InsString4,
$InsString5,
$InsString6,
$InsString7,
$InsString8,
$InsString9,
$InsString10
)
# Set the resources dll
Switch ($DLL)
{
"srvmsgs.dll" { $stringPathToDLL = "$SMSMSGSLocation\srvmsgs.dll" }
"provmsgs.dll" { $stringPathToDLL = "$SMSMSGSLocation\provmsgs.dll" }
"climsgs.dll" { $stringPathToDLL = "$SMSMSGSLocation\climsgs.dll" }
}
# Load Status Message Lookup DLL into memory and get pointer to memory
$ptrFoo = $Win32LoadLibrary::LoadLibrary($stringPathToDLL.ToString())
$ptrModule = $Win32GetModuleHandle::GetModuleHandle($stringPathToDLL.ToString())
# Set severity code
Switch ($Severity)
{
"Informational" { $code = 1073741824 }
"Warning" { $code = 2147483648 }
"Error" { $code = 3221225472 }
}
# Format the message
$result = $Win32FormatMessage::FormatMessage($flags, $ptrModule, $Code -bor $MessageID, 0, $stringOutput, $sizeOfBuffer, $stringArrayInput)
if ($result -gt 0)
{
# Add insert strings to message
$objMessage = New-Object System.Object
$objMessage | Add-Member type NoteProperty name MessageString value $stringOutput.ToString().Replace("%11","").Replace("%12","").Replace("%3%4%5%6%7%8%9%10","").Replace("%1",$InsString1).Replace("%2",$InsString2).Replace("%3",$InsString3).Replace("%4",$InsString4).Replace("%5",$InsString5).Replace("%6",$InsString6).Replace("%7",$InsString7).Replace("%8",$InsString8).Replace("%9",$InsString9).Replace("%10",$InsString10)
}
Return $objMessage
}
#################
## MAIN SCRIPT ##
#################
# SQL query for component status
$Query = "
Select
ComponentName,
ComponentType,
Case
when Status = 0 then 'OK'
when Status = 1 then 'Warning'
when Status = 2 then 'Critical'
End as 'Status',
Case
when State = 0 then 'Stopped'
when State = 1 then 'Started'
when State = 2 then 'Paused'
when State = 3 then 'Installing'
when State = 4 then 'Re-installing'
when State = 5 then 'De-installing'
End as 'State',
Case
When AvailabilityState = 0 then 'Online'
When AvailabilityState = 3 then 'Offline'
When AvailabilityState = 4 then 'Unknown'
End as 'AvailabilityState',
Infos,
Warnings,
Errors
from vSMS_ComponentSummarizer
where TallyInterval = N'$TallyInterval'
and MachineName = '$SiteServer'
and SiteCode = '$SiteCode '
and Status in (1,2)
Order by Status,ComponentName
"
$Results = Get-SQLData Query $Query
# Convert results to HTML
$HTML = $Results |
ConvertTo-Html Property "ComponentName","ComponentType","Status","State","AvailabilityState","Infos","Warnings","Errors" Head $Style Body "<h2>Components in a Warning or Error State</h2>" CssUri "http://www.w3schools.com/lib/w3.css" |
Out-String
$HTML = $HTML + "<h2></h2><h2>Last $SMCount Error or Warning Status Messages for…</h2>"
If ($Results)
{
# Start PInvoke Code
$sigFormatMessage = @'
[DllImport("kernel32.dll")]
public static extern uint FormatMessage(uint flags, IntPtr source, uint messageId, uint langId, StringBuilder buffer, uint size, string[] arguments);
'@
$sigGetModuleHandle = @'
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
'@
$sigLoadLibrary = @'
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string lpFileName);
'@
$Win32FormatMessage = Add-Type MemberDefinition $sigFormatMessage name "Win32FormatMessage" namespace Win32Functions PassThru Using System.Text
$Win32GetModuleHandle = Add-Type MemberDefinition $sigGetModuleHandle name "Win32GetModuleHandle" namespace Win32Functions PassThru Using System.Text
$Win32LoadLibrary = Add-Type MemberDefinition $sigLoadLibrary name "Win32LoadLibrary" namespace Win32Functions PassThru Using System.Text
#End PInvoke Code
$sizeOfBuffer = [int]16384
$stringArrayInput = {"%1","%2","%3","%4","%5", "%6", "%7", "%8", "%9"}
$flags = 0x00000800 -bor 0x00000200
$stringOutput = New-Object System.Text.StringBuilder $sizeOfBuffer
# Process each resulting component
Foreach ($Result in $Results)
{
# Query SQL for status messages
$Component = $Result.ComponentName
$SMQuery = "
select
top $SMCount
smsgs.RecordID,
CASE smsgs.Severity
WHEN -1073741824 THEN 'Error'
WHEN 1073741824 THEN 'Informational'
WHEN -2147483648 THEN 'Warning'
ELSE 'Unknown'
END As 'SeverityName',
case smsgs.MessageType
WHEN 256 THEN 'Milestone'
WHEN 512 THEN 'Detail'
WHEN 768 THEN 'Audit'
WHEN 1024 THEN 'NT Event'
ELSE 'Unknown'
END AS 'Type',
smsgs.MessageID,
smsgs.Severity,
smsgs.MessageType,
smsgs.ModuleName,
modNames.MsgDLLName,
smsgs.Component,
smsgs.MachineName,
smsgs.Time,
smsgs.SiteCode,
smwis.InsString1,
smwis.InsString2,
smwis.InsString3,
smwis.InsString4,
smwis.InsString5,
smwis.InsString6,
smwis.InsString7,
smwis.InsString8,
smwis.InsString9,
smwis.InsString10
from v_StatusMessage smsgs
join v_StatMsgWithInsStrings smwis on smsgs.RecordID = smwis.RecordID
join v_StatMsgModuleNames modNames on smsgs.ModuleName = modNames.ModuleName
where smsgs.MachineName = '$SiteServer'
and smsgs.Component = '$Component'
and smsgs.Severity in ('-1073741824','-2147483648')
Order by smsgs.Time DESC
"
$StatusMsgs = Get-SQLData Query $SMQuery
# Put desired fields into an object for each result
$StatusMessages = @()
foreach ($Row in $StatusMsgs)
{
$Params = @{
MessageID = $Row.MessageID
DLL = $Row.MsgDLLName
Severity = $Row.SeverityName
InsString1 = $Row.InsString1
InsString2 = $Row.InsString2
InsString3 = $Row.InsString3
InsString4 = $Row.InsString4
InsString5 = $Row.InsString5
InsString6 = $Row.InsString6
InsString7 = $Row.InsString7
InsString8 = $Row.InsString8
InsString9 = $Row.InsString9
InsString10 = $Row.InsString10
}
$Message = Get-StatusMessage @params
$StatusMessage = New-Object psobject
Add-Member InputObject $StatusMessage Name Severity MemberType NoteProperty Value $Row.SeverityName
Add-Member InputObject $StatusMessage Name Type MemberType NoteProperty Value $Row.Type
Add-Member InputObject $StatusMessage Name SiteCode MemberType NoteProperty Value $Row.SiteCode
Add-Member InputObject $StatusMessage Name "Date / Time" MemberType NoteProperty Value $Row.Time
Add-Member InputObject $StatusMessage Name System MemberType NoteProperty Value $Row.MachineName
Add-Member InputObject $StatusMessage Name Component MemberType NoteProperty Value $Row.Component
Add-Member InputObject $StatusMessage Name Module MemberType NoteProperty Value $Row.ModuleName
Add-Member InputObject $StatusMessage Name MessageID MemberType NoteProperty Value $Row.MessageID
Add-Member InputObject $StatusMessage Name Description MemberType NoteProperty Value $Message.MessageString
$StatusMessages += $StatusMessage
}
# Add to the HTML code
$HTML = $HTML + (
$StatusMessages |
ConvertTo-Html Property "Severity","Date / Time","MessageID","Description" Head $Style Body "<h2>$Component</h2>" CssUri "http://www.w3schools.com/lib/w3.css" |
Out-String
)
}
# Fire the email
Send-MailMessage @EmailParams Body $Html BodyAsHtml
}