Convert ConfigMgr Error Codes to Plain Text Messages for Reporting – Part 1

I demoed this at MMS Jazz 2019 Tip and Tricks. This is Part 1 of a 3-part series.
Part 2 covers creating an Azure Function.
Part 3 covers how to create a Power BI function to use an Azure Function.

If you have ever checked the logs in ConfigMgr, you’ve likely run across an error code in a format like 0x87D00664 or -2016410012. If use the error lookup feature in CMTrace, you can generally convert the error to plain text. In the ConfigMgr console, if you are looking at deployment statuses or even SSRS reports, you will see the plain text listed, but that data doesn’t exist in the database.

When I started trying to build a Power BI report to provide detailed status messages for Windows 10 Feature Updates, I discovered that the plain text wasn’t in the database as I expected. I decided to see where the existing SSRS reports got the data so I opened the Troubleshooting 2 – Deployment Errors report from the ConfigMgr Console. Using the queries from that report, I began to build my Power BI report. However, whenever I got to the Error Description field, I realized that the query used by the dataset doesn’t actually include the text, just the error codes!

I discovered that the Error Description column in the SSRS report references a DLL using this expression =SrsResources.Localization.GetErrorMessage(Fields!ErrorCode.Value,User!Language).

The DLL is SrsResources.dll and can be found in the bin folder of you ConfigMgr console installation directory. I was intrigued. As I began searching the web for this DLL, I found several posts which used it in PowerShell, so I started testing with it. It turns out, this file has a lot of power. Not only can it look up error codes, but it can look up a number of items including Status Messages.

Using PowerShell to Return Error Messages

You can load the DLL into PowerShell and list the available methods. Some of them are for internal use of the DLL (or so it seems). Run this code block at the top of your script to load the dll into PowerShell.

$DllPath = "E:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\SrsResources.dll"
Add-Type -Path $DllPath

You can load the DLL into PowerShell and list the available methods. Some of them are for internal use of the DLL (or so it seems).

[SrsResources.Localization].DeclaredMethods.Name

The list of available methods:

PS C:\> [SrsResources.Localization].DeclaredMethods.Name
Initialize
SetResourceLocation
GetDBString
GetDBString
GetErrorMessage
GetStatusMessage
GetString
GetStringEscaped
GetMigrationJobEntityMessageString
GetGuidString
GetGuidStringShort
GetCcmEvalDetail
GetStateMessageShort
GetStateMessage
GetCustomErrorMessage
GetLocalTime
FormatTimeSpanFromMinutes
FormatTime
FormatTime
FormatTime
FormatShortTime
FormatShortDate
FormatShortDate
FormatShortDateTime
FormatTime
GetSettingUserUnits
ConvertToUserUnits
GetAppRequirementName
GetAlertDisplayName
GetDBStringInternal
GetDBStrings
GetErrorMessageInternal
GetStatusMessageInternal
LocalizeInsertionStrings
GetSettingUnits
GetStateMessageInternal

To get the parameter options for each method, you can simply reference the Method name without () like: [SrsResources.Localization]::GetErrorMessage

##############################
#GetErrorMessage
[SrsResources.Localization]::GetErrorMessage

#Result#
#OverloadDefinitions
#-------------------
#static string GetErrorMessage(string errorId, string language)

##############################
#GetStatusMessage
[SrsResources.Localization]::GetStatusMessage

#Result#
#OverloadDefinitions
#-------------------
#static string GetStatusMessage(int messageId, int severity, string dllName, #string language, Params string[] insertionStrings)

##############################
#FormatShortDate

[SrsResources.Localization]::FormatShortDate

#Result#
#OverloadDefinitions
#-------------------
#static string FormatShortDate(int year, int month, int day, string language)
#static string FormatShortDate(datetime dt, string language)
##############################

The method this post will focus on mostly is GetErrorMessage, but the GetStatusMessage method is pretty sweet too. If you pass values to these two methods, you will get something like this:

$ErrorCode = 0x87D00664
[SrsResources.Localization]::GetErrorMessage($ErrorCode,"en-US")

#Result
#Updates handler job was cancelled

A quick note about GetStatusMessages. If you run it without it’s last parameter, you will get something like this:

[SrsResources.Localization]::GetStatusMessage(30000,1073741824,"","en-US")

#Result
#%11User "%1" created a package named "%5 %3 %4 %6" (%2).%12

The % values are expecting values from the status message Insertion Strings. Run this SQL query SELECT * FROM v_StatMsgWithInsStrings and you will see the columns InsString4 through InsString10. The values in this view all can be passed to the GetStatusMessages method to populate the placeholder % values like this:

