Get the Owner of a Process in PowerShell – P/Invoke and Ref/Out Parameters
An often asked question in the Managed Code world is “How do I get the owner of a process?” The same question is starting to come up around PowerShell, and the answer to both is:
The .Net Framework does not yet support this API, so the solution is to write the P/Invoke calls into the Win32 API yourself.
This gets a little more complicated with PowerShell, though, since we abstract the .Net framework one level even further.
A good (and most flexible) solution is to write a cmdlet. That’s the approach that Tony took in his Add-ProcessOwner cmdlet. However, for the large subset of P/Invoke calls that have relatively simple signatures, we can use the PowerShell scripting language to do what we want. The solution is Invoke-Win32 – a function which uses code generation to dynamically create a.Net P/Invoke definition, and then calls that P/Invoke method. The very bright Juoku Kynsijarvi gave an example of this in the PowerShell newsgroup a long time ago.
This script also gives an example of the [Ref] type we added in our most recent drop. The [Ref] type is a reference to a variable. When you pass a [Ref] type to a native method with an OUT parameter, we allow that method to change the value of your variable.
If you want to interact with the value of a variable in a [Ref] type in PowerShell, you explicitly get and set its .Value property.
##############################################################################
##
## Get-ProcessOwner.ps1
## Get the owner of a process.
##
##############################################################################
param([System.Diagnostics.Process] $process)
$TOKEN_QUERY = 0x0008
function Main
{
$token = [IntPtr]::Zero
$processHandle = $process.Handle
if($processHandle -ne $null)
{
if((OpenProcessToken $process.Handle $TOKEN_QUERY ([Ref] $token)) -eq 0)
{
throw "Could not retrieve token for $($process.ProcessName)"
}
}
if($token -ne 0)
{
new-object System.Security.Principal.WindowsIdentity $token
CloseHandle $token > $null
}
}
## Invoke a Win32 P/Invoke call.
function Invoke-Win32([string] $dllName, [Type] $returnType,
[string] $methodName, [Type[]] $parameterTypes, [Object[]] $parameters)
{
## Begin to build the dynamic assembly
$domain = [AppDomain]::CurrentDomain
$name = New-Object 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]
## parameters.
$method = $type.DefineMethod($methodName, 'Public,HideBySig,Static,PinvokeImpl',
$returnType, $parameterTypes)
foreach($refParameter in $refParameters)
{
$method.DefineParameter($refParameter, "Out", $null)
}
## Apply the P/Invoke constructor
$ctor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([string])
$attr = New-Object Reflection.Emit.CustomAttributeBuilder $ctor, $dllName
$method.SetCustomAttribute($attr)
## Create the temp
orary 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]
}
}
function OpenProcessToken([IntPtr] $handle, [UInt32] $tokenAccess, [Ref] $token)
{
$parameterTypes = [IntPtr], [UInt32], [Ref]
$parameters = $handle, $tokenAccess, $token
Invoke-Win32 "advapi32.dll" ([Int]) "OpenProcessToken" $parameterTypes $parameters
}
function CloseHandle([IntPtr] $handle)
{
$parameterTypes = [IntPtr]
$parameters = [IntPtr] $handle
Invoke-Win32 "kernel32.dll" ([Bool]) "CloseHandle" $parameterTypes $parameters
}
. Main