PowerShell Cookbook

Search

Categories

 

On this page

More P/Invoke in PowerShell
Get the Owner of a Process in PowerShell – P/Invoke and Ref/Out Parameters
Set-Location, and [Environment]::CurrentDirectory
PowerShell Solves a Mystery
How Do I Search the Registry for a Value in PowerShell?
“Simple Where” as a Where-Object Shortcut
Current Working Directory with PowerShell and .Net Calls
Precision Computing Turns One
Caffe Artigiano in Vancouver
SecureStrings in PowerShell

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: 220
This Year: 20
This Month: 0
This Week: 0
Comments: 533

Sign In

 Tuesday, July 25, 2006
Tuesday, July 25, 2006 8:13:15 PM (Pacific Daylight Time, UTC-07:00) ( )

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?  :)

Comments [0] | | # 
 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

 

Comments [3] | | # 
 Tuesday, July 18, 2006
Tuesday, July 18, 2006 11:55:01 PM (Pacific Daylight Time, UTC-07:00) ( )

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)

Comments [1] | | # 
 Monday, July 17, 2006
Monday, July 17, 2006 5:20:54 PM (Pacific Daylight Time, UTC-07:00) ( )

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!

Comments [1] | | # 
 Wednesday, July 12, 2006
Wednesday, July 12, 2006 11:29:39 PM (Pacific Daylight Time, UTC-07:00) ( )

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

Comments [0] | | # 
 Wednesday, July 05, 2006
Wednesday, July 05, 2006 11:23:36 PM (Pacific Daylight Time, UTC-07:00) ( )

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.”

Comments [1] | | # 
 Monday, June 26, 2006
Monday, June 26, 2006 8:41:30 PM (Pacific Daylight Time, UTC-07:00) ( )

A question that occasionally comes up is, “Why doesn’t PowerShell change the [Environment]::CurrentDirectory while I navigate around the shell?”  This is always in the context of using a relative file path while working with the .Net framework.

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 baseline.xml

[C:\temp]
PS:38 > $reader.BaseURI
file:///C:/Documents and Settings/<user>/baseline.xml

[C:\temp]
PS:37 > $reader = new-object System.Xml.XmlTextReader (resolve-path baseline.xml)

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

 

Comments [2] | | # 
 Thursday, June 08, 2006
Thursday, June 08, 2006 3:48:37 PM (Pacific Daylight Time, UTC-07:00) ( )

It was a year ago now that Precision Computing, in its current form, opened its doors.  I’ve been writing something blog-like for longer, but it previously involved updating the HTML by hand :)

It’s been an interesting year – one in which we’ve seen the PowerShell community blossom from a group of passionate BetaPlace users, to a much larger and more diverse community.  We’ve played with a handful of new releases, experienced a long-awaited name change,  and perhaps most importantly – learned about PowerShell’s official release plans.

At Microsoft, we have a tradition for celebrating times like this.  When their anniversary rolls around, many people place chocolate on a chair outside of their office – usually 1 lb. for every year they’ve been at Microsoft.  It’s a great tradition – the people you work with get to indulge a little, and everybody enjoys the water cooler type of conversation that tends  to happen.

I obviously can’t distribute chocolate on the internet, but I’ve been thinking for a while about how to recreate the essence of the Microsoft chocolate tradition.  Here’s the idea:

If you own a blog, add a comment with a link to what you consider your "best" post in the last year or so.  If you don’t own a blog, add a comment with a link to a blog post that you consider to be very insightful or interesting.  Then, click around and enjoy the water cooler conversation!

Now, on for another year!

Comments [7] | | # 
 Tuesday, June 06, 2006
Tuesday, June 06, 2006 4:36:55 PM (Pacific Daylight Time, UTC-07:00) ( )

On a recent trip to Vancouver, I accidentally stumbled into a cafe called Caffe Artigiano right downtown (on Hornsby street,) and was extremely impressed.  The walls are decorated with pictures of gorgeous Latte art, and the drinks are stellar.  It turns out that the place is the home of the Canadian Barista Champion for three years running!

And with drinks like this?  It makes me want to go back to working for a coffee shop :)

 


Picture taken from their blog @ http://www.dwelltime.net/, although mine looked similar!

Comments [2] | | # 
 Thursday, June 01, 2006
Thursday, June 01, 2006 6:17:47 PM (Pacific Daylight Time, UTC-07:00) ( )

When you use any string in the CLR, it retains that string so that it can efficiently reuse it later.  Unlike most.Net data, unused strings persist even through garbage collection cycles.  While this data is in memory, there is always the chance that it could get captured in a crash dump, or swapped to disk in a paging operation. Because some data (such as passwords, and other confidential information) may be sensitive, the .Net framework includes the SecureString class – a container for text data that the CLR stores in memory in an encrypted form.  Code that needs to interact with the plain text data inside of a SecureString does so as securely as possible.

When a cmdlet author asks you for sensitive data (i.e.: a password,) the best practice is to designate that password parameter as a SecureString in order to help keep your password confidential.   You can supply the parameter with a SecureString variable as input, or the host prompts you for the SecureString if you do not provide one.  We also have two cmdlets (ConvertTo-SecureString and ConvertFrom-SecureString) that allow you to round-trip this data to disk in a secure fashion.

By default, the SecureString cmlets use Windows’ Data Protection API when they convert your SecureString to and from a plain text representation.  The encryption key is based on your Windows logon credentials, so only you can decrypt the data that you’ve encrypted.  If you want the exported data to work on another system or separate user account, you can use the parameter sets that let you provide an explicit key.  PowerShell treats the data as an opaque blob – and so should you.

However, there are many instances when you may want to automatically provide the SecureString input to a cmdlet, rather than have the host prompt you for it.  In these situations, the ideal solution is to import a previously exported SecureString from disk (using ConvertTo-SecureString.)  This retains the confidentiality of your data, and still allows you to automate the input. 

If the data is highly dynamic (ie: coming from a CSV,) then the best approach is

$secureString = ConvertTo-SecureString "Kinda Secret" -AsPlainText –Force

The Cmdlet requires the Force parameter to ensure you acknowledge the fact that PowerShell cannot protect plain text data – even after you’ve put it in a SecureString.

The new –AsPlainText parameter set doesn’t exist in RC1, so the equivalent function is:

function New-SecureString([string] $plainText)
{
   $secureString = new-object System.Security.SecureString

   foreach($char in $plainText.ToCharArray())
   {
      $secureString.AppendChar($char)
   }

   $secureString
}

[Edit: Fixed typo]

Comments [3] | | #