Use PowerShell to Interact with the Windows API: Part 1

Use PowerShell to Interact with the Windows API: Part 1

  • Comments 2
  • Likes

Summary: Guest blogger, Matt Graeber, discusses how to use Windows PowerShell to interact with Windows API functions in Part 1 of a three-part series.

Microsoft Scripting Guy, Ed Wilson, is here. Guest blogger, Matt Graeber, is back. Matt first joined us as a guest yesterday with his post Use PowerShell and Regular Expressions to Search Binary Data. 

Welcome back, Matt… 

I often finding myself needing to use Windows PowerShell to interact with Windows API functions to accomplish a low-level task. For those who are not familiar with the Windows API, basically, it refers to the functionality that is exposed by your built-in system DLLs. For example, kernel32.dll exposes hundreds of functions that can be used by developers for interacting with the operating system. 

In Windows PowerShell, there are three ways to interact with Windows API functions: 

  1. Use the Add-Type cmdlet to compile C# code. This is the officially documented method.
  2. Get a reference to a private type in the .NET Framework that calls the method.
  3. Use reflection to dynamically define a method that calls the Windows API function.

Background to using Add-Type

In the examples that follow, I use the CopyFile function in kernel32.dll as the function that Windows PowerShell will interact with. Now, you may have just asked the question, “Why would I want to call CopyFile when Windows PowerShell already has the Copy-Item cmdlet?” 

That’s a very good question, indeed. As it turns out though, there are certain file paths that the Windows PowerShell file provider doesn’t know how to handle. In particular, it doesn’t know how to interpret special device object paths, such as: \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy2\Windows\System32\calc.exe. These are the kinds of paths that you need to deal with when you interact with files that are backed up by the Volume Shadow Copy Service. For example, running the following command lists the device object paths of each volume shadow copy: 

# Run this from an administrative prompt

Get-WmiObject Win32_ShadowCopy | Select-Object DeviceObject 

Before diving into using Windows PowerShell to call CopyFile, it is helpful to have some background on C/C++ types vs. .NET types. According to MSDN documentation (see CopyFile function), CopyFile has the following function definition: 

Image of code

So CopyFile has a return type of BOOL and three parameters—two ‘T’ strings and a bool. The key to interacting with Win API functions is knowing how to convert these C/C++ types to the equivalent .NET type. Fortunately, there is a site dedicated to this cause: PINVOKE.NET , which provides a treasure trove of the type of information you need to perform this task. It is worth a bookmark in your favorites. 

If you search for CopyFile on PINVOKE.NET, you will find the following C# definition: 

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern bool CopyFile(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);

You can now see the translation of C/C++ types to .NET types:

BOOL -> bool

LPCTSTR -> string 

Now, you should have the background needed to start using Windows PowerShell to interact with the CopyFile function. Let’s jump into our first method of interaction, Add-Type.

Using Add-Type to call the CopyItem function

The Add-Type cmdlet is used to define .NET types that will be made available to your Windows PowerShell session. What’s really cool about it is that it can compile C# code on the fly. If you view the Help for Add-Type, you’ll find some good examples of how to call Windows API functions. 

As an example, the following code allows me to call the CopyItem function within Windows PowerShell: 

$MethodDefinition = @'

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]

public static extern bool CopyFile(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);

'@

$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -Namespace 'Win32' -PassThru

# You may now call the CopyFile function

# Copy calc.exe to the user's desktop

$Kernel32::CopyFile("$($Env:SystemRoot)\System32\calc.exe", "$($Env:USERPROFILE)\Desktop\calc.exe", $False) 

The $MethodDefinition variable simply contains the C# definition that I took from PINVOKE.NET with one minor modification: I defined the CopyFile method to be public. Methods that are added with Add-Type must be public to easily interact with them in Windows PowerShell. 

I then call Add-Type and provide the C# source code, a type name, and a namespace. By specifying the type name and namespace, after calling Add-Type you can reference the new type in WindowsPowerShell with `[Win32.Kernel32]`.

Lastly, by default, Add-Type doesn’t output the type definition that it creates. The -PassThru parameter tells it to output the type definition.

After calling Add-Type, you can finally call CopyFile directly within Windows PowerShell. The previous example simply copies calc.exe to the user’s desktop. It’s worth noting the two colons that follow the $Kernel32 variable. These indicate that you are calling a static .NET method. All static methods are called this way in Windows PowerShell.

To wrap things up, I wrote the Copy-RawItem function that wraps the CopyFile function nicely. The full script is also available in the Script Center Repository: Copy-RawItem (Add-Type Version).

Copy-RawItem Function:

function Copy-RawItem

{

<#

.SYNOPSIS

    Copies a file from one location to another including files contained within DeviceObject paths.

.PARAMETER Path

    Specifies the path to the file to copy.

.PARAMETER Destination

    Specifies the path to the location where the item is to be copied.

.PARAMETER FailIfExists

    Do not copy the file if it already exists in the specified destination.

.OUTPUTS

    None or an object representing the copied item.

.EXAMPLE

    Copy-RawItem '\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy2\Windows\System32\config\SAM' 'C:\temp\SAM'

#>

    [CmdletBinding()]

    [OutputType([System.IO.FileSystemInfo])]

    Param (

        [Parameter(Mandatory = $True, Position = 0)]

        [ValidateNotNullOrEmpty()]

        [String]

        $Path,

        [Parameter(Mandatory = $True, Position = 1)]

        [ValidateNotNullOrEmpty()]

        [String]

        $Destination,

        [Switch]

        $FailIfExists

    )

    $MethodDefinition = @'

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]

    public static extern bool CopyFile(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);

'@

 

    $Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -Namespace 'Win32' -PassThru

 

    # Perform the copy

    $CopyResult = $Kernel32::CopyFile($Path, $Destination, ([Bool] $PSBoundParameters['FailIfExists']))

 

    if ($CopyResult -eq $False)

    {

        # An error occured. Display the Win32 error set by CopyFile

        throw ( New-Object ComponentModel.Win32Exception )

    }

    else

    {

        Write-Output (Get-ChildItem $Destination)

    }

}

The following image illustrates using the Copy-RawItem function.

Image of command output

The only code in the Copy-RawItem that warrants explanation is the last few lines where error checking occurs. The MSDN documentation states that if CopyFile returns FALSE, an error occurred. In C/C++, you would determine the cause for the error by calling GetLastError. In Windows PowerShell, you can channel the error by throwing a ComponentModel.Win32Exception object. This is what allows you to pry out the error from a Win API function without calling GetLastError.

~Matt

Thank you, Matt. Join us tomorrow when Matt will continue this series.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • This is great stuff. Needed something like this for a beginning.

    Thank you for your efforts and time.

  • This is great stuff. Needed something like this for a beginning.

    Thank you for your efforts and time.