Archives for the Month of July, 2006

More P/Invoke in PowerShell

In my last post, I gave an example of how to use P/Invoke through PowerShell  to implement functionality not immediately available via the .Net framework.  My example was to get the owner of a process, and Abhishek wisely pointed out that  this is easier through WMI.

So here’s another example.  It’s actually the one I had written originally, but it didn’t give me an opportunity to illustrate [Ref] parameters, or [Out] parameters via P/Invoke.

param([int] $opacity)

$GWL_EXSTYLE = -20
$WS_EX_LAYERED = 0x80000
$LWA_ALPHA = 0x00000002

## Invoke a Win32 P/Invoke call.
function Invoke-Win32([string] $dllName, [Type] $returnType,
   [string] $methodName, [Type[]] $parameterTypes, [Object[]] $parameters)
{
   (... from last post ...)
# http://www.leeholmes.com/blog/GetTheOwnerOfAProcessInPowerShellPInvokeAndRefOutParameters.aspx } function GetWindowLong($hWnd, $style) { $parameterTypes = [IntPtr], [int] $parameters = [IntPtr] $hWnd, [int] $style Invoke-Win32 "user32" ([Int]) "GetWindowLong" $parameterTypes $parameters } function SetWindowLong($hWnd, $style, $value) { $parameterTypes = [IntPtr], [int], [int] $parameters = [IntPtr] $hWnd, [int] $style, [int] $value Invoke-Win32 "user32" ([Int]) "SetWindowLong" $parameterTypes $parameters } function GetForegroundWindow { Invoke-Win32 "user32" ([Int]) "GetForegroundWindow" } function SetLayeredWindowAttributes([byte] $opacity) { $window = GetForegroundWindow $oldStyle = GetWindowLong $window $GWL_EXSTYLE $result = SetWindowLong $window $GWL_EXSTYLE ($oldStyle -bor $WS_EX_LAYERED) if($result -gt 0) { $parameterTypes = [IntPtr], [int], [byte], [int] $parameters = [IntPtr] $window, [int] 0x00FFFFFF, [byte] $opacity, [int] $LWA_ALPHA [void] (Invoke-Win32 "user32" ([Int]) "SetLayeredWindowAttributes" $parameterTypes $parameters) } } if($opacity -gt 100) { throw "Opacity must be between 0 and 100" } "Switch to the window that you want to adjust. The change will happen in 2 seconds." "(Note that console windows are not supported)" sleep 2 SetLayeredWindowAttributes ($opacity * 255 / 100)

P.S.  I’m off to the Mediterranean until the middle of August.  Can you feed the cats while I’m gone?  🙂

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

 

Set-Location, and [Environment]::CurrentDirectory

One question that comes up frequently is, “Why does PowerShell not change its [System.Environment]::CurrentDirectory as I navigate around the shell?

One of the difficult aspects of this comes from the fact that PowerShell supports multiple pipelines of execution.  Although it’s not directly exposed yet, users will soon be able to suspend jobs to the background, and other concurrent tasks.

The current directory affects the entire process, so if we change the directory as you navigate around the shell, you risk corrupting the environment of jobs you have running in the background.

When you use filenames in .Net methods, the best practice is to use fully-qualified path names.  The Resolve-Path cmdlet makes this easy:

[C:\temp]
PS:37 > $reader = new-object System.Xml.XmlTextReader (Resolve-Path baseline.xml)
Suggestion: An alias for Resolve-Path is rvpa

[C:\temp]
PS:38 > $reader.BaseURI
file:///C:/temp/baseline.xml

If you wish to access a path that doesn’t already exist, use the Join-Path  cmdlet:

(Join-Path (Get-Location) newfile.txt)

PowerShell Solves a Mystery

One of Raymond Chen’s readers recently noticed that Google purchased AdWords for Raymond’s name.  The destination of the sponsored link?  http://www.google.com/jobs 🙂

This came up on our internal bloggers alias, so people started poking around to see what other bloggers / blog readership had outstanding “job offers.”  Not being one for repetitive, manual computer labour, I let PowerShell definitively answer the question.

In a temporary Outlook message, I expanded the alias so that it listed the names of all members.  I saved that into a text file, and ran the following script:

[C:\temp]
PS:6 > gc bloggers.txt -delimiter ";" |
>>    % { $_ | where { $wc.DownloadString("
http://www.google.com/search?q={0}" -f $_) -match "Want to work" } }
>>
 Brad Abrams;
 Eric Gunnerson;
 Raymond Chen;

A bit easier than searching for the 965 members of that alias by hand!

How Do I Search the Registry for a Value in PowerShell?

The question came up recently in an internal discussion list, “How do I search the Registry for a value in PowerShell?

In the FileSystem, we have the select-string cmdlet to do the hard work for you.  We don’t have the equivalent yet for other stores, so unfortunately the answer is to write ‘grep’ from scratch.  It’s manageable, though.

The key here is to think of registry key values like you would think of content in a file:

Directories have items, items have content.
Registry keys have properties, properties have values.

The way to get property values in PowerShell is the Get-ItemProperty cmdlet.

So:

cd HKCU:
Get-ChildItem . –rec –ea SilentlyContinue

Gets you all of the subkeys in the registry, just like you might get all of the files on your hard drive.  We then pass that into the “Get-ItemPropery” cmdlet, to get the content of the properties:

| foreach { Get-ItemProperty –Path $_.PsPath }

To check for matches, we use the –match operator:

... (Get-ItemProperty -Path $_.PsPath) -match "evr.dll"

But that just outputs a bunch of “Yes” and “No” answers.  We in fact want to output the key name if this matches, so we wrap that in an If statement and output the path:

... if( (Get-ItemProperty -Path $_.PsPath) -match "evr.dll") { $_.PsPath }

That gives us a script-like representation of:

######################################################################
##
## Search-RegistryKeyValues.ps1
## Search the registry keys from the current location and down for a
## given key value.
##
######################################################################

param([string] $searchText = $(throw "Please specify text to search for."))

gci . -rec -ea SilentlyContinue | 
   % { 
      if((get-itemproperty -Path $_.PsPath) -match $searchText)
      { 
         $_.PsPath
      } 
   } 

 

Or a “one-liner of”: 

gci . -rec -ea SilentlyContinue | % { if((get-itemproperty -Path $_.PsPath) -match "<SomeText>") { $_.PsPath} }

“Simple Where” as a Where-Object Shortcut

The Where-Object cmdlet is incredibly powerful, in that it allows you to filter your output based on arbitrary criteria.  For extremely simple filters (such as filtering based only on a comparison to a single property,) though the syntax can get a little ungainly:

get-process | where { $_.Handles -gt 1000 }

For this type of situation, it is easy to write a function to offload all of the syntax  to the function itself:

get-process | wheres Handles gt 1000
dir | wheres PsIsContainer

The following snippet implements the “simple where” functionality:

function wheres($property, $operator = "eq", $matchText = "$true")
{
   Begin { $expression = "`$_.$property -$operator `"$matchText`"" }
   Process { if(invoke-expression $expression) { $_ } }
}

Much better!

Note: although extremely simple, this solution can be improved.   Since we pass arbitrary input to the Invoke-Expression cmdlet, it is quite easy to make syntactic mistakes that foul up the call to Invoke-Expression.  Also, this function can be abused if you don’t fully trust the input that you pass it:

dir | wheres PsIsContainer eq 'False" -and (write-host "Hello") -and "'

Bruce Payette expands on this function (and problem) in his upcoming book, “PowerShell in Action.”