Retrieving ConfigMgr Status Messages with PowerShell

Did you know you can retrieve status messages from ConfigMgr, including the message descriptions, using PowerShell? I pieced together some code I found on the internet with some of my own code, and came up with the following script.  Essentially, it’s the equivalent of running the “All Status Messages from a Specific System” from the status message queries in ConfigMgr.  You can return results either into the console, CSV file, or to the PowerShell GridView for a more authentic “Status Message Viewer” feel.

The script works by running a SQL query to the ConfigMgr database to retrieve the status messages (you need “db_datareader” access to the database with your logged-on account). But the message descriptions themselves are not stored in the database, so to retrieve them we will talk to the ConfigMgr message DLLs.  These DLLs can be found on your site server, and contain the message strings to go with the message IDs.

Configure the Script

First, you will need to define the defaults for a couple of parameters in the script:

$SQLServer – the name of the SQL server hosting the ConfigMgr database.  Include the instance name if applicable, eg MYSQLBOX-01, or MYSQLBOX-01\INST_SCCM etc

$Database – the name of the ConfigMgr database, eg CM_ABC

$SMSMSGSLocation – the location of the “smsmsgs” directory containing the “srvmsgs.dll”,”provmsgs.dll” and “climsgs.dll”.  The default location will be “C:\Program Files\Microsoft Configuration Manager\bin\X64\system32\smsmsgs”

Running the Script

To retrieve all status messages from computer “PC001” in the last 7 days:

Get-CMStatusMessages -ComputerName PC001

To retrieve all status messages from server “SCCMSRV-01” in the last 24 hours and output to CSV:

Get-CMStatusMessages -ComputerName SCCMSRV-01 -TimeInHours 24 -CSV

To retrieve all status messages from computer “PC001” in the last 7 days and output to GridView:

Get-CMStatusMessages -ComputerName PC001 -GridView


Default output is to console:

CaptureCSV output:

Capture2Output to GridView for “Status Message Viewer” like experience, where you can also filter for specific components or MessageIDs etc:


Taking it Further

You could add additional SQL queries in the script to return the kind of results you want to see, and call them using a parameter, to extend the capability of the script.

The Script

Download here: Get-CMStatusMessages


    Returns the Status Messages from a specified computer in a specified time period

    Queries the ConfigMgr database to find a list of status messages for a client or server.  The status message descriptions are then retrieved from the ConfigMgr message DLLs.  Equivalent to
    the "All Status Messages from a Specific System" Status Message query. The results will be returned to the console by default, unless the CSV or GridView switches are used.

.PARAMETER ComputerName
    The name of the computer to retrieve status messages for

    The number of hours past for which to retrieve status messages. Default is 7 days.

    Use this switch to return results into a CSV file

    Use this switch to return results into PowerShell's Out-GridView

    The name of your SQL server, including the instance where relevant, eg SQLSRV-01, or SQLSRV-01\INST_SCCM etc

    The name of your ConfigMgr database

    The location of the "smsmsgs" directory containing the "srvmsgs.dll","provmsgs.dll" and "climsgs.dll".  The standard location is used as the default.

    Get-CMStatusMessages -ComputerName PC001
    Returns to the console all status messages for the system "PC001" in the last 7 days.

    Get-CMStatusMessages -ComputerName PC001 -CSV
    Returns to a CSV file all status messages for the system "PC001" in the last 7 days.

    Get-CMStatusMessages -ComputerName PC001 -GridView -TimeInHours 24
    Returns to PowerShell's GridView all status messages for the system "PC001" in the last 24 hours.

    Cmdlet name:      Get-CMStatusMessages
    Author:           Trevor Jones
    Acknowledgments:  Some of this code is adapted from code available on the internet
    Contact:          @trevor_smsagent
    DateCreated:      2015-07-22


        [Parameter(Mandatory=$True, HelpMessage="The name of the computer to retrieve status message for")]
        [Parameter(Mandatory=$False, HelpMessage="The number of hours past in which to retrieve status messages")]
            [int]$TimeInHours = "168",
        [Parameter(Mandatory=$False, HelpMessage="The SQL server name (and instance name where appropriate)")]
            [string]$SQLServer = “SQLSRV-01\INST_SCCM”,
        [Parameter(Mandatory=$False, HelpMessage="The name of the ConfigMgr database")]
            [string]$Database = “CM_ABC”,
        [Parameter(Mandatory=$False, HelpMessage="The location of the smsmsgs directory containing the message DLLs")]
            [string]$SMSMSGSLocation = "C:\Program Files\Microsoft Configuration Manager\bin\X64\system32\smsmsgs"

