{"id":3331,"date":"2013-06-26T00:01:00","date_gmt":"2013-06-26T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2013\/06\/26\/use-powershell-to-interact-with-the-windows-api-part-2\/"},"modified":"2013-06-26T00:01:00","modified_gmt":"2013-06-26T00:01:00","slug":"use-powershell-to-interact-with-the-windows-api-part-2","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/use-powershell-to-interact-with-the-windows-api-part-2\/","title":{"rendered":"Use PowerShell to Interact with the Windows API: Part 2"},"content":{"rendered":"<p><strong style=\"font-size: 12px\">Summary<\/strong><span style=\"font-size: 12px\">: Guest blogger, Matt Graeber, talks more about interacting with the Windows API.<\/span><\/p>\n<p>Microsoft Scripting Guy, Ed Wilson, is here. Matt Graeber is back today with Part 2 of a three-part series that started yesterday: <a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/2013\/06\/25\/use-powershell-to-interact-with-the-windows-api-part-1.aspx\" target=\"_blank\">Use PowerShell to Interact with the Windows API: Part 1<\/a>.<\/p>\n<p>Now, here&rsquo;s Matt&hellip;<\/p>\n<p>In my last post, I described how to use the <strong>Add-Type<\/strong> cmdlet to interact with the Windows API; specifically, the <strong>CopyFile<\/strong> function in kernel32.dll. Today, I&rsquo;m going to describe another method in which you can interact with Windows API functions: extracting private methods from the .NET Framework.<\/p>\n<p>For most people, the <strong>Add-Type<\/strong> method will always be sufficient. However, for people like me who write security and forensics tools, where a minimal forensic footprint is necessary, <strong>Add-Type<\/strong> doesn&rsquo;t cut it because it writes temporary files to disk and calls csc.exe to compile C# code. So if having a minimal forensic footprint is a necessity for your script (or if you like to do things the hard way), there are other methods of calling Windows API functions.<\/p>\n<p>A large portion of the .NET Framework is actually built on the Windows API. It&rsquo;s just not exposed to you publicly. Pulling out the Win API functions that are used by the .NET Framework requires a basic understanding of the internal layout of .NET.<\/p>\n<p>There are several tools available for exploring the .NET Framework, including these:<\/p>\n<ul>\n<li><a href=\"http:\/\/www.red-gate.com\/products\/dotnet-development\/reflector\/\" target=\"_blank\">.NET Reflector&nbsp;8<\/a><\/li>\n<li><a href=\"http:\/\/ilspy.net\/\" target=\"_blank\">ILSpy .NET Decompiler<\/a><\/li>\n<\/ul>\n<p>What you need to know for our purposes is that Windows API function calls in .NET are usually non-public (that is, private), static methods that have a <strong>DllImport<\/strong> attribute associated with them. To help us dig into the .NET Framework, I wrote a helper function called <a href=\"http:\/\/gallery.technet.microsoft.com\/scriptcenter\/Find-WinAPIFunction-4166b223\" target=\"_blank\">Find-WinAPIFunction<\/a>. (The script is also available in the Script Center Repository.) It searches the loaded modules in a Windows PowerShell session for a reference to a private Windows API function.<\/p>\n<p style=\"padding-left: 30px\"><strong>Find-WinAPIFunction function<\/strong>:<span style=\"font-size: 12px\">&nbsp;<\/span><\/p>\n<p style=\"padding-left: 30px\">function Find-WinAPIFunction<\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">&lt;#<\/p>\n<p style=\"padding-left: 30px\">.SYNOPSIS<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Searches all loaded assemblies in your PowerShell session for a<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Windows API function.<\/p>\n<p style=\"padding-left: 30px\">.PARAMETER Module<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Specifies the name of the module that implements the function. This<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; is typically a system dll (e.g. kernel32.dll).<\/p>\n<p style=\"padding-left: 30px\">.PARAMETER FunctionName<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Specifies the name of the function you&#8217;re searching for.<\/p>\n<p style=\"padding-left: 30px\">.OUTPUTS<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; [System.Reflection.MethodInfo]<\/p>\n<p style=\"padding-left: 30px\">.EXAMPLE<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Find-WinAPIFunction kernel32.dll CopyFile<\/p>\n<p style=\"padding-left: 30px\">#&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; [CmdletBinding()]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; [OutputType([System.Reflection.MethodInfo])]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Param<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; (<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Parameter(Mandatory = $True, Position = 0)]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [ValidateNotNullOrEmpty()]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [String]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Module,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Parameter(Mandatory = $True, Position = 1)]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [ValidateNotNullOrEmpty()]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [String]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $FunctionName<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; )<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; [System.AppDomain]::CurrentDomain.GetAssemblies() |<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ForEach-Object { $_.GetTypes() } |<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ForEach-Object { $_.GetMethods(&#8216;NonPublic, Public, Static&#8217;) } |<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ForEach-Object { $MethodInfo = $_; $_.GetCustomAttributes($false) } |<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Where-Object {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $MethodInfo.Name.ToLower() -eq $FunctionName.ToLower() -and<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $_.Value -eq $Module<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } | ForEach-Object { $MethodInfo }<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p>The <strong>Find-WinAPIFunction<\/strong> function works by drilling down through all of the loaded assemblies in a Windows PowerShell session. To understand what&rsquo;s going on, think of your Windows PowerShell session as a series of containers. It starts with an AppDomain. Each Windows PowerShell session has a default AppDomain, which you can think of as an execution sandbox.<\/p>\n<p>Within an AppDomain, there are multiple loaded assemblies. An assembly is a container for a module, and it is typically a DLL, such as mscorlib.dll or System.dll. Within assemblies, are modules that are containers for types (that is, classes).<\/p>\n<p>Finally, a type is a container for members. Members consist of methods, properties, fields, events, nested types, and constructors. The concept of members should be familiar to those who are familiar with the <strong>Get-Member<\/strong> cmdlet. The following diagram may help illustrate this concept of compartmentalization more clearly.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3124.hsg-6-26-13-1.png\"><img decoding=\"async\" title=\"Image of compartments\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3124.hsg-6-26-13-1.png\" alt=\"Image of compartments\" \/><\/a><\/p>\n<p><strong>Find-WinAPIFunction<\/strong> starts by iterating through all assemblies in the current AppDomain. It then iterates through all types within those assemblies, ultimately looking for the method you&rsquo;re searching for.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/1348.hsg-6-26-13-2.png\"><img decoding=\"async\" title=\"Image of command output\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/1348.hsg-6-26-13-2.png\" alt=\"Image of command output\" \/><\/a><\/p>\n<p>Now that we know that a <strong>CopyFile<\/strong> method exists, we know that we can use it in Windows PowerShell. However, rather than searching for it each time with <strong>Find-WinAPIFunction<\/strong>, let&rsquo;s pull out some information that will allow us to quickly get a reference to our target <strong>CopyFile<\/strong> method:<\/p>\n<p><a href=\"https:\/\/msdnshared.blob.core.windows.net\/media\/TNBlogsFS\/prod.evol.blogs.technet.com\/CommunityServer.Blogs.Components.WeblogFiles\/00\/00\/00\/76\/18\/0882.Capture.PNG\"><img decoding=\"async\" title=\"Image of command output\" src=\"https:\/\/msdnshared.blob.core.windows.net\/media\/TNBlogsFS\/prod.evol.blogs.technet.com\/CommunityServer.Blogs.Components.WeblogFiles\/00\/00\/00\/76\/18\/0882.Capture.PNG\" alt=\"Image of command output\" \/><\/a><\/p>\n<p>As can be seen in this screenshot, we need the name of the assembly and type that contains the <strong>CopyFile<\/strong> method. These are respectively mscorlib.dll and Microsoft.Win32.Win32Native.<\/p>\n<p>Now let&rsquo;s pull everything together with another implementation of the <strong>Copy-RawItem<\/strong> function, which made its debut in Part&nbsp;1 of this series. This time, we have the <a href=\"http:\/\/gallery.technet.microsoft.com\/scriptcenter\/Copy-RawItem-Private-NET-78917643\" target=\"_blank\">Private .NET Method version<\/a>, which is also available in the Script Center Repository.<\/p>\n<p style=\"padding-left: 30px\"><strong>Copy-RawItem Private .NET Method version<\/strong>:<\/p>\n<p style=\"padding-left: 30px\">function Copy-RawItem<\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">&lt;#<\/p>\n<p style=\"padding-left: 30px\">.SYNOPSIS<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Copies a file from one location to another including files contained within DeviceObject paths.<\/p>\n<p style=\"padding-left: 30px\">.PARAMETER Path<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Specifies the path to the file to copy.<\/p>\n<p style=\"padding-left: 30px\">.PARAMETER Destination<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Specifies the path to the location where the item is to be copied.<\/p>\n<p style=\"padding-left: 30px\">.PARAMETER FailIfExists<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Do not copy the file if it already exists in the specified destination.<\/p>\n<p style=\"padding-left: 30px\">.OUTPUTS<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; None or an object representing the copied item.<\/p>\n<p style=\"padding-left: 30px\">.EXAMPLE<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Copy-RawItem &#8216;\\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy2\\Windows\\System32\\config\\SAM&#8217; &#8216;C:\\temp\\SAM&#8217;<\/p>\n<p style=\"padding-left: 30px\">#&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; [CmdletBinding()]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; [OutputType([System.IO.FileSystemInfo])]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Param (<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Parameter(Mandatory = $True, Position = 0)]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [ValidateNotNullOrEmpty()]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [String]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Path,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Parameter(Mandatory = $True, Position = 1)]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [ValidateNotNullOrEmpty()]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [String]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Destination,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Switch]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $FailIfExists<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; )<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; # Get a reference to the internal method &#8211; Microsoft.Win32.Win32Native.CopyFile()<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $mscorlib = [AppDomain]::CurrentDomain.GetAssemblies() | ? {$_.Location -and ($_.Location.Split(&#8216;\\&#8217;)[-1] -eq &#8216;mscorlib.dll&#8217;)}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $Win32Native = $mscorlib.GetType(&#8216;Microsoft.Win32.Win32Native&#8217;)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $CopyFileMethod = $Win32Native.GetMethod(&#8216;CopyFile&#8217;, ([Reflection.BindingFlags] &#8216;NonPublic, Static&#8217;))<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; # Perform the copy<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $CopyResult = $CopyFileMethod.Invoke($null, @($Path, $Destination, ([Bool] $PSBoundParameters[&#8216;FailIfExists&#8217;])))<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $HResult = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()<\/p>\n<p style=\"padding-left: 30px\">&nbsp; &nbsp;&nbsp;if ($CopyResult -eq $False -and $HResult -ne 0)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # An error occured. Display the Win32 error set by CopyFile<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw ( New-Object ComponentModel.Win32Exception )<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; else<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Write-Output (Get-ChildItem $Destination)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p>This version of <strong>Copy-RawItem<\/strong> provides identical functionality as the <strong>Add-Type<\/strong> version, but it implements the private method extraction technique described in this post. The function starts by getting a reference to mscorlib.dll, which is the assembly that contains the <strong>CopyFile<\/strong> method. It then gets a reference to the type that contains the Microsoft.Win32.Win32Native method by calling the <strong>GetType<\/strong> method. Finally, it gets a reference to the <strong>CopyFile<\/strong> method by calling the <strong>GetMethod<\/strong> method, specifying that a <strong>NonPublic<\/strong>, <strong>Static<\/strong> method is requested.<\/p>\n<p>The technique described requires a bit more knowledge of the layout of the .NET Framework. However, armed with this knowledge, you will be able to pull internal functionality into Windows PowerShell, which wouldn&rsquo;t otherwise be available to you.<\/p>\n<p>In the last and final post of the series, we&rsquo;ll dig into .NET internals even more and see how to use reflection to dynamically build a method that calls Windows API functions.<\/p>\n<p>The two scripts used today are available in the Script Center Repository:<\/p>\n<ul>\n<li><a href=\"http:\/\/gallery.technet.microsoft.com\/scriptcenter\/Find-WinAPIFunction-4166b223\" target=\"_blank\">Find-WinAPIFunction<\/a><\/li>\n<li><a href=\"http:\/\/gallery.technet.microsoft.com\/scriptcenter\/Copy-RawItem-Private-NET-78917643\" target=\"_blank\">Private .NET Method version<\/a><\/li>\n<\/ul>\n<p>~Matt<\/p>\n<p>Thanks, Matt. Join us tomorrow as Matt brings you the final part of this series.<\/p>\n<p>I invite you to follow me on <a href=\"http:\/\/bit.ly\/scriptingguystwitter\" target=\"_blank\">Twitter<\/a> and <a href=\"http:\/\/bit.ly\/scriptingguysfacebook\" target=\"_blank\">Facebook<\/a>. If you have any questions, send email to me at <a href=\"mailto:scripter@microsoft.com\" target=\"_blank\">scripter@microsoft.com<\/a>, or post your questions on the <a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\">Official Scripting Guys Forum<\/a>. See you tomorrow. Until then, peace.<\/p>\n<p><strong>Ed Wilson, Microsoft Scripting Guy<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary: Guest blogger, Matt Graeber, talks more about interacting with the Windows API. Microsoft Scripting Guy, Ed Wilson, is here. Matt Graeber is back today with Part 2 of a three-part series that started yesterday: Use PowerShell to Interact with the Windows API: Part 1. Now, here&rsquo;s Matt&hellip; In my last post, I described how [&hellip;]<\/p>\n","protected":false},"author":596,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[56,431,3,63,45],"class_list":["post-3331","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-guest-blogger","tag-matt-graeber","tag-scripting-guy","tag-security","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Summary: Guest blogger, Matt Graeber, talks more about interacting with the Windows API. Microsoft Scripting Guy, Ed Wilson, is here. Matt Graeber is back today with Part 2 of a three-part series that started yesterday: Use PowerShell to Interact with the Windows API: Part 1. Now, here&rsquo;s Matt&hellip; In my last post, I described how [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/3331","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=3331"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/3331\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=3331"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=3331"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=3331"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}