There are times when neither PowerShell’s cmdlets nor scripting language directly support a feature you need. In most of those situations, PowerShell’s direct support for the .NET Framework provides another avenue to let you accomplish your task. In some cases, though, even the .NET Framework does not support a feature you need to resolve a problem, and the only way to resolve your problem is to access the core Windows APIs.
For complex API calls (ones that take highly structured data), the solution is to write a PowerShell cmdlet that uses the P/Invoke (Platform Invoke) support in the .NET Framework. The P/Invoke support in the .NET Framework lets you access core Windows APIs directly.
Although it is possible to determine these P/Invoke definitions yourself, it is usually easiest to build on the work of others. If you want to know how to call a specific Windows API from a .NET language, the http://pinvoke.net web site is the best place to start.
If the API you need to access is straightforward (one that takes and returns only sim ple data types), however, Example 157 lets you call these Windows APIs directly from PowerShell.
Example 157. InvokeWindowsApi.ps1
############################################################################## ## ## InvokeWindowsApi.ps1 ## ## Invoke a native Windows API call that takes and returns simple data types. ## ## ie: ## ## ## Prepare the parameter types and parameters for the ## CreateHardLink function ## $parameterTypes = [string], [string], [IntPtr] ## $parameters = [string] $filename, [string] $existingFilename, [IntPtr]::Zero ##
Example 157. InvokeWindowsApi.ps1 (continued)
## ## Call the CreateHardLink method in the Kernel32 DLL ## $result = InvokeWindowsApi "kernel32" ([bool]) "CreateHardLink" ` ## $parameterTypes $parameters ## ############################################################################## param(
[string] $dllName, [Type] $returnType, [string] $methodName, [Type[]] $parameterTypes, [Object[]] $parameters )
## Begin to build the dynamic assembly $domain = [AppDomain]::CurrentDomain $name = NewObject Reflection.AssemblyName 'PInvokeAssembly' $assembly = $domain.DefineDynamicAssembly($name, 'Run') $module = $assembly.DefineDynamicModule('PInvokeModule') $type = $module.DefineType('PInvokeType', "Public,BeforeFieldInit")
## Go through all of the parameters passed to us. As we do this, ## we clone the user's inputs into another array that we will use for ## the P/Invoke call. $inputParameters = @() $refParameters = @()
for($counter = 1; $counter le $parameterTypes.Length; $counter++)
{ ## If an item is a PSReference, then the user ## wants an [out] parameter. if($parameterTypes[$counter 1] eq [Ref]) {
## Remember which parameters are used for [Out] parameters $refParameters += $counter
## On the cloned array, we replace the PSReference type with the ## .Net reference type that represents the value of the PSReference, ## and the value with the value held by the PSReference. $parameterTypes[$counter 1] =
$parameters[$counter 1].Value.GetType().MakeByRefType()
$inputParameters += $parameters[$counter 1].Value } else {
## Otherwise, just add their actual parameter to the ## input array. $inputParameters += $parameters[$counter 1]
} }
## Define the actual P/Invoke method, adding the [Out] ## attribute for any parameters that were originally [Ref]
Example 157. InvokeWindowsApi.ps1 (continued)
## parameters. $method = $type.DefineMethod($methodName, 'Public,HideBySig,Static,PinvokeImpl',
$returnType, $parameterTypes) foreach($refParameter in $refParameters) {
[void] $method.DefineParameter($refParameter, "Out", $null) }
## Apply the P/Invoke constructor $ctor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([string]) $attr = NewObject Reflection.Emit.CustomAttributeBuilder $ctor, $dllName $method.SetCustomAttribute($attr)
## Create the temporary type, and invoke the method. $realType = $type.CreateType()
$realType.InvokeMember($methodName, 'Public,Static,InvokeMethod', $null, $null, $inputParameters)
## Finally, go through all of the reference parameters, and update the ## values of the PSReference objects that the user passed in. foreach($refParameter in $refParameters) {
$parameters[$refParameter 1].Value = $inputParameters[$refParameter 1] }