# Function to get the status message description
function Get-StatusMessage {
param (

if ($DLL -eq "srvmsgs.dll")
    {$stringPathToDLL = "$SMSMSGSLocation\srvmsgs.dll"}
if ($DLL -eq "provmsgs.dll")
    {$stringPathToDLL = "$SMSMSGSLocation\provmsgs.dll"}
if ($DLL -eq "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()) 

if ($Severity -eq "Informational")
    {$code = 1073741824}
if ($Severity -eq "Warning")
    {$code = 2147483648}
if ($Severity -eq "Error")
    {$code = 3221225472}

$result = $Win32FormatMessage::FormatMessage($flags, $ptrModule, $Code -bor $iMessageID, 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)

# Open a database connection
$connectionString = “Server=$SQLServer;Database=$database;Integrated Security=SSPI;”
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString

# Define the SQl query
$Query = "
select 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,
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 = '$ComputerName'
and DATEDIFF(hour,smsgs.Time,GETDATE()) < '$TimeInHours'
Order by smsgs.Time DESC

# Run the query
$command = $connection.CreateCommand()
$command.CommandText = $query
$reader = $command.ExecuteReader()
$table = new-object “System.Data.DataTable”

# Close the connection

#Start PInvoke Code
$sigFormatMessage = @'
public static extern uint FormatMessage(uint flags, IntPtr source, uint messageId, uint langId, StringBuilder buffer, uint size, string[] arguments);

$sigGetModuleHandle = @'
public static extern IntPtr GetModuleHandle(string lpModuleName);

$sigLoadLibrary = @'
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 

# Put desired fields into an object for each result
$StatusMessages = @()
foreach ($Row in $Table.Rows)
        $Params = @{
            iMessageID = $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

if ($StatusMessages -ne $null)
        if ($CSV)
                $Date = Get-Date -Format HH-mm--dd-MMM-yy
                $Path = "$env:USERPROFILE\Documents\StatusMessages-$ComputerName-$Date.csv"
                $StatusMessages | Export-Csv -Path $Path -NoTypeInformation
                Invoke-Item -Path $Path
        if ($GridView)
                $StatusMessages | Out-GridView -Title "Status Messages for ""$ComputerName"" in the last $TimeInHours hours"
        else {$StatusMessages}
else { write-host "No status messages found" }

Create a Database of Error Codes and Descriptions for Windows and ConfigMgr

In a recent post, I described different ways to translate error codes for Windows and Configuration Manager into their friendly descriptions.  In this post, I will show you how to create a SQL database of known error codes and descriptions that you can join to in your SQL queries, to help simplify your troubleshooting, and I will also give some example queries you can use with Configuration Manager.

Windows and system error codes are standard and are published by Microsoft on MSDN, but there is no published resource of error codes for Configuration Manager 2012 onwards that I know of.  To have a database of all these codes is quite useful as they are not stored either in WMI or in the ConfigMgr database – only the error codes themselves are stored.  These codes are translated to their descriptions by the ConfigMgr console and the ConfigMgr SSRS Reports probably utilizing dll files.

I extracted a list of 11,839 error codes and descriptions using the SrsResource.dll, as described in the previous post, and exported them into a csv file.  Using the PowerShell function below, I converted each error code to give the hex and decimal codes for each.  In Configuration Manager, the log files and reports tend to use the hexadecimal value or the ‘signed integer’ decimal value for the error code, however WMI stores the codes as ‘unsigned integers’ (always positive or zero), therefore I have included all three for easy referencing.

function Convert-Number {

$binary = [Convert]::ToString($Number,2)

if ($ToBinary)

if ($ToHexadecimal)
        $hex = "0x" + [Convert]::ToString($Number,16)

if ($ToSignedInteger)
        $int32 = [Convert]::ToInt32($binary,2)
if ($ToUnSignedInteger)
        $Uint64 = [Convert]::ToUInt64($binary,2)

Using this function, you can convert between binary, hexadecimal, signed and unsigned integers:

CaptureTo import those codes into a SQL database, first download the attached XLSX file which contains all the codes, and save it in CSV format.  The error descriptions have had any line breaks removed so that they will import correctly.


Now run the following T-SQL code against your SQL instance.  It will create a new database called ‘ErrorCodes’ and import all the entries from the CSV into a new table called ‘WindowsErrorCodes’.  Change the path to the CSV file as needed.

I’m using the same SQL instance as my Configuration Manager database so I can easily reference the two.

Create Database ErrorCodes
USE ErrorCodes;
CREATE TABLE WindowsErrorCodes (
Hexadecimal VARCHAR(10) NOT NULL,
SignedInteger BIGINT NOT NULL,
UnSignedInteger BIGINT NOT NULL,
ErrorDescription NVARCHAR(MAX)

INSERT WindowsErrorCodes
FROM '<mycomputer>\C$\temp\ErrorCodes_Final.csv'

Now let’s run a quick query to find a Configuration Manager error description:


If I want to query for application deployment errors, similar to the PowerShell script in my last post, then I can use the following query entering the AssignmentID of the application deployment, which you can find from the ConfigMgr Console in the additional columns.  I will join the app deployment errors by their error code to my new database to return the error descriptions for each.  Join the ErrorCode field from the ConfigMgr database views with the SignedInteger field from the error code database.

select  app.ApplicationName, ass.CollectionName,
sys.Name0 as 'Computer Name',
det.ResourceID, det.CIVersion, det.ErrorCode, det.Errortype,
err.Hexadecimal, err.ErrorDescription
from v_CIErrorDetails det
inner join V_R_System sys on det.ResourceID = sys.ResourceID
inner join v_CIAssignmentToCI ci on det.CI_ID = ci.CI_ID
inner join v_CIAssignment ass on ci.AssignmentID = ass.AssignmentID
inner join v_ApplicationAssignment app on ci.AssignmentID = app.AssignmentID
left join ErrorCodes.dbo.WindowsErrorCodes err on det.ErrorCode = err.SignedInteger
where ci.AssignmentID = 16777540
order by sys.Name0



Cool 🙂

I can also get summary data categorized by the error code, for that deployment, again using the AssignmentID:

from vAppDeploymentErrorStatus err
inner join v_CIAssignment ass on err.AssignmentUniqueID = ass.Assignment_UniqueID
inner join vAppDTDeploymentSummary sum on err.DTCI = sum.DTCI
left join ErrorCodes.dbo.WindowsErrorCodes code on err.ErrorCode = code.SignedInteger
where err.assignmentID = 16777540
and sum.assignmentID = err.AssignmentID
and err.ErrorCode <> 0
order by Description, Total desc



Both of these queries together roughly equate to what you can see in the ConfigMgr Console > Deployments node:

capture4Since we now don’t depend on the Console or the SSRS reports to translate the error descriptions for us, we can go ahead and more easily create custom reports or SQL queries or PowerShell scripts to report this information for us 🙂