{"id":807,"date":"2022-09-20T05:46:45","date_gmt":"2022-09-20T12:46:45","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/powershell-community\/?p=807"},"modified":"2023-03-30T09:04:07","modified_gmt":"2023-03-30T16:04:07","slug":"powershell-registry-monitor","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/powershell-community\/powershell-registry-monitor\/","title":{"rendered":"PowerShell Registry Monitor"},"content":{"rendered":"<p>Recently, while discussing work-related topics, a co-worker asked me if there is a way of monitoring changes on a Windows registry key. I knew we can monitor files, with the <strong>System.IO.FileSystemWatcher<\/strong> .NET class, but never heard of registry monitoring. Well, turns out Windows provides an API for it, and with the help of Interop Services, we can call it from PowerShell.<\/p>\n<h2>About tools<\/h2>\n<p>To accomplish this, we will need to work with <strong>Platform Invoke<\/strong>, or <em>PinVoke<\/em> for short. It consists of a .NET library that wraps the native APIs to be called by managed .NET code. This library comes with Windows, on the Global Assembly Cache, and also with PowerShell Core.<\/p>\n<p>In addition to that, we will work with a couple of Windows API functions, listed below:<\/p>\n<ul>\n<li><strong>RegOpenKeyEx:<\/strong> Responsible for opening a <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/sysinfo\/handles-and-objects\">handle<\/a> to the key.<\/li>\n<li><strong>RegNotifyChangeKeyValue:<\/strong> Responsible for monitoring the key, and triggering an event when a change happens.<\/li>\n<li><strong>CreateEvent:<\/strong> Responsible for creating the event.<\/li>\n<li><strong>WaitForSingleObject:<\/strong> This will monitor the event, and return a result based on the outcome.<\/li>\n<li><strong>RegCloseKey:<\/strong> To close the handle to our registry key.<\/li>\n<li><strong>CloseHandle:<\/strong> To close the handle to the event created.<\/li>\n<\/ul>\n<p>The last two commands are not mandatory, because the Interop Services will wrap the handles in something called <strong>Safe Handle<\/strong>. This handle is released by the Garbage Collector at the end, but it&#8217;s not only a good practice, it creates the habit of monitoring object&#8217;s lifecycles. If we are looking into interoperating with Windows more often, we need to get used to how it manages memory, to avoid unexpected behavior.<\/p>\n<p>If you want a series of posts based on <strong>PinVoke<\/strong> and interoperability, let me know in the comments!<\/p>\n<h2>About definition<\/h2>\n<p>If we want to leverage <strong>System.Runtime.InteropServices<\/strong>, we need to write part of our code in C#. Don&#8217;t get intimidated, C# and PowerShell are very similar, and it won&#8217;t be hard at all. Let&#8217;s start by defining our functions.<\/p>\n<p>I will demonstrate step by step with <strong>RegOpenKeyEx<\/strong>, and the others will follow the same procedure. From Microsoft&#8217;s documentation page, the function definition looks like this:<\/p>\n<pre><code class=\"language-cpp\">LSTATUS RegOpenKeyExW(\n  [in]           HKEY    hKey,\n  [in, optional] LPCWSTR lpSubKey,\n  [in]           DWORD   ulOptions,\n  [in]           REGSAM  samDesired,\n  [out]          PHKEY   phkResult\n);<\/code><\/pre>\n<p>Don&#8217;t worry about the <code>W<\/code> at the end. Most of Windows functions have two versions, the <strong>ANSI<\/strong> version, and <strong>UNICODE<\/strong> version. Functions terminated in <code>A<\/code> are <strong>ANSI<\/strong> compliant, the <code>W<\/code> ones comply to <strong>UNICODE<\/strong>. If you call <strong>RegOpenKeyEx<\/strong>, Windows will route to one of the two.<\/p>\n<p>In order to represent this function with C#, we need to convert the parameter types. This process is often called <em>marshalling<\/em>. We can interpret these types as follows:<\/p>\n<ul>\n<li><code>HKEY<\/code>: This represents a <strong>handle<\/strong>. Handles are a type of <strong>Pointer<\/strong>, so we can represent it as <strong>System.IntPtr<\/strong>. Since memory addresses are numbers, this type is a special kind of integer.<\/li>\n<li><code>LPCWSTR<\/code>: A pointer to a constant string with 16-bit Unicode characters. For us, a <strong>System.String<\/strong>.<\/li>\n<li><code>DWORD<\/code>: A 32-bit unsigned integer. In other words, a <strong>System.UInt32<\/strong>.<\/li>\n<li><code>REGSAM<\/code>: A Registry Security Access Mask. We will talk about it in a bit.<\/li>\n<li><code>PHKEY<\/code>: A pointer to a variable that will receive the opened key handle. We know that we can represent pointers as <strong>System.IntPtr<\/strong>.<\/li>\n<li><code>LSTATUS<\/code>: The function&#8217;s return type. This maps to a <strong>long<\/strong>. We will represent it with <strong>System.Int<\/strong>.<\/li>\n<\/ul>\n<p>The <strong>REGSAM<\/strong> data type is a list of definitions that will map Registry Key security to unsigned integers, so we can represent it as a <strong>System.Uint32<\/strong>. We will be using the <strong>KEY_NOTIFY<\/strong> REGSAM, which translates to <code>0x0010<\/code>. At the end, our function definition will look something like this:<\/p>\n<pre><code class=\"language-csharp\">[DllImport(\"Advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\npublic static extern int RegOpenKeyExW(\n    IntPtr hKey,\n    string lpSubKey,\n    uint ulOptions,\n    uint samDesired,\n    out IntPtr phkResult\n);<\/code><\/pre>\n<p>The first line in square brackets is called <strong>DllImport Attribute<\/strong>. It&#8217;s what tells PinVoke which DLL contains the definition for <strong>RegOpenKeyExW<\/strong>. <code>CharSet = CharSet.Unicode<\/code> defines Unicode as our encoding, and <code>SetLastError = true<\/code> will set the last error with the corresponding Win32 error, if the function call fails. Setting the last error is crucial for debugging and troubleshooting these function calls.<\/p>\n<p>Following the same approach, we write the full code:<\/p>\n<pre><code class=\"language-csharp\">using System;\nusing System.Runtime.InteropServices;\n\nnamespace Win32\n{\n    public class Regmon\n    {\n        [DllImport(\"Advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n        public static extern int RegOpenKeyExW(\n            int hKey,\n            string lpSubKey,\n            int ulOptions,\n            uint samDesired,\n            out IntPtr phkResult\n        );\n\n        [DllImport(\"Advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n        public static extern int RegNotifyChangeKeyValue(\n            IntPtr hKey,\n            bool bWatchSubtree,\n            int dwNotifyFilter,\n            IntPtr hEvent,\n            bool fAsynchronous\n        );\n\n        [DllImport(\"Advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n        public static extern int RegCloseKey(IntPtr hKey);\n\n        [DllImport(\"Advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n        public static extern int CloseHandle(IntPtr hKey);\n\n        [DllImport(\"kernel32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n        public static extern IntPtr CreateEventW(\n            int lpEventAttributes,\n            bool bManualReset,\n            bool bInitialState,\n            string lpName\n        );\n\n        [DllImport(\"kernel32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n        public static extern int WaitForSingleObject(\n            IntPtr hHandle,\n            int dwMilliseconds\n        );\n    }\n}<\/code><\/pre>\n<p>Originally, the parameter <strong>lpEventAttributes<\/strong> is from the <strong>LPSECURITY_ATTRIBUTES<\/strong>, which is a structure. Since we are not going to use it, defining as <strong>int<\/strong> won&#8217;t cause troubles. If we were to use it, we could define <a href=\"https:\/\/docs.microsoft.com\/en-us\/previous-versions\/windows\/desktop\/legacy\/aa379560(v=vs.85)\">LPSECURITY_ATTRIBUTES<\/a>.<\/p>\n<h2>Writing the PowerShell code<\/h2>\n<p>Now that all the paper work is done, we can write the PowerShell code that will use these functions. To avoid filling your screen with repetitive code, I will represent the previous definition text as <code>$signature<\/code>. You just have to create a string that will receive the C# code. I use here-strings:<\/p>\n<pre><code class=\"language-powershell\">$signature = @'\n    Your code goes here.\n'@<\/code><\/pre>\n<p>The final script looks like this:<\/p>\n<pre><code class=\"language-powershell\">using namespace System.Runtime.InteropServices\n\n[CmdletBinding()]\nparam (\n    [Parameter(Mandatory)]\n    [string]$KeyPath,\n\n    [Parameter()]\n    [string]$LogPath = \"$PSScriptRoot\\RegMon-$(Get-Date -Format 'yyyyMMdd-hhmmss').log\",\n\n    [Parameter()]\n    [int]$Timeout = 0xFFFFFFFF #INFINITE\n)\n\nAdd-Type -TypeDefinition $signature\n\nif (!(Test-Path -Path $KeyPath)) { throw \"Registry key not found.\" }\n\nswitch -Wildcard ((Get-Item $KeyPath).Name) {\n    'HKEY_CLASSES_ROOT*' { $regdefault = 0x80000000 }\n    'HKEY_CURRENT_USER*' { $regdefault = 0x80000001 }\n    'HKEY_LOCAL_MACHINE*' { $regdefault = 0x80000002 }\n    'HKEY_USERS*' { $regdefault = 0x80000003 }\n    Default { throw 'Unsuported hive.' }\n}\n\n$handle = [IntPtr]::Zero\n$result = [Win32.Regmon]::RegOpenKeyExW($regdefault, ($KeyPath -replace '^.*:\\\\'), 0, 0x0010, [ref]$handle)\n$event = [Win32.Regmon]::CreateEventW($null, $true, $false, $null)\n\n&lt;#\nThis will run indefinitely until it fails or reaches a timeout.\nAdjust accordingly.\n#&gt;\n:Outer while ($true) {\n    $result = [Win32.Regmon]::RegNotifyChangeKeyValue(\n        $handle,\n        $false,\n        0x00000001L -bor #REG_NOTIFY_CHANGE_NAME\n        0x00000002L -bor #REG_NOTIFY_CHANGE_ATTRIBUTES\n        0x00000004L -bor #REG_NOTIFY_CHANGE_LAST_SET\n        0x00000008L, #REG_NOTIFY_CHANGE_SECURITY\n        $event,\n        $true\n    )\n    $wait = [Win32.Regmon]::WaitForSingleObject($event, $Timeout)\n\n    switch ($wait) {\n        0xFFFFFFFF { break Outer } #WAIT_FAILED\n\n        0x00000102L { #WAIT_TIMEOUT\n            $outp = 'Timeout reached.'\n            Write-Host $outp -ForegroundColor DarkGreen\n            Add-Content -FilePath $LogPath -Value $outp\n            break Outer\n        }\n\n        0 { #WAIT_OBJECT_0 ~&gt; Change detected.\n            $outp = \"Change triggered on the specified key. Timestamp: $(Get-Date -Format 'hh:mm:ss - dd\/MM\/yyyy').\"\n            Write-Host $outp -ForegroundColor DarkGreen\n            Add-Content -FilePath $LogPath -Value $outp\n        }\n    }\n}\n\n[Win32.Regmon]::CloseHandle($event)\n[Win32.Regmon]::RegCloseKey($handle)<\/code><\/pre>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p> When calling <strong>RegOpenKeyExW<\/strong> for the first time, we don&#8217;t have the handle to the key yet, so we specify which root key we want to use. The parameter <strong>lpSubKey<\/strong> is optional. When not specified, the function will monitor the root key. <\/div><\/p>\n<h2>Caveats<\/h2>\n<p>The <strong>RegNotifyChangeKeyValue<\/strong> is limited on what information it provides to the caller. If the parameter <strong>bWatchSubtree<\/strong> is false, the function will monitor only the key specified. If this parameter is true, the function monitors subtrees, but if an event is triggered, it will not inform which key was modified.<\/p>\n<p>Is there a way of getting more information about Registry Events? <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/etw\/registry\">Yes<\/a>, but this is a topic for another post.<\/p>\n<h2>Conclusion<\/h2>\n<p>I hope this post made calling Windows API Functions with PowerShell less intimidating. Once you get used to Platform Invoke, you will need a bigger toolbox to store your new tools.<\/p>\n<p>Thank you for following along, once again, and I will see you next time!<\/p>\n<p>Useful links.<\/p>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/native-interop\/pinvoke\">Platform Invoke<\/a><\/li>\n<li><a href=\"https:\/\/www.pinvoke.net\/\">A great resource of examples and how-tos for PinVoke<\/a><\/li>\n<\/ul>\n<p>Want to test, or give suggestions on our <strong>WindowsUtils<\/strong> PowerShell module? Check out <a href=\"https:\/\/github.com\/FranciscoNabas\/WindowsUtils\">Windows Utils<\/a>.<\/p>\n<p><a href=\"https:\/\/github.com\/FranciscoNabas\/\">See what I&#8217;m up to<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to set up a simple registry key monitor with PowerShell<\/p>\n","protected":false},"author":62334,"featured_media":77,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[13],"tags":[77,3,40,79,78],"class_list":["post-807","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell","tag-com","tag-powershell","tag-registry","tag-win32","tag-windows-management"],"acf":[],"blog_post_summary":"<p>How to set up a simple registry key monitor with PowerShell<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts\/807","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/users\/62334"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/comments?post=807"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts\/807\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/media\/77"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/media?parent=807"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/categories?post=807"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/tags?post=807"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}