SCCM Component Log Viewer

This page will show all the detailed SCCM component logs if they are on warning or critical status. It requires PowerShell Universal to display the data on the website. ( You can also just use the code generated just to use HTML & IIS to show the data)

# SCCM Server Log on Components
# Rui Qiu
# 9/3/2021

# Setup Parameters

$SiteServer = "XXX"
$SiteCode = "XXX"
$script:SMSMSGSLocation = "$env:SMS_ADMIN_UI_PATH\00000409"
$script:dataSource = 'XXX' 
$script:database = 'CM_XXX'
# Number of Status messages to report
$SMCount = 20
# Tally interval - see https://docs.microsoft.com/en-us/sccm/develop/core/servers/manage/about-configuration-manager-tally-intervals
$TallyInterval = '0001128000100008'


# Function to sql data
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)
    $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
}


# Start the job

# 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 | ForEach-Object {
    @{ 
        ComponentName = $_.ComponentName 
        Status = $_.Status
        Infos = $_.Infos
        Warnings = $_.Warnings
        Errors = $_.Errors
    }
}

# Setting up the table columns
$Columns = @(    
    New-UDTableColumn -Property ComponentName -Title "Component Name"
    New-UDTableColumn -Property Status -Title Status 
    New-UDTableColumn -Property Infos -Title Infos 
    New-UDTableColumn -Property Warnings -Title Warnings 
	New-UDTableColumn -Property Errors -Title Errors
)

# Setting up a table to display the summery data
New-UDTable -Data $Results -Columns $Columns -ShowSort -Title 'Components in a Warning or Error State' -ShowSearch -Dense -Export
New-UDElement -tag 'div' -attributes @{
    style = @{ 
        height = '20px'
    }
}

# If any components are on warning or critial status, will get detailed log
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
        }

        #Displaying Data
         $StatusMessages| ForEach-Object {
            @{ 
            Severity = $_.Severity 
            Date = $_.'Date / Time'
            Description = $_.Description           
            }
        }

        $Columns = @(    
    New-UDTableColumn -Property Description -Title Description
    New-UDTableColumn -Property Severity -Title Severity 
    New-UDTableColumn -Property 'Date / Time' -Title Date 
)

New-UDTable -Data $StatusMessages -Columns $Columns -ShowSort -Title $Component -ShowSearch -Dense -Export
New-UDElement -tag 'div' -attributes @{
    style = @{ 
        height = '20px'
    }
}

    }


}

1 thought on “SCCM Component Log Viewer”

Leave a Comment