다음을 통해 공유


Enumerating Status Message Strings in PowerShell

If you found this blog looking for a list of status messages in Configuration Manager, a sample output of the script described below is available here (xlsx).

At some point last year I had written a script to help with some internal work we were doing analysing status message occurances. We wanted a lookup table of status message strings that could be passed to folks who only had visibility of status message IDs and severity. We had old vbscripts that would do this, but they relied on a deprecated automation library from an older version of the SDK. When I asked some software engineers about this, they said that the library was based on some Win32 APIs. I set out to see if I could get those APIs working from PowerShell. :)

Generating status message strings is done using the FormatMessage function that is part of Win32 API. Generally function relies on a message table resource inside a DLL, a message ID and an array of insertion strings (placeholders). The function takes the MessageID looks it up in the tables inside the DLL, and then for each placeholder value inserts a string from the supplied string array.  It then return the completed string back to the caller. Configuration Manager has 3 such message table resource DLLs: srvmsgs.dll (site), provmsgs.dll (provider) and climmsgs.dll (client). If you'd like more information about these DLLs and status messages in Configuration Manager, see the CM SDK here: https://msdn.microsoft.com/en-us/library/jj218266.aspx & https://msdn.microsoft.com/en-us/library/jj218288.aspx.

The script uses a feature of .NET called PInvoke (Platform Invoke) that allows you to call out to unmanaged code and ultimately the underlying platform. Since PowerShell allows you to run .NET code inside of a script nicely, we can combine these two elements to enable us to call the FormatMessage function from PowerShell.  This is done by using the following example for FormatMessage:

$sigFormatMessage = @'
[DllImport("kernel32.dll")]
public static extern uint FormatMessage(uint flags, IntPtr source, uint messageId, uint langId, StringBuilder buffer, uint size, string[] arguments);
'@

We need three different function ultimately to get this working. A LoadLibrary and GetModuleHandle are required to load the DLL then get a handle that DLL to pass to FormatMessage.  Once we've got the PInvoke code snippets, we need to tell PowerShell to load these as references via an Add-Type call. Here is the PowerShell call for Format Message again:

$Win32FormatMessage = Add-Type -MemberDefinition $sigFormatMessage -name "Win32FormatMessage" -namespace Win32Functions -PassThru -Using System.Text

Once we have FormatMessage Win32 type declared in $Win32FormatMessage, we can call the FormatMessage function using the :: notation as follows:

$result = $Win32FormatMessage::FormatMessage($flags, $ptrModule, 1073741824 -bor $iMessageID, 0, $stringOutput, $sizeOfBuffer, $stringArrayInput)

You'll notice that the syntax of that last line looks weird. It looks weird because it isn't a .NET method call (and it certainly isn't any PowerShell like). This is because the Win32 API are C++ based, so we have to use the C++ syntax. The noticeable difference is that the output of the function is a return code integer and the string output is supplied as variable $stringOutput in the input to the function.  Without going into pointers and C++ syntax history (things best left to Computer Science courses) just accept that's the way it works. :)

You'll also notice that I'm using binary OR of 1073741824 with the Message ID.  The reason for this is that the status message ID in configuration manager are actually stored in our message resource DLLs as binary OR of their severity and status message ID. 1073741824 happens to be the integer value for Informational severity. 2147483648 is warning and 3221225472 is error if you're wondering.

That's the main/key parts to calling Win32 APIs via PowerShell for status message string matching. The script has additional logic to walk through the various permutations of status message IDs along with a bit of output and parameters. You can find the entire script here along with sample output for System Center 2012 R2 Configuration Manager here (xlsx).

Hopefully that provides you with a good explaination of how to get status message strings in PowerShell, and generally how to call Win32 API from PowerShell.

Special thanks to Sherry Kissinger and Michael Niehaus for nudging me and reminding me to post this. :)

Comments

  • Anonymous
    December 02, 2015
    You sir are my hero of the day! Thank you for posting this information.