PowerShell Cookbook

Search

Categories

 

On this page

PowerShell Cookbook Now Available
Interacting with SQL Databases in PowerShell: Invoke-SqlCommand
Managing INI files with PowerShell
Using PowerShell and PsExec to invoke expressions on remote computers
Invoking Generic Methods on Non-Generic Classes in PowerShell
PowerShell: The Definitive Guide Rough Cut now Available
Interested in being a Technical Reviewer?
Discovering Registry Settings - PowerShell and Process Monitor
Calling a Webservice from PowerShell
Add Custom Methods and Properties to Types in PowerShell
Get-HelpMatch - Search Help (Apropos) in PowerShell
O'Reilly PowerShell Quick Reference Now Available
Coming Soon: Windows PowerShell Pocket Guide

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: 262
This Year: 13
This Month: 0
This Week: 0
Comments: 818

Sign In

 Monday, October 29, 2007
Monday, October 29, 2007 4:02:20 PM (Pacific Standard Time, UTC-08:00) ( )

Update 8/16/2010: The much expanded second edition is now available.

As just announced by O’Reilly, the PowerShell Cookbook is now available!

As your experience grows in any technology, you learn and benefit from the combination of two distinct types of knowledge:

  1. What can I do with the technology?
  2. How do I accomplish a specific task in that technology?

Question #1 focuses on the technology. You learn the answers to #1 from training, exploration, podcasts, and technology-specific books. This is where you immerse yourself in the looping constructs, variable syntax, etc. In PowerShell’s case, that’s (not surprisingly) turned out to be PowerShell in Action, along with the many other excellent PowerShell learning resources out there.

Question #2 focuses on the task. You know what you want to do, and the only think in your way is technology. You learn the answers to #2 from experience, and lots of it. Experience is in short supply when you first start working with a technology, so blog posts and internet searches often tentatively fill that void. Until the O’Reilly cookbook arrives, that is, at which point you have pre-canned solutions to many of your most vexing questions. In PowerShell’s case, we finally have a resource to fill that void – the PowerShell Cookbook.

The PowerShell Cookbook focuses squarely on showing you how to use PowerShell to get your job done. It builds on a huge base of distilled knowledge, and includes:

  • Solutions to the most popular and searched-for TechNet / Script Center topics
  • Scripts that address the most common community, newsgroup, and new user questions
  • Scripts that wrap around and hide the complexity of advanced (but very useful) PowerShell scripting techniques
  • Task-based introduction to all of PowerShell’s major features

In addition to all of this pre-distilled knowledge, the cookbook’s appendix provides a very thorough and complete PowerShell reference. Thorough enough to be a book all of its own, but now expanded and finally in a printed format. It includes:

  • A complete reference to the PowerShell language and environment
  • An exhaustive regular expression reference, along with PowerShell examples
  • A complete listing of all PowerShell automatic variables
  • A guide to PowerShell’s standard verbs, which helps you write scripts that match the PowerShell naming guidelines
  • Hand-picked lists of the .NET classes, WMI classes, and COM objects most useful to system administrators
  • An exhaustive String and DateTime formatting reference, along with PowerShell examples

Since the book focuses so strongly on the same crowd that frequents Microsoft ScriptCenter, I tapped Dean Tsaltas (one of the original Scripting Guys) for feedback and a forward. Perhaps my greatest disappointment about the book is that we didn’t have room for his fantastic cover quote. I would be remiss to relegate his unbiased opinion to the Cavern-Of-Thoughts-Unpublished, so I include it here:

"Must-read of the season!  A stunning achievement! Tantalizing, witty, and entirely satisfying. Oh, wait -- that's the foreword."

Dean Tsaltas, Scripting Guy emeritus, author of the Foreword.

So, enjoy the book  — or at least the foreword :)

Comments [6] | | # 
 Thursday, October 18, 2007
Thursday, October 18, 2007 10:40:08 PM (Pacific Standard Time, UTC-08:00) ( )

Jeffrey McManus recently wrote about database queries with PowerShell – a small script that lets you query a SQL data store. This is really powerful. Rather than context switch into SQL Express (or TOAD, or your other favourite administration tool,) you can do what you need from PowerShell.

It goes even further, though. A lot of PowerShell's built-in commands have a set-oriented flavour: Where-Object, Select-Object, Group-Object, and Sort-Object. I also blogged about a set intersection script here: http://www.leeholmes.com/blog/CreatingSQLsJoinlikeFunctionalityInMSH.aspx. After spending a ton of time in Oracle databases during an internship at General Electric, I remember always wishing that DIR supported a WHERE clause. Well, with PowerShell, it does!

One thing Jeffrey's post doesn't fully highlight is that the .NET Framework can also return fully structured objects that represent the results of your query. If your SELECT query returns a recordset with Name and Address columns, you can get back objects with Name and Address properties. With that, you can slice and dice the results even further in the shell.

PS D:\Temp> Invoke-SqlCommand.ps1 -Sql "SELECT TOP 5 * FROM Orders" | Format-Table

    OrderID CustomerID   EmployeeID OrderDate   RequiredDat ShippedDate     ShipVia     Freight
                                                e
    ------- ----------   ---------- ---------   ----------- -----------     -------     -------
      10248 VINET                 5 7/4/1996... 8/1/1996... 7/16/199...           3       32.38
      10249 TOMSP                 6 7/5/1996... 8/16/199... 7/10/199...           1       11.61
      10250 HANAR                 4 7/8/1996... 8/5/1996... 7/12/199...           2       65.83
      10251 VICTE                 3 7/8/1996... 8/5/1996... 7/15/199...           1       41.34
      10252 SUPRD                 4 7/9/1996... 8/6/1996... 7/11/199...           2        51.3

The .NET Framework supports more than just SQL servers, though. It supports Access databases and Excel workbooks, too. Their connection strings are a black-art, so it is natural to wrap all of that magic in a script.

