PowerShell Cookbook

Twitter Updates

    follow me on Twitter

    Search

    Categories

     

    On this page

    Get the Owner of a Process in PowerShell – P/Invoke and Ref/Out Parameters

    Archive

    Blogroll

    Disclaimer
    I work for Microsoft.

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

    RSS 2.0 | Atom 1.0 | CDF

    Send mail to the author(s) E-mail

    Total Posts: 235
    This Year: 12
    This Month: 0
    This Week: 0
    Comments: 634

    Sign In

     Friday, July 21, 2006
    Friday, July 21, 2006 8:40:44 AM (Pacific Daylight Time, UTC-07:00) ( )

    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 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]
       }
    }

    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