├── LICENSE.txt ├── MessageTableReader.cs ├── ReadMe.MD └── azure-pipelines.yml /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Stuart Squibb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MessageTableReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace MessageTableReader 7 | { 8 | public class Reader 9 | { 10 | 11 | //Based on https://stackoverflow.com/questions/33498244/marshaling-a-message-table-resource 12 | 13 | [DllImport("kernel32.dll", SetLastError = true)] 14 | private static extern IntPtr LoadLibrary(string fileName); 15 | 16 | [DllImport("kernel32.dll", SetLastError = true)] 17 | private static extern IntPtr FindResource(IntPtr hModule, int lpID, int lpType); 18 | 19 | [DllImport("kernel32.dll", SetLastError = true)] 20 | private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo); 21 | 22 | [DllImport("kernel32.dll")] 23 | public static extern IntPtr LockResource(IntPtr hResData); 24 | 25 | [DllImport("kernel32.dll", SetLastError = true)] 26 | public static extern int FormatMessage( 27 | FormatMessageFlags dwFlags, 28 | IntPtr lpSource, 29 | uint dwMessageId, 30 | uint dwLangugeId, 31 | ref IntPtr lpBuffer, 32 | uint nSize, 33 | IntPtr Arguments 34 | ); 35 | 36 | [StructLayout(LayoutKind.Sequential)] 37 | struct MESSAGE_RESOURCE_BLOCK 38 | { 39 | public int LowId; 40 | public int HighId; 41 | public int OffsetToEntries; 42 | } 43 | 44 | [Flags] 45 | public enum FormatMessageFlags : uint 46 | { 47 | FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100, 48 | FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200, 49 | FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000, 50 | FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000, 51 | FORMAT_MESSAGE_FROM_HMODULE = 0x00000800, 52 | FORMAT_MESSAGE_FROM_STRING = 0x00000400, 53 | } 54 | 55 | public IList GetMessageList(string filepath = @"C:\WINDOWS\system32\msobjs.dll") 56 | { 57 | IList messageList = new List(); 58 | const int RT_MESSAGETABLE = 11; 59 | if (!File.Exists(filepath)) 60 | { 61 | throw new ApplicationException(string.Format("File {0} not found.", filepath)); 62 | } 63 | else 64 | { 65 | IntPtr hModule = LoadLibrary(filepath); 66 | IntPtr msgTableInfo = FindResource(hModule, 1, RT_MESSAGETABLE); 67 | IntPtr msgTable = LoadResource(hModule, msgTableInfo); 68 | IntPtr memTable = LockResource(msgTable); 69 | int numberOfBlocks = Marshal.ReadInt32(memTable); 70 | IntPtr blockPtr = IntPtr.Add(memTable, 4); 71 | int blockSize = Marshal.SizeOf(); 72 | for (int i = 0; i < numberOfBlocks; i++) 73 | { 74 | var block = Marshal.PtrToStructure(blockPtr); 75 | IntPtr entryPtr = IntPtr.Add(memTable, block.OffsetToEntries); 76 | for (int id = block.LowId; id <= block.HighId; id++) 77 | { 78 | var length = Marshal.ReadInt16(entryPtr); 79 | var flags = Marshal.ReadInt16(entryPtr, 2); 80 | var textPtr = IntPtr.Add(entryPtr, 4); 81 | var text = "Bad flags??"; 82 | if (flags == 0) 83 | { 84 | text = Marshal.PtrToStringAnsi(textPtr); 85 | } 86 | else if (flags == 1) 87 | { 88 | text = Marshal.PtrToStringUni(textPtr); 89 | } 90 | text = text.Replace("\r\n", ""); 91 | messageList.Add(string.Format("{0}:{1}", id, text)); 92 | entryPtr = IntPtr.Add(entryPtr, length); 93 | } 94 | blockPtr = IntPtr.Add(blockPtr, blockSize); 95 | } 96 | } 97 | return messageList; 98 | } 99 | 100 | public string GetMessage(uint messageid, string filepath = @"C:\WINDOWS\system32\msobjs.dll") 101 | { 102 | string messagetext = ""; 103 | if (!File.Exists(filepath)) 104 | { 105 | throw new ApplicationException(string.Format("File {0} not found.", filepath)); 106 | } 107 | else 108 | { 109 | IntPtr hModule = LoadLibrary(filepath); 110 | IntPtr textPtr = IntPtr.Zero; 111 | int returnValue = FormatMessage(FormatMessageFlags.FORMAT_MESSAGE_FROM_HMODULE | FormatMessageFlags.FORMAT_MESSAGE_ALLOCATE_BUFFER | FormatMessageFlags.FORMAT_MESSAGE_IGNORE_INSERTS, 112 | hModule, 113 | messageid, 114 | 0, 115 | ref textPtr, 116 | 0, 117 | IntPtr.Zero 118 | ); 119 | if (returnValue == 0) 120 | { 121 | throw new ApplicationException(string.Format("Message ID {0} not found in file {1}", messageid, filepath)); 122 | } 123 | messagetext = Marshal.PtrToStringAnsi(textPtr); 124 | messagetext = messagetext.Replace("\r\n", ""); 125 | } 126 | return messagetext; 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /ReadMe.MD: -------------------------------------------------------------------------------- 1 | # MessageTableReader 2 | 3 | A .Net class to allow access to the message table entries in a .dll or .exe file. This class can be used within PowerShell using ``Add-Type`` or compiled into a Windows library (.dll). 4 | 5 | [![Build Status](https://dev.azure.com/wightsci/GitHubRepos/_apis/build/status/wightsci.MessageTableReader?branchName=master)](https://dev.azure.com/wightsci/GitHubRepos/_build/latest?definitionId=3&branchName=master) 6 | 7 | ## Installation 8 | First, download the MessageTableReader.cs C# file from this repo. 9 | 10 | ### Method 1 11 | Load the file into a variable in your script by using ``Get-Content`` or by pasting it into a here-string. 12 | 13 | Import the class by running by running the following, where ```` is the name of the variable that you have loaded the C# into. 14 | ```PowerShell 15 | Add-Type -TypeDefinition 16 | ``` 17 | 18 | ### Method 2 19 | Import the class directly by running by running the following, where ```` is the path ot the C# file (or dll). 20 | ```PowerShell 21 | Add-Type -Path 22 | ``` 23 | ## Compiling the .DLL 24 | The C# file can be compiled using the C# complier ```csc.exe``` provided as part of the .Net framework. No Visual Studio required. The version of .Net must be 4.0 or greater. 25 | 26 | ``csc.exe /target:library /out:MessageTableReader.dll MessageTableReader.cs`` 27 | 28 | ## Using the Class 29 | 30 | ## Methods 31 | 32 | #### GetMessageList(string filename) 33 | GetMessageList returns a list of all of the messages and message IDs in a file. If no file name is provided, then ``C:\WINDOWS\system32\msobjs.dll`` is used. 34 | 35 | 36 | ### Example 37 | ```PowerShell 38 | PS C:\>$messageTable = New-Object MessageTableReader.Reader 39 | PS C:\>$messageTable.GetMessageList('C:\WINDOWS\system32\msobjs.dll') 40 | 279:Undefined Access (no effect) Bit 7 41 | 1536:Unused message ID 42 | 1537:DELETE 43 | 1538:READ_CONTROL 44 | 1539:WRITE_DAC 45 | 1540:WRITE_OWNER 46 | 1541:SYNCHRONIZE 47 | 1542:ACCESS_SYS_SEC 48 | 1543:MAX_ALLOWED 49 | ... 50 | ``` 51 | 52 | #### GetMessage(string id, string filename) 53 | GetMessage takes a message ID and file name and returns the text of the specific message in the file. If no file name is provided, then ``C:\WINDOWS\system32\msobjs.dll`` is used. 54 | 55 | ### Example 56 | ```PowerShell 57 | PS C:\>$messageTable = New-Object MessageTableReader.Reader 58 | PS C:\>$messageTable.GetMessage(14676,'C:\WINDOWS\system32\msobjs.dll') 59 | Active Directory Domain Services 60 | ``` 61 | 62 | ## What Use is MessageTableReader? 63 | 64 | MessageTableReader was originally conceived to help with monitoring and audit of Windows Event Logs. Many event log messages have placeholder codes that need to be looked up from .dll files. MessageTableReader can do this. There's more detail on this in my ebook *PowerShell and Windows Event Logs*. 65 | 66 | ## Why not just provide a .dll ? 67 | Some organisations are not able to introduce unapproved binary files into their environment (with good reason). The textual C# is an alternative, and the code can be easily scrutinised. -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Add steps that build, run tests, deploy, and more: 2 | # https://aka.ms/yaml 3 | 4 | trigger: 5 | branches: 6 | include: 7 | - master 8 | paths: 9 | exclude: 10 | - ReadMe.MD 11 | pool: 12 | vmImage: 'windows-latest' 13 | steps: 14 | - script: C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:$(System.DefaultWorkingDirectory)\mtr.dll $(System.DefaultWorkingDirectory)\messagetablereader.cs 15 | displayName: "Build Library using csc.exe" 16 | - powershell: | 17 | Add-Type -Path $(System.DefaultWorkingDirectory)\mtr.dll 18 | $mtr = New-Object MessageTableReader.Reader 19 | if ($mtr.GetMessage(16392) -eq 'MD5') { 20 | return $true 21 | } 22 | else { 23 | throw "GetMessage failed." 24 | } 25 | displayName: "Test assembly load and GetMessage method" --------------------------------------------------------------------------------