So natural that I include in the upcoming PowerShell Cookbook: Invoke-SqlCommand. (PS: Expect some good news about the book VERY soon.)

##############################################################################
##
## Invoke-SqlCommand.ps1
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
## Return the results of a SQL query or operation
##
## ie:
##
##    ## Use Windows authentication
##    Invoke-SqlCommand.ps1 -Sql "SELECT TOP 10 * FROM Orders"
##
##    ## Use SQL Authentication
##    $cred = Get-Credential
##    Invoke-SqlCommand.ps1 -Sql "SELECT TOP 10 * FROM Orders" -Cred $cred
##
##    ## Perform an update
##    $server = "MYSERVER"
##    $database = "Master"
##    $sql = "UPDATE Orders SET EmployeeID = 6 WHERE OrderID = 10248"
##    Invoke-SqlCommand $server $database $sql
##
##    $sql = "EXEC SalesByCategory 'Beverages'"
##    Invoke-SqlCommand -Sql $sql
##
##    ## Access an access database
##    Invoke-SqlCommand (Resolve-Path access_test.mdb) -Sql "SELECT * FROM Users"
##    
##    ## Access an excel file
##    Invoke-SqlCommand (Resolve-Path xls_test.xls) -Sql 'SELECT * FROM [Sheet1$]'
##
##############################################################################

param(
    [string] $dataSource = ".\SQLEXPRESS",
    [string] $database = "Northwind",      
    [string] $sqlCommand = $(throw "Please specify a query."),
    [System.Management.Automation.PsCredential] $credential
  )


## Prepare the authentication information. By default, we pick
## Windows authentication
$authentication = "Integrated Security=SSPI;"

## If the user supplies a credential, then they want SQL
## authentication
if($credential)
{
    $plainCred = $credential.GetNetworkCredential()
    $authentication = 
        ("uid={0};pwd={1};" -f $plainCred.Username,$plainCred.Password)
}

## Prepare the connection string out of the information they
## provide
$connectionString = "Provider=sqloledb; " +
                    "Data Source=$dataSource; " +
                    "Initial Catalog=$database; " +
                    "$authentication; "

## If they specify an Access database or Excel file as the connection
## source, modify the connection string to connect to that data source
if($dataSource -match '\.xls$|\.mdb$')
{
    $connectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=$dataSource; "

    if($dataSource -match '\.xls$')
    {
        $connectionString += 'Extended Properties="Excel 8.0;"; '

        ## Generate an error if they didn't specify the sheet name properly
        if($sqlCommand -notmatch '\[.+\$\]')
        {
            $error = 'Sheet names should be surrounded by square brackets, and ' +
                       'have a dollar sign at the end: [Sheet1$]'
            Write-Error $error
            return
        }
    }
}

## Connect to the data source and open it
$connection = New-Object System.Data.OleDb.OleDbConnection $connectionString
$command = New-Object System.Data.OleDb.OleDbCommand $sqlCommand,$connection
$connection.Open()

## Fetch the results, and close the connection
$adapter = New-Object System.Data.OleDb.OleDbDataAdapter $command
$dataset = New-Object System.Data.DataSet
[void] $adapter.Fill($dataSet)
$connection.Close()

## Return all of the rows from their query
$dataSet.Tables | Select-Object -Expand Rows


 

Comments [4] | | # 
 Tuesday, October 02, 2007
Tuesday, October 02, 2007 10:24:39 AM (Pacific Standard Time, UTC-08:00) ( )

The question came up on the newsgroup a few days ago on how to work with INI files from PowerShell.

A lot of great answers came up (using text parsing of the INI files,) but the Windows API actually supports reading and writing of INI file entries directly through its GetPrivateProfileString and WritePrivateProfileString functions. PowerShell doesn’t support P/Invoke to the Win32 API directly, but the Invoke-Win32 script given here does: http://www.leeholmes.com/blog/GetTheOwnerOfAProcessInPowerShellPInvokeAndRefOutParameters.aspx.

Since it’s reusable, that code sits better as a script of its own, named Invoke-WindowsApi (below.)

##############################################################################
##
## Invoke-WindowsApi.ps1
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
## Invoke a native Windows API call that takes and returns simple data types.
##
## ie:
##
## ## Prepare the parameter types and parameters for the 
## CreateHardLink function
## $parameterTypes = [string], [string], [IntPtr]
## $parameters = [string] $filename, [string] $existingFilename, [IntPtr]::Zero
## 
## ## Call the CreateHardLink method in the Kernel32 DLL
## $result = Invoke-WindowsApi "kernel32" ([bool]) "CreateHardLink" `
##     $parameterTypes $parameters
##
##############################################################################

param(
    [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)
{
   [void] $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]
}

 

Using this script, you can easily wrap these APIs in a more friendly PowerShell scripts.

##############################################################################
##
## Get-PrivateProfileString.ps1
##
## Get an entry from an INI file.
##
## ie:
##
##  PS >Get-PrivateProfileString.ps1 C:\winnt\system32\ntfrsrep.ini text DEV_CTR_24_009_HELP
##
##############################################################################

param(
    $file,
    $category,
    $key)

## Prepare the parameter types and parameter values for the Invoke-WindowsApi script
$returnValue = New-Object System.Text.StringBuilder 500
$parameterTypes = [string], [string], [string], [System.Text.StringBuilder], [int], [string]
$parameters = [string] $category, [string] $key, [string] ""
   [System.Text.StringBuilder] $returnValue, [int] $returnValue.Capacity, [string] $file

