PowerShellからWin32APIを呼ぶ

PowerShellからWin32APIを呼ぶ方法は、Precision Computing - Get the Owner of a Process in PowerShell -- P/Invoke and Ref/Out Parametersを起源にして? 色々紹介されてるみたいなのですが、自分で使ってる奴を紹介します。


function New-PType() {
[AppDomain]::CurrentDomain.DefineDynamicAssembly(
(New-Object Reflection.AssemblyName 'PInvokeAssembly'), 'Run'
).DefineDynamicModule('PInvokeModule').DefineType('PInvokeType', "Public,BeforeFieldInit")
}

# DLL名, 戻り値の型, メソッド名, 引数の型
function New-PMethod([string]$dll_name, [Type]$return_type, [string]$method_name, [Type[]]$parameter_types){
$ptype = New-PType
$ptype.DefineMethod(
$method_name, 'Public,HideBySig,Static,PinvokeImpl', $return_type, $parameter_types
).SetCustomAttribute(
(New-Object Reflection.Emit.CustomAttributeBuilder ([Runtime.InteropServices.DllImportAttribute].GetConstructor([string])), $dll_name)
)

# apiにメンバーを追加
$invoke = {
for($i = 0; $i -lt $this.ParameterTypes.Length; $i++){ $args[$i] = $args[$i] -as $this.ParameterTypes[$i] }
return $this.InvokeMember($this.MethodName, 'Public,Static,InvokeMethod', $null, $null, $args)
}
$api = $ptype.CreateType()
$api =
Add-Member -InputObject $api -MemberType NoteProperty -Name MethodName -Value $method_name -passthru
Add-Member -InputObject $api -MemberType NoteProperty -Name ParameterTypes -Value $parameter_types
Add-Member -InputObject $api -MemberType ScriptMethod -Name Invoke -Value $invoke
return $api
}

使い方としては、

$showWindowAsync = New-PMethod "user32.dll" ([Bool]) "ShowWindowAsync" @([IntPtr], [Int32])
$setForeGroundWindow = New-PMethod "user32.dll" ([Bool]) "SetForegroundWindow" @([IntPtr])
$isIconic = New-PMethod "user32.dll" ([Bool]) "IsIconic" @([IntPtr])

$showWindowAsync.Invoke($hwnd, 4)

こういう感じになります。上であげたAPIは全てウィンドウハンドラを引数にとるので、Get-Processで取得したプロセスから、MainWindowHandleを渡します。
面倒なのは、Win32 APIの引数や戻り値に対応する.NETの型を調べてやらなければいけないのと、当たり前ですがCのヘッダファイルがインクルード出来ないので、定数を全部数字で指定しなければならないことです。
対応する.NETの型は、MSDN : プラットフォーム呼び出しのデータ型をみるとわかります。
# といいつつ、いま改めてShowWindowAsyncの定義を調べたら、BOOL ShowWindowAsync(HWND hWnd, int nCmdShow);とかなっており、HWNDが対応表に載っていなくて行き詰まりかけましたが、検索したらHWNDはHANDLEの一種、のようなことが書いてあったので、そうしてみました。System.Diagnostics.ProcessのMainWindowHandleもIntPtrだし。しかし、ここはInt32を指定しても普通に動くので、実際にどうなのかはよくわかりません。というか、全体的によくわかっていませんが……
また、定数に関しては自分で定義しなおすしかないのかなぁという感じです。上の例で、ShowWindowAsyncの引数として渡している4は、SW_SHOWNOACTIVATEです。この辺は、VBなどからWin32 APIを呼び出すサンプルを探すと良いようです。ビットでフラグ立てる場合は、-bandとか-borのビット演算子を使います。