PowerShell Cookbook

Search

Categories

 

On this page

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: 214
This Year: 14
This Month: 1
This Week: 0
Comments: 522

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

 

Friday, July 21, 2006 3:51:46 PM (Pacific Daylight Time, UTC-07:00)
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.
Saturday, July 22, 2006 2:28:09 AM (Pacific Daylight Time, UTC-07:00)
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'"
$methodargs=@(1) //out parameter holder
$p.invokemethod("GetOwner",$methodargs)
"owner for $p.ProcessId is $($methodargs[0])"

rc2 should make it much easier
Saturday, July 22, 2006 4:28:41 AM (Pacific Daylight Time, UTC-07:00)
That's definitely another alternative. I was more using this as a canvas upon which to to lay a P/Invoke discussion, though :)
Name
E-mail
Home page

Comment (Some html is allowed: b, blockquote@cite, em, i, strike, strong, sub, super, u)  

Enter the code shown (prevents robots):