## Invoke the API
[void] (Invoke-WindowsApi "kernel32.dll" ([UInt32]) "GetPrivateProfileString" `
   $parameterTypes $parameters)

## And return the results
$returnValue.ToString()

and

##############################################################################
##
## Set-PrivateProfileString.ps1
##
## Set an entry from an INI file.
##
## ie:
##
##  PS >copy C:\winnt\system32\ntfrsrep.ini c:\temp\
##  PS >Set-PrivateProfileString.ps1 C:\temp\ntfrsrep.ini text `
##  >> DEV_CTR_24_009_HELP "New Value"
##  >>
##  PS >Get-PrivateProfileString.ps1 C:\temp\ntfrsrep.ini text DEV_CTR_24_009_HELP
##  New Value
##  PS >Set-PrivateProfileString.ps1 C:\temp\ntfrsrep.ini NEW_SECTION `
##  >> NewItem "Entirely New Value"
##  >>
##  PS >Get-PrivateProfileString.ps1 C:\temp\ntfrsrep.ini NEW_SECTION NewItem
##  Entirely New Value
##
##############################################################################

param(
    $file,
    $category,
    $key,
    $value)

## Prepare the parameter types and parameter values for the Invoke-WindowsApi script
$parameterTypes = [string], [string], [string], [string]
$parameters = [string] $category, [string] $key, [string] $value, [string] $file

## Invoke the API
[void] (Invoke-WindowsApi "kernel32.dll" ([UInt32]) "WritePrivateProfileString" $parameterTypes $parameters)

Comments [5] | | # 
Tuesday, October 02, 2007 8:02:52 AM (Pacific Standard Time, UTC-08:00) ( )

While eagerly awaiting PowerShell’s upcoming remoting functionality, many people turn to Sysinternals’ PsExec tool to build their own version. However, PowerShell seems to hang when called via PsExec on the remote machine. This has come up on the SysInternal forum (http://forum.sysinternals.com/forum_posts.asp?TID=10823) among other places, and is caused by the same issue outlined here: http://www.leeholmes.com/blog/UsingMshexeInteractivelyFromWithinOtherPrograms.aspx.

 

To work around this problem, you can give some input to the Powershell process. But to give it input, you need to use cmd.exe:

 

psexec \\server cmd /c "echo . | powershell dir 'c:\program files'"

 

Now, working around quote encoding and two levels of escape characters (cmd.exe and PowerShell) can be quite painful when crafting the PowerShell command this way. For that, you can use the –EncodedCommand parameter, which accepts a Base64-encoded version of your command.

 

$expression = "dir 'c:\program files'"

$commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)

$encodedCommand = [Convert]::ToBase64String($commandBytes)

psexec \\server cmd /c "echo . | powershell -EncodedCommand $encodedCommand"

 

And to make it even more PowerShelly, the –OutputFormat of XML lets you get back an XML representation of your command’s output. On your local system, PowerShell converts your output back to deserialized objects. From there, you can continue to manipulate the output with the object-oriented goodness you’ve come to expect of us J In the example below, the server processes the Where-Object query, but the client sorts the result on Handles.

 

PS >$command = { Get-Process | Where-Object { $_.Handles -gt 1000 } }

PS >Invoke-RemoteExpression \\LEE-DESK $command | Sort Handles

 

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName

-------  ------    -----      ----- -----   ------     -- -----------

   1025       8     3780       3772    32   134.42    848 csrss

   1306      37    50364      64160   322   409.23   4012 OUTLOOK

   1813      39    54764      36360   321   340.45   1452 iTunes

   2316     273    29168      41164   218   134.09   1244 svchost

 

Here’s a script that automates all of this for you:

 

Program: Start a Process on a Remote Machine

Example 21-4 lets you invoke PowerShell expressions on remote machines. It uses PsExec (from http://www.microsoft.com/technet/sysinternals/utilities/psexec.mspx) to support the actual remote command execution.

This script offers more power than just remote command execution, however. As Example 21-3 demonstrates, it leverages PowerShell’s capability to import and export strongly structured data, so you can work with the command output using many of the same techniques you use to work with command output on the local system. Example 21-3 demonstrates this power by filtering command output on the remote system but sorting it on the local system.

Example 21-3. Invoking a PowerShell expression on a remote machine

PS >$command = { Get-Process | Where-Object { $_.Handles -gt 1000 } }

PS >Invoke-RemoteExpression \\LEE-DESK $command | Sort Handles

 

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName

-------  ------    -----      ----- -----   ------     -- -----------

   1025       8     3780       3772    32   134.42    848 csrss

   1306      37    50364      64160   322   409.23   4012 OUTLOOK

   1813      39    54764      36360   321   340.45   1452 iTunes

   2316     273    29168      41164   218   134.09   1244 svchost

Since this strongly structured data comes from objects on another system, PowerShell does not regenerate the functionality of those objects (except in rare cases). For more information about importing and exporting structured data, see “Easily Import and Export Your Structured Data.”

Example 21-4. Invoke-RemoteExpression.ps1

##############################################################################

##

## Invoke-RemoteExpression.ps1

##

## Invoke a PowerShell expression on a remote machine. Requires PsExec from

## http://www.microsoft.com/technet/sysinternals/utilities/psexec.mspx

##

## ie:

##

##  PS >Invoke-RemoteExpression \\LEE-DESK { Get-Process }

##  PS >(Invoke-RemoteExpression \\LEE-DESK { Get-Date }).AddDays(1)

##  PS >Invoke-RemoteExpression \\LEE-DESK { Get-Process } | Sort Handles

##

##############################################################################

 

param(

  $computer = "\\$ENV:ComputerName",

  [ScriptBlock] $expression = $(throw "Please specify an expression to invoke."),

  [switch] $noProfile

  )

 

## Prepare the command line for PsExec. We use the XML output encoding so

## that PowerShell can convert the output back into structured objects.

$commandLine = "echo . | powershell -Output XML "

 

if($noProfile)

{

    $commandLine += "-NoProfile "

}

 

## Convert the command into an encoded command for PowerShell

$commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)

$encodedCommand = [Convert]::ToBase64String($commandBytes)

$commandLine += "-EncodedCommand $encodedCommand"

 

## Collect the output and error output

$errorOutput = [IO.Path]::GetTempFileName()

$output = psexec /acceptEula $computer cmd /c $commandLine 2>$errorOutput

 

## Check for any errors

$errorContent = Get-Content $errorOutput

Remove-Item $errorOutput

if($errorContent -match "Access is denied")

{

    $OFS = "`n"

    $errorMessage = "Could not execute remote expression. "

    $errorMessage += "Ensure that your account has administrative " +

        "privileges on the target machine.`n"

    $errorMessage += ($errorContent -match "psexec.exe :")

 

    Write-Error $errorMessage

}

 

## Return the output to the user

$output

 

 

Edit: Thanks to Otto's suggestion, Added the /acceptEula switch to PsExec to accept the EULA automatically. 

Comments [8] | | # 
 Monday, June 18, 2007
Monday, June 18, 2007 7:39:37 PM (Pacific Standard Time, UTC-08:00) ( )

A question recently came in asking, "How do you invoke a generic method on a non-generic class in PowerShell?"

In an earlier post (http://www.leeholmes.com/blog/CreatingGenericTypesInPowerShell.aspx), we talked about how to create generic types in PowerShell, but classes can contain generic methods even when the classes themselves don't represent generic types.

It is possible to call the generic methods on this type of class, with the complexity of the solution depending on the complexity of the class.

Take, for example, the following class definition:

// csc /target:library GenericClass.cs
// [Reflection.Assembly]::LoadFile("c:\temp\GenericClass.dll")
using System;

public class NonGenericClass
{
    public void SimpleGenericMethod<T>(T input)
    {
        Console.WriteLine("Hello Simple World: " + input);
    }

    public void GenericMethod<T>(T input)
    {
        Console.WriteLine("Hello World: " + input);
    }

    public void GenericMethod<T>(T input, int count)
    {
        for(int x = 0; x < count; x++)
            Console.WriteLine("Hello Multi Partially Generic World: " + input);
    }

    public void GenericMethod<T,U>(T input, U secondInput) where T : IEquatable<T> where U : IEquatable<U>
    {
        Console.WriteLine("Hello Multi Generic World: " + input.Equals(secondInput));
    }

    public static void GenericStaticMethod<T>(T input)
    {
        Console.WriteLine("Hello Static World: " + input);
    }
}

It gives an example of the breadth of the problem. We have a simple generic method, a generic method with overrides, and finally, a static generic method.

If you don't need to worry about naming conflicts (as in the SimpleGenericMethod and GenericStaticMethod methods,) the solution is just a few lines of code:

$nonGenericClass = New-Object NonGenericClass
$method = [NonGenericClass].GetMethod("SimpleGenericMethod")
$closedMethod = $method.MakeGenericMethod([string])
$closedMethod.Invoke($nonGenericClass, "Welcome!")

If you do have to worry about multiple methods with the same name, then the GetMethod() method can no longer help you. In that case, we need to call the GetMethods() method, and then find which method shares the proper parameter types.

To abstract all of this logic, use the following Invoke-GenericMethod script:

## Invoke-GenericMethod.ps1 
## Invoke a generic method on a non-generic type: 
## 
## Usage: 
## 
##   ## Load the DLL that contains our class
##   [Reflection.Assembly]::LoadFile("c:\temp\GenericClass.dll")
##
##   ## Invoke a generic method on a non-generic instance
##   $nonGenericClass = New-Object NonGenericClass
##   Invoke-GenericMethod $nonGenericClass GenericMethod String "How are you?"
##
##   ## Including one with multiple arguments
##   Invoke-GenericMethod $nonGenericClass GenericMethod String ("How are you?",5)
##
##   ## Ivoke a generic static method on a type
##   Invoke-GenericMethod ([NonGenericClass]) GenericStaticMethod String "How are you?"
## 

param( 
    $instance = $(throw "Please provide an instance on which to invoke the generic method"), 
    [string] $methodName = $(throw "Please provide a method name to invoke"), 
    [string[]] $typeParameters = $(throw "Please specify the type parameters"), 
    [object[]] $methodParameters = $(throw "Please specify the method parameters")
    ) 

## Determine if the types in $set1 match the types in $set2, replacing generic
## parameters in $set1 with the types in $genericTypes
function ParameterTypesMatch([type[]] $set1, [type[]] $set2, [type[]] $genericTypes)
{
    $typeReplacementIndex = 0
    $currentTypeIndex = 0

    ## Exit if the set lengths are different
    if($set1.Count -ne $set2.Count)
    {
        return $false
    }

    ## Go through each of the types in the first set
    foreach($type in $set1)
    {
        ## If it is a generic parameter, then replace it with a type from
        ## the $genericTypes list
        if($type.IsGenericParameter)
        {
            $type = $genericTypes[$typeReplacementIndex]
            $typeReplacementIndex++
        }

        ## Check that the current type (i.e.: the original type, or replacement
        ## generic type) matches the type from $set2
        if($type -ne $set2[$currentTypeIndex])
        {
            return $false
        }
        $currentTypeIndex++
    }

    return $true
}

## Convert the type parameters into actual types
[type[]] $typedParameters = $typeParameters

## Determine the type that we will call the generic method on. Initially, assume
## that it is actually a type itself.
$type = $instance

## If it is not, then it is a real object, and we can call its GetType() method
if($instance -isnot "Type")
{
    $type = $instance.GetType()
}

## Search for the method that:
##    - has the same name
##    - is public
##    - is a generic method
##    - has the same parameter types
foreach($method in $type.GetMethods())
{
    # Write-Host $method.Name
    if(($method.Name -eq $methodName) -and 
       ($method.IsPublic) -and 
       ($method.IsGenericMethod))
    {
        $parameterTypes = @($method.GetParameters() | % { $_.ParameterType })
        $methodParameterTypes = @($methodParameters | % { $_.GetType() })
        if(ParameterTypesMatch $parameterTypes $methodParameterTypes $typedParameters)
        {
            ## Create a closed representation of it
            $newMethod = $method.MakeGenericMethod($typedParameters)

            ## Invoke the method
            $newMethod.Invoke($instance, $methodParameters)

            return
        }
    }
}

## Return an error if we couldn't find that method
throw "Could not find method $methodName"


 

Comments [3] | | # 
 Thursday, May 10, 2007
Thursday, May 10, 2007 8:10:13 AM (Pacific Standard Time, UTC-08:00) ( )

With the book getting close to completion, O'Reilly has now posted a "Rough Cut" version of the book here: http://www.oreilly.com/catalog/9780596528492/. The Rough Cuts program gives you early access to the majority of the book's content before publication, with the option for an online and print bundle for a discounted price.

Comments [0] | | # 
 Wednesday, May 09, 2007
Wednesday, May 09, 2007 5:15:20 PM (Pacific Standard Time, UTC-08:00) ( )

[Edit: Thanks for all of the interest -- the review period is now closed.]

We're getting close to "content complete" of Windows PowerShell: The Definitive Guide. The next step is technical review, where we look for both high-level and low-level feedback on the content and structure.

Also, this is a book focused on administrators. While PowerShell uber-hackers are always appreciated, inexperience with PowerShell is extremely valuable, as well.

If you're interested in being a Technical Reviewer of the book, please let me know and I'll forward your information to O'Reilly. Feel free to leave a comment here, or click on the mail icon in the sidebar to the right.

Comments [12] | | # 
 Saturday, May 05, 2007
Saturday, May 05, 2007 2:43:02 PM (Pacific Standard Time, UTC-08:00) ( )

To give a glimpse into the writing process behind my upcoming "Windows PowerShell - The Definitive Guide" (O'Reilly,) I'll occasionally post entries "as the author sees it." This entry illustrates how to discover registry settings for programs that do not otherwise support scripted automation.

Discover Registry Settings for Programs

Problem

You want to automate the configuration of a program, but that program does not document its registry configuration settings.

Solution

Use Sysinternals' Process Monitor to observe registry access by that program. Process Monitor is available from http://www.microsoft.com/technet/sysinternals/FileAndDisk/processmonitor.mspx.

Discussion

In an ideal world, all programs would fully support command-line administration and configuration through PowerShell cmdlets. Many programs do not, however, so the solution is to look through their documentation in the hope that they list the registry keys and properties that control their settings. While many programs document their registry configuration settings, many still do not.

Although these programs may not document their registry settings, you can usually observe their registry access activity to determine the registry paths they use. To illustrate this, we will use the Sysinternals' Process Monitor to discover PowerShell's execution policy configuration keys. Although PowerShell documents these keys and makes its automated configuration a breeze, it illustrates the general technique.

Launch and configure Process Monitor

Once you've downloaded Process Monitor, the first step is to filter its output to include only the program you are interested in. By default, Process Monitor logs almost all registry and file activity on the system.

First, launch Process Monitor, then press Ctrl-E (or click the magnifying glass icon) to temporarily prevent it from capturing any data. Next, press Ctrl-X (or click the white sheet with an eraser icon) to clear the extra information that it captured automatically. Finally, drag the target icon and drop it on top of the application in question. You can press Ctrl-L (or click the funnel icon) to see the filter that Process Monitor now applies to its output.

Figure 18-2. Process Monitor ready to capture

Prepare to manually set the configuration option

Next, prepare to manually set the program's configuration option. Usually, this means typing and clicking all of the property settings, but just not pressing OK or Apply. For this PowerShell example, type the Set-ExecutionPolicy command line, but do not press Enter.

Figure 18-3. Preparing to apply the configuration option

Tell Process Monitor to begin capturing information

Switch to the Process Monitor window, then press Ctrl-E (or click the magnifying glass icon.) Process Monitor now captures all registry access for the program in question.

Manually set the configuration option

Press OK, Apply, or whatever action it takes to actually complete the program's configuration. For the PowerShell example, this means pressing Enter.

Tell Process Monitor to stop capturing information

Switch again to the Process Monitor window, then press Ctrl-E (or click the magnifying glass icon.) Process Monitor now no longer captures the application's activity.

Review the capture logs for registry modification

The Process Monitor window now shows all registry keys that the application interacted with when it applied its configuration setting.

Press Ctrl-F (or click the binoculars icon), then search for RegSetValue. Process Monitor highlights the first modification to a registry key.

Figure 18-4. Process Monitor's registry access detail

Press Enter (or double-click the highlighted row) to see the details about this specific registry modification. In this example, we can see that PowerShell changed the value of the ExecutionPolicy property (under HKLM:\Software\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell) to RemoteSigned. Press F3 to see the next entry that corresponds to a registry modification.

Automate these registry writes

Now that you know all registry writes that the application performed when it updated its settings, judgment and experimentation will help you determine which modifications actually represent this setting. Since PowerShell only performed one registry write (to a key that very obviously represents the execution policy,) the choice is pretty clear in this example.

Once you've discovered the registry keys, properties, and values that the application uses to store its configuration data, you can use the techniques discussed in @TODO Ref Modify or delete a registry key value to automate these configuration settings. For example:

PS >$key = "HKLM:\Software\Microsoft\PowerShell\1\" +

>>     "ShellIds\Microsoft.PowerShell"

>> 

PS >Set-ItemProperty $key ExecutionPolicy AllSigned

PS >Get-ExecutionPolicy

AllSigned

PS >Set-ItemProperty $key ExecutionPolicy RemoteSigned

PS >Get-ExecutionPolicy

RemoteSigned

Comments [0] | | # 
 Tuesday, February 27, 2007
Tuesday, February 27, 2007 9:17:41 PM (Pacific Standard Time, UTC-08:00) ( )

One question that comes up fairly frequently in our internal mailing list, the newsgroup, and the internet at large is how to call a webservice from PowerShell. In fact, several excellent PowerShellers have written about it: Keith, and Geert. In general, the guidance has been to use wsdl.exe to generate the webservice proxy, compile that proxy into a DLL, then finally load that DLL into memory.

This is a topic that I cover in my upcoming book, and initially wrote a script to automate these proxy generation steps. However, the prerequisite to running a script designed in that matter is fairly huge. Wsdl.exe doesn't come with the .NET Framework, so you need to have the .NET SDK installed. That was something that made me uncomfortable, so I instead opted for a solution that could generate the web service proxy without wsdl.exe.

The .NET Framework supports a few classes that make this possible, although the documentation for them is terrible at best :)

To give a glimpse into the writing process behind my upcoming "Windows PowerShell - The Definitive Guide" (O'Reilly,) I'll occasionally post entries "as the author sees it." This entry discusses calling a webservice from PowerShell.

 

Program: Connect-WebService

Although "screen scraping" (parsing the HTML of a web page) is the most common way to obtain data from the internet, web services are becoming increasingly common. Web services provide a significant advantage over HTML parsing, as they are much less likely to break when the web designer changes minor features in their design.

The only benefit to web services isn't their more stable interface, however. When working with web services, the .NET Framework allows you to generate proxies that let you interact with the web service as easily as you would work with a regular .NET object. That is because to you, the web service user, these proxies act almost exactly the same as any other .NET object. To call a method on the web service, simply call a method on the proxy.

The primary difference you will notice when working with a web service proxy (as opposed to a regular .NET object) is the speed and internet connectivity requirements. Depending on conditions, a method call on a web service proxy could easily take several seconds to complete. If your computer (or the remote computer) experiences network difficulties, the call might even return a network error message instead of the information you had hoped for.

 The following script allows you to connect to a remote webservice, if you know the location of its service description file (WSDL.) It generates the web service proxy for you, allowing you to interact with it as you would any other .NET object.

Example 9-3. Connect-WebService.ps1

##############################################################################

## Connect-WebService.ps1

##

## Connect to a given web service, and create a type that allows you to

## interact with that web service.

##

## Example:

##

##     $wsdl = "http://terraserver.microsoft.com/TerraService2.asmx?WSDL"

##     $terraServer = Connect-WebService $wsdl

##     $place = New-Object Place

##     $place.City = "Redmond"

##     $place.State = "WA"

##     $place.Country = "USA"

##     $facts = $terraserver.GetPlaceFacts($place)

##     $facts.Center

##############################################################################

param(

    [string] $wsdlLocation = $(throw "Please specify a WSDL location"),

    [string] $namespace,

    [Switch] $requiresAuthentication)

 

## Create the web service cache, if it doesn't already exist

if(-not (Test-Path Variable:\Lee.Holmes.WebServiceCache))

{

    ${GLOBAL:Lee.Holmes.WebServiceCache} = @{}

}

 

## Check if there was an instance from a previous connection to

## this web service. If so, return that instead.

$oldInstance = ${GLOBAL:Lee.Holmes.WebServiceCache}[$wsdlLocation]

if($oldInstance)

{

    $oldInstance

    return

}

 

## Load the required Web Services DLL

[void] [Reflection.Assembly]::LoadWithPartialName("System.Web.Services")

 

## Download the WSDL for the service, and create a service description from

## it.

$wc = new-object System.Net.WebClient

 

if($requiresAuthentication)

{

    $wc.UseDefaultCredentials = $true

}

 

$wsdlStream = $wc.OpenRead($wsdlLocation)

 

## Ensure that we were able to fetch the WSDL

if(-not (Test-Path Variable:\wsdlStream))

{

    return

}

 

$serviceDescription =

    [Web.Services.Description.ServiceDescription]::Read($wsdlStream)

$wsdlStream.Close()

 

## Ensure that we were able to read the WSDL into a service description

if(-not (Test-Path Variable:\serviceDescription))

{

    return

}

 

## Import the web service into a CodeDom

$serviceNamespace = New-Object System.CodeDom.CodeNamespace

if($namespace)

{

    $serviceNamespace.Name = $namespace

}

 

$codeCompileUnit = New-Object System.CodeDom.CodeCompileUnit

$serviceDescriptionImporter =

    New-Object Web.Services.Description.ServiceDescriptionImporter

$serviceDescriptionImporter.AddServiceDescription(

    $serviceDescription, $null, $null)

[void] $codeCompileUnit.Namespaces.Add($serviceNamespace)

[void] $serviceDescriptionImporter.Import(

    $serviceNamespace, $codeCompileUnit)

 

## Generate the code from that CodeDom into a string

$generatedCode = New-Object Text.StringBuilder

$stringWriter = New-Object IO.StringWriter $generatedCode

$provider = New-Object Microsoft.CSharp.CSharpCodeProvider

$provider.GenerateCodeFromCompileUnit($codeCompileUnit, $stringWriter, $null)

 

## Compile the source code.

$references = @("System.dll", "System.Web.Services.dll", "System.Xml.dll")

$compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters

$compilerParameters.ReferencedAssemblies.AddRange($references)

$compilerParameters.GenerateInMemory = $true

 

$compilerResults =

    $provider.CompileAssemblyFromSource($compilerParameters, $generatedCode)

 

## Write any errors if generated.        

if($compilerResults.Errors.Count -gt 0)

{

    $errorLines = ""

    foreach($error in $compilerResults.Errors)

    {

        $errorLines += "`n`t" + $error.Line + ":`t" + $error.ErrorText

    }

 

    Write-Error $errorLines

    return

}

## There were no errors.  Create the webservice object and return it.

else

{

    ## Get the assembly that we just compiled

    $assembly = $compilerResults.CompiledAssembly

 

    ## Find the type that had the WebServiceBindingAttribute.

    ## There may be other "helper types" in this file, but they will

    ## not have this attribute

    $type = $assembly.GetTypes() |

        Where-Object { $_.GetCustomAttributes(

            [System.Web.Services.WebServiceBindingAttribute], $false) }

 

    if(-not $type)

    {

        Write-Error "Could not generate web service proxy."

        return

    }

 

    ## Create an instance of the type, store it in the cache,

    ## and return it to the user.

    $instance = $assembly.CreateInstance($type)

    ${GLOBAL:Lee.Holmes.WebServiceCache}[$wsdlLocation] = $instance

    $instance

}

 

[Update: Several readers have requested that this script support web services that reqire credentials, and support web services that return the same type of object. Added parameters to allow this.]

Comments [8] | | # 
 Monday, December 04, 2006
Monday, December 04, 2006 12:29:19 AM (Pacific Standard Time, UTC-08:00) ( )

To give a glimpse into the writing process behind my upcoming "Windows PowerShell - The Definitive Guide" (O'Reilly,) I'll occasionally post entries "as the author sees it."  This entry discusses PowerShell's type extension files.

 

Add Custom Methods and Properties to Types

Problem

You want to add your own custom properties or methods to all objects of a certain type.¶

Solution

Use custom type extension files to add custom members to all objects of a type.

Discussion

Although the Add-Member cmdlet is extremely useful in helping you add custom members to individual objects, it requires that you add the members to each object that you want to interact with. It does not allow you to automatically add them to all objects of that type. For that purpose, PowerShell supports another mechanism custom type extension files.

Type extensions are simple XML files that PowerShell interprets. They allow you (as the administrator of the system) to easily add your own features to any type exposed by the system. If you write code (i.e.: a script or function) that primarily interacts with a single type of object, then that code might be better suited as an extension to the type, instead.

Since type extension files are XML files, ensure that your customizations properly encode the characters that have special meaning in XML files such as <, >, and &

For example, imagine a script that returns the free disk space on a given drive. That might be helpful as a script, but you might find it easier to instead make PowerShell's PsDrive objects themselves tell you how much free space they have left.¶

Getting Started

If you haven't already, the first step in creating a types extension file is to create an empty one. The best location for this is probably in the same directory as your custom profile, with the name Types.Custom.ps1xml

Example 3-2. Sample Types.Custom.ps1xml file

<?xml version="1.0" encoding="utf-8" ?>¶

<Types>¶

</Types>¶

Next, add a few lines to your PowerShell profile so that PowerShell loads your type extensions during startup:¶

$typeFile = (join-path (split-path $profile) "Types.Custom.ps1xml")¶

Update-TypeData -PrependPath $typeFile¶

By default, PowerShell loads several type extensions from the Types.ps1xml file in PowerShell's installation directory. The Update-TypeData cmdlet tells PowerShell to also look in your Types.Custom.ps1xml file for extensions. The –PrependPath parameter makes PowerShell favour your extensions over the built-in ones should there be a conflict.¶

Once you have a custom types file to work with, adding functionality becomes relatively straight forward. As a theme, we will do exactly what we alluded to earlier: add functionality to PowerShell's PsDrive type.¶

To support this, we need to extend your custom types file so that it defines additions to the System.Management.Automation.PSDriveInfo type. This is the type that the Get-PsDrive cmdlet generates.¶

<?xml version="1.0" encoding="utf-8" ?>¶

<Types>¶

  <Type>

    <Name>System.Management.Automation.PSDriveInfo</Name>

    <Members>

          add members such as <ScriptProperty> here

    </Members>

  </Type>

</Types>¶

Add a ScriptProperty

A ScriptProperty allows you to add properties (that get and set information) to types, using PowerShell script as the extension language. It consists of three child elements: the Name of the property, the Getter of the property (via the GetScriptBlock child,) and the Setter of the property (via the SetScriptBlock child.)¶

In both the GetScriptBlock and SetScriptBlock sections, the $this variable refers to the current object being extended. In the SetScriptBlock section, the $args[0] variable represents the value that the user supplied as the right-hand side of the assignment.¶

The following example adds an AvailableFreeSpace ScriptProperty to PSDriveInfo. When you access the property, it returns the amount of free space remaining on the drive. When you set the property, it outputs what changes you must make in order to obtain that amount of free space. ¶

Example 3-3. A ScriptProperty for the PSDriveInfo type

      <ScriptProperty>

        <Name>AvailableFreeSpace</Name>

        <GetScriptBlock>

          ## Ensure that this is a FileSystem drive

          if($this.Provider.ImplementingType -eq

             [Microsoft.PowerShell.Commands.FileSystemProvider])

          {

             ## Also ensure that it is a local drive

             $driveRoot = $this.Root

             $fileZone = [System.Security.Policy.Zone]::CreateFromUrl($driveRoot).SecurityZone

             if($fileZone -eq "MyComputer")

             {

                $drive = New-Object System.IO.DriveInfo $driveRoot

                $drive.AvailableFreeSpace

             }

          }

        </GetScriptBlock>

        <SetScriptBlock>

          ## Get the available free space

          $availableFreeSpace = $this.AvailableFreeSpace

 

          ## Find out the difference between what is available, and what they

          ## asked for.

          $spaceDifference = (([long] $args[0]) - $availableFreeSpace) / 1MB

 

          ## If they want more free space than they have, give that message

          if($spaceDifference -gt 0)

          {

             Write-Host ("To obtain $args bytes of free space, free $spaceDifference " +

                "megabytes.")

          }

          ## If they want less free space than they have, give that message

          else

          {

             $spaceDifference = $spaceDifference * -1

             Write-Host ("To obtain $args bytes of free space, use up $spaceDifference " +

                "more megabytes.")

          }

        </SetScriptBlock>

      </ScriptProperty>

Add an AliasProperty

An AliasProperty provides an alternative name (alias) for a property. The referenced property does not need to exist when PowerShell processes your type extension file, as the property might be added later by mechanisms such as the Add-Member cmdlet.¶

The following example adds a Free AliasProperty to PSDriveInfo. When you access the property, it returns the value of the AvailableFreeSpace property. When you set the property, it sets the value of the AvailableFreeSpace property. ¶

Example 3-4. An AliasProperty for the PSDriveInfo type

      <AliasProperty>

        <Name>Free</Name>

        <ReferencedMemberName>AvailableFreeSpace</ReferencedMemberName>

      </AliasProperty>

Add a ScriptMethod

A ScriptMethod allows you to define an action on an object, using PowerShell script as the extension language. It consists of two child elements: the Name of the property, and the Script

In the script element, the $this variable refers to the current object being extended. Like a stand-alone script, the $args variable represents the arguments to the method. Unlike stand-alone scripts, ScriptMethods do not support parameters (via the param statement.)¶

The following example adds a Remove ScriptMethod to PSDriveInfo. When you call this method with no arguments, the method simulates removing the drive (through the –WhatIf option to Remove-PsDrive.) If you call this method with $true as the first argument, it actually removes the drive from the PowerShell session.¶

Example 3-3. A ScriptMethod for the PSDriveInfo type

      <ScriptMethod>

        <Name>Remove</Name>

        <Script>

          $force = [bool] $args[0]

 

          ## Remove the drive if they use $true as the first parameter

          if($force)

          {

             $this | Remove-PSDrive

          }

          ## Otherwise, simulate the drive removal

          else

          {

             $this | Remove-PSDrive -WhatIf

          }

        </Script>

      </ScriptMethod>

Add other extension points

PowerShell supports several additional features in the types extension file, including CodeProperty, NoteProperty, CodeMethad, and MemberSet. Although not generally useful to end-users, developers of PowerShell providers and cmdlets will find these features helpful. For more information about these additional features, see the Windows PowerShell SDK, or MSDN documentation.¶

Comments [4] | | # 
 Saturday, October 21, 2006
Saturday, October 21, 2006 5:40:44 PM (Pacific Standard Time, UTC-08:00) ( )

To give a glimpse into the writing process behind my upcoming "Windows PowerShell - The Definitive Guide" (O'Reilly,) I'll occasionally post entries "as the author sees it."  The first in this series shows how to search the PowerShell help content for a phrase or pattern.

 

Program: Get-HelpMatch¶

Both the Get-Command and Get-Help cmdlets allow you to search for command names that match a given pattern. However, when you don't know exactly what you are looking for, you will more often have success searching through the help content for an answer. On Unix systems, this command is called Apropos.  Similar functionality does not exist as part of the PowerShell's help facilities, however.¶

That doesn't need to stop us, though, as we can write the functionality ourselves.¶

To run this program, supply a search string to the Get-HelpMatch script. The search string can be either simple text, or a regular expression. The script then displays the name and synopsis of all help topics that match. To see the help content for that topic, use the Get-Help cmdlet.¶

Example 1-4. Get-HelpMatch.ps1¶

##############################################################################¶

## Get-HelpMatch.ps1¶

##¶

## Search the PowerShell help documentation for a given keyword or regular¶

## expression.¶

## ¶

## Example:¶

##    Get-HelpMatch hashtable¶

##    Get-HelpMatch "(datetime|ticks)"¶

##############################################################################¶

 ¶

param($searchWord = $(throw "Please specify content to search for"))¶

 ¶

$helpNames = $(get-help *)¶

 ¶

foreach($helpTopic in $helpNames)¶

   $content = get-help -Full $helpTopic.Name | out-string¶

   if($content -match $searchWord)¶

  

      $helpTopic | select Name,Synopsis¶

  

 

Comments [1] | | # 
 Friday, September 29, 2006
Friday, September 29, 2006 2:18:27 PM (Pacific Standard Time, UTC-08:00) ( )

My O'Reilly PowerShell Quick Reference is now available – a 120-page guide (in PDF format) that provides the essential reference material for your day-to-day use of PowerShell.  With a concise explanation at your fingertips, there is no need to memorize esoteric topics like regular expressions and string formatting specifiers.

Aside from its straight factual reference material, the Quick Reference also provides an enormous amount of value by distilling large bodies of knowledge into their most usable forms, including:

  • The most useful classes from the .NET Framework
  • The most useful WMI classes for WMI-based tasks
  • The most useful COM automation objects

You can get it here: http://www.oreilly.com/catalog/windowspowershell/.  If you are interested in reviewing it for your blog or publication, let me know and I can try to arrange it.

Tip: If you decide to print it out, set your printer to do it double-sided, in booklet format.  Cut it in the middle, then staple it in the correct order -- it really looks great in that format.

[Edit: Please let me know if you would be interested in a printed version if it were available for slightly more.]

Comments [9] | | # 
 Friday, September 01, 2006
Friday, September 01, 2006 9:34:37 PM (Pacific Standard Time, UTC-08:00) ( )

Something extremely helpful in learning a technology such as PowerShell is a pocket guide and quick reference.  It doesn’t necessarily replace a structured tutorial as a learning resource, but fills an important role as a daily desktop reference.

Over the last while, I’ve been working with O’Reilly to create such a guide – as the most ambitious addition to their “Short Cuts” program to date.

The reference will be available in the very near future, so stay tuned!

Comments [0] | | #