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

 

6 Responses to “Get the Owner of a Process in PowerShell – P/Invoke and Ref/Out Parameters”

  1. Tony writes:

    Intersting post.

    The big problem is that even Administrator can’t get process owner information if that process is started by other user.

    You must either have System privilege or SeTakeOwnership Privilege (requires more work).

    So at this moment, the Add-ProcessOwner cmdlet is still useless.

  2. Abhishek225 writes:

    I am a little lost. Is this different from what GetOwner Method on Win32_Process wmiobject returns?

    On RC1 build I should be able to do:

    $p=get-wmiobject win32_process -f "ProcessName=’powershell.exe’"
    [email protected](1) //out parameter holder
    $p.invokemethod("GetOwner",$methodargs)
    "owner for $p.ProcessId is $($methodargs[0])"

    rc2 should make it much easier

  3. Lee writes:

    That’s definitely another alternative. I was more using this as a canvas upon which to to lay a P/Invoke discussion, though 🙂

  4. Calling C functions | WinSoft.se writes:

    […] the rest of the code is something written by Jouko Kynsijärvi, that Lee Holmes used to make the Invoke-Win32 function. This is my complete code. Three lines! Check this […]

  5. Sending Key Stokes to a Window In PowerShell | Adam Driscoll's Blog writes:

    […] to send the page down key stroke to a window (don’t ask me ). Using a combination of the Invoke-Win32 function by Lee Holmes (man, I love that function) and the .NET SendKeys class I was able to come […]

  6. PowerShell V4 – Что нового? (Часть 1) | Kazun writes:

    […] Используя WinAPI – Get the Owner of a Process in PowerShell – P/Invoke and Ref/Out Parameters […]

Leave a Reply