[SrsResources.Localization]::GetStatusMessage(30000,1073741824,"","en-US","ASD\Adam","0","1","2","3","4")

#Result
#User "ASD\Adam" created a package named "3 1 2 4" (0).

[SrsResources.Localization]::GetStatusMessage(30000,1073741824,"","en-US","ASD\Adam","Source","Is","A","This","Test")

#Result
#User "ASD\Adam" created a package named "This Is A Test" (Source).

Sourcing the Files

I want to use this on machines where I don’t have the ConfigMgr console installed but I discovered that SrsResources.dll doesn’t work on it’s own. It references a number of other files and these files must be present with the DLL anywhere you want to run it that doesn’t have the ConfigMgr Reporting Services role or ConfigMgr admin console installed. Copy all of the files from these locations to the same location as your PowerShell script. I am using a C:\Temp. Be sure to retain the folder structures for the source files.

It’s also worth noting that if you don’t need multiple languages, you can just grab the folders corresponding to your language code. It may take some trial and error to find the right ones, so I just grabbed them all. I also noticed that in my env, Some language codes didn’t work with the DLL. Your results may vary.

Copy the following files/folders to C:\Temp\Bin

E:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\SRSResources.dll
E:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\AdminUI.UIResources.dll

This folder i386 and subfolders with folder contents (don’t grab anything in the root of this folder)
E:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\i386

This should be a directory on your Reporting Services server.
This folder and all contents
E:\Program Files\SMS_SRSRP\Resources
E:\Program Files\SMS_SRSRP\resources\DatabaseResources.dll
C:\Windows\system32\wbem\wmiutils.dll

The final folder structure should look like this:
C:\Temp\Bin\
C:\Temp\Bin\i386
C:\Temp\Bin\i386\00000404
C:\Temp\Bin\i386\00000405
C:\Temp\Bin\i386\00000407
C:\Temp\Bin\i386\00000409
C:\Temp\Bin\i386\0000040C
C:\Temp\Bin\i386\0000040E
C:\Temp\Bin\i386\00000410
C:\Temp\Bin\i386\00000411
C:\Temp\Bin\i386\00000412
C:\Temp\Bin\i386\00000413
C:\Temp\Bin\i386\00000415
C:\Temp\Bin\i386\00000416
C:\Temp\Bin\i386\00000419
C:\Temp\Bin\i386\0000041d
C:\Temp\Bin\i386\0000041f
C:\Temp\Bin\i386\00000804
C:\Temp\Bin\i386\00000816
C:\Temp\Bin\i386\00000c0a
C:\Temp\Bin\resources
C:\Temp\Bin\resources\115
C:\Temp\Bin\resources\2000
C:\Temp\Bin\resources\2000\00000409
C:\Temp\Bin\resources\2001
C:\Temp\Bin\resources\2002
C:\Temp\Bin\resources\2002\00000409
C:\Temp\Bin\resources\36
C:\Temp\Bin\resources\StatusMessages
C:\Temp\Bin\resources\StatusMessages\00000409

This image has an empty alt attribute; its file name is image-52.png
This image has an empty alt attribute; its file name is image-53.png
This image has an empty alt attribute; its file name is image-54.png

Custom Error Lookup Script

Now that we have all of the files, we just need an easy to call script that will give us the error message text. This script will be the basis for the Azure Function in my next post.

Param (
    [string]$ExitCode = 0x87D00664,
    [string]$Language
)

#Default Path
#$DLLPath = "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\SrsResources.dll"

#Custom Path
$DLLPath = "C:\Temp\bin\SrsResources.dll"

Add-Type -Path $DLLPath

If(!($Language)) {
    $Language = "en-US"
}

If($ExitCode) {
    [int]$intCode = $ExitCode
    If ($intCode -eq 0 -or $intCode) {
        $Message = [SrsResources.Localization]::GetErrorMessage($intCode,$Language)
        If($Message) {
            Return $Message
        }
        Else {
            Return "No Result."
        }
    }
    Else {Return "Bad Exit Code."}
}
Else {
    Return "No exit code was specified."
}

Save the file into the root of the directory where your source files are and call it from the PowerShell command line.

.\Get-ErrorMessage.ps1 -ExitCode '0x87D00664' -Language 'en-US'

Summary

This post has ended up much longer than I expected and has been “in progress” for a few months now. I’m going to stop here on this one.

Check out Part 2 and Part 3

You Might Also Like

No Comments

Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1,625