Keith's profileKeith Hill's BlogPhotosBlogListsMore Tools Help
    October 30

    PowerShell - Find-PInvoke.ps1

    I had a need to find all PInvokes in an assembly.  At first I thought about searching the source code since I had it but ran into various problems:

    • Commented out PInvokes
    • DLL names specified via constants

    [Updated: 11-02-2009] I decided to go with the "reflection only load" approach to extract the PInvoke (DllImport) information.  The script file has been updated to reflect (pun intended) this new and better approach.  In fact, more data is gathered for each PInvoke including all of the values for the associated DllImportAttribute.  Now to see if I can get this included in the next version of PSCX.   It seems that the reflection only load is sensitive to assembly load failures.  Once an assembly fails to load you pretty much have to start a new PowerShell session to try again.  I'll keep looking into this.  For what appears to be a really neat capability (ReflectionOnlyLoad) it has some unfortunate short-comings.

    Here is some sample output when run on the PSCX snapin dlls.

    PS> gci C:\Users\Keith\Pscx\Trunk\Src\Pscx\bin\Debug\Pscx*.dll | .\Find-PInvoke |
            Sort DllName,AssemblyName,MethodName | ft AssemblyName,MethodName,TypeName -groupby DllName -auto

       DllImport: advapi32.dll

    Assembly      MethodName            TypeName                       
    --------      ----------            --------                       
    Pscx.Core.dll AdjustTokenPrivileges Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetTokenInformation   Pscx.Interop.UnsafeNativeMethods
    Pscx.Core.dll LogonUser             Pscx.Interop.NativeMethods     
    Pscx.Core.dll LookupPrivilegeName   Pscx.Interop.NativeMethods     
    Pscx.Core.dll LookupPrivilegeValue  Pscx.Interop.NativeMethods     
    Pscx.Core.dll OpenProcessToken      Pscx.Interop.NativeMethods     
    Pscx.Core.dll OpenThreadToken       Pscx.Interop.NativeMethods     

       DllImport: Fusion.dll

    Assembly      MethodName                 TypeName                 
    --------      ----------                 --------                 
    Pscx.Core.dll CreateAssemblyCache        Pscx.Interop.NativeMethods
    Pscx.Core.dll CreateAssemblyEnum         Pscx.Interop.NativeMethods
    Pscx.Core.dll CreateAssemblyNameObject   Pscx.Interop.NativeMethods
    Pscx.Core.dll CreateInstallReferenceEnum Pscx.Interop.NativeMethods
    Pscx.Core.dll GetCachePath               Pscx.Interop.NativeMethods

       DllImport: kernel32.dll

    Assembly      MethodName                       TypeName                       
    --------      ----------                       --------                       
    Pscx.Core.dll CloseHandle                      Pscx.Interop.NativeMethods     
    Pscx.Core.dll CreateConsoleScreenBuffer        Pscx.Interop.NativeMethods     
    Pscx.Core.dll CreateFile                       Pscx.Interop.NativeMethods     
    Pscx.Core.dll CreateHardLink                   Pscx.Interop.NativeMethods     
    Pscx.Core.dll CreateSymbolicLink               Pscx.Interop.NativeMethods     
    Pscx.Core.dll DeleteFile                       Pscx.Interop.NativeMethods     
    Pscx.Core.dll DeleteVolumeMountPoint           Pscx.Interop.NativeMethods     
    Pscx.Core.dll DeviceIoControl                  Pscx.Interop.UnsafeNativeMethods
    Pscx.Core.dll FindFirstVolumeMountPoint        Pscx.Interop.NativeMethods     
    Pscx.Core.dll FindNextVolumeMountPoint         Pscx.Interop.NativeMethods     
    Pscx.Core.dll FindVolumeMountPointClose        Pscx.Interop.NativeMethods     
    Pscx.Core.dll FreeLibrary                      Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetConsoleFontSize               Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetConsoleInputMode              Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetConsoleOutputMode             Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetConsoleWindow                 Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetCurrentConsoleFont            Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetCurrentProcess                Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetModuleHandle                  Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetProcAddress                   Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetProcAddress                   Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetShortPathName                 Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetStdHandle                     Pscx.Interop.NativeMethods     
    Pscx.Core.dll GetVolumeNameForVolumeMountPoint Pscx.Interop.NativeMethods     
    Pscx.Core.dll IsWow64Process                   Pscx.Interop.NativeMethods     
    Pscx.Core.dll LoadLibrary                      Pscx.Interop.NativeMethods     
    Pscx.Core.dll ReadConsoleInput                 Pscx.Interop.NativeMethods     
    Pscx.Core.dll ReadFile                         Pscx.Interop.NativeMethods     
    Pscx.Core.dll ReadFile                         Pscx.Interop.NativeMethods     
    Pscx.Core.dll RemoveDirectory                  Pscx.Interop.NativeMethods     
    Pscx.Core.dll SetConsoleActiveScreenBuffer     Pscx.Interop.NativeMethods     
    Pscx.dll      __SetVolumeLabel                 Pscx.Commands.IO.SetVolumeLabel

    October 29

    PowerShell 2.0 – Accessing Different Profiles

    Just ran across a new feature of PowerShell 2.0 that is convenient.  Need the path to the various profile scripts on a system?  Try this:

    PS> $profile.psextended | Format-List

    AllUsersAllHosts       : C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
    AllUsersCurrentHost    : C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
    CurrentUserAllHosts    : C:\Users\Keith\Documents\WindowsPowerShell\profile.ps1
    CurrentUserCurrentHost : C:\Users\Keith\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

    To access individually you can use $profile.AllUsersAllHosts.

    psmdtag:variable Profile

    October 21

    Windows PowerShell 2.0 String Localization

    One of the lesser known features in PowerShell 2.0 is that it supports string localization and pretty simply I might add.  Now for most developers and admins script localization probably isn’t going to be something you’ll worry about.  However if you are providing PowerShell based solutions to an international audience this feature will come in very handy in terms of broadening your reach.

    I believe the driving force behind this feature’s inclusion in PowerShell is that a critical component of Windows 7 – the Windows Troubleshooting Platform and associated troubleshooting packs – use PowerShell scripts extensively.  Obviously Windows is localized to the extreme so it stands to reason that the troubleshooting scripts, which can interact with the end user via text prompts, were required to be localized.  Enough context.  Let’s look at how localization works in PowerShell 2.0.

    First, the feature in PowerShell is referred to as “Data Sections” and is covered by the help topics:

    • about_data_sections
    • about_script_internationalization

    Let’s start by creating our string table file.  This is created as a .psd1 (data) file.  In this case, we will call it messages.psd1 and its contents are simply:

    # Contents of .\messages.psd1
    ConvertFrom-StringData @' Hello_F1=Hello {0} Goodbye=Goodbye '@

    This is a bit odd looking for a “data” file but the gist is that the ConvertFrom-StringData cmdlet returns a ordinary hashtable.  The string that ConvertFrom-StringData operates on has to be in a special format which is essentially one key/value pair per line where the key and value are separated by an “=”.  The “key” is how you will reference the “value” (actual localized string) from your script.  FWIW you could just have the psd1 file return a hashtable directly but then you have to quote each value.  So the format above is a bit cleaner and more like a traditional string table file.

    Now at this point you could and might be tempted to do something like this in your script:

    $msgs = .\messages.psd1

    But there is one tiny little problem - .psd1 files are not executable like .ps1 or .psm1 files are.  Hey, that’s probably why they are called “data” files.  :-)  Obviously PowerShell must provide a way to load these files and that mechanism is the Import-LocalizedData cmdlet.  It takes the path to a .psd1 file, loads it and then assigns the resulting hashtable to a variable name provided to the cmdlet.  Let’s see an example of this:

    # Contents of test.ps1
    Import-LocalizedData -BindingVariable msgs -FileName Messages.psd1 $msgs.Hello_F1 -f "John" $msgs.Goodbye

    The output of this script is:

    PS> .\test.ps1
    Hello John
    Goodbye

    You may be thinking that this seems like a pretty simple task, not worthy of requiring a dedicated cmdlet.  However there is one very important piece of “non-trivial” functionality that the Import-LocalizedData cmdlet provides and that is it searches through various culture specific sub directories looking for a matching .psd1 file for the user’s culture.  For those familiar with .NET development this is very similar to how the .NET binder looks through application’s base sub dirs for the appropriate satellite assembly for the user’s culture.  Let’s see an example of how this works.

    First, let’s create a German version of our string resources and put the file in a sub dir titled de-DE.  The name of this sub dir is important.  The name reflects the <language>-<region> that the localized strings target.  Here are the contents of .\de-DE\messages.psd1:

    # Contents of .\de-DE\messages.psd1
    ConvertFrom-StringData @' Hello_F1=Wie Geht {0} Goodbye=Auf Wiedersehen '@

    At this point our dir structure looks like this:

    .\test.ps1
    .\messages.psd1 (English – fallback)
    .\de-DE\messages.psd1

    Now I need to introduce a somewhat orthogonal but very handy function called Using-Culture that the PowerShell team blogged a long time ago.  I have updated it for PowerShell 2.0 and I use it to effectively simulate running PowerShell scripts in different cultures.

    function Using-Culture ([System.Globalization.CultureInfo]$culture =(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"),
    [ScriptBlock]$script=(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"))
    {
    $OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
    $OldUICulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture
    try {
    [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture
    [System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture
    Invoke-Command $script
    }
    finally {
    [System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture
    [System.Threading.Thread]::CurrentThread.CurrentUICulture = $OldUICulture
    }
    }

    Now let’s try running out test.ps1 script under the German language/region:

    PS> Using-Culture de-DE { .\test.ps1 }
    Wie Geht John
    Auf Wiedersehen

    Now that’s what I’m talking about!  Script localization without requiring an advanced degree.  Now, you might ask – what happens when somebody run’s in a culture that I have not localized for?  Let’s see:

    PS> Using-Culture fr-FR { .\test.ps1 }
    Hello John
    Goodbye

    When I listed the dir contents above I mentioned that the top-level messages.psd1 was the “fallback”.  Essentially if PowerShell can’t find the specified data file for the current culture it will “fallback” to the top-level data file or .\messages.psd1 in this case (which is English).  This may very well be a feature you never use but if you desire to reach a broad audience with your PowerShell 2.0 modules, consider pulling out your strings into a data file even if you only provide a data file for your native language.  This gives others a much better chance of localizing your module into other languages for you!

    It’s Official - Windows 7 and Windows PowerShell 2.0 for Everyone!

    Congratulations to the Windows and Windows PowerShell teams for two very excellent releases.  I’ve been using Windows 7 daily since January’s beta release and PowerShell 2.0 drops for even longer.  Both products are destined to be smash hits in my humble opinion.  I’m very excited about the new capabilities of PowerShell 2.0 and the fact that it’s a built-in and required component of Windows 7 means that the time when we can count on PowerShell “just being there” is getting closer.

    October 16

    Windows PowerShell 2.0 Virtual Launch Party

    I’m relaying this invite from Hal & Jonathan – hosts of the PowerScripting Podcast.

    Windows isn’t just about the GUI. Starting with Windows 7, you have built-in access to PowerShell version 2, an object-oriented scripting language and command shell. Please join PowerScripting Podcast hosts Jonathan Walz and PowerShell MVP Hal Rottenberg as they interview Distinguished Engineer Jeffrey Snover on launch day! Jeffrey is the chief architect responsible for PowerShell at Microsoft, and he’ll be covering what’s new with the tool and why every system administrator on the planet needs to be using it. If you’ve never attended PowerScripting Live, you are missing out on a great time. The show will be streamed live via Ustream, and viewers can chat with each other, as well as submit questions for the guest.

    October 12

    Windows PowerShell 2.0 and Windows Troubleshooting Platform Presentation

    Tonight I gave a presentation on the new features in Windows PowerShell 2.0.  I also demo how to create a Windows Troubleshooting Platform that is new in Windows 7.  WTP uses PowerShell scripts to do the heavy lifting of detecting and resolving root causes i.e. problems that need fixing.  I gave this presentation at the Northern Colorado.NET Special Interest Group.  As promised, here are the presentation materials: slide deck and Start-Demo samples.

    August 06

    PowerShell Community Extensions 1.2 Released

    We have released PSCX 1.2 just in time for Windows 7 RTM availability on MSDN and TechNet.  Thanks to those folks who tried out the beta for the past three months!  And a huge thanks goes to PowerShell MVP Oisin Grehan who put a lot of effort into 1.2 and is the reason you have Read-Archive and Expand-Archive cmdlets among other new features.
     
    The big enhancements for 1.2 are:
     
    • Supports both PowerShell version 1.0 and 2.0
    • Installer updated to allow you to choose between A) install PSCX  default profile, B) append PSCX init script to your existing profile or C) don't mess with my profile.
    • PSCX settings provider so that there is no impact on your global variables
    Major New Cmdlets
    • Read-Archive
    • Expand-Archive
    • Tail-File (with ability to wait for new output e.g. tail -f)
    • Get/Push/Pop-EnvironmentBlock for saving and restoring the state of your environment variables

    New Scripts

    • Invoke-BatchFile (eg InvokeBatchFile vcvarsall x86 and yes the environment variables created in the batch file persist into the PowerShell session)
    I hope you find as much utility in these cmdlets as I have.  I use them everyday - of course!  If you have any feedback please log it on the CodePlex project site on either the discussion forum or issue tracker.  And if you download PSCX and are passionate about it (one way or the other), please write a review on the download page for 1.2
     
    Next up for PSCX is 2.0 which will most likely target only PowerShell 2.0 and come in Module form.
    August 03

    Effective PowerShell Item 16: Dealing with Errors

    There are several facets to the subject of errors in PowerShell that you should understand to get the most out of PowerShell.  Some of these facets are error handling, error related global variables and error related preference variables.  But the most fundamental facet is the distinction between “terminating” and “non-terminating” errors.

    Terminating Errors

    Terminating errors will be immediately familiar to software developers who deal with exceptions.  If an exception is not handled it will cause the program to crash.  Similarly if a terminating error is not handled it will cause the current operation (cmdlet or script) to abort with an error.  Terminating errors and are generated by:

    • Cmdlet calling the ThrowTerminatingError API.
    • Exceptions escaping unhandled from a cmdlet
    • Script using the “throw” keyword to issue a terminating error
    • Script syntax errors

    The gist of a terminating error is that the code throwing the terminating error is indicating that it cannot reasonably continue and is aborting the requested operation.  As we will see later, you as the client of that code, have the ability to declare that you can handle the error and continue executing subsequent commands.  Terminating errors that are not handled propagate up through the calling code, prematurely terminating each calling function or script until either the error is handled or the original invoking operation is terminated.

    Here is an example of how a terminating error alters control flow:

    PS> "Before"; throw "Oops!"; "After"
    Before
    Oops!
    At line:1 char:16
    + "Before"; throw <<<<  "Oops!"; "After"
        + CategoryInfo          : OperationStopped: (Oops!:String) [], RuntimeException
        + FullyQualifiedErrorId : Oops!

    Note that “After” is not output to the console because “throw” issues a terminating error.

    Non-terminating Errors

    Have you ever experienced the following in older versions of Windows Explorer?  You open a directory with a large number of files, say your temp dir, and you want to empty it.  You select the entire contents of the directory, press Delete and wait.  Unfortunately some processes invariably have files open in the temp directory.  So after deleting a few files, you get an error from Windows Explorer indicating that it can’t delete some file.  You press OK and at this point Windows Explorer aborts the operation.  It treats the error effectively as a terminating error.  This can be very frustrating.  You select everything again, press Delete, Explorer deletes a few more files then errors and aborts again.  You rinse and repeat these steps until finally all the files that can be deleted are deleted.  This behavior is very annoying  and wastes your time.  In an automation scenario, premature aborts like this are often unacceptable.

    Having a special category of error that does not terminate the current operation is very useful in scenarios like the one outlined above. In PowerShell, that category is the non-terminating error.  Even though a non-terminating error does not terminate the current operation, the error is still logged to the $Error collection (discussed later) as well as displayed on the host’s console as is the case with terminating errors.  Non-terminating errors are generated by:

    • Cmdlet calling the WriteError API.
    • Script using the Write-Error cmdlet to log a non-terminating error
    • Exceptions thrown from calls to a member of a .NET object or type.

    Here is an example of how a non-terminating error does not alter control flow:

    PS> "Before"; Write-Error "Oops!"; "After"
    Before
    "Before"; Write-Error "Oops!"; "After" : Oops!
        + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
        + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException

    After

    Note the Write-Error command issues a non-terminating error that gets displayed on the host’s console then the script continues execution.

    Error Variables

    There are several global variables and global preference variables related to errors.  Here is a brief primer on them:

    • $? - contains the execution status of the last operation.  True indicates the operation succeeded without any errors.  False indicates either complete failure or partial success.  Note: for Windows executables the exit code is examined.  An exit code of 0 will be interpreted as success and non-zero as failure.  Some Windows console apps don’t honor this convention so it is usually better to inspect $LASTEXITCODE such that you can determine for yourself success or failure based your interpretation of the exit code.
    • $LASTEXITCODE – exit code of the last Windows executable invoked from this session.
    • $Error – collection (ArrayList to be specific) of errors that have occurred in the current session.  Errors are always inserted at the beginning of the collection.  As a result, the most recent error is always located at index 0.
    • $MaximumErrorCount – determines the size of the $Error collection.  Defaults to 256 which is the minimum value allowed.  Max value is 32768.
    • $ErrorActionPreference – influences the dispatching of non-terminating errors.  The default is ‘Continue’ which adds an entry to the $Error collection and displays the error on the host’s console.
    • $ErrorView – specifies one of two views for error records when they’re displayed on the host.  The default is ‘NormalView’ which displays several lines of information.  For production environments, you can set this to ‘CategoryView’ to get a succinct one line error message.  Remember that all the details are still available in the $Error collection.

    The $Error global variable can be used to inspect the details of up to the last $MaximumErrorCount number of errors that have occurred during the session e.g.:

    PS> $error[0] | fl * -force

    PSMessageDetails      :
    Exception             : System.IO.IOException: The process cannot access the file '\Temp\FX
                            SAPIDebugLogFile.txt' because it is being used by another process.
                               at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
                               at System.IO.FileInfo.Delete()
                               at Microsoft.PowerShell.Commands.FileSystemProvider.RemoveFileSystemItem(FileSystemInfo file
                            SystemInfo, Boolean force)
    TargetObject          : \Temp\FXSAPIDebugLogFile.txt
    CategoryInfo          : WriteError: (\Temp\FXSAPIDebugLogFile.txt:FileInfo) [Remove-Item], IOException
    FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
    ErrorDetails          : Cannot remove item \Temp\FXSAPIDebugLogFile.txt: The process cannot
                             access the file '\Temp\FXSAPIDebugLogFile.txt' because it is being
                             used by another process.
    InvocationInfo        : System.Management.Automation.InvocationInfo
    PipelineIterationInfo : {0, 1}

    As the output above shows, errors in PowerShell are not just strings but rich objects.  The object may be a .NET exception with an embedded error record or just an error record,  The error record contains lots of useful information about the error and the context in which it occurred.

    The default output formatting of errors can be a bit hard to digest.  The PowerShell Community Extensions come with a handy Resolve-Error function that digs through the error information and surfaces the important stuff e.g.:

    PS> Resolve-Error # displays $error[0] by default

    PS> Resolve-Error $error[1]

    The $? global variable is handy for determining if the last operation encountered any errors e.g.:

    PS> Remove-Item $env:temp\*.txt -Recurse -Verbose
    VERBOSE: Performing operation "Remove File" on Target "...\Temp\foo.txt".
    VERBOSE: Performing operation "Remove File" on Target "...\Temp\FXSAPIDebugLogFile.txt".
    WriteError: (...\Temp\DebugLogFile.txt:FileInfo) [Remove-Item], IOException
    PS> $?
    False

    In this case, the Remove-Item cmdlet only partially succeeded.  It deleted two files but then encountered a non-terminating error. This failure to achieve complete success i.e. no errors, is indicated by $? returning False.

    Working with Non-Terminating Errors

    Sometimes you want to completely ignore non-terminating errors.  Who wants all that red text spilled all over their console especially when you don’t care about the errors you know you're going to get.  You can suppress the display of non-terminating errors either locally or globally.  To do this locally, just set the cmdlet’s ErrorAction parameter to SilentlyContinue e.g.

    Remove-Item $env:temp\*.txt -Recurse -Verbose -ErrorAction SilentlyContinue

    For interactive scenarios it is handy to use 0 instead of SilentlyContinue.  This works because SilentlyContinue is part of a enum and its integer value is 0.   So to save your wrists you can rewrite the above as:

    ri $env:temp\*.txt -r -v –ea 0

    Note that for a script I would use the first approach for readability.

    To accomplish the above globally, set the $ErrorActionPreference global preference variable to SilentlyContinue (or 0).  This will cause all non-terminating errors in the session to not be displayed on the host’s console.  However they will still be logged to the $Error collection. 

    Setting the $ErrorActionPreference to Stop can be useful in the following scenario.  If you misspell a command, PowerShell will generate a non-terminating error as shown below:

    PS> Copy-Itme .\_lesshst .\_lesshst.bak; $?; "After"
    The term 'Copy-Itme' is not recognized as the name of a cmdlet, function, scrip
    t file, or operable program. Check the spelling of the name, or if a path was i
    ncluded, verify that the path is correct and try again.
    At line:1 char:10
    + Copy-Itme <<<<  .\_lesshst .\_lesshst.bak; $?; "After"
        + CategoryInfo          : ObjectNotFound: (Copy-Itme:String) [], CommandNo
       tFoundException
        + FullyQualifiedErrorId : CommandNotFoundException

    False
    After

    In this case, the misspelled Copy-Itme command failed ($? returned False) but since the error was non-terminating, the script continues execution as shown by the output “After”. 

    If you are hard-core about correctness you can get PowerShell to convert non-terminating errors into terminating errors by setting $ErrorActionPreference to Stop which has global impact.  You can also do this one a cmdlet by cmdlet basis by setting the cmdlet’s –ErrorAction parameter to Stop.

    The last issue to be aware of regarding non-terminating errors is that a Windows executable that returns a non-zero exit code does not generate any sort of error.  The only action PowerShell takes is to set $? to False if the exit code is non-zero.  There is no error record created and stuffed into $Error.  In many cases, the failure of an external executable means your script cannot continue.  In this case, it is desirable to convert a failure exit code into a terminating error.  This can be done easily using the function below:

    function CheckLastExitCode {
        param ([int[]]$SuccessCodes = @(0), [scriptblock]$CleanupScript=$null)

        if ($SuccessCodes -notcontains $LastExitCode) {
            if ($CleanupScript) {
                "Executing cleanup script: $CleanupScript"
                &$CleanupScript
            }
            $msg = @"
    EXE RETURNED EXIT CODE $LastExitCode
    CALLSTACK:$(Get-PSCallStack | Out-String)
    "@
            throw $msg
        }
    }

    Note that Get-PSCallStack is specific to PowerShell v2.0.  Invoke CheckLastExitCode right after invoking an executable, well at least for those cases where you care if an executable returns an error.  This function provides a couple of handy features.  First, you can specify an array of acceptable success codes which is useful for exes that return 0 for failure and 1 for success and is also useful for exes that return multiple success codes.  Second, you specify a cleanup scriptblock that will get executed on failure.

    Handling Terminating Errors

    Handling terminating errors in PowerShell comes in two flavors.  Using the trap keyword which is supported in both version 1 and 2 of PowerShell.  Using try { } catch { } finally { } which is new to version 2.

    Trap Statement

    Trap is a mechanism available in other shell languages like Korn shell.  It effectively declares that either any error type or a specific error type is handled by the scriptblock following the trap keyword.  Trap has the interesting property that where ever it is declared in a scope, it is valid for that entire scope e.g.:

    Given the following script (trap.ps1):

    "Before"
    throw "Oops!"
    "After"
    trap { "Error trapped: $_" }

    Invoking it results in the following output:

    PS> .\trap.ps1
    Before
    Error trapped: Oops!
    Oops!
    At C:\Users\Keith\trap.ps1:2 char:6
    + throw <<<<  "Oops!"
        + CategoryInfo          : OperationStopped: (Oops!:String) [], RuntimeException
        + FullyQualifiedErrorId : Oops!

    After

    Note that it doesn’t matter that the trap statement is after the line that throws the error.  Also note that since the default value for $ErrorActionPreference is 'Continue', the error is displayed, logged to $Error but execution resumes at the next statement.  Note: within the context of a trap statement, $_ represents the error that was caught.

    Another thing to consider is whether to use Write-Host or Write-Output to display text in the trap statement.  The example above implicitly uses Write-Output.  This has the benefit that the text can be redirected to a log file.  The downside is that if the exception is handled and execution continues, that text will become part of the output for that scope which, in the case of functions and scripts, may not be desirable.

    If you want to execute cleanup code on failure but still terminate execution, we can change the trap statement to use the break keyword.  Consider the following script:

    function Cleanup() {"cleaning up"}
    trap { "Error trapped: $_"; continue }
    "Outer Before"
    & {
        trap { Cleanup; break }
        "Inner Before"
        throw "Oops!"
        "Inner After"
        Cleanup
    }   
    "Outer After"

    Note that the inner trap calls the Cleanup function but then propagates the error.  As a result, the “Inner After” statement never executes because control flow is transferred outside the scope of the trap statement.  The outer trap then catches the error, displays it and continues execution.  As a result, the “Outer After” statement is executed.

    The interaction between the control flow altering keywords valid in a trap statement (break, continue and return), the $ErrorActionPreference variable if no control flow altering keyword is used and the final behavior is somewhat complex as is demonstrated by the table below:

    Trap Behavior:

    Keyword Used Rely on $ErrorActionPreference Displays Error Propagates Error
    break Stop True True
    continue SilentlyContinue False False
    return Continue True False
    return<object>* N/A True False
    N/A Inquire Depends upon response Depends upon response

    * <object> is appended to the end of the trap scope’s output.

    All of the examples of trap shown above trap all errors.  You may want to trap only specific errors.  You can do this by specifying the type name of an exception to trap as shown below:

    trap [System.DivideByZeroException] { "Please don't divide by 0!"}
    $divisor = 0
    1/$divisor

    If you want to execute different code for different errors, you can define multiple trap statements in your script:

    trap [System.DivideByZeroException] { "Please don't divide by 0!"}

    trap [System.Management.Automation.CommandNotFoundException] { "Did you fat finger the command name?" }
    trap { "Anything not caught by the first two traps gets here" }

    If you define multiple trap statements for the same error type the first one wins and the others within the same scope are ignored.

    Try / Catch / Finally

    Version 2 of Windows PowerShell introduces try/catch/finally statements - a new error handling mechanism that most developers will be immediately familiar with.  There are two main differences between trap and try/catch/finally.  First, a trap anywhere in a lexical scope covers the entire lexical scope.  With a try statement, only the script within the try statement is checked for errors.  The second difference is that trap doesn’t support finally behavior i.e., always execute the finally statement whether the code in the try statement throws a terminating error or not.  In fact, any associated catch statements could also throw a terminating error and the finally statement would still execute. 

    You can fake finally behavior with trap by calling the same “finally” code from the end of the lexical scope *and* from the trap statement.  Consider the Cleanup function from the earlier example.  We want to always execute Cleanup whether the script errors or not.  The example shown in the previous section using the Cleanup function works OK unless the Cleanup function throws a terminating error. Then you run into the issue where Cleanup gets called again due to the trap statement.   This sort of cleanup is much easier to represent in your script using try/finally e.g.:

    function Cleanup($err) {"cleaning up"}
    trap { "Error trapped: $_"; continue }

    "Outer Before"
    try {
        "Inner Before"
        throw "Oops!"
        "Inner After"
    }   
    finally {
        Cleanup
    }
    "Outer After"

    This example results in Cleanup always getting called whether or not the script in the try statement generates a terminating error.  It also shows that you can mix and match trap statements with try/catch/finally.

    One last example shows how you can use catch to handle different error types uniquely:

    function Cleanup($err) {"cleaning up"}
    trap { "Error trapped: $_"; continue }

    "Outer Before"
    try {
        "Inner Before"
        throw "Oops!"
        "Inner After"
    }   
    catch [System.DivideByZeroException] {
        "Please don't divide by 0!"
    }
    catch [System.Management.Automation.CommandNotFoundException] {
        "Did you fat finger the command name?"
    }
    catch {
        "Anything not caught by the first two catch statements gets here"
    }
    finally {
        Cleanup
    }
    "Outer After"

    The use of the finally statement is optional as is the catch statement.  The valid combinations are try/catch, try/finally and try/catch/finally.

    In summary, PowerShell’s error handling capabilities are quite powerful especially the ability to distinguish between non-terminating and terminating errors.  With the addition of the new try/catch/finally support in version 2.0 the important scenario of resource cleanup is easy to handle.

    August 02

    Tail-File Cmdlet Coming in PSCX 1.2

    Occasionally I need to tail some very large log files over the network.  In this scenario, the standard PowerShell approach:

    PS> Get-ChildItem *.log | %{ Get-Content $_ | Select –Last 1 }

    just isn’t practical for performance reasons.  So the final drop of PSCX 1.2 will have a new cmdlet called Tail-File that is optimized for tailing the end of large (even huge) log files.  Here’s a performance comparison of the two approachs:

    19# measure-command { tail-file \\Keith-PC\C\*.txt -count 1 }

    Days              : 0
    Hours             : 0
    Minutes           : 0
    Seconds           : 0
    Milliseconds      : 7
    Ticks             : 70813
    TotalDays         : 8.19594907407407E-08
    TotalHours        : 1.96702777777778E-06
    TotalMinutes      : 0.000118021666666667
    TotalSeconds      : 0.0070813
    TotalMilliseconds : 7.0813

    20# measure-command {Get-ChildItem \\Keith-PC\C\*.txt | %{ Get-Content $_ | Select -Last 1 }}

    Days              : 0
    Hours             : 0
    Minutes           : 0
    Seconds           : 17

    Milliseconds      : 656
    Ticks             : 176567027
    TotalDays         : 0.000204359984953704
    TotalHours        : 0.00490463963888889
    TotalMinutes      : 0.294278378333333
    TotalSeconds      : 17.6567027
    TotalMilliseconds : 17656.7027

    That is a speed-up of approximately 2500x.  That is pretty significant especially if you need to do this to a number of large log files.  For reference, the above test tailed two medium size log files (total size: 37.4 MB):

    -a---     5/23/2009  6:33 AM      4173558 dirlist.txt
    -a---     5/23/2009  6:33 AM     35053946 filelist.txt

    Tail-File handles ASCII and Unicode encoding as well UTF8 as long as it contains only ASCII characters i.e. it will not choke on the UTF8 byte order mark.  Oh yeah, it also supports active tailing (tail –f) via the Wait parameter.  Use Ctrl+C to return control back to the console.

    July 17

    A Simple Go Function to Simplify Navigating to Popular Directories

    Before PowerShell came along I had a number of doskey aliases set up like “gt” to go to the temp dir.  I have since converted those aliases to a single g function (short for “go”):

       1: function g($shortcut) {
       2:     $ht = @{
       3:         bin = "C:\Bin";
       4:         doc = "$([Environment]::GetFolderPath('MyDocuments'))";
       5:         sys = "$env:SystemRoot\System32";
       6:         t   = "$env:Temp";
       7:         win = "$env:SystemRoot";
       8:         cf  = "$env:CommonProgramFiles";
       9:         pf  = "$env:ProgramFiles";
      10:         www = "$env:SystemDrive\inetpub\wwwroot";
      11:         gac = "$env:SystemRoot\Assembly\GAC";
      12:         tfs = "C:\Tfs"
      13:         clr = "$([System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())";
      14:     }
      15:  
      16:     if ($shortcut) {
      17:         if (!$ht["$shortcut"]) { 
      18:             throw "$shortcut isn't valid shortcut. Execute g with no params to see valid shortcuts." 
      19:         }
      20:         cd $ht.$shortcut
      21:     }
      22:     else {
      23:         "Valid shortcuts are:"
      24:         $ht.Keys | Sort | select @{n='Shortcut';e={$_}},@{n='Destination';e={$ht.$_}}
      25:     }
      26: }

     

    Usage is pretty simple.  Execute g to see all shortcuts and the associated location:

    PS C:\> g
    Valid shortcuts are:

    Shortcut    Destination
    --------   -----------
    bin         C:\Bin
    cf          C:\Program Files\Common Files
    clr         C:\Windows\Microsoft.NET\Framework64\v2.0.50727\
    doc         C:\Users\Keith\Documents
    gac         C:\Windows\Assembly\GAC
    pf          C:\Program Files
    sys         C:\Windows\System32
    t           C:\Users\Keith\AppData\Local\Temp
    tfs         C:\Tfs
    win         C:\Windows
    www         C:\inetpub\wwwroot

    To set-location to the wwwroot dir execute the following:

    PS C:\> g www
    C:\inetpub\wwwroot

    It’s a trivial function but if you spend a lot of time at the PowerShell prompt, it (or something similar) is quite handy.

    May 22

    PSCX Tips Part 2

    This is a continuation of PSCX Tips Part 1.  Here are some more things you can do with the PowerShell Community Extensions.

    If you do .NET software development and partially strong name your assemblies then you know that at some point in your official build process you need to finish the strong-naming process.  When we have done this, we cycle through all the exes and dlls in a common output dir and apply the full strong name.  Problem is that we have some native binaries mixed in with the managed binaries.  So we need some quick way to test if a file is a managed assembly.  This is where Test-Assembly comes in handy:

    PS> Get-ChildItem | Where {$_ –match '\.(dll|exe)$' –and (Test-Assembly $_)} |
        Foreach {sn.exe /Ra $_.fullname $pathToKeyFile}

    On 64-bit Windows you may want to know whether a binary is a 32-bit or 64-bit binary.  That’s easy with Get-PEHeader:

    PS> Get-PEHeader .\wordpad.exe

    Type                    : PE64
    LinkerVersion           : 9.0
    OperatingSystemVersion  : 6.1
    ImageVersion            : 6.1
    SubsystemVersion        : 6.1
    SizeOfCode              : 604672
    SizeOfInitializedData   : 3915776
    SizeOfUninitializedData : 0
    AddressOfEntryPoint     : 65624
    BaseOfCode              : 4096
    BaseOfData              : 0
    ImageBase               : 4294967296
    SectionAlignment        : 4096
    FileAlignment           : 512
    Win32VersionValue       : 0
    SizeOfImage             : 4538368
    SizeOfHeaders           : 1536
    Checksum                : 4583061
    Subsystem               : Windows
    DllCharacteristics      : 33088
    SizeOfStackReserve      : 524288
    SizeOfStackCommit       : 8192
    SizeOfHeapReserve       : 1048576
    SizeOfHeapCommit        : 4096
    LoaderFlags             : 0

    Need to test if the current user is elevated on Vista or Windows 7?  Use Test-UserGroupMembership e.g.:

    PS> Test-UserGroupMembership -GroupName Administrators
    False

    Need to know what the 8.3 filename is for a file?  Use Get-ShortPath e.g.:

    PS> Get-ShortPath '.\Windows Azure SDK'
    C:\PROGRA~1\WI4C87~1

    Need a touch like utility to change a file’s various time fields?  Try Set-FileTime e.g.:

    PS> Set-FileTime .\test.csv -Modified (Get-Date).AddDays(-2)

    Need to compute the hash for a string or an entire file using various algorithms e.g.:

    PS> "hello world" | Get-Hash

    Algorithm: MD5

    Path       :
    HashString : E42B054623B3799CB71F0883900F2764

    PS> Get-Hash .\uszip.wsdl -Algorithm SHA1

    Algorithm: SHA1

    Path       : C:\Users\Keith\uszip.wsdl
    HashString : 74910AE77B4089496D5EF829DF3F0485BF855EB9

    Need to convert between different OS style line endings in files?  Check out these cmdlets;

    • ConvertTo-MacOs9LineEnding
    • ConvertTo-UnixLineEnding
    • ConvertTo-WindowsLineEnding

    Need to convert from or to Base64?  Try out the ConvertTo-Base64 and ConvertFrom-Base64 cmdlets e.g.:

    PS> [byte[]](1..255) | ConvertTo-Base64
    AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5
    Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFy
    c3R1dnd4eXp7fH1+f4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6Slpqeoqaqr
    rK2ur7CxsrO0tba3uLm6u7y9vr/AwcLDxMXGx8jJysvMzc7P0NHS09TV1tfY2drb3N3e3+Dh4uPk
    5ebn6Onq6+zt7u/w8fLz9PX29/j5+vv8/f7/

    When parsing legacy exe text, do you ever need to skip either the first couple of lines or perhaps the last line (or both)?

    Skip-Object is very convenient here because you don’t need to know the length of the sequence you are dealing with.  Here’s an example:

    PS> 1..10 | Skip-Object -First 2 -Last 1 -Index 4,6
    3
    4
    6
    8
    9

    That’s enough for this post but there’s still plenty more in PSCX.  Stay tuned.

    May 20

    PSCX Tips

    We recently released a beta of the PowerShell Community Extensions that supports PowerShell V2 in addition to V1.   This version also supports 64-bit versions of Windows.  The primary purpose of PSCX is to flesh out the functionality of PowerShell such that tools like cygwin and MKS Toolkit aren’t needed.  We aren’t completely there with PSCX yet but there is quite a bit of useful functionality in PSCX.  Here are some examples.

    Enhanced CD function has browser like backwards and forwards history:

    PS> cd $pshome;cd $pscx:home;cd $pscx:ScriptsDir;cd~
    C:\Windows\System32\WindowsPowerShell\v1.0
    C:\Users\Keith\Pscx\Trunk\Src\PscxSnapin\bin\Debug
    C:\Users\Keith\Pscx\Trunk\Src\PscxSnapin\bin\Debug\Scripts
    C:\Users\Keith

    PS> cd

         # Directory Stack:
       --- ----------------
         0 C:\Users\Keith
         1 C:\Windows\System32\WindowsPowerShell\v1.0
         2 C:\Users\Keith\Pscx\Trunk\Src\PscxSnapin\bin\Debug
         3 C:\Users\Keith\Pscx\Trunk\Src\PscxSnapin\bin\Debug\Scripts
    ->   4 C:\Users\Keith

    # Use cd - and cd + to move backwards and forwards through stack
    PS> cd -
    C:\Users\Keith\Pscx\Trunk\Src\PscxSnapin\bin\Debug\Scripts

    PS> cd +
    C:\Users\Keith

    # Or specify an entry number to jump to directly
    PS> cd –1
    C:\Windows\System32\WindowsPowerShell\v1.0

    # cd up several dir levels at once
    PS> cd ....

    Sending PowerShell output to the clipboard:

    PS> Get-Process | Out-Clipboard

    # The following uses the alias ocb and sets output width to prevent text truncation
    PS> Get-ChildItem env: | ocb –width 9999

    # To get the raw data without any PowerShell formatting
    PS> $pwd | Write-Clipboard
     

    Inspecting the contents of a file ala od.exe  (octal dump):

    # View the first 16 bytes of powershell.exe
    PS> Format-Hex $pshome\powershell.exe –count 16

    Address:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F ASCII
    -------- ----------------------------------------------- ----------------
    00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ..............

    # Using the fhex alias to inspect a text file to see if it has the UTF-8 BOM
    PS>
    fhex $pshome\FileSystem.format.ps1xml -count 48

    Address:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F ASCII
    -------- ----------------------------------------------- ----------------
    00000000 EF BB BF 3C 3F 78 6D 6C 20 76 65 72 73 69 6F 6E ...<?xml version
    00000010 3D 22 31 2E 30 22 20 65 6E 63 6F 64 69 6E 67 3D ="1.0" encoding=
    00000020 22 75 74 66 2D 38 22 20 3F 3E 20 0D 0A 3C 21 2D "utf-8" ?> ..<!-

    Formatting XML:

    # Ever have an XML string display as one long line, try this out
    PS> "<a><b><c><d/></c></b></a>" | Format-Xml # Alias is fxml
    <a>
      <b>
        <c>
          <d />
        </c>
      </b>
    </a>

    Testing XML for well-formedness and against a schema:

    PS> "<a><b><c><d/></c></b></a>" | Test-Xml
    True
    PS> "<a><b><c><d/><c></b></a>" | Test-Xml -verbose
    VERBOSE: The 'c' start tag on line 1 does not match the end tag of 'b'. Line 1, position 19.
    False

    PS> $schemaPaths
    C:\Program Files\Microsoft Visual Studio 9.0\Xml\Schemas\1033\Microsoft.Build.xsd
    C:\Program Files\Microsoft Visual Studio 9.0\Xml\Schemas\1033\MSBuild\Microsoft.Build.Core.xsd
    C:\Program Files\Microsoft Visual Studio 9.0\Xml\Schemas\1033\MSBuild\Microsoft.Build.CommonTypes.xsd

    PS> Test-Xml .\ConsoleApplication1.csproj -SchemaPath $schemaPaths
    True

    Applying a XSL transform to a file:

    PS> Convert-Xml My.xml –XsltPath My.xsl –EnableScript

    There is a lot more functionality in PSCX such as the ability to create, read and expand ZIP and TAR files.  But that is enough of a teaser for this post.  Stay tuned for more info on what’s in PSCX.

    May 14

    PSCX 1.2 Beta Available

    Last night we released a beta of the PowerShell Community Extensions 1.2.  The release supports Windows PowerShell V2 and has better support for 64-bit Windows.  PowerShell MVP/Rockstar Oisin Grehan has added two very useful cmdlets to this release: Read-Archive and Expand-Archive which works not only on TAR and ZIP files but also on ISO files.  Give it a spin.  You can download it here.  Be sure to let us know if you run into any problems either via the PSCX discussion forum or issue tracker.  BTW, there are two known issues on x64 Windows.  The cmdlets Get-PEHeader and Test-Assembly don’t work.  After a week or so, we’ll look at officially releasing 1.2.

    May 02

    PowerShell V2 Remoting on Workgroup Joined Computers – YES It Can Be Done

    There are a number of extra steps to take to get V2 remoting to work on workgroup joined computers like the ones in your home – unless you’re running a DC at home (sick puppy).  First up, is a registry setting that makes V2 remoting work on workgroup computers:

    PS> new-itemproperty -path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -name LocalAccountTokenFilterPolicy -propertyType DWord -value 1

    Be sure to run this from an elevated prompt.  Then from that same elevated prompt, execute the following cmdlet:

    PS> Enable-PSRemoting

    To verify that remoting is working on this PC by executing the following command (also from an elevated prompt):

    PS> Enter-PSSession localhost

    If remoting is working you should get a prompt something like this:

    [localhost]: PS C:\Users\Keith\Documents>

    Note: If you are trying to get PowerShell 2.0 remoting working on an XPMode virtual machine (you know the one you get for free with Windows 7 Pro or higher) then you need to enable Classic share & security model for local accounts like so: 

    1. Run Secpol.msc
    2. Navigate to Security Settings -> Local Policies -> Security Options -> "Network access: Share and security model for local accounts
    3. Change it to "Classic" mode.

    Now try re-running Enable-PSRemoting and it should work.

    Finally, on the PC(s) that you want to use to initiate remoting, execute this command so that all the local target computers are trusted:

    PS> set-item wsman:localhost\client\trustedhosts -value *

    Now, at this point you should be able to enter a new pssession to a remote computer.  Note that you don’t have to be in an elevated prompt to do this.  However you will need to pass your credentials to the remote computer like so:

    PS> $cred = Get-Credential  # Type in the username/password for an admin account on the remote PC
    PS> Enter-PSSession MediaCenterPC –cred $cred

    Credential Delegation

    One other area that can bite you is credential delegation.  Here is the scenario.  Say you remote into a PC and from that PC you want to access files on another PC via a UNC share.  As things stand now, you run into the following error:

    [mediacenterpc]: PS C:\Users\Keith\Documents> dir \\homeserver\photos
    Get-ChildItem : Cannot find path '\\homeserver\photos' because it does not exist.
        + CategoryInfo          : ObjectNotFound: (\\homeserver\photos:String) [Get-ChildItem], ItemNotFoundException
        + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

    To fix this you have to enable credential delegation (second hop) and use CredSSP authentication.  First on the target/remote computer you need to run this in an elevated prompt:  Note that you can’t run this from a V2 remoting session (you’ll get “access is denied”):

    PS> Enable-WSManCredSSP –Role Server

    Now on your client/local computer execute the following from an elevated prompt for each remote computer you need credential delegation for:

    PS> Enable-WSManCredSSP –Role Client –DelegateComputer <computer_name>

    We are close but there is one last step and it requires a tweak via the global policy editor.  Run gpedit.msc and navigate to Computer Configuration –> Administrative Templates –> System –> Credential Delegation as shown below:

    image

    Open up the “Allow Delegating Fresh Credentials with NTLM-only Server Authentication” setting.  Enable the setting and then click on the “Show…” button to add a server to the list.  I added mine like so:

    image

    Press OK and then press the “Apply” button on the previous dialog to apply the setting.  Now credential delegation will work for that configured remote computer.  Note that when you enter a new PSSession you have to use CredSSP authentication as shown below:

    PS> Enter-PSSession MediaCenterPC -Cred $cred -Authentication CredSSP
    [mediacenterpc]: PS C:\Users\Keith\Documents> dir \\HomeServer\Software

        Directory: \\HomeServer\Software

    Mode                LastWriteTime     Length Name
    ----                -------------     ------ ----
    d----         7/21/2008   9:37 PM            Add-Ins
    d----         7/21/2008  10:01 PM            Home PC Restore CD

    Note that from the remoting session on MediaCenterPC, I can now see the files shared from my Windows Home Server.  Woohoo!  It isn’t exactly as straight forward as I would like but it can be done.

    April 05

    Generating New-Object Wrapper Functions for an Assembly

    One of the best parts of being a Microsoft MVP is having conversations with all the other, really bright MVPs.  The PowerShell team just recently added Doug Finke as an MVP – congratulations Doug!  It isn’t too uncommon for the PowerShell MVPs to challenge and push one another.  It is a great learning experience!

    Over the weekend, Doug and I engaged in one of these and in the end we came up with a little bit of PowerShell script (V2 required for the generics support) that can automatically generate wrapper functions around the constructors of public .NET types in an assembly.  These wrapper functions are strongly typed (well mostly) and support generic types.  Here’s the script:

    param($assemblyName)
    Set-StrictMode -Version Latest

    [Reflection.Assembly]::LoadWithPartialName($assemblyName).GetExportedTypes() |
    % {
        $fname = $_.FullName
        $typeArgs  = @($_.GetGenericArguments() | %{"`$Of" + $_.Name})
        $typeDecls = @($_.GetGenericArguments() | %{"[string]`$Of" + $_.Name})
        $_.GetConstructors('Instance,Public') |
        % {
            $paramArgs  = @($_.GetParameters() | %{"`$" + $_.Name})
            $paramDecls = @($_.GetParameters() | 
                %{"$(if (!$_.ParameterType.ContainsGenericParameters) {"[$($_.ParameterType)]"})`$$($_.Name)"})
            if ("$paramDecls" -notmatch '\*') {
                $OFS = ','
                if ($typeArgs.Length -gt 0) {
                    $genFname = $fname.Substring(0, $fname.LastIndexOf('`'))
                    $decls = $typeDecls + $paramDecls
                    @"
    function New-$($genFname.Replace('.','')) {
        param($decls)
        New-Object `"$genFname$("`[$typeArgs`]")`" $paramArgs
    }
     

    "@            
                }
                else {
                    @"
    function New-$($fname.Replace('.','')) {
        param($paramDecls)
        New-Object `"$fname`" $paramArgs
    }
     
    "@
            
                }
            }
        }
    }


    NOTE: if you copy/paste this script, watch out for whitespace after the here strings’ opening sequence – @” – there shouldn’t be any. 
     

    If you run it on mscorlib, it will product wrapper functions like so:

    function New-SystemGuid {
        param([int]$a,[System.Int16]$b,[System.Int16]$c,[System.Byte[]]$d)
        New-Object "System.Guid" $a,$b,$c,$d
    }



    function New-SystemCollectionsGenericDictionary {
        param([string]$OfTKey,[string]$OfTValue)
        New-Object "System.Collections.Generic.Dictionary[$OfTKey,$OfTValue]"
    }





    function New-SystemCollectionsGenericList {
        param([string]$OfT)
        New-Object "System.Collections.Generic.List[$OfT]"
    }

    Then you can create a generic list like so:

    PS> $list = New-SystemCollectionsGeneicList –OfT double
    PS> $list.add(1.1)
    PS> $list.add("two")
    Cannot convert argument "0", with value: "two", for "add" to type "System.Double"

    Effective PowerShell Item 15: Using the Output Field Separator $OFS

    $OFS is the “output field separator” variable.  Whatever value it contains will be used as the string separator between elements of an array that is rendered to a string.  For example, consider the following array definition and subsequent rendering to string:

    PS> $array = 1,2,3
    PS> "$array"

    What would you expect the resulting string to be?  Here’s the output:

    1 2 3

    How does PowerShell go about rendering elements of an array into a single string?  It is pretty simple as you would expect.  Each element is converted to its string representation.  The only other detail left is to determine what characters to use to separate each element in the final string.  The $OFS variable is not initially created by PowerShell and if it doesn’t exist, PowerShell uses a single space character to separate elements as you can see in the example above.  What is neat is that PowerShell gives you the ability change the separator string by setting the $OFS variable like this:

    PS> $OFS = ', '
    PS> "$array"
    1, 2, 3

    Note that the separator doesn’t have to be single character.  It doesn’t even have to be a string, but in the end whatever value that is assigned to $OFS is converted to a string e.g.:

    PS> $OFS = $true
    PS> "$array"
    1True2True3

    This is an admittedly weird example.  In the common case, you will just assign a string to $OFS like “, “ or “`t” or “`n”, etc. 

    $OFS also works for multi-dimensional arrays e.g.:

    PS> $array = new-object 'int[,]' 2, 3
    PS> $array[0,0] = 1
    PS> $array[0,1] = 2
    PS> $array[0,2] = 3
    PS> $array[1,0] = 4
    PS> $array[1,1] = 5
    PS> $array[1,2] = 6
    PS> $OFS = ', '
    PS> "$array"
    1, 2, 3, 4, 5, 6

    Unfortunately, $OFS doesn’t work so well for jagged arrays:

    PS> $array = @(@(1,2),@(3,4))
    PS> $OFS = ', '
    PS> "$array"
    System.Object[], System.Object[]

    # Let’s try a different approach – not so satisfying
    PS> "$($array[0]), $($array[1])"

    1, 2, 3, 4

    When I see folks use [string]::Join() or –join in version 2 of PowerShell, I wonder if it would be better to use $OFS and string rendering.  Here is an example I came across recently:

    $typeDecls  = @($_.GetGenericArguments() | %{"[string]`$Of" + $_.Name}) –join ', '
    $paramDecls = @($_.GetParameters() | % { "[$($_.ParameterType)]`$$($_.Name)" }) –join ', '

    $decls = $typeDecls
    $decls += $(if ($decls –and $paramDecls) { ', ' })
    $decls += $(if ($paramDecls) { $paramDecls })

    function New-$fname($decls) { … }

    Using $OFS the script changes to:

    $OFS = ', '
    $typeDecls  = @($_.GetGenericArguments() | %{"[string]`$Of" + $_.Name})
    $paramDecls = @($_.GetParameters() | % { "[$($_.ParameterType)]`$$($_.Name)" })

    $decls = $typeDecls + $paramDecls

    function New-$fname("$decls") { … }

    In this example, the use of $OFS shines because you benefit by delaying the string rendering of the arrays until the last moment.  In this case, I wanted to keep both $typeDecls and $paramDecls as arrays so that they could be concatenated together and then rendered as a string containing a comma separated list.  If these two variables had been converted to strings earlier, as in the “before” script above, then you need special case logic in the event $typeDecls and/or $paramDecls are empty.

    March 21

    PowerShell Function Names

    PowerShell allows you to use many different characters in your function names besides [_aA-zZ][_aA-zA0-9], which is the typical regex recipe for function and method identifiers in a fair number of languages.  However, in PowerShell you have a much larger palette of characters to choose from e.g.:

    function ?? ($expr, $default = $(throw "Must specify default value")) {
    if ($expr -ne $null) {
    $expr
    }
    else {
    $default
    }
    }

    Function names like these can help you create operators for an internal DSL.  The primary downside is that the operator has to be used in a pre-fix manner e.g.:

    PS> ?? ($env:LogDir) $env:temp
    C:\Users\Keith\AppData\Local\Temp

    Things get really interesting when you start to use mathematical symbols.  Check this out:

    PS> function √($num) { [Math]::Sqrt($num) }
    PS> √ 81
    9
    PS> function φ { (1 + (√ 5)) / 2 }
    PS> φ
    1.61803398874989

    This is not really practical since the mathematical symbol characters (0x221A and 0x3C6) aren’t easy to type at the console but it shows the extent to which you can use radically different characters to name your functions.

    psmdtag:script - function name

    March 16

    Image File Resizing Using the PowerShell Community Extensions

    A reader asked me about using PowerShell to process image files.  While PowerShell doesn’t natively support doing this you could perform this task by using PowerShell’s support for .NET which supports image resizing.  However if you have the PowerShell Community Extensions (PSCX) installed you can do this pretty easily using cmdlets like so:

    PS> Import-Bitmap .\Foo.jpg | Resize-Bitmap -Percent 50 | 
    >> Export-Bitmap -path fooSmall.jpg -Format jpeg

    Can you feel the power?!

    psmdtag:cmdlet - Bitmap manipulation

    March 15

    Windows 7 Media Center Update

    The problem we were seeing with movie DVD playback has been fixed!  NVidia released a Windows 7 driver update for the GeForce 6200 video card in late February.  I installed it two weeks ago and we haven’t had any problems with DVD playback since.

    I’ve got four computers at home now running Windows 7.  A number of these are dual boot machines that can dual boot into Vista or Windows 7.  I’m finding it harder and harder to boot back into Vista.  The performance and reliability of Windows 7 has been great so far (other than the Media Center issues) and I have really adapted to the new taskbar.  I’m looking forward to the final release!