# Wednesday, March 10, 2010

This is unashamedly a post for developers, in particular those with an interest in functional languages. With the advent of PowerShell 2.0, some of you may have noticed that ScriptBlocks - which I suppose could also be called anonymous functions or lambdas - gained a new method: GetNewClosure. Closures are one of the essential tools for functional programming., something I’ve been trying to learn more about over the last few years. I don’t really have an opportunity to use it in work other than the hybrid trickery available in C# 3.0, but I have been tinkering a lot with PowerShell 2.0 to see if some of the tricks of the functional trade could be implemented. It’s just a shell language, but there are some nice features in there that enable a wide variety of funky stuff.

Partial Application

In a nutshell, partial application of a function is when you pass in only some of the parameters and get a function back that can accept the remaining parameters:

# define a simple function
function test {
    param($a, $b, $c);
    "a: $a; b: $b; c:$c"
}

# partially apply with -c parameter
$f = merge-parameter (gcm test) -c 5

# partially apply with -c and -a then execute with -b (papp is an alias)
& (papp (papp (gcm test) -c 3) -a 2) -b 7

# partially apply the get-command cmdlet with -commandtype
# and assign the result to a new function
si function:get-function (papp (gcm get-command) -commandtype function)

This is by no means a complete implementation of a partial application framework for powershell. The merge-parameter function (aliased to papp) currently only works with the default parameterset and does not mirror any of the parameteric attributes in the applied function or cmdlet. I'm not saying it couldn't do that, but this is purely a proof of concept. The module is listed below and is also available from PoshCode at http://poshcode.org/1687

# save as functional.psm1 and drop into your module path
Set-StrictMode -Version 2

$commonParameters = @("Verbose",
                      "Debug",
                      "ErrorAction",
                      "WarningAction",
                      "ErrorVariable",
                      "WarningVariable",
                      "OutVariable",
                      "OutBuffer")

<#
.SYNOPSIS
    Support function for partially-applied cmdlets and functions.
#>
function Get-ParameterDictionary {
    [outputtype([Management.Automation.RuntimeDefinedParameterDictionary])]
    [cmdletbinding()]
    param(
        [validatenotnull()]
        [management.automation.commandinfo]$CommandInfo,
        [validatenotnull()]
        [management.automation.pscmdlet]$PSCmdletContext = $PSCmdlet
    )
    
    # dictionary to hold dynamic parameters
    $rdpd = new-object Management.Automation.RuntimeDefinedParameterDictionary

    try {
        # grab parameters from function
        if ($CommandInfo.parametersets.count > 1) {
            $parameters = $CommandInfo.ParameterSets[[string]$CommandInfo.DefaultParameterSet].parameters
        } else {
            $parameters = $CommandInfo.parameters.getenumerator() | % {$CommandInfo.parameters[$_.key]}
        }        
                
        $parameters | % {
            
            write-verbose "testing $($_.name)"
                                    
            # skip common parameters        
            if ($commonParameters -like $_.Name) {                                  
                
                write-verbose "skipping common parameter $($_.name)"
                
            } else {
                
                $rdp = new-object management.automation.runtimedefinedparameter
                $rdp.Name = $_.Name
                $rdp.ParameterType = $_.ParameterType
                
                # tag new parameters to match this function's parameterset
                $pa = new-object system.management.automation.parameterattribute
                $pa.ParameterSetName = $PSCmdletContext.ParameterSetName
                $rdp.Attributes.Add($pa)
                
                $rdpd.add($_.Name, $rdp)
            }
            
        }
    } catch {
    
        Write-Warning "Error getting parameter dictionary: $_"
    }
    
    # return
    $rdpd
}

<#
.SYNOPSIS
    Function that accepts a FunctionInfo or CmdletInfo reference and one or more parameters
    and returns a FunctionInfo bound to those parameter(s) and their value(s.)
.DESCRIPTION
    Function that accepts a FunctionInfo or CmdletInfo reference and one or more parameters
    and returns a FunctionInfo bound to those parameter(s) and their value(s.)
    
    Any parameters "merged" into the function are removed from the available parameters for
    future invocations. Multiple chained merge-parameter calls are permitted.
.EXAMPLE

    First, we define a simple function:
    
    function test {
        param($a, $b, $c, $d);
        "a: $a; b: $b; c:$c; d:$d"
    }
    
    Now we merge -b parameter into functioninfo with the static value of 5, returning a new
    functioninfo:
    
    ps> $x = merge-parameter (gcm test) -b 5
    
    We execute the new functioninfo with the & (call) operator, passing in the remaining 
    arguments:
    
    ps> & $x -a 2 -c 4 -d 9
    a: 2; b: 5; c: 4; d: 9
    
    Now we merge two new parameters in, -c with the value 3 and -d with 5:
    
    ps> $y = merge-parameter $x -c 3 -d 5
    
    Again we call $y with the remaining named parameter -a:
    
    ps> & $y -a 2
    a: 2; b: 5; c: 3; d: 5
.EXAMPLE

    Cmdlets can also be subject to partial application. In this case we create a new
    function with the returned functioninfo:
    
    ps> si function:get-function (merge-parameter (gcm get-command) -commandtype function)
    ps> get-function
                
.PARAMETER _CommandInfo
    The FunctionInfo or CmdletInfo into which to merge (apply) parameter(s.)
    
    The parameter is named with a leading underscore character to prevent parameter
    collisions when exposing the targetted command's parameters and dynamic parameters.
.INPUTS
    FunctionInfo or CmdletInfo
.OUTPUTS
    FunctionInfo
#>
function Merge-Parameter {    
    [OutputType([Management.Automation.FunctionInfo])]
    [CmdletBinding()]
    param(
        [parameter(position=0, mandatory=$true)]
        [validatenotnull()]
        [validatescript({
            ($_ -is [management.automation.functioninfo]) -or `
            ($_ -is [management.automation.cmdletinfo])
        })]
        [management.automation.commandinfo]$_Command
    )
    
    dynamicparam {
        # strict mode compatible check for parameter
        if ((test-path variable:_command)) {
            # attach input functioninfo's parameters to self
            Get-ParameterDictionary $_Command $PSCmdlet
        }
    }

    begin {
        write-verbose "merge-parameter: begin"
        
        # copy our bound parameters, except common ones              
        $mergedParameters = new-object 'collections.generic.dictionary[string,object]' $PSBoundParameters
        
        # remove our parameters, leaving only target function/CommandInfo's args to curry in
        $mergedParameters.remove("_Command") > $null
        
        # remove common parameters
        $commonParameters | % {
            if ($mergedParameters.ContainsKey($_)) {
                $mergedParameters.Remove($_)  > $null
            }
        }
    }
    
    process {
        write-verbose "merge-parameter: process"
        
        # temporary function name
        $temp = [guid]::NewGuid()

        $target = $_Command

        # splat our fixed named parameter(s) and then splat remaining args
        $partial = {
            [cmdletbinding()]
            param()
            
            # begin dynamicparam
            dynamicparam
            {                
                $targetRdpd = Get-ParameterDictionary $target $PSCmdlet
        
                # remove fixed parameters
                $mergedParameters.keys | % {
                    $targetRdpd.remove($_) > $null
                }
                $targetRdpd
            }
            begin {
                write-verbose "i have $($mergedParameters.count) fixed parameter(s)."
                write-verbose "i have $($targetrdpd.count) remaining parameter(s)"
            }
            # end dynamicparam
            process {
                $boundParameters = $PSCmdlet.MyInvocation.BoundParameters
                
                # remove common parameters (verbose, whatif etc)
                $commonParameters | % {
                    if ($boundParameters.ContainsKey($_)) {
                        $boundParameters.Remove($_)  > $null
                    }
                }
                
                # invoke command with fixed parameters and passed parameters (all named)
                . $target @mergedParameters @boundParameters
                
                if ($args) {
                    write-warning "received $($args.count) arg(s) not part of function."
                }
            }
        }
        
        # emit function/CommandInfo
        new-item -Path function:$temp -Value $partial.GetNewClosure()
    }
    
    end {
        # cleanup
        rm function:$temp
    }    
}

new-alias papp Merge-Parameter -force

Export-ModuleMember -Alias papp -Function Merge-Parameter, Get-ParameterDictionary

Have fun[ctional]!

posted on Wednesday, March 10, 2010 8:01:07 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Friday, March 05, 2010

Paths in PowerShell are tough to understand [at first.] PowerShell Paths - or PSPaths, not to be confused with Win32 paths - in their absolute forms, come in two distinct flavours:

  • Provider-qualified: FileSystem::c:\temp\foo.txt
  • Drive-qualified: c:\temp\foo.txt

It's very easy to get confused over provider-internal (The ProviderPath property of a resolved PathInfo – and the bold portion of the provider-qualified path above) and drive-qualified paths since they look the same if you look at the default FileSystem provider drives. That is to say, the PSDrive has the same name (C) as the native backing store, the windows filesystem (C). So, to make it easier for yourself to understand the differences, create yourself a new PSDrive:

ps c:\> new-psdrive temp filesystem c:\temp\
ps c:\> cd temp:
ps temp:\>

Now, let's look at this again:

  • Provider-qualified: FileSystem::c:\temp\foo.txt
  • Drive-qualified: temp:\foo.txt

A bit easier this time to see what’s different this time. The bold text to the right of the provider name is the ProviderPath.

So, your goals for writing a generalized provider-friendly Cmdlet (or advanced function) that accepts paths are:

  • Define a LiteralPath path parameter aliased to PSPath
  • Define a Path parameter (which will resolve wildcards / glob)
  • Assume you are receiving PSPaths, NOT native provider-paths

Point number three is especially important. Also, obviously LiteralPath and Path should belong in mutually exclusive parameter sets.

Relative Paths

A good question is: how do we deal with relative paths being passed to a Cmdlet. As you should assume all paths being given to you are PSPaths,  let’s look at what the Cmdlet below does:

ps temp:\> write-zip -literalpath foo.txt

The command should assume foo.txt is in the current drive, so this should be resolved immediately in the ProcessRecord or EndProcessing block like (using the scripting API here to demo):

$provider = $null;
$drive = $null
$providerPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("foo.txt", [ref]$provider, [ref]$drive)

Now you everything you need to recreate the two absolute forms of PSPaths, and you also have the native absolute ProviderPath. To create a provider-qualified PSPath for foo.txt, use $provider.Name + “::” + $providerPath. If $drive is not null (your current location might be provider-qualified in which case $drive will be null) then you should use $drive.name + ":\" + $drive.CurrentLocation + "\" + "foo.txt" to get a drive-qualified PSPath.

Have fun!

posted on Friday, March 05, 2010 1:02:31 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Wednesday, February 24, 2010

PowerShell guru and Admin-extraordinare Jeff Hicks asked a great question that many a Windows administrator has probably asked themselves when working with Hashtables in PowerShell: Why do the key/values come out in a different order than put in? This is a great question and the answer lies in computer science theory, particularly computational complexity theory. Rather than bore you with a ton of nonsense about O(1), O(n) and other propeller-head dribble, I figure I could explain it in terms everyone should be able to understand.

Hashtables, Buckets and HashCodes = Rolodex, Index Cards and Surnamescard-index

Yes, if the light hasn’t gone on yet, it will soon. Every .NET object includes a method called GetHashCode. This method returns a number that represents the identity of the object in a kind of fuzzy way. I say “fuzzy” because the hash code for a given instance of an object can be different on different platforms (xp, vista, 2003 etc) or on different bitness (64 vs 32 bit.) This method is used by hashtable to get the “surname” of an object. Instead of Index Cards, a Hashtable uses “buckets” to separate groups of objects. Any given Hashcode will naturally fall into a particular bucket as the function (result) of a high-speed optimized algorithm, much like any given surname naturally falls under a particular letter of the alphabet. Finally, it should be clear to you that using index cards [Hashtable buckets] is way faster than flicking through an unsorted folder [randomized list.]

Just like a Rolodex, the order you add names to it doesn’t dictate the order they are in as you flip through the cards sequentially, which is analogous to sending a Hashtable to out-default.

posted on Wednesday, February 24, 2010 5:38:55 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Sunday, January 17, 2010

Did you know that when you run Get-Help against a cmdlet to find out about its parameters, you might not be getting the whole truth? Certain cmdlets, especially those that operate on providers (FileSystem, Certificate, Registry etc) can adopt new parameters on the fly, depending on the path they are acting on. For example, when use you Get-Content on the file system (drive c: etc), it gets three new parameters in addition to the static ones listed by Get-Help (but more about this later): Delimiter, Encoding and Wait.

Determining Dynamic Parameters using Get-Help

Get-Help has a new parameter, –Path, which lets you give the help system some context for determining dynamic parameters:

-Path <string>
    Gets help that explains how the cmdlet works in the specified provider path. Enter a Windows PowerShell provider path.

    This parameter gets a customized version of a cmdlet help topic that explains how the cmdlet works in the specified Windows PowerShell provider path. This parameter is effective only for help about a provider cmdlet and only when the provider includes a custom version of the provider cmdlet help topic.

    To see the custom cmdlet help for a provider path, go to the provider path location and enter a Get-Help command or, from any path location, use the Path parameter of Get-Help to specify the provider path. For more information, see about_Providers.

Determining Dynamic Parameters using Get-Command

Get-Command has a new parameter, –ArgumentList, which acts similarly to unveil what dynamic parameters might be attached to a cmdlet for a given parameterset and path/literalpath if available on the chosen cmdlet. I’ve written a simple function that takes a cmdlet name as an argument and will display all of the dynamic parameters available for a cmdlet for each distinct provider:

# this function will pass a drive name in position 0 as an unnamed argument
# most path-oriented cmdlets accept this binding
function Get-DynamicParameter {
    param(        
        [string]$command
    ) 
  
    $parameters = @{}
    get-psdrive | sort -unique provider | % {
        $parameters[$_.provider.name] = gcm $command -args "$($_.name):" | % {
            $c = $_; @($_.parameters.keys) | sort | ? {
                $c.parameters[$_].isdynamic
            }
        }
    }
    $parameters    
}

Example use:

PS> Get-DynamicParameter get-content

Name                           Value
----                           -----
Alias
FileSystem                     {Delimiter, Encoding, Wait}
AssemblyCache
Registry
Environment
WSMan
Certificate
FeedStore
Function
Variable
PscxSettings

NOTE: when you don’t pass any context parameters to get-command via –argumentlist, it will take your current location as the context for dynamic parameters (if any are found.) So running get-command from c:\ instead hklm:\ will give you the additional parameters Delimiter, Encoding and Wait.

Have fun!

posted on Sunday, January 17, 2010 9:33:04 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Sunday, November 01, 2009

One of the things that never quite fit well with me with the remoting feature in PowerShell 2.0 is that while you can “telnet” to remote systems with Enter-PSSession and import commands and do all sorts of cool tricks, there is no way to send or retrieve files from the console. It seems like such a waste that you configure WinRM up with SSL and Kerberos and get this nice encrypted channel up, but if you want to transfer files you have to revert to file shares, remote desktop or classic ftp.

Back in the “good ole’ days” of BBSs and FidoNet, people used to use simple protocols like XModem (advancing to YModem and then ZModem) or Kermit that worked by streaming character data directly to your terminal. It wasn’t fast or particularly efficient, but it got the job done. I thought I’d take a crack at building something similar for PowerShell, and this first 0.5 release is the fruits of this weekend’s tinkering. At the moment it only can “pull” a file to the local system from a remote session, but the next release will allow “pushing” a file from a local system to a remote session.

image

The reason I named it after XModem is because it works in a similar way: files are not “pulled” from the remote server, but instead are “pushed.” X[YZ]Modem file transfer was initiated by the remote end. I’ll not spoil the fun by explaining how it works, but I think you’ll enjoy pulling it apart. It’s in a module format and is implemented in pure script.

Requirements

  • PowerShell 2.0 installed on both client and server with remoting enabled to the location of the file being transferred.

E.g. if you want to grab a file using Get-RemoteFile from a remote server, you must be able to create a valid PSSession to it with the New-PSSession cmdlet. When Send-LocalFile is implemented, you’ll need remoting enabled in the other direction too.

  • The PMODEM module must be findable on both the client and server via import-module and must be the same version.

Here’s the Get-RemoteFile function help (via –?):

NAME
    Get-RemoteFile

SYNOPSIS
    Retrieves a file from a remote computer via a supplied PSession.


SYNTAX
    Get-RemoteFile [-Session] <pssession> [-RemoteFile] <string> [[-LocalPath] <string>] [[-PacketSize] <int32>]
	[-PassThru] [-AsJob] [<commonparameters>]


DESCRIPTION
    Retrieves a remote file from a server via a supplied PSSession. All communication
    is performed out-of-band, yet inside the secure WinRM channel.

    No other ports, file shares or any other special configuration is needed. However,
    the PMODEM module must be on the remote computer and findable in its $ENV:PSModulePath;
    the protocol versions must also match on both ends. You will be warned of any
    misconfiguration(s).

    When not running asynchronously, progress records are generated.


RELATED LINKS

REMARKS
    To see the examples, type: "get-help Get-RemoteFile -examples".
    For more information, type: "get-help Get-RemoteFile -detailed".
    For technical information, type: "get-help Get-RemoteFile -full".

Things coming in later releases: wildcards/multiple file support, compression and integration via proxy functions (copy-item/move-item/remove-item/rename-item etc).

Download PModem

Grab pmodem-0.5.zip and unzip it into a folder in your $ENV:PSModulePath on the client and server computers you want to use PMODEM on.

Have fun!

posted on Sunday, November 01, 2009 8:57:10 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
# Friday, October 30, 2009

UPDATE: Some general information on remoting: Hey, Scripting Guy! Tell Me About Remoting in Windows PowerShell 2.0
UPDATE 2:
This also applies to machines not attached to a domain (thanks @alexandair)

If you’re running Windows 7 (and if not, why not?) you may have noticed that premium versions include a license for Virtual XP Mode. (read more at http://www.microsoft.com/windows/virtual-pc/download.aspx) Essentially it’s an integrated version of Virtual PC with a full copy of Windows XP SP2 running in it. It’s pretty nice – you can have programs from the virtualized XP instance in your Windows 7 Start Menu and they can even share your desktop.

Problem

First thing you might think as a PowerShell fan is to put PowerShell v2.0 on Virtual XP Mode so you can tinker around with PowerShell Remoting (don’t forget to install .NET Framework 3.5 SP1 first!) Unfortunately it’s not all plain sailing as a default security configuration in Virtual XP prevents the Enable-PSRemoting Cmdlet from succeeding:

PS C:\Documents and Settings\XPMUser> Enable-PSRemoting

WinRM Quick Configuration
Running command "Set-WSManQuickConfig" to enable this machine for remote management through WinRM service.
 This includes:
    1. Starting or restarting (if already started) the WinRM service
    2. Setting the WinRM service type to auto start
    3. Creating a listener to accept requests on any IP address
    4. Enabling firewall exception for WS-Management traffic (for http only).

Do you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):
WinRM has been updated to receive requests.
WinRM service type changed successfully.
WinRM service started.

Set-WSManQuickConfig : Access is denied. 
At line:50 char:33
+             Set-WSManQuickConfig <<<<  -force
    + CategoryInfo          : InvalidOperation: (:) [Set-WSManQuickConfig], InvalidOperationException
    + FullyQualifiedErrorId : WsManError,Microsoft.WSMan.Management.SetWSManQuickConfigCommand

Resolution

Thankfully, it’s a quick fix; log into VXP and perform Start > Run… “gpedit.msc” and navigate your way to:

Computer Configuration > Windows Settings > Security Settings > Local Policies > Security Options

…and change: Network access: Sharing and security model for local accounts

…to: Classic – local users authenticate as themselves.

Now run Enable-PSRemoting again and it should work. No need to reboot!

fix_gpo_security_network_access

posted on Friday, October 30, 2009 12:46:44 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Wednesday, October 28, 2009

( from: http://support.microsoft.com/default.aspx/kb/968929 – downloads at foot of page )

Windows PowerShell is a command-line shell and scripting language that is designed for system administration and Automation. Built on the Microsoft .NET Framework, Windows PowerShell enables IT professionals and developers to control and automate the administration of Windows and applications.

New features that are introduced in Windows PowerShell 2.0 include the following:

  • Remoting
    Windows PowerShell 2.0 lets you run commands on one or more remote computers from a single computer that is running Windows PowerShell. PowerShell remoting allows for multiple ways of connecting. These ways include interactive (1:1), fan-out (1:many), and fan-in (many:1 by using the IIS hosting model).
  • Integrated Scripting Environment
    PowerShell Integrated Scripting Environment (ISE) enables you to run interactive commands and edit and debug scripts in a graphical environment. The main features include color-coded syntax, selective execution, graphical debugging, Unicode support, and context-sensitive help.
  • Modules
    Modules allow for script developers and administrators to partition and organize their Windows PowerShell code in self-contained, reusable units. Code from a module executes in its own self-contained context and does not affect the state outside the module.
  • Advanced functions
    Advanced functions are functions that have the same capabilities and behavior as cmdlets. However, they are written completely in the Windows PowerShell language, instead of compiled C#.
  • Background jobs
    Windows PowerShell 2.0 allows for running a command or expression asynchronously and "in the background" without interacting with the console.
  • Eventing
    This feature adds support to the Windows PowerShell engine infrastructure for listening, forwarding, and acting on management and system events.
  • Script internationalization
    This new feature enables Windows PowerShell scripts to display messages in the spoken language that is specified by the UI culture setting on the user's computer.
  • Script debugging
    New debugging features were added to Windows PowerShell that let you set breakpoints on lines, columns, variables, and commands, and that let you specify the action that occurs when the breakpoint is hit.
  • New cmdlets
    Windows PowerShell 2.0 introduces over 100 built-in cmdlets. These cmdlets, excluding other tasks, enables you to do computer-related, event log, and performance counter management tasks.

WinRM 2.0

WinRM is the Microsoft implementation of WS-Management Protocol, a standard Simple Object Access Protocol (SOAP)–based, firewall-friendly protocol that allows for hardware and operating systems from different vendors to interoperate. The WS-Management Protocol specification provides a common way for systems to access and exchange management information across an IT infrastructure.

WinRM 2.0 includes the following new features:

  • The WinRM Client Shell API provides functionality to create and manage shells and shell operations, commands, and data streams on remote computers.
  • The WinRM Plug-in API provides functionality that enables a user to write plug-ins by implementing certain APIs for supported resources and operations.
  • WinRM 2.0 introduces a hosting framework. Two hosting models are supported. One is Internet Information Services (IIS)-based and the other is WinRM service-based.
  • Association traversal lets a user retrieve instances of Association classes by using a standard filtering mechanism.
  • WinRM 2.0 supports delegating user credentials across multiple remote computers.
  • Users of WinRM 2.0 can use Windows PowerShell cmdlets for system management.
  • WinRM has added a specific set of quotas that provide a better quality of service and allocate server resources to concurrent users. The WinRM quota set is based on the quota infrastructure that is implemented for the IIS service.

System requirements

WinRM 2.0 and PowerShell 2.0 can be installed on the following supported operating systems:

  • Windows Server 2008 with Service Pack 2
  • Windows Server 2003 with Service Pack 2
  • Windows Vista with Service Pack 2
  • Windows Vista with Service Pack 1
  • Windows XP with Service Pack 3
Windows PowerShell 2.0 requires the Microsoft .NET Framework 2.0 with Service Pack 1.

BITS 4.0

BITS 4.0 can be installed on the following supported operating systems:
  • Windows Server 2008 with Service Pack 2
  • Windows Vista with Service Pack 2
  • Windows Vista with Service Pack 1
posted on Wednesday, October 28, 2009 9:27:29 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Wednesday, October 21, 2009

You might not know it, but when you import a PowerShell module you can pass it one or more arguments by way of Import-Module's -ArgumentList (aliased to -Args) parameter. While it looks like passing parameters to a standard ps1 file, there are some limitations. Take this module for an example:

# -- begin foo.psm1 --
param()
 
. {
  param(
     [validateset("a","b","c")]
     $letter
 
     function Get-Letter {
        "You initialized the module with $letter"
     }
} @args
# -- end foo.psm1

ps> import-module foo -args b
ps> get-letter
"You initialized the module with b"

You might notice that I am dotting (dot-sourcing or dot-executing) the scriptblock. By doing this, you are ensuring that anything declared in the scriptblock is imported into the calling scope. You can also apply advanced-function style validation to the module initializer by splatting (@) the module arguments ($args). If the scriptblock was called instead (via & { ... } @args) then the nested scope created from the call (&) operator prevents the function Get-Letter from being exported from the module because it goes out of scope when the called scriptblock completes. So, why not put the param block at the top of the module where param() is now you might ask? Because that mechanism is broken and/or partially implemented in v2, and completely ignores [cmdletbinding()] directives and validators. Note also that there is no way to use switches in the traditional sense with module arguments; instead you can pass boolean literals like $true or $false which will be mapped positionally to any declared switches.

So where might you use this technique of module initializers? You could create a generalized custom module that works with your various development environments, or SQL clusters/servers. You pass the name of the environment (or server) to the module on import, and all of the functions exported then are "bound" to that cluster (or server) so you don't have to continually pass each function the cluster (or server) name as an argument.

Have fun!

posted on Wednesday, October 21, 2009 11:05:08 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Wednesday, October 14, 2009

Updated: now use a temporary file to set text to avoid overflowing command-line buffer

The Windows Clipboard – accessible via System.Windows.Forms.Clipboard – requires an STA thread to read/write to it. By default, the console version of PowerShell 2.0 (i.e. not ISE) starts in MTA mode. This means that read/writing via this class is unreliable. Rather than always starting up console PowerShell in STA mode via the –STA flag, you can use this flag in a sneakier way to get what you want:

function Set-ClipboardText {
        param($text)

        # need to use temp file to avoid exceeding command-line length limit
        $temp = [io.path]::GetTempFileName()

        try {
            set-content -Path $temp -Value $text

            $command = {
                    add-type -an system.windows.forms
                    [System.Windows.Forms.Clipboard]::SetText((get-content $args))
            }
            
            powershell -sta -noprofile -command $command -args $temp

        } finally {
            if ((test-path $temp)) {
                remove-item $temp
            }
        }
}

function Get-ClipboardText {
        $command = {
                add-type -an system.windows.forms
                [System.Windows.Forms.Clipboard]::GetText()
        }
        powershell -sta -noprofile -command $command
}

Essentially we are running PowerShell as a child process temporarily in STA mode, skipping loading the profile and executing a scriptblock.

posted on Wednesday, October 14, 2009 11:53:35 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Friday, October 09, 2009

Asynchronous callback delegates are not a friend to PowerShell. They are serviced by the .NET threadpool which means that if they point to script blocks, there will be no Runspace available to execute them. Runspaces are thread-local resources in the PowerShell threadpool. The .NET threadpool, operating independently, is not too interested in coordinating callbacks with PowerShell. So what do we do?

There is one feature of PowerShell 2.0 that is capable of running scriptblocks in a pseudo-asynchronous manner: Eventing. Any events bound to with Register-ObjectEvent, EngineEvent or WmIEvent can have associated scriptblocks that will get executed when the associated event is raised. So, if we can somehow convert an asynchronous callback to a .NET event then we can run scriptblocks in response to Async .NET Callbacks. I’ve written a simple function called New-ScriptBlockCallback that helps us do exactly that:

#requires -version 2.0

function New-ScriptBlockCallback {
    param(
        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [scriptblock]$Callback
    )
<#
    .SYNOPSIS
        Allows running ScriptBlocks via .NET async callbacks.

    .DESCRIPTION
        Allows running ScriptBlocks via .NET async callbacks. Internally this is
        managed by converting .NET async callbacks into .NET events. This enables
        PowerShell 2.0 to run ScriptBlocks indirectly through Register-ObjectEvent.         

    .PARAMETER Callback
        Specify a ScriptBlock to be executed in response to the callback.
        Because the ScriptBlock is executed by the eventing subsystem, it only has
        access to global scope. Any additional arguments to this function will be
        passed as event MessageData.
        
    .EXAMPLE
        You wish to run a scriptblock in reponse to a callback. Here is the .NET
        method signature:
        
        void Bar(AsyncCallback handler, int blah)
        
        ps> [foo]::bar((New-ScriptBlockCallback { ... }), 42)                        

    .OUTPUTS
        A System.AsyncCallback delegate.
#>
    # is this type already defined?    
    if (-not ("CallbackEventBridge" -as [type])) {
        Add-Type @"
            using System;
            
            public sealed class CallbackEventBridge
            {
                public event AsyncCallback CallbackComplete = delegate { };

                private CallbackEventBridge() {}

                private void CallbackInternal(IAsyncResult result)
                {
                    CallbackComplete(result);
                }

                public AsyncCallback Callback
                {
                    get { return new AsyncCallback(CallbackInternal); }
                }

                public static CallbackEventBridge Create()
                {
                    return new CallbackEventBridge();
                }
            }
"@
    }
    $bridge = [callbackeventbridge]::create()
    Register-ObjectEvent -input $bridge -EventName callbackcomplete -action $callback -messagedata $args > $null
    $bridge.callback
}
posted on Friday, October 09, 2009 2:09:30 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1] Trackback
# Friday, October 02, 2009

ArgumentTransformationAttributes are attached to function parameters. They intercept the value coming in and optionally transform it before it is assigned to the target parameter. This is how the powershell type system is enforced:

function Foo ( [string]$str ) { ... }
(gi function:foo).parameters.str.attributes
TypeId
------
System.Management.Automation.ArgumentTypeConverterAttribute
System.Management.Automation.ParameterAttribute

This type converter derives from ArgumentTransformationAttribute. It uses the public utility type [Management.Automation.LanguagePrimitives] (which is full of cool stuff btw) to coerce incoming types to the designated type constraint, in this case [string].

As an aside, you can use the credential attribute on a function parameter too:

function do-something ([system.management.automation.credential()]$cred) { $cred }
do-something # get-credential dialog pops up if $cred is not explicitly passed

And while a little strange but definitely empowering from a future extensibility point of view, you can decorate parameters with entirely inappropriate attributes:

function Foo ( [obsolete($true)][string]$bar ) { ... }

System.ObsoleteAttribute is used in C#/VB.NET to tell the compiler that usage of the decorated item should raise a warning (false) or error (true)

posted on Friday, October 02, 2009 8:46:51 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Tuesday, August 18, 2009

One thing that has been lamented frequently about PowerShell is that it is very difficult to log, if not impossible, to log all of the various types of streams it has to a single source. The legacy Windows shell CMD, and Unix shells like Bash, Ksh etc only deal with three streams: stdin, stdout and stderr for Input, Ouput and Error respectively. PowerShell has many more: Input, Output, Verbose, Warning, Debug, Progress and Error. Finally, the APIs in v2.0 offer enough hooks to unify the logging but you got to work a bit to make it come together. Well, to be honest, you got me doing the work. The rest is easy ;)

import-module .\scriptlogger.psm1 -force

$logger = New-ScriptLogger

# override error handler
$logger.ErrorHandler = {
    param($record)
    
    $record.tostring() >> scriptlog.txt
}

# override verbose handler
$logger.VerboseHandler = {
    param($record)
    
    $record.message >> scriptlog.txt
}

# run scriptblock with logging
$logger.Invoke(
    {
        $verbosepreference='continue';
        $erroractionpreference = 'continue';
        $debugpreference = 'continue';
        write-verbose "verbose";
        write-error "an error";
        write-warning "a warning"
        Write-debug "debug string"
        "this is output"
        1,2,3
    })
And here is the module; save it as ScriptLogger.psm1. By default, all logging goes to an attached debugger, like sysinternals DbgView. You can override any of the handlers like above and do what you want. Each handler receives one argument: a ErrorRecord, WarningRecord, VerboseRecord, DebugRecord or ProgressRecord. All of these Types are native powershell types and are documented on MSDN.
<#
    Name     : Universal Script Logging Module (ScriptLogger.psm1)
    Version  : 0.1
    Author   : Oisin Grehan (MVP)
    Site     : http://www.nivot.org/
#>
function New-ScriptLogger {
    New-Module -AsCustomObject -ScriptBlock {
        
        $script:ps              = [powershell]::Create()
        $script:ar              = $null
        $script:module          = $ExecutionContext.SessionState.Module
        
        [scriptblock]
        $script:ErrorHandler    = {
            param(
                [Management.Automation.ErrorRecord]
                $record
            )
            [diagnostics.debug]::writeline(
                "Error: " + $record.tostring());
        }
        [scriptblock]
        $script:WarningHandler  = {
            param(
                [Management.Automation.WarningRecord]
                $record
            )        
            [diagnostics.debug]::writeline(
                "Warning: " + $record.message);
        }
        [scriptblock]
        $script:VerboseHandler  = {
            param(
                [Management.Automation.VerboseRecord]
                $record
            )
            [diagnostics.debug]::writeline(
                "Verbose: " + $record.message);
        }
        [scriptblock]
        $script:DebugHandler    = {
            param(
                [Management.Automation.DebugRecord]
                $record
            )
            [diagnostics.debug]::writeline(
                "Debug: " + $record.message);
        }
        [scriptblock]
        $script:ProgressHandler = {
            param(
                [Management.Automation.ProgressRecord]
                $record
            )        
            [diagnostics.debug]::writeline(
                "Progress: " + $record);
        }
        
        $script:Handlers   = @{
            Error = Register-ObjectEvent $ps.Streams.Error DataAdded -Action {
                & $event.MessageData {& $ErrorHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Warning = Register-ObjectEvent $ps.Streams.Warning DataAdded -Action {
                & $event.MessageData {& $WarningHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Verbose = Register-ObjectEvent $ps.Streams.Verbose DataAdded -Action {
                & $event.MessageData {& $VerboseHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Debug = Register-ObjectEvent $ps.Streams.Debug DataAdded -Action {
                & $event.MessageData {& $DebugHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Progress = Register-ObjectEvent $ps.Streams.Progress DataAdded -Action {
                & $event.MessageData {& $ProgressHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
        }
        
        function Invoke {
            param(
                [validatenotnullorempty()]
                [scriptblock]$script
            )
            
            try {
            
                write-host -foreground green "Running"
                
                $ps.commands.clear()
                $command = new-object management.automation.runspaces.command $script, $true            
                $ps.commands.addcommand($command) > $null                
                $ps.invoke() # returns output                
            
            } catch {
            
                # oops-ee!
                write-host -foreground red "Unhandled terminating error: $_"
                $record = new-object management.automation.errorrecord $(
                    new-object exception $_.tostring()), "TerminatingError", "NotSpecified", $null
                & $ErrorHandler $record
            
            } finally {
            
                write-host -foreground green "Complete"
            
            }
        }
                
        Export-ModuleMember -Function Invoke -Variable ErrorHandler, WarningHandler, VerboseHandler, DebugHandler, ProgressHandler
    }
}

Have fun!

posted on Tuesday, August 18, 2009 10:58:24 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1] Trackback
# Friday, August 14, 2009

update: 2009-09-27 - removed usage of closures - turns out i hadn't tested that modification properly. oops.

Here’s a quick one – in order to sway excessive BASH jealously, I knocked this up to persist the last 100 history items between PowerShell sessions. It hooks the special engine (as opposed to object) event “poweshell.exiting” and runs a script to save history to an XML file using the universally useful Export-CliXML cmdlet. Another trick in there is to use a closure to capture the value of the $historyPath variable. I need to do this because powershell event handlers use their own runspace (and will lose the values of the variables in the current runspace). I also could have passed the value via the –MessageData parameter and done it that way, but I figured I’m already in v2 territory so lets use that feature ;-)

# save last 100 history items on exit
$historyPath = Join-Path (split-path $profile) history.clixml

# hook powershell's exiting event & hide the registration with -supportevent.
Register-EngineEvent -SourceIdentifier powershell.exiting -SupportEvent -Action {
    Get-History -Count 100 | Export-Clixml (Join-Path (split-path $profile) history.clixml) }

# load previous history, if it exists
if ((Test-Path $historyPath)) {
    Import-Clixml $historyPath | ? {$count++;$true} | Add-History
    Write-Host -Fore Green "`nLoaded $count history item(s).`n"
}

Dump this into your profile and have fun!

posted on Friday, August 14, 2009 9:28:58 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Thursday, August 13, 2009

I’ve been reliably informed (and double checked) and I’m happy to relay to you all that PowerShell 2.0 is available as part of the Windows Management Framework RC. This includes the following components:

  • WinRM 2.0
  • Windows PowerShell 2.0
  • BITS 4.0

This is the culmination of nearly three years’ of work to bring Windows to the cutting edge of automation technology. Grab it while it’s toasty from:

https://connect.microsoft.com/windowsmanagement/Downloads

Spread the word!

posted on Thursday, August 13, 2009 10:06:02 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Saturday, July 18, 2009

updated 2009/7/20: added link to PSEventing for v1.0 event handling
updated 2009/7/24: added link to Get-Delegate script for v1.0 callbacks

There have been some nice improvements made in the latest build of PowerShell with respect to interop with the "callback” pattern in .NET. What exactly are callbacks anyway? It’s exactly what it sounds like, pretty much. In .NET there are sometimes APIs you need to call that expect you to hand them delegates (pointers to methods) which that API will call some time in the future, usually based on certain conditions being fulfilled. If that sounds a bit like .NET events, you’d be right. An event is a much gussied-up callback - it’s just a way of permitting multiple methods to be invoked in response to some condition.

Synchronous Callbacks in PowerShell v1.0

In .NET there are two types of callbacks: Asynchronous (non-blocking) and synchronous (blocking). In PowerShell v1.0, the only callbacks that were catered for were for EventHandler Delegates. This is the method signature that most of the Windows Forms controls expect to call back to in response to button clicks etc. You may have seen code similar to:

$button = new-object system.windows.forms.button
$button.add_Click( { $form.Close() } )

This works because in PowerShell v1.0, there is specialized support for this kind of callback to methods with the EventHandler signature, that is to say, methods with parameters of (object sender, EventArgs e). PowerShell is able to run the ScriptBlock in response to the button being clicked and will even pass the two arguments to the scriptblock for you. When the form is shown from a PowerShell script, there is a single thread that is running the message loop for the application. It is this same thread that handles running the script. In the PowerShell engine, there is a pool of threads created at startup, and each of them has its own “Runspace” for running scripts. Because it is one of the PowerShell threads that is running the application, that same thread is able to run the ScriptBlocks in its Runspace when called upon to do so. Although it is in a slightly roundabout way, this is an example of a synchronous callback. This single application thread is effectively waiting (blocked) for the callback to occur in response to a button click (in reality, it’s doing other things while waiting, but it’s still waiting.)

Some rather creative folks, namely one of the primary developers of PowerShell, wrote a delegate/scriptblock binder in pure script some years ago which you can use to pass script to a .NET api to be called back to in a synchronous manner.  See: Creating arbitrary delegates to ScriptBlocks.

If you want to work with .NET events in version 1.0 of PowerShell, you’ll need an add-on, like my PSEventing Snap-In.

Synchronous Callbacks in PowerShell v2.0

In the latest and greatest version of PowerShell, v2.0 RC (which comes with the public Windows 7 RC), synchronous callbacks got a whole lot easier. PowerShell is now able to deal with pretty much ANY delegate signature, automatically. Lets test this by using the .NET 3.5 System.Func<T, TResult> delegate. This is a generic delegate which lets us pass a method to an API expecting a callback to a method which has one parameters of type T1, and will return type T2. Because it’s generic, we get to pick which parameters. Lets demo creating a ScriptBlock that will be passed a DateTime and returns a String:

add-type –assembly system.core # load .net 3.5
$callback = [system.func[datetime, string]] { param($date); "the date is $date" }
$callback.Invoke( [datetime]::now )
# returns
”the date is 2009/07/14 21:50:35”

We can even take advantage of PowerShell’s super-versatile parameter binder by passing it in a string and having it get coerced to DateTime with ne’er a Parse in sight!

$callback.Invoke(“1/1/2009”)
”the date is 2009/01/01 00:00:00”

So, what’s the point of all this? Why go to all that trouble? Why not just write a function? The point is that functions cannot be called by .NET directly. So when would you need a callback like this in a PowerShell script?

Calling Web Services with Invalid, Untrusted or Expired SSL Certificates

The title says it all – sometimes you need to do this. Usually it’s because you’re working with self-signed, expired or otherwise invalid certificates on a QA or Development system. The New-WebServiceProxy Cmdlet in v2.0 is great for calling Web Services, but it doesn’t have a switch to ignore invalid certificates. If you were writing .NET code in C# or VB.NET, the way go about this is to pass a callback method to an API that expects a RemoteCertificateValidationCallback Delegate. This delegate is designed to point to a method that is passed a handful of arguments describing the attempted connection, and is expected to return a boolean; that is to say, true or false. True means “sure, the connection looks fine – go for it.” A value of False being returned tells .NET to stop the connection before it happens.

The amount of .NET code needed to do this is not a ton, but it’s still a fair handful of lines. Check out this example here: http://blog.jameshiggs.com/2008/05/01/c-how-to-accept-an-invalid-ssl-certificate-programmatically/

Now let’s see how much PowerShell script is needed for this same task:

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

Bwahahahah! Eat that, Mr. C# coder! Again, PowerShell’s binder comes into play here and automatically casts our ScriptBlock to a RemoteCertificateValidateCallback delegate (it’s not really a cast – there is no conversion – it’s a sizeable chunk of code.) From this point on, any attempts to use the New-WebServiceProxy Cmdlet with dodgy SSL certificates will succeed without so much as a warning. In fact, pretty much any other classes, like WebRequest will behave the same way. It’s important to note that this only affects the current AppDomain, that is to say, the current PowerShell process. Other processes on the system will continue to stick their nose up at dodgy SSL certs. Quit PowerShell and restart it -- or set that ServerCertificateValidationCallback property to $null -- and all is right in the world again. This works as long as you use one of PowerShell’s threads to do the work of connecting; i.e. don’t use an asynchronous request. This ensures there is a Runspace available to execute this ScriptBlock.

Converting Synchronous Callbacks into Events

This is really quite straightforward. Taking the previous example, we would generate an event inside the scriptblock using New-Event, and then bind one or more event handlers using the Register-EngineEvent Cmdlet:

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {
		new-event -SourceIdentifier SslCheck -MessageData $args > $null
		$true
	}
# dump out arguments to cert validate callback
Register-EngineEvent -SourceIdentifier SslCheck -Action { write-output $args }
Next time, we'll get into some meatier stuff. Have fun!

In Part Two: Asynchronous Callbacks in PowerShell v1.0 and v2.0

posted on Saturday, July 18, 2009 4:22:00 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback

update: missing backtick ` to escape the $verbosepreference variable

A question came up on stack overflow (you don’t know what that is? shame on you!) today from someone asking how they could capture the Verbose stream from a pipeline they ran in a C# program. As it turns out, the same technique is used in script, so I’ll give that example instead since I’m sure the C# guys and gals will have no problem converting the script.

The key is using the new (to v2.0) System.Management.Automation.PowerShell Type, which has a built-in Type Accelerator of [powershell]. It has a static method, Create, which is used to create an instance. This instance is pretty much ready to roll. It has a Streams property, which is of Type PSDataStreams. This Type has properties representing each collection of Error, Progress, Verbose, Debug and Warning.

$ps = [powershell]::create()
$ps.Commands.AddScript("`$verbosepreference='continue'; write-verbose 42")
$ps.invoke()
$ps.streams.verbose
Which yields the VerboseRecord that was written out:
Message InvocationInfo                              PipelineIterationInfo
------- --------------                              ---------------------
42      System.Management.Automation.InvocationInfo {0, 0}
What's important to note about the above example is that I had to set the $verbosepreference to at least "continue" (the default is silentlycontinue) in order for the verbose record to be written. Have fun!
posted on Saturday, July 18, 2009 12:02:53 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Friday, May 22, 2009

update #1 2009/5/23: noted that local jobs (start-job) no longer require an elevated shell.

update #2 2009/5/28: ISE object model and shortcut changes; update for set-psessionconfiguration cmdlet (new screenshot); module changes (highlighted new manifest member names, binary module can now be root module);

I’ve been meaning to write this for a while, but it’s been a busy time. This is a comparison of the significant differences between the standalone CTP3 or Windows 7 Beta version and the version that comes with Win7 RC (6.1.7100.0). For all intents and purposes, the CTP3 version (6.1.6469.0) is exactly the same as the Windows 7 Beta (6.1.7000.0) version.

This is not going to be an exhaustive list of differences, but I will continue to update this post as I find more things worth documenting. It should be safe to bookmark the permalink. One of the nicest things about this release is that it appears that, without exception, all built-in Cmdlets have help. A lot of things have been cleaned up and fixed in this build, from formatting of text to typos and minor bugs/glitches.

Cmdlet Differences

This is a table listing Cmdlets that have either changed (navy), been added (green, underlined) or removed (red, strike-through.) Changed Cmdlets have their parameters listed in the second column. A changed parameter means that its Type has been changed; e.g. it accepts a different .NET object than before. This is generally nothing to worry about since the corresponding source of such objects is usually changed to match – typically another Cmdlet. Parameters  and Cmdlets that have not changed, are not listed.

Cmdlet Parameters
Invoke-Command
Remove-Computer
Add-Computer
Rename-Computer
Test-Connection
Export-FormatData
Receive-Job
Start-Job
Get-Module
New-ModuleManifest
Set-PSBreakpoint
Enable-PSRemoting
Remove-PSSession
Enter-PSSession
New-PSSession
Export-PSSession
Import-PSSession
New-PSSessionOption
Get-WSManInstance
Set-PSSessionConfiguration ShowSecurityDescriptorUI

Alias Changes

Some tweaking of aliases here. Personally I find alias changes in general to get under my skin. Aliases are the first thing I learn and the first thing to trip me up when things change. Regardless, the changes appear to make sense and are perhaps a bit more mnemonic than before.

Removed

emm (Export-ModuleMember), which (Get-Command) and grid (Out-Gridview)

Added

ise (powershell_ise.exe), rmo (Remove-Module) and saps (Start-Process)

Changed

imo –> ipmo (Import-Module)

Language Enhancements

The major change that has come to light so far is that statements are now allowed on the right hand of an expression without having to use subexpressions. This is a great fix, and one that will reduce the margin for error (and confusion) by a large amount. Previously in CTP3, in order to use a statement like “if”, you had to use the following syntax:

$result = $( if ($true) { 42 } )

Now, you can drop the $( and ):

$result = if ($true) { 42 }

or

$sequence = foreach ($i in 0..15) { [math]::pow(2, $i) }

This is truly great stuff.

Jobs/Remoting

The best news here is the abundant help now available at your fingertips. Lots of examples and meaty information concerning PSSessions and PSSessionConfigurations.

Local Jobs

Local jobs created now with Start-Job { ... } use an IPC channel to talk to the local WinRM service to create jobs instead of using the more heavyweight HTTP channel. Yes, you can infer from this that local jobs are still out of process; they run in their own isolated runspace and have no access to the interactive session. What this means in simpler terms is that local jobs are a lot faster now to get started. The biggest win for local jobs is that they no longer require an elevated shell! you can submit local jobs as a regular user now – just not remote ones (unless the applicable remote PSSessionConfiguration is set to allow this - by default the ACL is admins only).

Remoting

A welcome addition to this build is a new, dedicated Enable-PSRemoting Cmdlet:

The Enable-PSRemoting cmdlet configures the computer to receive Windows PowerShell remote commands that are sent by using the WS-Management technology.

You need to run this command only once on each computer that will receive commands. You do not need to run it on computers that only send commands. Because the configuration activates listeners, it is prudent to run it only where it is needed.

The Enable-PSRemoting cmdlet performs the following operations:

  • Runs the Set-WSManQuickConfig cmdlet, which performs the following tasks:
    • Starts the WinRM service.
    • Sets the startup type on the WinRM service to Automatic.
    • Creates a listener to accept requests on any IP address.
    • Enables a firewall exception for WS-Management communications.
  • Enables all registered Windows PowerShell session configurations to receive instructions from a remote computer.
    • Registers the "Microsoft.PowerShell" session configuration, if it is not already registered.
    • Registers the "Microsoft.PowerShell32" session configuration on 64-bit computers, if it is not already registered.
    • Removes the "Deny Everyone" setting from the security descriptor for all the registered session configurations.
    • Restarts the WinRM service to make the preceding changes effective.

To run this cmdlet on Windows Vista, Windows Server 2008, and later versions of Windows, you must start Windows PowerShell with the "Run as administrator" option.

Session Configurations

There are a raft of Cmdlets dedicated to managing session configurations. So what is a session configuration? To qoute the ever-present help system:

A session configuration is a group of settings on the local computer that define the environment for the Windows PowerShell sessions that are created when remote users connect to the local computer.

Administrators of the computer can use session configurations to protect the computer and to define custom environments for users who connect to the computer.

Administrators can also use session configurations to determine the permissions that are required to connect to the computer remotely. By default, only members of the Administrators group have permission to use the session configuration to connect remotely, but you can change the default settings to allow all users, or selected users, to connect remotely to your computer.

Session configurations are a feature of Web Services for Management (WS-Management) based Windows PowerShell remoting. They are used only when you use the New-PSSession, Invoke-Command, or Enter-PSSession cmdlets to connect to a remote computer.

Note: To manage the session configurations on a computer that is running Windows Vista, Windows Server 2008, or a later version of Windows, start Windows PowerShell with the "Run as administrator" option.

image

That SecurityDescriptorSddl property looks like a lot of fun to modify, right? Don’t worry, the ShowSecurityDescriptorUI comes to the rescue:

image

Modules

Manifest Members

Modules have received some nice incremental improvements as well as some syntactic changes. You should have noticed above that the manifest fields (exposed as parameters on New-ModuleManifest)  have changed to reflect the two-stage process of how a module’s members get exposed to the importer’s scope. A module passively exports members with Export-ModuleMember, and the caller actively imports them with Import-Module; hence “FunctionsToExport,” which subtly says that this can be imported.

Binary Modules

A binary module is now allowed to be the root module in a manifest by pointing the ModuleToProcess key at the assembly, e.g. ModuleToProcess = “Pscx.dll.” This seems more intuitive than before with the [seemingly] arbitrary restriction that they must be secondary to a text based psm1.

Built-In Modules

Shipping with Windows 7 for PowerShell comes two new Modules along with the others you should have noticed that arrived with CTP3 (BitsTransfer/FileTransfer and PSDiagnostics.)

AppLocker

AppLocker provides simple, powerful, rule-based structures for specifying which applications can run that are centrally managed using Group Policy. It introduces "publisher rules" that are based on an application's digital signature, making it possible to build strong rules that account for application updates. For example, an organization can create a rule to "allow all versions greater than 1.0 of Microsoft Dynamics CRM to run if signed by Microsoft." With correctly structured rules, IT professionals can safely deploy updates to allowed applications without having to build a new rule for each version update.

About AppLocker on TechNet

TroubleshootingPack

Windows Troubleshooting Platform (WTP) provides ISVs, OEMs, and administrators the ability to write troubleshooting packs that discover and resolve software and hardware issues, such as configuration issues, failed hardware, network issues, and application compatibility issues. In WTP, an issue is referred to as a root cause. Previously, troubleshooting software and hardware issues was a manual process; however, using WTP you can automate the process of fixing the most common issues that the user might encounter.

About WTP on MSDN

PowerShell Integrated Scripting Environment

The ISE has been vastly improved in terms of usability and sharpness in this build. Difficult to isolate any one part of it that is much better; the whole experience is just way smoother and intuitive.

image

ISE Object Model

The object model naming has changed quite a bit to be more intuitive and friendly to beginners. Who needs to know about Runspaces? It’s a Tab, silly!

CTP3 RC
$psise.CurrentOpenedRunspace $psise.CurrentPowerShellTab
$psise.CurrentOpenedRunspace.ToolsMenu $psise.CurrentPowerShellTab.AddOnsMenu
$psise.CurrentOpenedRunspace.OpenedFiles $psise.CurrentPowerShellTab.Files
$psise.CurrentOpenedRunspace.OpenedFiles.RemoveUnsaved($file) $psise.CurrentPowerShellTab.Files.Remove($file,$true)
$psise.OpenedRunspaces $psise.PowerShellTabs
$psise.OpenedRunspaces[1].Execute("string") $psise.PowerShellTabs[1].Invoke({scriptBlock})*
$psise.CurrentOpenedFile $psise.CurrentFile
$psise.Options.LocalHelp $psise.Options.UseLocalHelp

* Note: You may only invoke script on a tab other than the tab you’re using to execute this command (because you can’t run two commands at the same time on the same tab!)

The PowerShell team posted a way to make the RC build fairly compatible with CTP3 by adding new members to the $PSISE object which will proxy attempts to use the old API to the new API. The post is called “Update-TypeData, ISE CTP3 vs ISE RC, and Teched2009 Demos.”

Shortcut Changes

The shortcut to jump to the Script Pane is now Ctrl+I. (use Ctrl+D to jump to the Command pane).

Summary

The RC build is all about bugfixes and incremental improvements. I’m sure there’ll be more fixes and additions as we near RTW/RTM and I’ll be here to post as much info about them as possible. This is still just a drop in the ocean of what PowerShell 2.0 can do, and I’ll post more technical demos of features over the next little while.

Tips: run “Get-Help about_*” to get a list of all the overview topics for the various features in PowerShell v2.

Have fun!

posted on Friday, May 22, 2009 5:25:49 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Thursday, May 14, 2009

Hey, so we did the unthinkable. We released another version of the PowerShell Community Extensions. We’re calling it a beta, because we’ve be so swamped with Real Life stuff that we’re not 100% confident that it is defect-free. It’s not going to murder your servers or anything, but there might be some documentation missing and other minor stuff. We’d really appreciate it if you can give it a test-drive. If you’re running PowerShell v1.0 or v2.0 CTP3, please use the MSI installer. It will upgrade your Pscx 1.1.1 install if you have one. If, on the other hand, you are running Windows 7 RC and/or have a later version of PowerShell than v2 CTP3, you can download the zipped module and unzip to your user profile module directory at ~\documents\windowspowershell\modules\ and load it using “import-module pscx.”

Thanks for your infinite patience (yes, it’s been a while and we’re sorry) and please leave comments and issues on the tracker at http://pscx.codeplex.com/

View the 1.2 Beta release page.

posted on Thursday, May 14, 2009 8:28:13 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Saturday, March 28, 2009

After some hints from Ibrahim on the Microsoft PowerShell team, I realised it was possible to rewrite the dynamic module body generation from the Get-Interface function in my last post without using string literals and nested here-string tricks. I was able to increase the brevity by using a new [to PowerShell] v2 feature called “Closures.” Ibrahim talks a bit about the technique over on the official PowerShell Blog. It’s a common feature in so-called Functional languages and there are plenty of other tidbits to read about it, both academic and practical if you search online.

A closure in PowerShell, in short, lets you take a snapshot of a ScriptBlock by calling its GetNewClosure() method. The snapshot is taken of its variables and will let you pass it around so it is no longer directly tied to the particular scope chain it was declared in. As some are fond of saying though, Thar Be Dragons. Although closing around a ScriptBlock will make copies of the PSVariables that are within, it is still subject to the whims of .NET – particularly ByRef and ByValue semantics. If a ScriptBlock contains a PSVariable pointing to an integer, it’s safe to say that integer will be copied and is frozen as that value as long as you remain within the domain of the current SessionState context (more about this later).

.NET intervenes: ByRef and ByVal

Closing around a ScriptBlock that contains PSVariables that point to reference types, however - like Arrays for example – will only duplicate the reference, not the instance itself. If elements in the array are changed via the PSVariable from the originating scope, the corresponding array element in the closure will also change. After all – you got a copy of the pointer, not the value itself. This is the very essence of ByRef and ByVal.

Let’s take a look at the revised Get-Interface script:

function Get-Interface {

#.Synopsis
#   Allows PowerShell to call specific interface implementations on any .NET object. 
#.Description
#   Allows PowerShell to call specific interface implementations on any .NET object. 
#
#   As of v2.0, PowerShell cannot cast .NET instances to a particular interface. This makes it
#   impossible (shy of reflection) to call methods on explicitly implemented interfaces.   
#.Parameter Object
#   An instance of a .NET class from which you want to get a reference to a particular interface it defines.
#.Parameter InterfaceType
#   An interface type, e.g. [idisposable], implemented by the target object.
#.Example
#   // a class with explicitly implemented interface   
#   public class MyObj : IDisposable {
#      void IDisposable.Dispose()
#   }
#   
#   ps> $o = new-object MyObj
#   ps> $i = get-interface $o ([idisposable])
#   ps> $i.Dispose()      
#.ReturnValue
#   A PSCustomObject with ScriptMethods and ScriptProperties representing methods and properties on the target interface.
#.Notes
# AUTHOR:    Oisin Grehan http://www.nivot.org/
# LASTEDIT:  2009-03-28 18:37:23
# REVISION:  0.2

    [CmdletBinding()]
    param(
        [ValidateNotNull()]
        $Object,
        
        [ValidateScript( { $_.IsInterface } )]
        [type]$InterfaceType
    )

    $script:t  = $Object.GetType()
    
    try {
        
        $script:m  = $t.GetInterfaceMap($InterfaceType)

    } catch [argumentexception] {
        
        throw "Interface $($InterfaceType.Name) not found on ${t}!"
    }

    $script:im = $m.InterfaceMethods
    $script:tm = $m.TargetMethods
    
    # TODO: use param blocks in functions instead of $args
    #       so method signatures are visible via get-member
    
    $body = {
         param($o, $i) 
         
         $script:t  = $o.GetType()
         $script:m  = $t.GetInterfaceMap($i)
         $script:im = $m.InterfaceMethods
         $script:tm = $m.TargetMethods
                  
         for ($ix = 0; $ix -lt $im.Count; $ix++) {
            
            $mb = $im[$ix]

            # for the function body, we close over $ix to capture the index
            # so even on the next iteration of this loop, the $ix value will
            # be frozen within the function's scriptblock body
            set-item -path function:script:$($mb.Name) -value {

                # call corresponding target method
                $tm[$ix].Invoke($o, $args)

            }.GetNewClosure() -verbose -force

            if (!$mb.IsSpecialName) {
                # only export the function if it is not a getter or setter.
                Export-ModuleMember $mb.Name -verbose
            }
         }
    }

    write-verbose $body.tostring()    
    
    # create dynamic module
    $module = new-module -ScriptBlock $body -Args $Object, $InterfaceType -Verbose
    
    # generate method proxies - all exported members become scriptmethods
    # however, we are careful not to export getters and setters.
    $custom = $module.AsCustomObject()
    
    # add property proxies - need to use scriptproperties here.
    # modules cannot expose true properties, only variables and 
    # we cannot intercept variables get/set.
    
    $InterfaceType.GetProperties() | % {

        $propName = $_.Name
        $getter   = $null
        $setter   = $null
       
        if ($_.CanRead) {
            
            # where is the getter methodinfo on the interface map?
            $ix = [array]::indexof($im, $_.GetGetMethod())
            
            # bind the getter scriptblock to our module's scope
            # and generate script to call target method
            #
            # NOTE: we cannot use a closure here because sessionstate
            #       is rebound to the module's, and $ix would be lost
            $getter = $module.NewBoundScriptBlock(
                [scriptblock]::create("`$tm[{0}].Invoke(`$o, @())" -f $ix))
        }
        
        if ($_.CanWrite) {
            
            # where is the setter methodinfo on the interface map?
            $ix = [array]::indexof($im, $_.GetSetMethod())
            
            # bind the setter scriptblock to our module's scope
            # and generate script to call target method
            #
            # NOTE: we cannot use a closure here because sessionstate
            #       is rebound to the module's, and $ix would be lost
            $setter = $module.NewBoundScriptBlock(
                [scriptblock]::create(
                    "param(`$value); `$tm[{0}].Invoke(`$o, `$value)" -f $ix))
        }
        
        # add our property to the pscustomobject
        $prop = new-object management.automation.psscriptproperty $propName, $getter, $setter
        $custom.psobject.properties.add($prop)
    }
    
    # insert the interface name at the head of the typename chain (for get-member info)
    $custom.psobject.TypeNames.Insert(0, $InterfaceType.FullName)
    
    # dump our pscustomobject to pipeline
    $custom
}

The changes are around line 56, where I now declare $body with a script syntax, no longer using [scriptblock]::create. Where previously I used a subexpression inside a here-string to general literal function declarations, I now use a “for” loop and set-item to create the functions (thanks Ibrahim!). You can see that I am calling GetNewClosure() on the function body each time I create a new function. This “captures” the value of the $ix index variable, so that the value will not change on subsequent loops. If I did not use closures, when the for loop ended, all of the functions would be using the same terminating value of the loop for $ix, which would be $im.count + 1. Doh.

Module SessionState Context, Wha?

So if I ripped out that literal string parsing stuff for the module $body, why am I still using it for the property getter/setter generation? (lines 115 / 130)  Well, I have no choice: Creating a new module creates an entirely new SessionState; in simple terms, session state is essentially a giant Hashtable in which PSVariables are stored. If I closed over those get/set ScriptBlocks containing the $ix PSVariable, when it is bound to the module it will use the new module’s SessionState – which is empty! The value of $ix would be undefined. The other variables in those strings are escaped, because they will be evaluated in the module’s context later, when the getters and setters are invoked. Those variables are seeded by the module’s initialization done at the new-module call at line 88.

It’s all very succinct, but rest assured, very powerful.

Have fun!

NOTE: I realise that some more refactoring could probably eliminate this script parsing, but the value is in understanding how modules may affect usage of closures, and the problems that may occur.

posted on Saturday, March 28, 2009 6:23:11 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, March 26, 2009

This is a bit of a hardcore example of the power that modules have brought to the table in v2.0. First, a little background - PowerShell’s type adaptation system has always ignored the concept of interfaces. Frankly, it never really needed to pay them any attention. The adapted view of any instance of a .NET class is just an aggregate of all its methods; the most derived instance is the default, and only, view. This is the most simple and most frequently used view. However, this all goes to hell when you bring in the notion of explicit interfaces.

Explicit Interface Definitions

Here’s an example of a C# class, Test, with two interfaces: IFace, and IFaceEx. I’ve put this whole example in PowerShell script so you can easily test it without firing up Visual Studio. Add-Type cmdlet to the rescue!

if (-not ("Test" -as [type])) {    
    add-type @"
        public interface IFace {
            string Hello(string name);
            string Goodbye();
        }
        
        public interface IFaceEx {
            string Hello(string name);
            string Goodbye();
            string Prop1 {
                get;
                set;
            }                
        }
        
        public class Test : IFace, IFaceEx {
            public string Hello(string name) {
                return "Hello" + name + " from IFace";
            }
            public string Goodbye() {
                return "Goodbye from IFace";
            }            
            string IFaceEx.Hello(string name) {
                return "Hello " + name + " from IFaceEx";
            }
            string IFaceEx.Goodbye() {
                return "Goodbye from IFaceEx";
            }
            
            private string _prop1 = "Foo";
            
            string IFaceEx.Prop1 {
                get { return _prop1; }
                set { _prop1 = value; }
            }
        }
"@    
}

If you new up an instance of Test, you’ll see that the explicit interface definitions are hidden. Any calls to Hello(string) will invoke the IFace implementation.

There is no way to call IFaceEx.Hello without using reflection!

So, this is where the beauty of modules can help us. The following function will take an instance of a .NET class, and the interface you want a  reference to, and will return a PSCustomObject with ScriptMethods and ScriptProperties bound to that interface’s contract. You don’t have to do this only with explicit interfaces, any interfaces will work ;-)

Introducing Get-Interface

A script is worth a thousand words.

function Get-Interface {

#.Synopsis
#   Allows PowerShell to call specific interface implementations on any .NET object. 
#.Description
#   Allows PowerShell to call specific interface implementations on any .NET object. 
#
#   As of v2.0, PowerShell cannot cast .NET instances to a particular interface. This makes it
#   impossible (shy of reflection) to call methods on explicitly implemented interfaces.   
#.Parameter Object
#   An instance of a .NET class from which you want to get a reference to a particular interface it defines.
#.Parameter InterfaceType
#   An interface type, e.g. [idisposable], implemented by the target object.
#.Example
#   // a class with explicitly implemented interface   
#   public class MyObj : IDisposable {
#      void IDisposable.Dispose()
#   }
#   
#   ps> $o = new-object MyObj
#   ps> $i = get-interface $o ([idisposable])
#   ps> $i.Dispose()      
#.ReturnValue
#   A PSCustomObject with ScriptMethods and ScriptProperties representing methods and properties on the target interface.
#.Notes
#   AUTHOR:    Oisin Grehan http://www.nivot.org/
#   LASTEDIT:  2009-03-25 11:35:23

    [CmdletBinding()]
    param(
        [ValidateNotNull()]
        $Object,
        
        [ValidateScript( { $_.IsInterface } )]
        [type]$InterfaceType
    )

    $private:t  = $Object.GetType()
    
    try {
        
        $private:m  = $t.GetInterfaceMap($InterfaceType)

    } catch [argumentexception] {
        
        throw "Interface $($InterfaceType.Name) not found on ${t}!"
    }

    $private:im = $m.InterfaceMethods
    $private:tm = $m.TargetMethods
    
    # TODO: use param blocks in functions instead of $args
    #       so method signatures are visible via get-member
    
    $body = [scriptblock]::Create(@"
        param(`$o, `$i)    
        
        `$script:t  = `$o.GetType()
        `$script:m  = `$t.GetInterfaceMap(`$i)
        `$script:im = `$m.InterfaceMethods
        `$script:tm = `$m.TargetMethods
        
        # interface methods $($im.count)
        # target methods $($tm.count)
        
        $(
            for ($ix = 0; $ix -lt $im.Count; $ix++) {
                $mb = $im[$ix]
                @"
                function $($mb.Name) {
                    `$tm[$ix].Invoke(`$o, `$args)
                }

                $(if (!$mb.IsSpecialName) {
                    @"
                    Export-ModuleMember $($mb.Name)

"@
                })
"@
            }
        )
"@)
    write-verbose $body.tostring()    
    
    # create dynamic module
    $module = new-module -ScriptBlock $body -Args $Object, $InterfaceType -Verbose
        
    # generate method proxies - all exported members become scriptmethods
    # however, we are careful not to export getters and setters.
    $custom = $module.AsCustomObject()
    
    # add property proxies - need to use scriptproperties here.
    # modules cannot expose true properties, only variables and 
    # we cannot intercept variables get/set.
    
    $InterfaceType.GetProperties() | % {

        $propName = $_.Name
        $getter   = $null
        $setter   = $null
       
        if ($_.CanRead) {
            
            # where is the getter methodinfo on the interface map?
            $ix = [array]::indexof($im, $_.GetGetMethod())
            
            # bind the getter scriptblock to our module's scope
            # and generate script to call target method
            $getter = $module.NewBoundScriptBlock(
                [scriptblock]::create("`$tm[{0}].Invoke(`$o, @())" -f $ix))
        }
        
        if ($_.CanWrite) {
            
            # where is the setter methodinfo on the interface map?
            $ix = [array]::indexof($im, $_.GetSetMethod())
            
            # bind the setter scriptblock to our module's scope
            # and generate script to call target method
            $setter = $module.NewBoundScriptBlock(
                [scriptblock]::create(
                    "param(`$value); `$tm[{0}].Invoke(`$o, `$value)" -f $ix))
        }
        
        # add our property to the pscustomobject
        $prop = new-object management.automation.psscriptproperty $propName, $getter, $setter
        $custom.psobject.properties.add($prop)
    }
    
    # insert the interface name at the head of the typename chain (for get-member info)
    $custom.psobject.TypeNames.Insert(0, $InterfaceType.FullName)
    
    # dump our pscustomobject to pipeline
    $custom
}
Here's how to use it, with our example Test class as added earlier:
$if = Get-Interface (new-object test) ([ifaceex]) -verbose
$if.Hello("Oisin") # returns Hello Oisin from IFaceEx

$if.Prop1 = "Test" # property setter
$if.Prop1 # propery getter

Have fun!

posted on Thursday, March 26, 2009 9:02:44 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, March 19, 2009

There’s a bit of a flap going on now around the blogosphere as various vendors, eager to get onto the PowerShell train, are doing the bare minimum to get their product “powershellized.” No one has spent any time reading – or if they did, they weren’t successful in trying to understand it – the Microsoft Command Line Standard.

Modules as Namespaces

That’s right, modules are not as crazy sounding as they seem. They can be used quite simply to just group a load of functions together while allowing easy disambiguation should there be a name collision with another function or cmdlet. It wasn’t always this easy.

Namespaces in PowerShell v1.0

In PowerShell v1.0 it was possible to load two snap-ins that contained identically named commands. This causes PowerShell to spit out an error about ambiguous commands should you try to invoke one. The answer to this was to get the containing snap-in name, and prefix it to the cmdlet name using a backslash as a separator:

$o = Pscx\New-Object Collections.Generic.Dictionary –Of String, Int

As you can see, this lets us call the hypothetical PowerShell Community Extensions version of New-Object to create a generic type. If you want to call the original one, you would have to prefix it like this:

$o = Microsoft.PowerShell.Utility\New-Object Int[] 5

Blech - that’s a bit of a lengthy sentence, but the solution to this is to use an alias. Aliases are found before Cmdlets in the search path:

New-Alias New-Object Microsoft.PowerShell.Utility\New-Object
$o = New-Object Int[] 5 

The reason we need an alias here is because if you just typed the cmdlet without the snap-in prefix, PowerShell v1.0 complains that it doesn’t know which one you want. We forgot to add the mind reader snap-in!

How about functions in PowerShell v1.0? If you dot source a ps1 file that contains functions that already exist in the caller’s session, how do you disambiguate them? Oops! You can’t. They are overwritten. Ouch.

You cannot use the namespace\ prefix for functions in v1 - the support is just not there. However…

Namespaces in PowerShell v2.0

Things are a lot better in this version. Let’s say you have two groups of functions that you use in your business. One for your SharePoint farms, and another set for your Citrix farms. Let’s ignore the perfectly acceptable idea of prefixing the noun to differentiate for the moment and just imagine we have two ps1 files, containing ideally named function with approved verb, simple noun:

# sharepoint functions
function Get-Server {
   # ... gets sharepoint server
}
function Get-Farm {
   # ... gets sharepoint farm
}

And the second script:

# citrix functions
function Get-Server {
    # ... gets citrix server
}
function Get-Farm {
    # ... gets citrix farm
}

If you tried to load these up with via a dot source, one after the other, the functions from the second ps1 would overwrite the first lot. So this is where the wonder world of modules is entered. First step:

After renaming both ps1 files to use the psm1 extension instead, you have created modules of the simplest form. Ignoring the details of how this is deployed (there are several ways – documented on Microsoft and on many blogs, including this one), let's look at how it's loaded and used:

import-module citrix
# Now our functions are loaded. Invocation options:
# assuming no clash, invoke with simple names
get-farm bleh
# load sharepoint module (it also has get-farm)
import-module sharepoint
# ok, sharepoint functions are loaded too, so we have two get-farm commands
# and the last loaded wins, so citrix get-farm is inaccessible through simple syntax, but...
# let's refer specifically to the citrix module function
citrix\get-farm
# and for sharepoint:
sharepoint\get-farm
# and thirdly, if you don't want to use the module qualifier (or "namespace") then you ALSO have the
# choice of applying a prefix on import - this is the primary use case for quick interactive use:
import-module citrix -prefix ctx
# now you can call functions like:
get-ctxfarm
# or even citrix\get-ctxfarm if you so felt like it, but redundant.
# same for sharepoint
import-module sharepoint -prefix sp
get-spfarm

Make sense so far? Unfortunately, as mentioned earlier, in v1.0 the module qualifier (or "namespace") works ONLY for binary commands imported in a Snap-In DLL. Let’s see how that works with get-childitem, a command from one of the preloaded Snap-Ins in PowerShell:

gcm get-childitem | select pssnapin

outputs:

PSSnapIn
--------
Microsoft.PowerShell.Management

Ergo, we invoke it like:
Microsoft.PowerShell.Management\get-childitem
# ... outputs file listing ...

Module Aliasing

Some modules may have longer names that you would be comfortable to type all the time. Thankfully, there is a trick you can do that takes advantage of the fact that modules can be nested. Let’s say you have a module from a vendor that is called something like “Vendor.Division.Product” and it has a cmdlet in it called Get-Thing that happens to clash with another Get-Thing you have loaded. Normally to disambiguate, you have to type:

import-module vendor.division.product
Vendor.Division.Product\Get-Thing

…which is a bit annoying. Instead, wrap the initial import-module statement in a dynamic module where you supply an explicit name for it, and import that instead ;-)

new-module –name product { import-module vendor.division.product } | import-module
# you can now invoke the command like this:
product\get-thing

Any modules that are imported inside a module have their commands automatically exported as if they are part of the root module (unless you control visibility with export-modulemember). Cool, eh?

Have fun!

posted on Thursday, March 19, 2009 10:20:46 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Monday, March 09, 2009

UPDATED 2009-03-11: BUGFIXES FOR CTP3! PLEASE RE-DOWNLOAD v0.2.1

One of the things I hear now and then is that while PowerShell the language can be mastered with the requisite effort, the .NET namespace is considerable harder to get a handle on. Wouldn’t it be nice if you could type things like:

PS> get-help -object [string]::format

Since Functions take precedence over native Cmdlets in the command search order, the first thing you might think is to create a function with the same name, get-help, that can do this. The function itself would have the same parameters as the native Cmdlet, and add one of its own: -object. If the arguments do not belong to the “object” ParameterSet, then the function would splat (using the @ operator) the original arguments to the native Cmdlet transparently and transfer control. The user is none the wiser we had a quick peek at the arguments. So, before we run off duplicating a Cmdlet’s surface through script, first stop, Command Proxies!

Command Proxies

First off, required reading: Extending and/or Modifying Commands with Proxies. Ok, now that you’ve got read that entirely and gotten it  out of the way, you understand that the [ProxyCommand]::Create(…) method lets you automatically generate a function that delegates to a steppable pipeline wrapping the original Cmdlet, right? What I did then is to pack all of these functions into a psm1 module, create a nice psd1 module manifest and away we go.

The ObjectHelp Get-Help Extension Module

So here’s a quick look at the help for my module, in friendly, easy to read Cmdlet help style. The code for the PSM1 module file is way too large to dump here on my blog like I would usually do, so it’s available at as an attachment at the foot of the page. This help below is in a comment in the psm1 file itself.

    NAME
   
        ObjectHelp Extensions Module 0.2 for PowerShell 2.0 CTP3
    
    SYNOPSIS
   
         Get-Help -Object allows you to display usage and summary help for .NET Types and Members.
        
    DETAILED DESCRIPTION
   
        Get-Help -Object allows you to display usage and summary help for .NET Types and Members.
   
        If local documentation is not found and the object vendor is Microsoft, you will be directed
        to MSDN online to the correct page. If the vendor is not Microsoft and vendor information
        exists on the owning assembly, you will be prompted to search for information using Microsoft
        Live Search.
    
    TODO
    
         * localize strings into PSD1 file
         * Implement caching in hashtables. XMLDocuments are fat pigs.
         * Support getting property/field help
         * PowerTab integration
         * Test with Strict Parser
            
    EXAMPLES

        # get help on a type
        PS> get-help -obj [int]

        # get help against live instances
        PS> $obj = new-object system.xml.xmldocument
        PS> get-help -obj $obj

        or even:
       
        PS> get-help -obj 42
       
        # get help against methods
        PS> get-help -obj $obj.Load

        # explictly try msdn
        PS> get-help -obj [regex] -online

        # go to msdn for regex's members
        PS> get-help -obj [regex] -online -members

    CREDITS
   
        Author: Oisin Grehan (MVP)
        Blog  : http://www.nivot.org/
   
        Have fun! 

Usage Examples

So what does it actually look like when you call help? If the help is available locally, it is displayed inline in the console (or ISE). Help for all types in MSCorlib and System is pre-cached. Any other help for types in assemblies belonging to the BCL (Base Class Libraries – effectively the stock .NET assemblies) will be loaded on-demand. This typically takes just a few seconds and will cached for the rest of your session.

image

TODO and BUGS

Right now, it cannot deal with properties. That is to say, trying:

PS> get-help –object $s.length

where $s is a string, will get help on the property _type_, not the property itself. I have some ideas to get around this, so if you can wait for 0.3, I’d be happy. Of course you can wait. You have no choice. :D

Download

The two files come in a zip file. Unzip this file to ~\documents\windowspowershell\modules\objecthelp\ where ~ is your home directory. On vista/win7 this would be c:\users\username. On XP, it would be c:\documents and settings\username. To load it, just execute:

PS> import-module objecthelp

That’s it! Have fun!

posted on Monday, March 09, 2009 11:08:54 PM (Eastern Standard Time, UTC-05:00)  #    Comments [3] Trackback
# Friday, February 13, 2009

A couple of months ago I had a brief Q&A with the cdndevs guys over on blogs.msdn.com. It looks like it went live recently because I’ve been getting stopped in the streets and harassed for a photo etc. No, not really. But if you want to read, take a look over at http://blogs.msdn.com/cdndevs/archive/2009/02/12/mvp-insider-q-a-with-oisin-grehan.aspx ;-)

posted on Friday, February 13, 2009 6:55:32 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
# Tuesday, February 10, 2009

Some of you may have heard that CTP3 has support for transferring files from remote servers using BITS (Background Intelligent Transfer Service) and quite possibly had a little dig around for it. It’s not easy to find as using “get-help BITS” will not return any help unless you’ve already loaded the module. Not a great win for discoverability, but hey, sometimes you just have to read the manual, expletive deleted. So, lets have a quick look at how this works.

image

So, ok, we can see we’ve got a module for FileTransfer (BITS), but where did this come from? Take a peek into $pshome\Modules to find out. This is the location for system-wide (global) modules. It rests under $PSHome, which is the home for PowerShell, in $env:systemroot\windowspowershell\v1.0.

Yes, even PowerShell v2 lives there. This is one of those unfortunate things whereby the team decided early on that they would support side-by-side versions of PowerShell, implying that v2 might not be backwards compatible with v1. This is reflected also in the choice of file extension: ps1. The extension ps2 would have been used for v2. This was probably a poor show of faith in their own abilities to design such a great version one product, because as it turns out, v1.0 was such an excellent release that they were able to build on it for v2 without compromising, or breaking backwards compatibility (apart from a couple of minor edge cases, mainly bugfixes). So, the end result is that future versions of PowerShell will inherit the $pshome, pay a little inheritance tax and hopefully avoid foreclosure :).

The files in the FileTransfer module directory are laid out as follows:

image

(Hey look! another built-in module – PSDiagnostics - I’ll leave that one for you guys to explore).

So, there are four files there. A ps1xml Format file, which tells PowerShell how to textually render the .NET objects returned by the Cmdlets. A psd1 Module Manifest file, which tells PowerShell what files comprise the Module and a binary DLL which in this case does NOT contain the Cmdlets, but instead is what’s called an “Interop Assembly.”  (sometimes known as Primary Interop Assemblies or PIAs, read about them here: Primary Interop Assemblies – there are subtle differences between IAs and PIAs, but not enough to warrant discussion here). Essentially this DLL lets .NET, and by extension, PowerShell, talk to the native COM APIs that wrap the BITS services on XP,Vista,Win7,Win 2003 and Win 2008. Lets take a peek into the Module Manifest:

  1. @{  
  2. GUID="{8FA5064B-8479-4c5c-86EA-0D311FE48875}" 
  3. Author="Microsoft Corporation" 
  4. CompanyName="Microsoft Corporation" 
  5. Copyright="c Microsoft Corporation. All rights reserved." 
  6. ModuleVersion="1.0.0.0" 
  7. Description="Powershell File Transfer Module" 
  8. PowerShellVersion="2.0" 
  9. CLRVersion="2.0" 
  10. NestedModules="Microsoft.BackgroundIntelligentTransfer.Management" 
  11. FormatsToProcess="FileTransfer.Format.ps1xml" 
  12. RequiredAssemblies=Join-Path $psScriptRoot "Microsoft.BackgroundIntelligentTransfer.Management.Interop.dll" 

If it looks like I just dumped a Hashtable out, you’re right. That’s all a manifest is: a Hashtable. Of course, the key names are important and in this case, lets take a look at two in particular, NestedModules and RequiredAssemblies.

The RequiredAssemblies key is responsible for actively loading .NET assemblies containing standard .NET types. It does not extract Providers and Cmdlets and add them to the runspace; this is what the other key, NestedModules, does. Like I said already, this DLL is the interop assembly. So where is the NestedModules loading that other module from? I don’t see any other DLLs in the directory, and I know there are no Cmdlets in that interop assembly. Lets take a peek at one of the Cmdlets itself and find out:

image

Aha, it’s reading it from the GAC. When PowerShell was installed, it must have installed this DLL there. This is the DLL that contains our Cmdlets. I know this is true because I used one of the Cmdlets to tell me where it lives. “GCM” is the alias for Get-Command which will get us metadata about any given command in PowerShell (including functions – new to v2).

So, lets use a one-liner to get the names of all the Cmdlets in this module, and the synopsis summary for the help:

  1. gcm -module filetransfer | % { $_ | select name, @{Name="Help";Expression={& $_ -? | select -expand synopsis }}} | convertto-html –fragment 

This yields the following:

Name Help
Add-FileTransfer Adds one or more files to an existing Background Intelligent Transfer Service (BITS) t ransfer job.
Clear-FileTransfer Cancels a Background Intelligent Transfer Service (BITS) transfer job.
Complete-FileTransfer Completes a Background Intelligent Transfer Service (BITS) transfer job.
Get-FileTransfer Retrieves the associated BitsJob object for an existing Background Intelligent Transfe r Service (BITS) transfer job.
New-FileTransfer Creates a new Background Intelligent Transfer Service (BITS) transfer job.
Resume-FileTransfer Resumes a Background Intelligent Transfer Service (BITS) transfer job.
Set-FileTransfer Modifies the properties of an existing Business Intelligent Transfer Service (BITS) tr ansfer job.
Suspend-FileTransfer Suspends a Background Intelligent Transfer Service (BITS) transfer job.

I guess this covers it for the moment. Going into a full discussion about how BITS actually works is worth another post in itself. Suffice it to say, there are plenty of examples in the help itself. Just run:

get-help add-filetransfer –example | more

To see an example on how to use the Cmdlet (or any of the others). The team have done a good job here.

Have fun!

posted on Tuesday, February 10, 2009 1:38:27 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
# Tuesday, February 03, 2009

It’s been quite a few years -- November 14, 2006 to be exact -- since the final release of PowerShell 1.0. Let’s see what the lazy buggers have been up to since then. Only kidding, they’re far from lazy; we’ve gained an extra one hundred and six new cmdlets, moving from 131 to 237. We’ve also gained another provider, WSManProvider which lets you explore and manipulate the WS-Man configuration. Finally, the number of public Types (.NET classes usable by 3rd parties to extend PowerShell) has increased from 447 to 749. So how do I know all this? Well, some months ago I wrote a suite of build analysis Cmdlets for examining the assemblies that comprise PowerShell. In its current form it’s of very little use to 3rd parties, but I’ve had some requests to open it up and allow it to analyze any binary modules and snap-ins, which is a good idea I think. Watch this space. Anyway, I’m going to dump out some interest information on the differences between v1.0 and v2.0 CTP3, along with the one-liners I’m using to generate the information.

update feb 5: added breaking changes

Breaking Changes to Windows PowerShell 1.0

The following changes in Windows PowerShell V2.0 CTP3 might prevent features designed for Windows PowerShell 1.0 from working correctly.

    • The value of the PowerShellVersion registry entry in HKLM\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine has been changed to 2.0.
    • New cmdlets and variables have been added. These are listed below. These new elements might conflict with variables and functions in profiles and scripts.
    • -IEQ operator does a case insensitive comparison on characters.
    • Get-Command gets functions along with cmdlets by default.
    • Any native command that generates a user interface blocks if it is pipelined to the Out-Host cmdlet.
    • Added new language keywords: Begin, Process, and End. Any commands called begin, process or end are interpreted as language keywords and might result in parsing errors.
    • Cmdlet name resolution has changed. In Windows PowerShell 1.0, a runtime error was generated when two Windows PowerShell snap-ins exported cmdlets with the same name. In Windows PowerShell V2, the last cmdlet loaded is the one that is executed if the cmdlet name is not qualified by a snap-in name.
    • Terminating errors that are thrown in a pipeline do not terminate the pipeline. Instead, they are written to the host.
    • A function called with '-?' parameter gets the help topic for the function, if one is included in the function.

There are no changes to Cmdlets other than Get-Command’s –PSSnapin parameter is now an alias to a –Module parameter. This reflects the move away from administrator-installed snap-ins, and towards the friendlier Module system. For all intents and purposes, you can treat v1 snapins as modules as load them as such with the Import-Module Cmdlet.

New Cmdlets

Here are the new Cmdlets, sorted by noun and alongside the textual synopsis is one is present in the CTP3 help, or the syntax if there is no help. The command I ran to generate this was:

  1. compare-psbuildinfo (import-psbuildinfo .\rtm-6-0-6000-16386.psbuild) (import-psbuildinfo .\win7beta1-6-1-7000-0.psbuild) | ? {$_.diff -eq "Added"} | sort noun | % { $_ | add-member -name Synopsis -member noteproperty -value (& $_.name -?|select -expand synopsis) -passthru } | select name,synopsis | convertto-html -fragment > new-cmdlets.htm 

Name Synopsis
Invoke-Command Runs commands on local and remote computers.
Add-Computer Adds computers to a domain or workgroup.
Remove-Computer Removes computers from workgroups or domains.
Rename-Computer Renames a computer.
Restore-Computer Starts a system restore on the local computer.
Checkpoint-Computer Creates a system restore point on the local computer.
Restart-Computer Restarts ("reboots") the operating system on local and remote computers.
Stop-Computer Stops (shuts down) local and remote computers.
Reset-ComputerMachinePassword Resets the machine account password for the computer.
Disable-ComputerRestore Disables the System Restore feature on the specified file system drive.
Enable-ComputerRestore Enables the System Restore feature on the specified file system drive.
Get-ComputerRestorePoint Gets the restore points on the local computer.
Test-ComputerSecureChannel Test-ComputerSecureChannel [-Repair] [-Server <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm]
Test-Connection Sends ICMP echo request packets ("pings") to one or more computers.
Export-Counter The Export-Counter cmdlet takes PerformanceCounterSampleSet objects and exports them as counter log files.
Import-Counter Imports performance counter log files (.blg, .csv, .tsv) and creates the objects that represent each counter sample in the log.
Get-Counter Gets performance counter data from local and remote computers.
ConvertFrom-Csv Converts object properties in CSV format into CSV versions of the original objects.
ConvertTo-Csv Converts .NET objects into a series of comma-separated, variable-length (CSV) strings.
Register-EngineEvent Register-EngineEvent [-SourceIdentifier] <String> [[-Action] <ScriptBlock>] [-MessageData <PSObject>] [-SupportEvent] [-Forward] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Unregister-Event Unregister-Event [-SourceIdentifier] <String> [-Force] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm] Unregister-Event [-SubscriptionId] <Int32> [-Force] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm]
New-Event New-Event [-SourceIdentifier] <String> [[-Sender] <PSObject>] [[-EventArguments] <PSObject[]>] [[-MessageData] <PSObject>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Remove-Event Remove-Event [-SourceIdentifier] <String> [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm] Remove-Event [-EventIdentifier] <Int32> [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm]
Wait-Event Wait-Event [[-SourceIdentifier] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Get-Event Get-Event [[-SourceIdentifier] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Show-EventLog Displays the event logs of the local or a remote computer in Event Viewer.
New-EventLog Creates a new event log and a new event source on a local or remote computer.
Remove-EventLog Deletes an event log or unregisters an event source.
Clear-EventLog Deletes all entries from specified event logs on the local or remote computers.
Write-EventLog Writes an event to an event log.
Limit-EventLog Sets the event log properties that limit the size of the event log and the age of its entries.
Get-EventSubscriber Get-EventSubscriber [[-SourceIdentifier] <String>] [-Force] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Export-FormatData Export-FormatData [-InputObject <ExtendedTypeDefinition[]>] [-FilePath <String>] [-Force] [-NoClobber] [-IncludeScriptBlock] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Get-FormatData Get-FormatData [[-TypeName] <String[]>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Out-GridView Sends output to an interactive table in a separate window.
Clear-History Deletes entries from the command history.
Get-HotFix Gets the QFE updates that have been applied to the local and remote computers.
Stop-Job Stops a Windows PowerShell background job.
Wait-Job Suppresses the command prompt until one or all of the Windows PowerShell background jobs running in the session are complete.
Remove-Job Deletes a Windows PowerShell background job.
Start-Job Starts a Windows PowerShell background job.
Get-Job Gets Windows PowerShell background jobs that are running in the current session.
Receive-Job Gets the results of the Windows PowerShell background jobs in the current session. You can use this cmdlet to retrieve the output and errors of background jobs.
Update-List Adds and removes items from a property value that contains a collection of objects.
Import-LocalizedData Imports language-specific data into scripts and functions based on the UI culture that is selected for the operating system.
Send-MailMessage Sends an e-mail message.
Get-Module Get-Module [[-Name] <String[]>] [-All] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Get-Module [[-Name] <String[]>] [-ListAvailable] [-Recurse] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Remove-Module Remove-Module [-Name] <String[]> [-Force] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm] Remove-Module [-ModuleInfo] <PSModuleInfo[]> [-Force] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm]
New-Module New-Module [-ScriptBlock] <ScriptBlock> [-Function <String[]>] [-Cmdlet <String[]>] [-ReturnResult] [-AsCustomObject] [-ArgumentList <Object[]>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] New-Module [-Name] <String> [-ScriptBlock] <ScriptBlock> [-Function <String[]>] [-Cmdlet <String[]>] [-ReturnResult] [-AsCustomObject] [-ArgumentList <Object[]>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Import-Module Import-Module [-Name] <String[]> [-Prefix <String>] [-Function <String[]>] [-Cmdlet <String[]>] [-Variable <String[]>] [-Alias <String[]>] [-Force] [-PassThru] [-AsCustomObject] [-Version <Version>] [-ArgumentList <Object[]>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Import-Module [-Assembly] <Assembly[]> [-Prefix <String>] [-Function <String[]>] [-Cmdlet <String[]>] [-Variable <String[]>] [-Alias <String[]>] [-Force] [-PassThru] [-AsCustomObject] [-Version <Version>] [-ArgumentList <Object[]>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Import-Module [-ModuleInfo] <PSModuleInfo[]> [-Prefix <String>] [-Function <String[]>] [-Cmdlet <String[]>] [-Variable <String[]>] [-Alias <String[]>] [-Force] [-PassThru] [-AsCustomObject] [-Version <Version>] [-ArgumentList <Object[]>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Test-ModuleManifest Test-ModuleManifest [-Path] <String> [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
New-ModuleManifest New-ModuleManifest [-Path] <String> -NestedModules <String[]> [-Guid <Guid>] -Author <String> -CompanyName <String> -Copyright <String> [-ModuleToProcess <String>] [-ModuleVersion <Version>] -Description <String> [-PowerShellVersion <Version>] [-ClrVersion <Version>] [-RequiredModules <IDictionary[]>] -TypesToProcess <String[]> -FormatsToProcess <String[]> [-ScriptsToProcess <String[]>] -RequiredAssemblies <String[]> -OtherFiles <String[]> [-ExportedFunctions <String[]>] [-ExportedAliases <String[]>] [-ExportedVariables <String[]>] [-ExportedCmdlets <String[]>] [-PrivateData <Object>] [-PassThru] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm]
Export-ModuleMember Export-ModuleMember [[-Function] <String[]>] [-Cmdlet <String[]>] [-Variable <String[]>] [-Alias <String[]>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Register-ObjectEvent Register-ObjectEvent [-InputObject] <PSObject> [-EventName] <String> [[-SourceIdentifier] <String>] [[-Action] <ScriptBlock>] [-MessageData <PSObject>] [-SupportEvent] [-Forward] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Start-Process Starts one or more processes on the local computer.
Debug-Process Debugs one or more processes running on the local computer.
Wait-Process Waits for the processes to be stopped before accepting more input.
Enable-PSBreakpoint Enables the breakpoints in the current console.
Disable-PSBreakpoint Disables the breakpoints in the current console.
Remove-PSBreakpoint Deletes breakpoints from the current console.
Set-PSBreakpoint Sets a breakpoint on a line, command, or variable.
Get-PSBreakpoint Gets the breakpoints that are set in the current console.
Get-PSCallStack Displays the current call stack.
Remove-PSSession Closes one or more Windows PowerShell sessions (PSSessions).
Enter-PSSession Starts an interactive session with a remote computer.
Exit-PSSession Ends an interactive session with a remote computer.
Get-PSSession Gets the Windows PowerShell sessions (PSSessions) in the current session.
Export-PSSession Saves commands from another session in a script module file.
Import-PSSession Imports cmdlets, aliases, functions, and other command types from another session on a local or remote computer into the current session.
New-PSSession Creates a persistent connection to a local or remote computer.
Set-PSSessionConfiguration Set-PSSessionConfiguration [-Name] <String> [-ApplicationBase <String>] [-ThreadApartmentState <ApartmentState>] [-ThreadOptions <PSThreadOptions>] [-StartupScript <String>] [-MaximumReceivedDataSizePerCommandMB <Nullable`1>] [-MaximumReceivedObjectSizeMB <Nullable`1>] [-SecurityDescriptorSddl <String>] [-Force] [-NoServiceRestart] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Set-PSSessionConfiguration [-Name] <String> [-AssemblyName] <String> [-ConfigurationTypeName] <String> [-ApplicationBase <String>] [-ThreadApartmentState <ApartmentState>] [-ThreadOptions <PSThreadOptions>] [-StartupScript <String>] [-MaximumReceivedDataSizePerCommandMB <Nullable`1>] [-MaximumReceivedObjectSizeMB <Nullable`1>] [-SecurityDescriptorSddl <String>] [-Force] [-NoServiceRestart] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Enable-PSSessionConfiguration Enable-PSSessionConfiguration [[-Name] <String[]>] [-Force] [-SecurityDescriptorSddl <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Disable-PSSessionConfiguration Disable-PSSessionConfiguration [[-Name] <String[]>] [-Force] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Register-PSSessionConfiguration Register-PSSessionConfiguration [-Name] <String> [-ProcessorArchitecture <String>] [-ApplicationBase <String>] [-ThreadApartmentState <ApartmentState>] [-ThreadOptions <PSThreadOptions>] [-StartupScript <String>] [-MaximumReceivedDataSizePerCommandMB <Nullable`1>] [-MaximumReceivedObjectSizeMB <Nullable`1>] [-SecurityDescriptorSddl <String>] [-Force] [-NoServiceRestart] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Register-PSSessionConfiguration [-Name] <String> [-AssemblyName] <String> [-ConfigurationTypeName] <String> [-ProcessorArchitecture <String>] [-ApplicationBase <String>] [-ThreadApartmentState <ApartmentState>] [-ThreadOptions <PSThreadOptions>] [-StartupScript <String>] [-MaximumReceivedDataSizePerCommandMB <Nullable`1>] [-MaximumReceivedObjectSizeMB <Nullable`1>] [-SecurityDescriptorSddl <String>] [-Force] [-NoServiceRestart] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Unregister-PSSessionConfiguration Unregister-PSSessionConfiguration [-Name] <String> [-Force] [-NoServiceRestart] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Get-PSSessionConfiguration Get-PSSessionConfiguration [[-Name] <String[]>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Get-Random Gets a random number or selects objects randomly from a collection.
Set-StrictMode Establishes and enforces coding rules in expressions, scripts, and script blocks.
ConvertFrom-StringData Converts a string containing one or more "name=value" pairs to a hash table.
Undo-Transaction Rolls back the active transaction.
Use-Transaction Adds the script block to the active transaction.
Complete-Transaction Commits the active transaction.
Get-Transaction Gets the current (active) transaction.
Start-Transaction Starts a transaction.
Add-Type Adds a .NET type (a class) to a Windows PowerShell session.
New-WebServiceProxy Creates a Web service proxy object that lets you use and manage the Web service in Windows PowerShell.
Get-WinEvent Gets events from event logs and event tracing log files on local and remote computers. This cmdlet runs only on Windows Vista and later versions of Windows.
Register-WmiEvent Register-WmiEvent [-Class] <String> [[-SourceIdentifier] <String>] [[-Action] <ScriptBlock>] [-Namespace <String>] [-Credential <PSCredential>] [-ComputerName <String>] [-Timeout <Int64>] [-MessageData <PSObject>] [-SupportEvent] [-Forward] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Register-WmiEvent [-Query] <String> [[-SourceIdentifier] <String>] [[-Action] <ScriptBlock>] [-Namespace <String>] [-Credential <PSCredential>] [-ComputerName <String>] [-Timeout <Int64>] [-MessageData <PSObject>] [-SupportEvent] [-Forward] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Set-WmiInstance Creates or modifies instances of WMI classes.
Invoke-WmiMethod Calls WMI methods.
Remove-WmiObject Deletes WMI classes and instances.
Disconnect-WSMan Disconnect-WSMan [[-ComputerName] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Connect-WSMan Connect-WSMan [[-ComputerName] <String>] [-ApplicationName <String>] [-Authentication <AuthenticationMechanism>] [-Credential <PSCredential>] [-OptionSet <Hashtable>] [-Port <Int32>] [-SessionOption <SessionOption>] [-UseSSL] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Connect-WSMan [-Authentication <AuthenticationMechanism>] [-ConnectionURI <Uri>] [-Credential <PSCredential>] [-OptionSet <Hashtable>] [-Port <Int32>] [-SessionOption <SessionOption>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Test-WSMan Test-WSMan [[-ComputerName] <String>] [-Authentication <AuthenticationMechanism>] [-Credential <PSCredential>] [-Port <Int32>] [-UseSSL] [-ApplicationName <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Invoke-WSManAction Invoke-WSManAction [-ResourceURI] <Uri> [-Action] <String> [[-SelectorSet] <Hashtable>] [-Authentication <AuthenticationMechanism>] [-ConnectionURI <Uri>] [-Credential <PSCredential>] [-FilePath <String>] [-OptionSet <Hashtable>] [-SessionOption <SessionOption>] [-ValueSet <Hashtable>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Invoke-WSManAction [-ResourceURI] <Uri> [-Action] <String> [[-SelectorSet] <Hashtable>] [-ApplicationName <String>] [-Authentication <AuthenticationMechanism>] [-ComputerName <String>] [-Credential <PSCredential>] [-FilePath <String>] [-OptionSet <Hashtable>] [-Port <Int32>] [-SessionOption <SessionOption>] [-UseSSL] [-ValueSet <Hashtable>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Get-WSManCredSSP Get-WSManCredSSP [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Enable-WSManCredSSP Enable-WSManCredSSP [-DelegateComputer] <String[]> [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Disable-WSManCredSSP Disable-WSManCredSSP [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Remove-WSManInstance Remove-WSManInstance [-ResourceURI] <Uri> [-SelectorSet] <Hashtable> [-ApplicationName <String>] [-Authentication <AuthenticationMechanism>] [-ComputerName <String>] [-Credential <PSCredential>] [-OptionSet <Hashtable>] [-Port <Int32>] [-SessionOption <SessionOption>] [-UseSSL] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Remove-WSManInstance [-ResourceURI] <Uri> [-SelectorSet] <Hashtable> [-Authentication <AuthenticationMechanism>] [-ConnectionURI <Uri>] [-Credential <PSCredential>] [-OptionSet <Hashtable>] [-SessionOption <SessionOption>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
New-WSManInstance New-WSManInstance [-ResourceURI] <Uri> [-SelectorSet] <Hashtable> [-ApplicationName <String>] [-Authentication <AuthenticationMechanism>] [-ComputerName <String>] [-Credential <PSCredential>] [-FilePath <String>] [-OptionSet <Hashtable>] [-Port <Int32>] [-SessionOption <SessionOption>] [-UseSSL] [-ValueSet <Hashtable>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] New-WSManInstance [-ResourceURI] <Uri> [-SelectorSet] <Hashtable> [-Authentication <AuthenticationMechanism>] [-ConnectionURI <Uri>] [-Credential <PSCredential>] [-FilePath <String>] [-OptionSet <Hashtable>] [-SessionOption <SessionOption>] [-ValueSet <Hashtable>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Get-WSManInstance Get-WSManInstance [-ResourceURI] <Uri> [-ApplicationName <String>] [-Authentication <AuthenticationMechanism>] [-ComputerName <String>] [-ConnectionURI <Uri>] [-Credential <PSCredential>] [-Dialect <Uri>] [-Fragment <String>] [-OptionSet <Hashtable>] [-Port <Int32>] [-SelectorSet <Hashtable>] [-SessionOption <SessionOption>] [-UseSSL] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Get-WSManInstance [-ResourceURI] <Uri> [-ApplicationName <String>] [-Authentication <AuthenticationMechanism>] [-BasePropertiesOnly] [-ComputerName <String>] [-ConnectionURI <Uri>] [-Credential <PSCredential>] [-Dialect <Uri>] -Enumerate [-Filter <String>] [-OptionSet <Hashtable>] [-Port <Int32>] [-References] [-ReturnType <String>] [-SessionOption <SessionOption>] [-Shallow] [-UseSSL] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Set-WSManInstance Set-WSManInstance [-ResourceURI] <Uri> [[-SelectorSet] <Hashtable>] [-ApplicationName <String>] [-Authentication <AuthenticationMechanism>] [-ComputerName <String>] [-Credential <PSCredential>] [-Dialect <Uri>] [-FilePath <String>] [-Fragment <String>] [-OptionSet <Hashtable>] [-Port <Int32>] [-SessionOption <SessionOption>] [-UseSSL] [-ValueSet <Hashtable>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Set-WSManInstance [-ResourceURI] <Uri> [[-SelectorSet] <Hashtable>] [-Authentication <AuthenticationMechanism>] [-ConnectionURI <Uri>] [-Credential <PSCredential>] [-Dialect <Uri>] [-FilePath <String>] [-Fragment <String>] [-OptionSet <Hashtable>] [-SessionOption <SessionOption>] [-ValueSet <Hashtable>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Set-WSManQuickConfig Set-WSManQuickConfig [-UseSSL] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
New-WSManSessionOption New-WSManSessionOption [-ProxyAccessType <ProxyAccessType>] [-ProxyAuthentication <ProxyAuthentication>] [-ProxyCredential <PSCredential>] [-SkipCACheck] [-SkipCNCheck] [-SkipRevocationCheck] [-SPNPort <Int32>] [-OperationTimeout <Int32>] [-NoEncryption] [-UseUTF16] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Select-Xml Select-Xml [-XPath] <String> [-Path] <String[]> [-Namespace <Hashtable>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Select-Xml [-XPath] <String> [-Xml] <XmlNode[]> [-Namespace <Hashtable>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] Select-Xml [-XPath] <String> [-Content] <String[]> [-Namespace <Hashtable>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
ConvertTo-Xml Creates an XML-based representation of an object.

Whew, that’s quite a few. Now, onto the API.

.NET API Differences

This time, I’m just dumping out the namespaces and Types per namespace. It would be a bit much to dump out all the Types themselves. The one-liner:

  1. $v1.api | group namespace | select name, count | convertto-html -fragment > .\v1-types.htm 
v1.0 RTM API

Here are the namspaces and respective public Type count for v1.0:

Name Count
Microsoft.PowerShell.Commands 196
Microsoft.PowerShell 11
Microsoft.PowerShell.Commands.Internal.Format 4
System.Management.Automation 169
System.Management.Automation.Internal 6
System.Management.Automation.Host 15
System.Management.Automation.Runspaces 33
System.Management.Automation.Provider 13

v2.0 CTP3 API

Here are the namspaces and respective public Type count for v2.0 CTP3:

Name Count
Microsoft.PowerShell.Commands 311
Microsoft.PowerShell.Commands.GetCounter 4
Microsoft.PowerShell 14
Microsoft.PowerShell.Commands.Internal.Format 4
Microsoft.WSMan.Management 40
System.Management.Automation 262
System.Management.Automation.Internal 7
System.Management.Automation.Runspaces 61
System.Management.Automation.Host 17
System.Management.Automation.Remoting 10
System.Management.Automation.Provider 14
Microsoft.PowerShell.Commands.Internal 4
Microsoft.PowerShell.Commands.Management 1

As you can see, there has been a nice expansion of namespaces and Types to work with, mostly coming from the sterling work being done to generalize the “Jobs” infrastructure. Also, a fair chunk is tied up with the refactoring and reorganizing of Runspaces to allow for jobs (which also covers eventing), remoting and background pipelines. This is something I will cover in more detail in a future post.

Have fun!

posted on Tuesday, February 03, 2009 7:18:14 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, January 22, 2009

You might have noticed these two accelerators in the list I published recently along with the technique how to add your own type accelerators. It’s not immediately clear that they are related, but they very much are. This is going to be a fairly developer-oriented post, so there won’t be any hand-holding here.

[PowerShell]

The [PowerShell] accelerator is aliased to System.Management.Automation.PowerShell. It has one static method, Create(), which returns an instance of the class itself. In v1, if you wanted to set up asynchronous pipelines and do other fancy stuff, you had to get your hands dirty with Runspace and Pipeline instances and know how to wire them all together. The [PowerShell] class makes it a lot easier to do this; think of it a “PowerShell Pipeline Runner” class. At its simplest, you can just “new up” an instance, assign some script using the AddScript method and call Invoke(). You can run this asynchronously using the BeginInvoke/EndInvoke pattern which should be familiar to all .NET developers who’ve done a bit of threading work.

  1. # create a new pipeline  
  2. $ps = [powershell]::create()  
  3.  
  4. # add a command (returns Command object)  
  5. [void] $ps.AddScript("ls")  
  6.  
  7. # invoke synchronously, returning results of "ls."  
  8. $results = $ps.Invoke()  
  9.  
  10. # clean up  
  11. $ps.dispose() 

[RunspaceFactory]

This accelerator is aliased to System.Management.Automation.Runspaces.RunspaceFactory. It has two static methods of interest, CreateRunspace and CreateRunspacePool. I’m going to focus on the latter because it has more interesting uses which you will see. This latter method lets you create a collection of Runspace instances that are essentially reusable. The great thing is that you don’t have to worry about any of the details. It just works; this leads me to the next part: queuing local pipeline jobs to be run in the background.

The pool you create can be constrained in many ways by using the various overloads of the CreateRunspacePool. You can even pass it a RunspaceConnectionInfo object so that the queued pipelines are run on remote servers. This is done by using the New-PSSession cmdlet to create a session to a remote machine running PowerShell 2.0 with WinRM configured correctly.

Queueing Pipelines to a Runspace Pool

This is where the magic really happens. Simply new up a PowerShell instance, assign the pool to it and run a command. New up as many PowerShell instnaces as you like, and as long as you assign each of them the same pool, the pool automagically looks after processing them as fast as it can and will never go over its hard limits you give it for the number of simultaneous runspaces. In this next script, I set up a pool of three runspaces. I then queue up six pipelines to be run. I am using the BeginInvoke method to start the command in the background. You’ll see when it runs that each command will make a beep of a certain frequency when it finally starts up. You can hear the first three jobs start up pretty much straight away, as each completes, another starts up. Magic!

  1. #require -version 2.0  
  2.  
  3. # create a pool of 3 runspaces  
  4. $pool = [runspacefactory]::CreateRunspacePool(1, 3)  
  5. $pool.Open()  
  6.  
  7. write-host "Available Runspaces: $($pool.GetAvailableRunspaces())" 
  8.  
  9. $jobs = @()  
  10. $ps = @()  
  11. $wait = @()  
  12.  
  13. # run 6 background pipelines  
  14. for ($i = 0; $i -lt 6; $i++) {  
  15.      
  16.    # create a "powershell pipeline runner"  
  17.    $ps += [powershell]::create()  
  18.      
  19.    # assign our pool of 3 runspaces to use  
  20.    $ps[$i].runspacepool = $pool 
  21.      
  22.    $freq = 440 + ($i * 10)  
  23.    $sleep = (1 * ($i + 1))  
  24.      
  25.    # test command: beep and wait a certain time  
  26.    [void]$ps[$i].AddScript(  
  27.         "[console]::Beep($freq, 30); sleep -seconds $sleep")  
  28.      
  29.    # start job  
  30.    write-host "Job $i will run for $sleep second(s)" 
  31.    $jobs += $ps[$i].BeginInvoke();  
  32.      
  33.    write-host "Available runspaces: $($pool.GetAvailableRunspaces())" 
  34.      
  35.    # store wait handles for WaitForAll call  
  36.    $wait += $jobs[$i].AsyncWaitHandle  
  37. }  
  38.  
  39. # wait 20 seconds for all jobs to complete, else abort  
  40. $success = [System.Threading.WaitHandle]::WaitAll($wait, 20000)  
  41.  
  42. write-host "All completed? $success" 
  43.  
  44. # end async call  
  45. for ($i = 0; $i -lt 6; $i++) {  
  46.  
  47.     write-host "Completing async pipeline job $i" 
  48.  
  49.     try {  
  50.  
  51.         # complete async job  
  52.         $ps[$i].EndInvoke($jobs[$i])  
  53.  
  54.     } catch {  
  55.       
  56.         # oops-ee!  
  57.         write-warning "error: $_" 
  58.     }  
  59.  
  60.     # dump info about completed pipelines  
  61.     $info = $ps[$i].InvocationStateInfo  
  62.  
  63.     write-host "State: $($info.state) ; Reason: $($info.reason)" 
  64. }  
  65.  
  66. # should show 3 again.  
  67. write-host "Available runspaces: $($pool.GetAvailableRunspaces())" 

Feel free to post questions or requests for clarification, but I did say it was a bit tough and developer-oriented. I have a hint that our good friend and fellow MVP, Karl Prosser (aka Klumsy), will be wrapping up some of this stuff into some nice admin-oriented background  command functions.

IMPORTANT:

Due to the WaitAll call at line 40, this script will not work in an STA thread (i.e. in PowerShell ISE). Use PowerShell.EXE to run this script. Everything else will work in ISE fine.

Have fun!

posted on Thursday, January 22, 2009 2:38:03 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
# Tuesday, December 30, 2008

A module manifest file is a PowerShell data file (.psd1) with the same name as the module directory. For example, the FileTransfer module installed with CTP3 -- which contains Cmdlets for BITS -- is in a directory called $pshome\Modules\FileTransfer and contains a manifest file called $pshome\Modules\FileTransfer\FileTransfer.psd1. Module manifests are optional, but highly recommended.

A module manifest contains a hashtable declared using the @{} hashtable literal syntax. Valid keys are listed in the following table:

Key

Required

Type

Description

ModuleToProcess

Optional

String

Script module or binary module file associated with this manifest which also becomes the root module for nested modules. If no module is specified, the manifest itself becomes the root module for nested modules.

A binary module is the new name for a snap-in DLL. v1 style snapins can be loaded this way, without having to register them first with installutil.exe; A binary module does NOT need a PSSnapIn class defined.

ModuleVersion

Required

String convertible to System.Version

Version of the module

GUID

Optional

String

Unique identifier for the module which can be used to verify against the module name

Author

Optional

String

Identifies the author of the module

CompanyName

Optional

String

Identifies the company that created the module

Copyright

Optional

String

Module copyright

Description

Optional

String

Describes the contents of the module

PowerShellVersion

Optional

String convertible to System.Version

Minimum required version of the PowerShell engine

CLRVersion

Optional

String convertible to System.Version

Minimum required version of the CLR

RequiredModules

Optional

List of module names

List of modules that must already be loaded globally (note: required modules are not loaded automatically - this could optionally be done using a script in ScriptsToProcess).

RequiredAssemblies

Optional

String array

List of assemblies that will be loaded using the same algorithm as Add-Type

ScriptsToProcess

Optional

String array

Identifies the list of scripts to process when the module is imported. These scripts are dot sourced into the caller’s environment. Only .ps1 files can be specified.

TypesToProcess

Optional

String array

List of .ps1xml type files to process using Update-TypeData

FormatsToProcess

Optional

String array

List of .ps1xml format files to process using Update-FormatData

NestedModules

Optional

String array

List of .ps1, .psm1, .psd1, and .dll files to process on Import-Module. Files are processed in the order listed. DLLs are scraped for cmdlets/providers and .ps1 script files are dot sourced into the module’s session state.

ExportedFunctions

Optional

String array, wildcards supported

List of functions to export. If not defined or if asterisk is specified, all functions imported from nested modules are re‑exported. To prevent export, use the empty string ‘’.

ExportedCmdlets

Optional

String array, wildcards supported

List of cmdlets to export. If not defined or if asterisk is specified, all cmdlets imported from nested modules are re‑exported. To prevent export, use the empty string ‘’.

A big change in CTP3 is that binary Cmdlets can now be scoped – previously they were always global.

ExportedVariables

Optional

String array, wildcards supported

List of variables to export. If not defined or if asterisk is specified, all variables imported from nested modules are re‑exported. To prevent export, use the empty string ‘’.

ExportedAliases

Optional

String array, wildcards supported

List of aliases to export. If not defined or if asterisk is specified, all aliases imported from nested modules are re‑exported. To prevent export, use the empty string ‘’.

PrivateData

Optional

Object

Data to be passed to the module via the manifest file.

I’ve highlighted certain things in the description that are important to note. These are the things that might trip you up.

Have fun!

posted on Tuesday, December 30, 2008 11:48:20 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Saturday, December 27, 2008

Meson tweeted a request asking if it was possible to get a list of language keywords from PowerShell itself. The answer is officially “No,” but like most things of this nature, there’s always a sneaky way:

  1. [type]::gettype("System.Management.Automation.KeywordTokenReader")|%{$_.InvokeMember("_keywordTokens", "NonPublic,Static,GetField", $null, $_,@())}  

More interestingly this list turned up a new script keyword, dynamicparam. I haven’t seen it in action yet but it sounds like another “advanced function” feature to bring functions and cmdlets closer to parity. I may need to add another article to my dynamic parameter series ;-)

posted on Saturday, December 27, 2008 12:21:38 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, December 25, 2008

This is an interesting exercise to show the power of PowerShell’s language to explore and manipulate object models, specifically its own. You all should be familiar with Type Accelerators: The short name syntax for accessing commonly used .NET Types. An example would be [wmi] – this is the same as typing [System.Management.ManagementObject]. So, how can we find all of the current existing Type Accelerators? Well, after cracking open PowerShell with our favourite decompilation tool, Reflector, the class in question is System.Management.Automation.TypeAccelerators. Here’s what it looks like:

  1. internal static class TypeAccelerators  
  2. {  
  3.     // Fields  
  4.     private static Dictionary<string, Type> allTypeAccelerators;  
  5.     internal static Dictionary<string, Type> builtinTypeAccelerators;  
  6.     internal static Dictionary<string, Type> userTypeAccelerators;  
  7.  
  8.     // Methods  
  9.     static TypeAccelerators();  
  10.     public static void Add(string typeName, Type type);  
  11.     internal static void FillCache(Dictionary<string, Type> cache);  
  12.     internal static string FindBuiltinAccelerator(Type type);  
  13.     public static bool Remove(string typeName);  
  14.  
  15.     // Properties  
  16.     public static Dictionary<string, Type> Get { get; }  
  17. }  
  18.  

Interestingly, the methods that let you add and remove your own accelerators are marked Public. The Type itself is internal, but the dictionary named “userTypeAccelerators” is positively tantalizing. It looks like perhaps the team have plans to let people add their own accelerators! Then again, this is a CTP, and this may change in the future. Well, let’s see if we can finish off what the team half started ;-)

First thing we need to do is get a reference to the internal class. The C# heads amongst you will start thinking about using reflection to get your hands on the type, but actually there’s an easier way. PowerShell’s language is incredibly flexible and through sneakiness, you can use System.Type’s GetType method to invoke any public method without reverting to tricky reflection calls. First of all, lets add our own user-defined Type Accelerator which is aliased to this internal class itself:

  1. # get a reference to the Type   
  2. $acceleratorsType = [type]::gettype("System.Management.Automation.TypeAccelerators")  
  3.  
  4. # add an accelerator for this type ;-)  
  5. $acceleratorsType::Add("accelerators", $acceleratorsType)  
  6.  
  7. # will return all built-in accelerators (property)  
  8. [accelerators]::get 
  9.  
  10. # add a user-defined accelerator  
  11. [accelerators]::add([string], [type])  
  12.  
  13. # remove a user-defined accelerator  
  14. [accelerators]::remove([string])  

I’ve split the Type retrieval and Add methods into two lines for brevity. The parser is actually flexible enough to understand the more pithy ([type]::gettype("System.Management.Automation.TypeAccelerators"))::Add(…).

So what do we have in CTP3?

Name Type
int System.Int32
long System.Int64
string System.String
char System.Char
bool System.Boolean
byte System.Byte
double System.Double
decimal System.Decimal
float System.Single
single System.Single
regex System.Text.RegularExpressions.Regex
array System.Array
xml System.Xml.XmlDocument
scriptblock System.Management.Automation.ScriptBlock
switch System.Management.Automation.SwitchParameter
hashtable System.Collections.Hashtable
type System.Type
ref System.Management.Automation.PSReference
psobject System.Management.Automation.PSObject
pscustomobject System.Management.Automation.PSObject
psmoduleinfo System.Management.Automation.PSModuleInfo
powershell System.Management.Automation.PowerShell
runspacefactory System.Management.Automation.Runspaces.RunspaceFactory
runspace System.Management.Automation.Runspaces.Runspace
ipaddress System.Net.IPAddress
wmi System.Management.ManagementObject
wmisearcher System.Management.ManagementObjectSearcher
wmiclass System.Management.ManagementClass
adsi System.DirectoryServices.DirectoryEntry
adsisearcher System.DirectoryServices.DirectorySearcher
accelerators System.Management.Automation.TypeAccelerators

Btw, I generated the above list with this one liner:

  1. [accelerators]::Get.getenumerator() | `  
  2.     select @{Name="Name"; expression={$_.key}},  
  3.            @{name="Type"; expression={$_.value}} | `  
  4.     convertto-html -fragment > .\accelerators.html  

Have fun!

posted on Thursday, December 25, 2008 3:29:55 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
# Tuesday, December 23, 2008

Just to be completely silly, I thought I’d do a series of posts on CTP3 features themed around Christmas. Hmm. That might read better as a series of Christmas posts themed around CTP3. A very original idea I’m sure, but hey, those who know me will know that I never pass up the opportunity to make a bad joke. So, without further adieu:

On the first day of Christmas, Jeffrey gave to me:

Nested Here-Strings

Here-Strings can now be embedded within each other to make it even easier to construct literal documents! Delimit any nested code between $( and ) and then continue to use a nested string within that as if it was completely stand alone. It just works! Cool, eh?

  1. function Get-CommandDefinitionHtml {  
  2.       
  3.     # this tells powershell to allow advanced features,  
  4.     # like the [validatenotnullorempty()] attribute below.  
  5.     [CmdletBinding()]  
  6.     param(  
  7.         [ValidateNotNullOrEmpty()]  
  8.         [string]$name 
  9.     )  
  10.  
  11.     $command = get-command $name 
  12.       
  13.     # Look mom! I'm a cmdlet!  
  14.     $PSCmdlet.WriteVerbose("Dumping HTML for " + $command)  
  15.       
  16. @"  
  17.     <html>  
  18.         <head>  
  19.             <title>$($command.name)</title>  
  20.         </head>  
  21.         <body>  
  22.             <table border="1">  
  23. $(  
  24.     $command.parametersets | % {  
  25. @" 
  26.  
  27.             <tr>  
  28.                 <td>$($_.name)</td>  
  29.                 <td>  
  30.                     <table border="1">  
  31.                         <tr>  
  32.                             <th colspan="8">Parameters</th>  
  33.                               
  34. $(  
  35.         $count = 0  
  36.         $_.parameters | % {  
  37.             if (0 -eq ($count % 8)) {  
  38. @"  
  39.                         </tr>  
  40.                         <tr>  
  41. "@  
  42.             }                  
  43. @"  
  44.                             <td>$($_.name)</td>  
  45. "@              
  46.             $count++  
  47.     }  
  48. )  
  49.                         </tr>                          
  50.                     </table>  
  51.                 </td>  
  52.             </tr>  
  53. "@  
  54.     }  
  55. )  
  56.             </table>          
  57.         </body>  
  58.     </html>  
  59. "@      
  60. }  
  61.  
  62. Get-CommandDefinitionHtml get-item > out.html  
  63.  
  64. # show in browser  
  65. invoke-item out.html 
posted on Tuesday, December 23, 2008 4:16:03 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Monday, December 22, 2008

To quote the completely understated download blurb:

Windows PowerShell V2 CTP3 introduces several significant features to Windows PowerShell 1.0 and Windows PowerShell V2 CTPs that extends its use, improves its usability, and allows you to control and manage the Windows environment more easily and comprehensively.

The release notes are quite extensive. Here is the section on breaking changes from CTP2:

Breaking Changes to Windows PowerShell V2 (CTP2)

The following changes in Windows PowerShell V2.0 CTP3 might prevent features designed for Windows PowerShell 2.0 CTP2 from working correctly.

  • Following cmdlets have been renamed
    • Add-Module to Import-Module
    • Get-Event to Get-WinEvent
    • *-Runspace to *-PSSession
    • Push-Runspace to Enter-PSSession
    • Pop-Runspace to Exit-PSSession
    • *-PSEvent to *-Event
    • Register-PSEvent to Register-EngineEvent
    • *-PSTransaction to *-Transaction
    • *-PSJob to *-Job
    • *-PSEventSubscriber to *-EventSubscriber
    • *-Bite to *-FileTransfer

  • Following parameters have been renamed
    • Import-LocalizedData: Culture to UICulture
    • Invoke-Command: Runspace to Session, Shell to ConfigurationName
    • Get-Job: SessionId to Id
    • Receive-Job: Runspace to Session, SessionId to Id
    • Remove-Job: SessionId to Id
    • Start-Job: Command to Scriptblock
    • Stop-Job: SessionId to Id
    • Wait-Job: SessionId to Id
    • Get-PSSession: RemoteRunspaceID to InstanceId, SessionId to Id
    • New-PSSession: Runspace to Session, Shell to ConfigurationName
    • Enter-PSSession: Runspace to Session, RemoteRunspaceID to InstanceId, SessionId to Id, Shell to ConfigurationName
    • Remove-PSSession: Runspace to Session, RemoteRunspaceID to InstanceId, SessionId to Id

  • Following parameters have been deleted
    • Export-ModuleMember: Update, ExportList
    • Set-Service: Include, Exclude

  • Following variables have been renamed
    • $CommandLineParameters to $PSBoundParameters
    • $PSPackagePath to $PSModulePath

  • Packages have been renamed to Modules. Packages folder is now renamed to Modules folder. A module imported into another module is now treated as a nested module instead of a peer module. This allows a new module to wrap or repackage one or more existing modules.

  • "Script cmdlets" have been renamed to "advanced functions." The “cmdlet” keyword has been replaced with the “function” keyword. For script cmdlet functionality, use CmdletBinding attribute in the function’s param block. For more information, see about_functions_advanced.
  • The Config-WSMan.ps1 script in the $pshome directory has been replaced by the Enable-PSRemoting function. To configure your system for WS-Management remoting, use the following command:

Enable-PSRemoting –force

Note: If you have upgraded from the Windows PowerShell V2 CTP2 release to the Windows PowerShell V2 CTP3 release, to configure your system for WS-Management remoting, type:

Unregister-PSSessionConfiguration * -force;

Register-PSSessionConfiguration Microsoft.PowerShell –force;

Enable-PSRemoting –force

  • In the Out-GridView cmdlet, the drop-down list used to filter objects is now called “Query” instead of “Filter”.

  • The following changes have been made to Windows PowerShell Integrated Scripting Environment (ISE):
    • The name of the application has changed from “Graphical Windows PowerShell” to “Windows PowerShell Integrated Scripting Environment (ISE)”
    • The executable name has changed from “gpowershell.exe” to “powershell_ise.exe”
    • The profile name has changed from “\Users\<username>\Documents\WindowsPowerShell\Microsoft.GPowerShell_profile.ps1” to “\Users\<username>\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1”
    • The term “runspace” has been replaced with “PowerShell tab”.

Get it from http://www.microsoft.com/downloads/details.aspx?FamilyID=c913aeab-d7b4-4bb1-a958-ee6d7fe307bc – piping hot!

posted on Monday, December 22, 2008 9:59:52 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Sunday, December 21, 2008

This time around, I thought I’d show how to work with programmatically generated dynamic parameters, as opposed to the statically defined sets we saw the last time. The context here is a fairly silly Cmdlet, but it’s good enough to demonstrate the concept end to end. It’s a Cmdlet for removing a file. It takes one string parameter which is the path to the file. The dynamic parameter I’m going to add is a –Force parameter. The trick is, this parameter will only be added if the current user is an administrator (XP), or is elevated as one (Vista).

This first portion of the Cmdlet defines the usual stuff like a verb and noun. This time though, I’m using a regular class constructor. It’s not often you see constructors in simple Cmdlets because typically all one would usually do is override one or more of the three processing methods BeginProcessing, ProcessRecord and EndProcessing. In this case, I need to create a instance of a “RuntimeDefinedParameterDictionary,” which does exactly what it says on the tin. It’s a dictionary of parameters, the key being a string (the name of the parameter) and the value being a instance of a RuntimeDefinedParameter class. These classes are all members of the System.Management.Automation namespace.

In the constructor, I’m calling a generic method defined as AddDynamicParameter<T>(string name). This is only called if the current user is an admin. I’ve defined three generic helper methods which you can see a little further down below.

  1. [Cmdlet(VerbsCommon.Remove, NOUN_FILE)]  
  2. public class RemoveFileCommand : PSCmdlet, IDynamicParameters  
  3. {  
  4.     private const string NOUN_FILE      = "File";  
  5.     private const string SWITCH_FORCE   = "Force";  
  6.  
  7.     private readonly RuntimeDefinedParameterDictionary _parameters;  
  8.  
  9.     public RemoveFileCommand()  
  10.     {  
  11.         _parameters = new RuntimeDefinedParameterDictionary();  
  12.  
  13.         // we only want to add the -Force parameter if  
  14.         // the current user is an administrator  
  15.         var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());  
  16.  
  17.         // if vista and not elevated, this will be false even  
  18.         // if you are a member of administrators.  
  19.         if (principal.IsInRole(WindowsBuiltInRole.Administrator))  
  20.         {  
  21.             this.AddDynamicParameter<SwitchParameter>(SWITCH_FORCE);  
  22.         }  
  23.     }  
  24.  
  25.     [Parameter]  
  26.     public string FilePath  
  27.     {  
  28.         get;  
  29.         set;  
  30.     }   
  31.  
  32.     protected override void EndProcessing()  
  33.     {  
  34.         // does file exist?  
  35.         if (File.Exists(FilePath))  
  36.         {  
  37.             RemoveFile();  
  38.         }  
  39.         else 
  40.         {  
  41.             WriteWarning(FilePath + " does not exist.");  
  42.         }  
  43.     } 

This is a simple piece of code who’s role is to remove the file specified by the user. I’ve omitted the actual code that would delete the file for brevity. I’m using another helper method to see if the –Force parameter has been added to the Cmdlet’s definition, and if so, was it specified by the invoker of the Cmdlet. The idea is here is that the Cmdlet will not remove the file if it’s marked Read-Only, but if –Force is specified it will remove the R/O attribute and continue as planned.

  1. private void RemoveFile()  
  2. {  
  3.     // read file attributes  
  4.     var attribs = File.GetAttributes(FilePath);  
  5.  
  6.     // is read-only attribute set?  
  7.     if ((attribs & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)  
  8.     {  
  9.         bool shouldForce;  
  10.  
  11.         // see if the dynamic switch -Force was added (and specified)  
  12.         if (TryGetSwitchParameter(SWITCH_FORCE, out shouldForce))  
  13.         {  
  14.             WriteVerbose("Force.IsPresent: " + shouldForce);  
  15.         }  
  16.  
  17.         if (shouldForce)  
  18.         {  
  19.             RemoveReadOnlyAttribute();  
  20.         }  
  21.         else 
  22.         {  
  23.             WriteWarning(FilePath + " is marked Read-Only!");  
  24.             return;  
  25.         }  
  26.     }  
  27.  
  28.     // ... code to remove file ...  

And the final piece is here. The first method GetDynamicParameters, belongs to the IDynamicParameters interface and tells PowerShell that the Cmdlet may return extra parameters not defined on the class itself. In this case, we are returning a RuntimeDefinedParameterDictionary instead of a statically defined parameters on a nested class.

  1. public object GetDynamicParameters()  
  2. {  
  3.     return _parameters;  
  4. }  
  5.  
  6. // add a simple parameter of type T to this cmdlet  
  7. private void AddDynamicParameter<T>(string name)  
  8. {  
  9.     // create a parameter of type T.  
  10.     var parameter = new RuntimeDefinedParameter  
  11.                     {  
  12.                         Name = name,  
  13.                         ParameterType = typeof (T),                                  
  14.                     };  
  15.  
  16.     // add the [parameter] attribute  
  17.     var attrib = new ParameterAttribute  
  18.                  {                               
  19.                      ParameterSetName =  
  20.                         ParameterAttribute.AllParameterSets  
  21.                  };  
  22.       
  23.     parameter.Attributes.Add(attrib);  
  24.  
  25.     _parameters.Add(name, parameter);  
  26. }  
  27.  
  28. private bool TryGetSwitchParameter(string name, out bool isPresent)  
  29. {  
  30.     RuntimeDefinedParameter parameter;  
  31.       
  32.     if (TryGetDynamicParameter(name, out parameter))  
  33.     {  
  34.         isPresent = parameter.IsSet;  
  35.         return true;  
  36.     }  
  37.  
  38.     isPresent = false;  
  39.     return false;  
  40. }  
  41.  
  42. // get a parameter of type T.  
  43. private bool TryGetParameter<T>(string name, out T value)  
  44. {  
  45.     RuntimeDefinedParameter parameter;  
  46.       
  47.     if (TryGetDynamicParameter(name, out parameter))  
  48.     {  
  49.         value = (T)parameter.Value;  
  50.         return true;  
  51.     }  
  52.  
  53.     value = default(T);  
  54.       
  55.     return false;  
  56. }  
  57.  
  58. // try to get a dynamically added parameter  
  59. private bool TryGetDynamicParameter(string name, out RuntimeDefinedParameter value)  
  60. {  
  61.     if (_parameters.ContainsKey(name))  
  62.     {  
  63.         value = _parameters[name];  
  64.         return true;  
  65.     }  
  66.  
  67.     // need to set this before leaving the method  
  68.     value = null;  
  69.  
  70.     return false;  
  71. }  

Finally, the three helper methods are revealed. One is used for checking for dynamic SwitchParameters, the next is for ordinary parameters that return type T, the generic argument. The third method, TryGetDynamicParameter is used by the first two methods.

I hope this helps reveal some of the mystery behind dynamic parameters on Cmdlets. There is one more type of dynamic parameter that I will be examining in one of my next few posts, that is the scenario where a provider (like the FileSystemProvider or RegistryProvider) passes a dynamic parameter to a Cmdlet. In this particular case, the provider can only pass dynamic parameters to the built-in Cmdlets that operate on providers, e.g. Get-ChildItem, Get-Item, Get-ItemProperty etc.

Have fun!

posted on Sunday, December 21, 2008 5:28:27 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Monday, December 15, 2008

The dynamic duo / masterminds of PowerShell will be cornered and fiercely grilled by none other than our very own master podcaster, Hal Rottenberg this Thursday.  From the mouth of the suffixed one himself:

Coming up on the PowerScripting Live show this Thursday will be Jeffrey Snover,
the architect for PowerShell as I’m sure you all know, and he’ll be accompanied
by none other than Bruce Payette, author of PowerShell in Action and a core
developer on the PowerShell team.

We’re excited and we hope you can make it this Thursday at 9pm EST!

The live stream address is http://www.ustream.tv/channel/powerscripting-podcast

So if you want to get the lowdown on CTP3 (maybe), join us, the unwashed masses as we clamour to be near our idols. A lock of their hair and a signed discarded printout of directions to building 18 could be yours!

I made that last bit up. Who cares! This is going to be cool! Join us!

posted on Monday, December 15, 2008 4:14:40 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Tuesday, December 02, 2008

Normally parameters for a Cmdlet are defined directly as properties on the Cmdlet class itself. There are a few other ways for a Cmdlet to define parameters, and in this short series of three parts, I’ll cover the three variants you’ll likely to encounter as you become a more experienced PowerShell developer. First thing you need to do is to implement the System.Management.Automation.IDynamicParameters interface on your Cmdlet. This interface has one member, GetDynamicParameters. This interface can be implemented on either a Provider or a Cmdlet. In the former case, a provider is able to add new parameters at runtime to certain built-in Cmdlets like Get-ChildItem, Get-Item & Get-ItemProperty. It is typically used in a path context-aware manner – e.g. depending on whether the current argument is a folder or item, and what Type such a folder or item might be. We’re just to cover the Cmdlet variant in this post.

image

My example is just a simple Cmdlet named Get-OSVersion. It writes out Environment.OSVersion to the pipeline, and will dynamically add a parameter. It adds –EnsureElevated if you’re running Vista or higher, otherwise it will add –EnsureAdministrator. Statically defined dynamic parameter sets are defined by creating a simple class and decorating that class with parameter/properties you want to surface as if that class was the Cmdlet itself, then returning it from the GetDynamicProperties method at runtime. Simple!

  1. [Cmdlet(VerbsCommon.Get, "OSVersion")]   
  2. public class GetOSVersionCommand : PSCmdlet, IDynamicParameters   
  3. {   
  4.     private const string SWITCH_VISTA = "EnsureElevated";   
  5.     private const string SWITCH_WINXP = "EnsureAdministrator";   
  6.   
  7.     protected override void EndProcessing()   
  8.     {   
  9.         WriteObject(Environment.OSVersion);   
  10.            
  11.         string switchName = IsVistaOrHigher() ?   
  12.             SWITCH_VISTA : SWITCH_WINXP;   
  13.   
  14.         // not really a warning, just nice yellow text.   
  15.         WriteWarning(String.Format("{0}.IsPresent {1}",   
  16.             switchName, IsSwitchPresent(switchName)));   
  17.     }   
  18.   
  19.     // IDynamicParameters.GetDynamicParameters   
  20.     public object GetDynamicParameters()   
  21.     {   
  22.         if (IsVistaOrHigher())   
  23.         {   
  24.             return new VistaParameters();   
  25.         }   
  26.         else  
  27.         {   
  28.             return new WinXPParameters();   
  29.         }   
  30.     }   
  31.        
  32.     private bool IsSwitchPresent(string name)   
  33.     {   
  34.         // parameters bound at runtime   
  35.         Dictionary<stringobject> parameters = MyInvocation.BoundParameters;   
  36.   
  37.         // determine whether switch is set   
  38.         if (parameters.ContainsKey(name))   
  39.         {   
  40.             return ((SwitchParameter) parameters[name]).IsPresent;   
  41.         }   
  42.         return false;   
  43.     }   
  44.   
  45.     private static bool IsVistaOrHigher()   
  46.     {   
  47.         return (Environment.OSVersion.Version.Major >= 6);   
  48.     }   
  49.   
  50.     // dynamic parameters for Vista   
  51.     internal class VistaParameters   
  52.     {   
  53.         [Parameter]   
  54.         public SwitchParameter EnsureElevated   
  55.         {   
  56.             getset;   
  57.         }   
  58.     }   
  59.   
  60.     // dynamic parameters for Windows XP   
  61.     // (powershell won't run on windows 2000 ;-))   
  62.     internal class WinXPParameters   
  63.     {   
  64.         [Parameter]   
  65.         public SwitchParameter EnsureAdministrator   
  66.         {   
  67.             getset;   
  68.         }   
  69.     }   
  70. }  

Of course, adding Dynamic Parameters is no good if you don’t know how to read them back. In the code, you can see I’m using MyInvocation.BoundParameters to check if the Switches have been set.

posted on Tuesday, December 02, 2008 5:21:10 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Wednesday, November 19, 2008

It seems lots of people are catching the PowerShell bug these days. People who previously considered themselves as administrators have learned enough of the magic from using PowerShell from day to day over the last few months that they feel comfortable enough to tinker around with C#.

This is an example Cmdlet skeleton that is designed to work with Files and Directories. It looks after resolving wildcards, supports –WhatIf and –Confirm, accepting files and/or directories from the pipeline or accepting strings of paths from the pipeine. It also ensures that the only types of paths it can accept lie on the File System. Here’s an example of the invocation styles it supports:

cmdlet

Here’s the source code for this. It doesn’t really do a lot. It spits out two different types of custom objects: one for a file, and another for a directory. Feel free to do what you like with it; it’s yours. Have fun.

using System;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using Microsoft.PowerShell.Commands;
namespace PSQuickStart
{
    [Cmdlet(VerbsCommon.Get, Noun,
        DefaultParameterSetName = ParmamSetPath,
        SupportsShouldProcess = true)
    ]
    public class GetFileMetadataCommand : PSCmdlet
    {
        private const string Noun = "FileMetadata";
        private const string ParamSetLiteral = "Literal";
        private const string ParmamSetPath = "Path";
        private string[] _paths;
        private bool _shouldExpandWildcards;
        [Parameter(
            Position = 0,
            Mandatory = true,
            ValueFromPipeline = false,
            ValueFromPipelineByPropertyName = true,
            ParameterSetName = ParamSetLiteral)
        ]
        [Alias("PSPath")]
        [ValidateNotNullOrEmpty]
        public string[] LiteralPath
        {
            get { return _paths; }
            set { _paths = value; }
        }
        [Parameter(
            Position = 0,
            Mandatory = true,
            ValueFromPipeline = true,
            ValueFromPipelineByPropertyName = true,
            ParameterSetName = ParmamSetPath)
        ]
        [ValidateNotNullOrEmpty]
        public string[] Path
        {
            get { return _paths; }
            set
            {
                _shouldExpandWildcards = true;
                _paths = value;
            }
        }
        protected override void ProcessRecord()
        {
            foreach (string path in _paths)
            {
                // This will hold information about the provider containing
                // the items that this path string might resolve to.                
                ProviderInfo provider;
                // This will be used by the method that processes literal paths
                PSDriveInfo drive;
                // this contains the paths to process for this iteration of the
                // loop to resolve and optionally expand wildcards.
                List<string> filePaths = new List<string>();
                if (_shouldExpandWildcards)
                {
                    // Turn *.txt into foo.txt,foo2.txt etc.
                    // if path is just "foo.txt," it will return unchanged.
                    filePaths.AddRange(this.GetResolvedProviderPathFromPSPath(path, out provider));
                }
                else
                {
                    // no wildcards, so don't try to expand any * or ? symbols.                    
                    filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
                        path, out provider, out drive));
                }
                // ensure that this path (or set of paths after wildcard expansion)
                // is on the filesystem. A wildcard can never expand to span multiple
                // providers.
                if (IsFileSystemPath(provider, path) == false)
                {
                    // no, so skip to next path in _paths.
                    continue;
                }
                // at this point, we have a list of paths on the filesystem.
                foreach (string filePath in filePaths)
                {
                    PSObject custom;
                    // If -whatif was supplied, do not perform the actions
                    // inside this "if" statement; only show the message.
                    //
                    // This block also supports the -confirm switch, where
                    // you will be asked if you want to perform the action
                    // "get metadata" on target: foo.txt
                    if (ShouldProcess(filePath, "Get Metadata"))
                    {
                        if (Directory.Exists(filePath))
                        {
                            custom = GetDirectoryCustomObject(new DirectoryInfo(filePath));
                        }
                        else
                        {
                            custom = GetFileCustomObject(new FileInfo(filePath));
                        }
                        WriteObject(custom);
                    }
                }
            }
        }
        private PSObject GetFileCustomObject(FileInfo file)
        {
            // this message will be shown if the -verbose switch is given
            WriteVerbose("GetFileCustomObject " + file);
            // create a custom object with a few properties
            PSObject custom = new PSObject();
            custom.Properties.Add(new PSNoteProperty("Size", file.Length));
            custom.Properties.Add(new PSNoteProperty("Name", file.Name));
            custom.Properties.Add(new PSNoteProperty("Extension", file.Extension));
            return custom;
        }
        private PSObject GetDirectoryCustomObject(DirectoryInfo dir)
        {
            // this message will be shown if the -verbose switch is given
            WriteVerbose("GetDirectoryCustomObject " + dir);
            // create a custom object with a few properties
            PSObject custom = new PSObject();
            int files = dir.GetFiles().Length;
            int subdirs = dir.GetDirectories().Length;
            custom.Properties.Add(new PSNoteProperty("Files", files));
            custom.Properties.Add(new PSNoteProperty("Subdirectories", subdirs));
            custom.Properties.Add(new PSNoteProperty("Name", dir.Name));
            return custom;
        }
        private bool IsFileSystemPath(ProviderInfo provider, string path)
        {
            bool isFileSystem = true;
            // check that this provider is the filesystem
            if (provider.ImplementingType != typeof(FileSystemProvider))
            {
                // create a .NET exception wrapping our error text
                ArgumentException ex = new ArgumentException(path +
                    " does not resolve to a path on the FileSystem provider.");
                // wrap this in a powershell errorrecord
                ErrorRecord error = new ErrorRecord(ex, "InvalidProvider",
                    ErrorCategory.InvalidArgument, path);
                // write a non-terminating error to pipeline
                this.WriteError(error);
                // tell our caller that the item was not on the filesystem
                isFileSystem = false;
            }
            return isFileSystem;
        }
    }
}
posted on Wednesday, November 19, 2008 5:58:18 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, November 13, 2008

Here’s just a quick rundown of the changes in PowerShell 2.0 in comparison to the last public release, CTP2. This may be helpful to those of you who don’t have access to the Windows 7 PDC bits. Remember, things are still subject to change - it’s not in beta yet.

Unchanged cmdlets are not listed here.

Added (or renamed*) Cmdlets

Enter-PSSession
Remove-PSSession
Get-PSSession
Exit-PSSession
Get-PSSessionConfiguration
Unregister-PSSessionConfiguration
Register-PSSessionConfiguration
Debug-Process
Test-ModuleManifest
New-ModuleManifest
New-WSManSessionOption
New-PSSession
Import-PSSession
Export-PSSession
Set-PSSessionConfiguration
Get-WSManInstance
Get-WSManCredSSP
Enable-WSManCredSSP
Set-WSManInstance
Set-WSManQuickConfig
New-WSManInstance
Remove-WSManInstance
Ping-WSMan
New-WebServiceProxy
Get-PSTransaction
Connect-WSMan
Disable-WSManCredSSP
Invoke-WSManAction
Disconnect-WSMan
Get-ComputerRestorePoint
Disable-ComputerRestore
Enable-ComputerRestore
Get-Counter
Clear-EventLog
Export-Counter
Import-Counter
Checkpoint-Computer
Restart-Computer
Stop-Computer
Ping-Computer
Resume-BitsTransfer
Set-BitsTransfer
Suspend-BitsTransfer
Add-BitsFile
Clear-BitsTransfer
Remove-EventLog
Restore-Computer
New-Module
Get-HotFix
Complete-BitsTransfer
Write-EventLog
Show-EventLog
Limit-EventLog
Get-BitsTransfer
New-BitsTransfer
New-EventLog

Changed Cmdlets (parameter or parameterset differences)

Invoke-Command
Receive-PSJob
Set-WmiInstance
Stop-PSJob
Wait-PSJob
Remove-PSJob
Export-ModuleMember
Start-PSJob
Get-PSJob
Add-Module
Set-Service

   

Removed (or renamed*) Cmdlets

Pop-Runspace
New-Runspace
Push-Runspace
Get-Runspace
Remove-Runspace

   

* e.g. The *-Runspace Cmdlets have been renamed to *-PSSession

posted on Thursday, November 13, 2008 11:15:00 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Monday, November 10, 2008

Fellow MVP Hal Rottenberg asked me to highlight this short and painless survey we’re trying to get out to y’all.

Think of the following in terms of impact on the greatest number of people at a tech convention such as Microsoft TechEd or MMS. Your opinions will help shape our plans for future shows.

Thanks!

Hal Rottenberg
Community Director, PowershellCommunity.org

It shouldn’t take you too long ;-)

posted on Monday, November 10, 2008 10:29:41 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, November 06, 2008

So it looks like the High King of PowerShell has spoken over at TechED Europe today and answered the question that sits on every child’s lips in the back of a moving car: “Are we there yet?”

“Nope. Not Yet.”

“So when, dad?”

Dmitry Sotnikov (who’s  not my dad, btw) of PowerGUI fame broke the news to the web at large, and I’m just shamelessly grabbing the HTML directly from his post:

  • December 2008 - CTP 3 (Community Technology Preview) or Beta 1 if it meets the internal criteria and all names/features are finalized.
  • RTM - end of 2009/early 2010 as part of Windows 7 and Windows Server 2008 R2.
  • RTM for XP, 2003, Vista, and 2008 - as a downloadable package (with new WinRM bundled in there) only a few months later.

Yay!

posted on Thursday, November 06, 2008 1:23:50 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Tuesday, November 04, 2008

I’d say a lot of folks will be chomping at the bit for this. It’s the Tk to PowerShell’s Tcl and is really the final piece of the puzzle where the shell’s position as the de-facto Windows Shell stands anyway. Well, maybe Remoting support for XP and Windows Server 2003 is ahead there just a bit, but a forms designer must be a close second.

Check it out: http://blog.sapien.com/index.php/2008/11/03/free-primalforms-tool-for-powershell-released/

Congratulations guys on shipping such a fine tool, for FREE. All it costs you is an email address. Yah, sell your soul, it’ll be worth it.

posted on Tuesday, November 04, 2008 12:32:56 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
# Thursday, October 30, 2008

Fellow PowerShell MVP and developer all-round expert administrator Brandon Shell asked me how he could navigate to a Cmdlet’s implementation in Lutz Roeder’s Reflector -- which has now been acquired by Red-Gate Software btw. At first I was going to explain to him how snap-ins work and how to figure out where things are, and then a really simple trick hit me that uses some fairly secret command-line parameter that Reflector can accept, namely the /select parameter:

  1. function Reflect-Cmdlet {  
  2.     param([Management.Automation.CommandInfo]$command)  
  3.     if ($input) {  
  4.         trap { $_; break }  
  5.         $command = $input | select -first 1  
  6.     }      
  7.          
  8.     # resolve to command if this is an alias  
  9.     while ($command.CommandType -eq "Alias") {  
  10.         $command = Get-Command ($command.definition)  
  11.     }  
  12.       
  13.     $name = $command.ImplementingType      
  14.     $DLL = $command.DLL  
  15.       
  16.     if (-not (gcm reflector.exe -ea silentlycontinue)) {  
  17.         throw "I can't find Reflector.exe in your path." 
  18.     }  
  19.        
  20.     reflector /select:$name $DLL 

Just pipe the output of get-command to it, like: gcm dir | reflect-cmdlet and Reflector will open up with the class selected (it takes a few seconds).

Update: Doug pointed out in a comment that the gcm reflector.exe line could benefit from an erroraction to keep it silent on failure, so only the throw message shows. Thanks Doug!

posted on Thursday, October 30, 2008 1:44:40 PM (Eastern Standard Time, UTC-05:00)  #    Comments [3] Trackback
# Wednesday, October 15, 2008

The verbs list for naming your Cmdlets is pretty strict. You should definitely try to pick one of the known verbs. You can see them here:

function Get-Verb {
    [psobject].assembly.getexportedtypes() | `
        ? {$_.name -like "Verbs*"} | gm -static -membertype property | `
        sort Name | select Name
}

ps> Get-Verb

The noun end of the spectrum is pretty open-ended. Single nouns are better, and try to use the singular if you can. Cmdlets typically return one or more objects. so the singular form is the convention - better than trying to name your Cmdlet "Get-Noun(s)" which is just plain silly.

To prefix or not to prefix: this is a sticky one. The Religious Right, aka the Microsoft Powershell team, dictate that you should not prefix. The reality is more complicated and is under constant debate. If you pick a verb-noun pair that already exists and is currently loaded into powershell, you must disambiguate the command with the snapin name (snapinname\verb-noun) or it won't run. The snapin name is typically long and ugly. A noun prefix is typically short and sweet. The problem with the latter is that your commands are now harder to discover for someone who doesn't know they are there (and as such doesn't know your magic prefix). Ultimately though, people load your snapin explicitly and should know the prefix, so I tend to lean towards this although I feel a strong connection with the "One Noun Way."

(taken from one of my newgroup posts)

posted on Wednesday, October 15, 2008 12:20:19 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Monday, September 29, 2008

Specifically, get/set-content works with providers that facilitate content reading and writing. Out-File works only with the FileSystem provider and is for all intents and purposes an alias to the > redirection operator.

An interesting thing to note about get-content and set-content is that you use them indirectly every day. Both the variable and function providers support get- and set-content, and when you use the dollar ($) syntax to read or write to a variable, you are implictly calling get-content and set-content against a path in the variable provider. E.g.

ps> $foo = 5

...is the same as:

ps> ${variable:foo} = 5

e.g. set the content of item foo in the psdrive called "Variable" to 5. This is why you can also write to a file like:

ps> ${c:\temp\foo.txt} = "some content"

...and read it back with:

ps> ${c:\temp\foo.txt}
some content

Ultimately, when using the $ syntax, the "variable" drive is the default drive to read and write content from. Elegant, eh?

posted on Monday, September 29, 2008 3:25:09 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Friday, September 05, 2008

Are you sick and tired of switching the VMWare virtual network adapters from the “public” network profile to a “private” network profile each time you reboot or do something else that causes Vista/Win2008 to forget what you told it to do ten minutes ago? This is probably a familiar sigh (I’m using Workstation):

vmware-unidentified-network

As you can see, the lower two connections are seen to be in a “Public network.” This has the net effect of making Windows believe the whole machine is exposed to a public network, and it will trigger the public profile for Windows Firewall. This is normally a good thing, but in this case it’s just an annoyance. These two network connections are not actually external gateways that can let in the bad guys. They are just dummy adapters for VMWare’s host bridging which allow the virtual machine to access the host machine’s network. Because they don’t identify themselves properly, they are flagged as an “unidentified network,” and your changes are never persisted.

So what do you do? The magic information is buried in MSDN:

Vista automatically identifies and monitors the networks to which a computer connects. If the NDIS_DEVICE_TYPE_ENDPOINT flag is set, the device is an endpoint device and is not a connection to a true external network. Consequently, Windows ignores the endpoint device when Windows identifies networks. The Network Awareness APIs indicate that the device does not connect the computer to a network. For end users in this situation, the Network and Sharing Center and the network icon in the notification area do not show the NDIS endpoint device as connected. However, the connection is shown in the Network Connections Folder. Also, if NDIS_DEVICE_TYPE_ENDPOINT is set, the Windows Firewall ignores the connection when Windows Firewall enforces public, private, or domain policies.

So I hacked together a PowerShell script that will scan your adapters for VMWare’s virtual NICs and make the necessary registry changes. It will also disable/enable cycle the adapters so that the changes take effect. After this is done, you will see them in your Network Connections page – albeit lacking a network category -- and the connections will no longer appear in the Network and Sharing Center nor will they affect your Windows Firewall policy no matter how many times you reboot!

vmware-unidentified-network-1

Here’s the script source below. It requires that you run it in an elevated shell in order to write the registry changes and cycle the adapters’ enabled state.

  1. # see http://msdn2.microsoft.com/en-us/library/bb201634.aspx  
  2. #  
  3. # *NdisDeviceType   
  4. #  
  5. # The type of the device. The default value is zero, which indicates a standard  
  6. # networking device that connects to a network.  
  7. #  
  8. # Set *NdisDeviceType to NDIS_DEVICE_TYPE_ENDPOINT (1) if this device is an  
  9. # endpoint device and is not a true network interface that connects to a network.  
  10. # For example, you must specify NDIS_DEVICE_TYPE_ENDPOINT for devices such as  
  11. # smart phones that use a networking infrastructure to communicate to the local  
  12. # computer system but do not provide connectivity to an external network.   
  13. #  
  14. # Usage: run in an elevated shell (vista/longhorn) or as adminstrator (xp/2003).  
  15. #  
  16. # PS> .\fix-vmnet-adapters.ps1  
  17.  
  18. # boilerplate elevation check  
  19.  
  20. $identity = [Security.Principal.WindowsIdentity]::GetCurrent()  
  21. $principal = new-object Security.Principal.WindowsPrincipal $identity 
  22. $elevated = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)  
  23.  
  24. if (-not $elevated) {  
  25.     $error = "Sorry, you need to run this script" 
  26.     if ([System.Environment]::OSVersion.Version.Major -gt 5) {  
  27.         $error += " in an elevated shell." 
  28.     } else {  
  29.         $error += " as Administrator." 
  30.     }  
  31.     throw $error 
  32. }  
  33.  
  34. function confirm {  
  35.     $host.ui.PromptForChoice("Continue", "Process adapter?",  
  36.         [Management.Automation.Host.ChoiceDescription[]]@("&No", "&Yes"), 0) -eq $true 
  37. }  
  38.  
  39. # adapters key  
  40. pushd 'hklm:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}' 
  41.  
  42. # ignore and continue on error  
  43. dir -ea 0  | % {  
  44.     $node = $_.pspath  
  45.     $desc = gp $node -name driverdesc  
  46.     if ($desc -like "*vmware*") {  
  47.         write-host ("Found adapter: {0} " -f $desc.driverdesc)  
  48.         if (confirm) {  
  49.             new-itemproperty $node -name '*NdisDeviceType' -propertytype dword -value 1  
  50.         }  
  51.     }  
  52. }  
  53. popd  
  54.  
  55. # disable/enable network adapters  
  56. gwmi win32_networkadapter | ? {$_.name -like "*vmware*" } | % {  
  57.       
  58.     # disable  
  59.     write-host -nonew "Disabling $($_.name) ... " 
  60.     $result = $_.Disable()  
  61.     if ($result.ReturnValue -eq -0) { write-host " success." } else { write-host " failed." }  
  62.       
  63.     # enable  
  64.     write-host -nonew "Enabling $($_.name) ... " 
  65.     $result = $_.Enable()  
  66.     if ($result.ReturnValue -eq -0) { write-host " success." } else { write-host " failed." }  
  67. }  

Have fun.

posted on Friday, September 05, 2008 4:13:52 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1] Trackback
# Tuesday, August 12, 2008

It’s getting harder to post useful scripting tips for PowerShell these days as there are so many talented hardcore scripting bloggers around. My day to day is job is not system/network/server administrator; I’m a .NET developer, having started the C# habit about eight or nine years ago with the early CLR 1.0 beta. So, from here on in, I’ve decided that a better direction for me to take from now on is that of a PowerShell developer,  as opposed to a scripter. There are very few (if any?) dedicated PowerShell developer blogs around, and so I figured I should try to fill that gap as best I can. I have a not insignificant amount of experience writing providers, cmdlets and other widgety bits, so it’s a good time to share my experiences. Of course, my way is not “the” way, so please reply with your own experiences/advice/mocking/whatever. ;-)

That said, I am not eschewing scripting altogether – I have some stuff in the pipeline (har-har) concerning areas I’m interested in, like eventing and jobs/remoting.

posted on Tuesday, August 12, 2008 10:03:20 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Monday, August 11, 2008

Just a handy tip - you don't have to cast enum types in powershell usually. It will do that for you if that's the only constructor (or method) overload that makes sense:

$accessrule = New-Object system.security.AccessControl.FileSystemAccessRule $userName, `
     "Modify",  "ContainerInherit,ObjectInherit", "None", "Allow"

So when I say "only overload that makes sense," what do I mean by that? Take this method for example which has two overloads (meaning it has two different sets of parameters you could use):

void MyMethod(string a, string b, string c)

void MyMethod(string a, enum b, string c)

If you try to call that method with $o.MyMethod("foo", "foo", "foo"), it will pick the first version that takes three strings. In that case, you would have to cast/convert the enum to its native type so that powershell will pick the right method.

posted on Monday, August 11, 2008 12:38:22 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Sunday, August 10, 2008

With PowerShell’s new –STA startup switch, interacting with the Windows Forms object model was never so easy:

PS> $text = & {powershell –sta {add-type –a system.windows.forms; [windows.forms.clipboard]::GetText()}}

Easy, eh?

posted on Sunday, August 10, 2008 11:36:37 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Tuesday, August 05, 2008
blocked file properties

As we all [should] know, running scripts downloaded from the Internet is a risky business. But sometimes you know exactly where they came from, and you trust the source. The problem arrives when you’re on a server without any of your familiar utilities and you’ve just downloaded a zip of several ps1 scripts. Unzipping the zip via the windows built-in zip handler in explorer will preserve the Zone.Identifier information for the extracted files. This means that even if you have your Execution Policy set to RemoteSigned (which most people seem to have – it’s a sensible balance), the now “local” scripts are treated as remote and they will not run. Ideally you should “unblock” the zip file before extracting the files; all extracted files are then also “unblocked.” Unblocking a file is as simple as right-clicking it in Explorer and choosing “Properties.” (see figure 1).

Now, sometimes you don’t have this luxury. Either someone else downloaded/extracted the files or you are logged in remotely via PowerShell Remoting/WINRM for example. Thankfully, the annoyingly talented Mark Russinovich has written a great little tool for stripping NTFS ADS (alternate data streams – where the zone indentifier information is attached to a regular file) called streams.exe. He’s also made the tool easily available via a UNC path: \\live.sysinternals.com\tools\streams.exe Usage is simple: just start in the root directory of the extracted scripts and run: streams –s –d *.ps1 ; the –s means traverse subdirectories and –d instructs it to delete any alternate data streams from the files.

posted on Tuesday, August 05, 2008 11:35:42 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Monday, July 28, 2008

The CodePlex guys do it again. This is a great feature that will surely help large projects garner a bit more help and support through the use of better developer documentation for getting contributors up to speed. The only thing that’s missing is syntax highlighting support for the PowerShell language. Vote for the feature here - Wiki Syntax Highlighting for PowerShell

You’ll need a CodePlex account for this I think. More information on the July 22nd release:

Syntax highlighting has been added to project wiki pages. See the Wiki Markup Guide for details.
Also added is mailing list support for project discussions: start or respond to a discussion from your e-mail client; get notifications of new responses as they come in, or in daily digest format. Now users can subscribe to an entire project’s discussions, including all new discussions posted to the project. See Mailing Lists Documentation for more information.
Feature requests addressed in this release:
Syntax Highlighting Extension
Feature request: Mailing lists
Issues addressed in this release:
HTTPS should be dropped
HTTPS in forums causes browser warnings
Feature request: please let me input ENTER in release description textbox
Pressing enter while in textarea submits form

posted on Monday, July 28, 2008 4:29:46 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback

In the latest PowerShellCX changeset, archive read and extract support is finally available. Sorry there are no binary builds yet. Use the Extract-Archive cmdlet as a stand-alone to extract all files. Use Read-Archive to generate ArchiveEntry objects which can be piped through Where-Object for filtering and eventual consumption by Extract-Archive (which can accept ArchiveEntry as well as FileInfo pipeline input). I’ve got progress reporting working too for extract. I’ve added support for reading/extracting SevenZip, Arj, BZip2, Cab, Chm, Cpio, Deb, GZip, Iso, Lzh, Lzma, Nsis, Rar, Rpm, Tar, Wim, Z & Zip.  Yes, you did see ISO support in that list ;-) Write support is only zip, bzip2, tar and gzip still.

There are more features coming, including encrypted archive support.

posted on Monday, July 28, 2008 5:28:00 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Wednesday, July 09, 2008

I just hacked this one up a few minutes ago to let me get an Oracle, Access, Excel/CSV or SQL Server database connection quickly without fiddling with provider strings and other things that just fall out of my head as fast as they get put in. It uses an old trick of creating a zero-length file with an UDL extension and then executing it. The function returns an operable OleDbConnection object, albeit a closed one unless you specifiy –Open as a switch.

  1. function get-oledbconnection ([switch]$Open) {  
  2.     $null | set-content ($udl = "$([io.path]::GetTempPath())\temp.udl");  
  3.     $psi = new-object Diagnostics.ProcessStartInfo  
  4.     $psi.CreateNoWindow = $true 
  5.     $psi.UseShellExecute = $true 
  6.     $psi.FileName = $udl 
  7.     $pi = [System.Diagnostics.Process]::Start($psi)  
  8.     $pi.WaitForExit()  
  9.     write-host (gc $udl)  
  10.     if (gc $udl) {  
  11.         $conn = new-object data.oledb.oledbconnection (gc $udl)[2]  
  12.         if ($Open) { $conn.Open() }  
  13.     }  
  14.     $conn 
  15. }  

You’ll notice I said OleDbConnection. This means you should pick an OLEDB provider on the provider page for best (read: working) results. Cancelling returns nothing.

image

Ah, how I love the little blue box of typographical terrific-ness.

posted on Wednesday, July 09, 2008 8:18:44 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Tuesday, July 01, 2008

Why do such a thing? Well, if you’ve created your own aliases for commands and you try to give someone your ps1 script file, it will not run because they have not got the same aliases defined as you. Also, scripts that use fully-resolved names like “Get-ChildItem” are more readable for a newcomer to PowerShell than one that is using the unix-like “ls" alias for example. "Get-ChildItem" leads quite directly to the MSDN documentation, but "ls" might lead you anywhere. Before you publish a script online somewhere for the world to use, it’s important that you try to remove any aliases and replace them with the native command names.

Doing this kind of thing has been talked about before but it was always a very difficult thing to do with PowerShell v1.0, what with the lack of BNF documentation describing the grammar etc. Thankfully, it's a lot easier to do with PowerShell v2.0 (currently at release CTP2) because the team has exposed the Tokenizer for use in scripts. There's been suprisingly little use of it so far, so I figured I'd start the ball rolling with a series of articles based around it. So, let's look at an example script that uses aliases and put it through the meat grinder:

image

As you can see, it spits out the expanded script to the output stream. The informational messages are written to the host, so they won't interfere if you redirect the output to a file like: .\resolve-aliases.ps1 in.ps1 > out.ps1

Here's the Resolve-Aliases.ps1 script itself:

  1. #requires -version 2  
  2. param($filename = $(throw "need filename!"))  
  3.  
  4. $lines = $null 
  5. $path = Resolve-Path $filename -ErrorAction 0  
  6.  
  7. if ($path) {  
  8.     $lines = Get-Content $path.path  
  9. } else {  
  10.     Write-Warning "Could not find $filename" 
  11.     exit  
  12. }  
  13.  
  14. # Initialize  
  15. $parser = [system.management.automation.psparser]  
  16. $errors = new-object system.management.automation.psparseerror[] 0  
  17.  
  18. do {  
  19.     $tokens = $parser::tokenize($lines, [ref]$errors)     
  20.     $retokenize = $false 
  21.       
  22.     if ($errors.count -gt 0) {  
  23.         Write-Warning "$($errors.count) error(s) found in script." 
  24.         $errors 
  25.         exit  
  26.     }  
  27.  
  28.     # look through tokens for commands  
  29.     $tokens | % {  
  30.         if ($_.Type -eq "Command") {  
  31.             $name = $_.Content  
  32.               
  33.             # is it an alias?  
  34.             # we use -literal here so '?' isn't treated as wildcard  
  35.             if ((!($name -eq ".")) -and (Test-Path -LiteralPath alias:$name)) {  
  36.                   
  37.                 # gcm may return more than one match, so specify "alias"  
  38.                 # filtering against name kludges the '?' alias/wildcard   
  39.                 $command = gcm -CommandType alias $name | ? { $_.name -eq $name }  
  40.                               
  41.                 # resolve alias which may lead to another alias  
  42.                 # so loop until we reach a non-alias  
  43.                 do {  
  44.                     $command = Get-Command $command.definition  
  45.                 } while ($command.CommandType -eq "Alias")  
  46.                   
  47.                 Write-Host -NoNewline "Resolved " 
  48.                 Write-Host -NoNewline -ForegroundColor yellow $name 
  49.                 write-host -nonewline " to "   
  50.                 write-host -ForegroundColor green $command.name           
  51.                   
  52.                 # Use a stringbuilder to replace the alias in the line  
  53.                 # pointed to in the Token object. StringBuilder has a much  
  54.                 # more precise Replace method than String. This allows us to  
  55.                 # replace the token with 100% confidence.  
  56.                 $sb = New-Object text.stringbuilder $lines[$_.startline - 1]  
  57.                 $sb = $sb.replace($name, $command.Name, $_.startcolumn - 1, $_.length)  
  58.                 $lines[$_.startline - 1] = $sb.tostring()  
  59.                   
  60.                 # now that we've replaced a token, the script needs to be reparsed  
  61.                 # as offsets have changed on this line.   
  62.                 $retokenize = $true 
  63.                   
  64.                 # break out of pipeline, (not 'do' loop)  
  65.                 continue;  
  66.             }  
  67.         }  
  68.     }  
  69. } while ($retokenize)  
  70.  
  71. Write-Host "" # blank line  
  72.  
  73. # output our modified script  
  74. $lines 

Of course, this requires PowerShell v2.0 CTP2. Next in the series, I'll give you a script to check your ps1 scripts for backwards compatibility against PowerShell 1.0. That should be handy for those naughty admins out there who despite all the warnings have installed v2 in production. ;-)

posted on Tuesday, July 01, 2008 6:43:07 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2] Trackback
# Friday, June 27, 2008

Someone on a private mailing list I'm on lamented the problem with powershell's '>' redirection operator defaulting inflexibly to use unicode for encoding the output file. This is not very compatible for NT's ancient console subsystem which works best with ASCII data. Fortunately, there's an easy workaround to fix this:

Due to the magic of command discovery and the fact that > really does use out-file, you can "fix" this by placing the following in your $profile:

function out-file($FilePath, $Encoding, [switch]$Append) {
$input | microsoft.powershell.utility\out-file $filepath -encoding ascii `
     -append:$append
}


From now on, > will be forced to use ASCII encoding. This works because functions have higher precedence than built-in commands in powershell's command discovery search.

UPDATE: Rather annoyingly, I'm informed that this particular workaround doesn't work on v1.0 of PowerShell. I tested the above on v2.0CTP2 only. Doh.

posted on Friday, June 27, 2008 12:03:07 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1] Trackback
# Wednesday, June 18, 2008

MoW poked at me, so I guess I can’t let the crazy dutchman down:

How old were you when you started using computers?

10 or 11

What was your first machine?

An Amstrad CPC 464 with Green screen.

What was the first real script you wrote?

I vaguely remember being delighted at having a rocket (Chr$(239)) ascend the Amstrad’s screen when I figured out that STEP –1 was the key in getting a for/next loop to count backwards.

What languages have you used?

    • Powershell, VBScript, JavaScript, Tcl, Perl, Batch/4NT
    • Z80A, 8088/8086 (NECv20/v30) assembler
    • Basic, Turbo Pascal, Turbo C, C++, Java, C#, VB.NET

I’m a bit of a jack-of-all-trades when it comes to development. The list above is just what I can remember ;-)

What was your first professional Sysadmin Developer gig?

After helping out a friend’s dad put together a training course for Visual Basic 3.0, I then managed to blag a job coding Ireland’s first ever major e-commerce site using MS Merchant Server 1.0 (which predated ASP 1.0) for a leading ISP.

If you knew then what you know now, would you have started in IT?

I might have started a little bit earlier even!

If there is one thing you learned along the way that you would tell new Sysadmins (or Devs), what would it be?

Just one thing? Learn Regular Expressions as it will pay you back ten-fold for whatever time you put into it.

What is the most fun you have had scripting?

There’s only one answer here: writing extensions for PowerShell.

This particular branch of the meme ends here as I don’t think I can tag anyone who hasn’t been tagged already.

posted on Wednesday, June 18, 2008 8:31:00 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Friday, June 06, 2008

In the spirit of "tidying things up," and pushing out nearly-there projects, I turned my attention to http://www.codeplex.com/PSMobile. I have my fingers in way too many pies I think and I'm just not finding enough time to get things finished off - but enough is enough. I am trying to clear my plate, and this is fruit number #2 of that attempt at self-correction. I have some other important out of band work that I need to complete, but I find I cannot concentrate on that until these personal projects that have been niggling me for ages are dealt with. Anyway, lets dispel a couple of questions with a screenshot:

Requirements

  • ActiveSync 4.2 or higher (or Windows Mobile Device Centre 6.0+ on Vista) Download
  • A Windows Mobile device (PocketPC/SmartPhone 2002, 2003, 2003SE, Windows Mobile 5, 6 or 6.1)
  • Windows PowerShell 1.0 or 2.0 (CTP) Download

Features

wm61-device

  • Copy, Move, Delete items between folders on your device (including Storage Card) with standard PowerShell Cmdlets
  • Move/Copy files to/from your device and your desktop with ConvertTo-WMFile and ConvertFrom-WMFile
  • Get device information and manipulate and explore the registry with a rich device object returned from Get-WMDevice
  • Invoke-Item against remote items to or execute or trigger their associated applications
  • Invoke-Item with -Local switch to attempt to execute a remote file in the context of your local desktop (e.g. office docs or images/videos)
  • New "Mode" attributes specific to Windows Mobile file attributes: (I)nRom, Rom(M)odule
  • File/Folder objects' attributes can be modified with .Attributes properties just like FileInfos etc.
  • Tab completion with MoW's PowerTab Download

File Manipulation

A picture's worth a thousand words.

images-screenshot

Cmdlets and Definitions

Here's a table of the syntax for the included Cmdlets.

Cmdlet Definition WhatIf / Confirm
ConvertFrom-WMFile * [-Path] [-Destination] [-Force] [-Verbose] Yes
  [-LiteralPath] [-Destination] [-Force] [-Verbose] Yes
ConvertTo-WMFile * [-Path] [-Destination] [-Force] [-Verbose] Yes
  [-LiteralPath] [-Destination] [-Force] [-Verbose] Yes
Get-WMDeviceInfo [-Verbose]  
Get-WMMemoryInfo [-Verbose]  
Get-WMStoreInfo [-Verbose]  
Start-WMActiveSync [-Verbose]  
Stop-WMActiveSync [-Verbose]  
Start-WMProcess [-LiteralPath] [[-Arguments] ] [-Verbose]  
Get-WMDevice [-Verbose]  

* These Cmdlets that accept a path will bind to pipeline input via PSPath property name.

If you've got any problems, suggestions or ideas, please post into the discussions board on the web site. Have fun!

posted on Friday, June 06, 2008 7:00:15 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [5] Trackback
# Thursday, June 05, 2008

I took a few hours yesterday to "tidy up my room" so to speak, so I built a nice MSI installer, updated the help, CodePlex Wiki and examples and closed all bugs. This is probably the final release now that PowerShell 2.0 CTP2 has introduced support for eventing, so thanks for all the support.

New Features

  • Multiple named queue support and default queue with -QueueName parameter
  • Better COM support, window message pumping etc.
  • NoFlush / Peek parameter support for queue reading
  • Get-EventQueue command added for viewing queues and their message counts.

Cmdlet Name Changes

  • Get-Event -> Read-Event
  • Connect-EventListener -> Connect-Event
  • Disconnect-EventListener -> Disconnect-Event

Additionally, several niggling bugs closed (including the one where read-event -wait would return immediately with no events).

http://www.codeplex.com/pseventing

For an advanced example: Foreground / Background Swappable Downloads In PowerShell.

posted on Thursday, June 05, 2008 9:20:10 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Friday, May 23, 2008

UPDATE May 26th: You must run PowerShell v2.0 CTP in STA mode for this to work. Start the shell, then run "powershell -sta" from the command line to start a new version of the shell in "single thread apartment" mode (STA). This is required for WPF to work correctly.

That is a bit of a mouthful of a title for this post but it's the best I could come up with. This post takes some of James' scripty bits and Jaykul's scripty bits and shows you how to create a countdown timer written in PowerShell script that runs in the background without blocking input. Just like Jaykul's original clock, you can drag it around and right-clicking it will close it. His version was the current time and it also showed some system resources. I changed it into a countdown and removed the other nested graphs. When it hits 00:00:00 it turns red. Here's what it looks like:

countdown

Here's the source of invoke-background.ps1:

  1. param([string]$scriptName)  
  2.  
  3. # original script James Brundage (blogs.msdn.com/powershell)  
  4.  
  5. $rs = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()  
  6. $rs.ApartmentState, $rs.ThreadOptions = “STA”, “ReuseThread”  
  7. $rs.Open()  
  8.  
  9. # Reference the WPF assemblies  
  10. $psCmd = {Add-Type}.GetPowerShell()  
  11. $psCmd.SetRunspace($rs)  
  12. $psCmd.AddParameter("AssemblyName", "PresentationCore").Invoke()  
  13. $psCmd.Command.Clear()  
  14.  
  15. $psCmd = $psCmd.AddCommand("Add-Type")  
  16. $psCmd.AddParameter("AssemblyName", "PresentationFramework").Invoke()  
  17. $psCmd.Command.Clear()  
  18.  
  19. $psCmd = $psCmd.AddCommand("Add-Type")  
  20. $psCmd.AddParameter("AssemblyName", "WindowsBase").Invoke()  
  21.  
  22. $sb = $executionContext.InvokeCommand.NewScriptBlock(  
  23.     (Join-Path $pwd $scriptname)  
  24. )  
  25.  
  26. $psCmd = $sb.GetPowerShell()  
  27. $psCmd.SetRunspace($rs)  
  28. $null = $psCmd.BeginInvoke()  

Next, here's the modified clock script:

  1. param (  
  2.     [timespan]$period = (New-Object system.TimeSpan(0,5,0)),  
  3.     $clockxaml="<path to xaml file>\clock.xaml" 
  4. )  
  5.  
  6. ### Import the WPF assemblies  
  7. Add-Type -Assembly PresentationFramework  
  8. Add-Type -Assembly PresentationCore  
  9.  
  10. $clock = [Windows.Markup.XamlReader]::Load(   
  11.          (New-Object System.Xml.XmlNodeReader (  
  12.             [Xml](Get-Content $clockxaml) ) ) )  
  13.  
  14. $then = [datetime]::Now  
  15.  
  16. $red = [System.Windows.Media.Color]::FromRgb(255,0,0)  
  17. $redbrush = new-object system.windows.media.solidcolorbrush $red 
  18. $label = $clock.FindName("ClockLabel")  
  19. $done = $false 
  20.  
  21. # Create a script block which will update the UI  
  22. $updateBlock = {     
  23.    if (!$done) {  
  24.         # update the clock  
  25.         $elapsed = ([datetime]::Now - $then)  
  26.         $remaining = $null;  
  27.           
  28.         if ($elapsed -lt $period) {  
  29.             $remaining = ($period - $elapsed).ToString().substring(0,8)  
  30.         } else {  
  31.             $label.Foreground = $redbrush         
  32.             $remaining = "00:00:00" 
  33.             $done = $true 
  34.         }         
  35.         $clock.Resources["Time"] = $remaining 
  36.    }  
  37. }  
  38.  
  39. ## Hook up some event handlers   
  40. $clock.Add_SourceInitialized( {  
  41.    ## Before the window's even displayed ...  
  42.    ## We'll create a timer  
  43.    $timer = new-object System.Windows.Threading.DispatcherTimer  
  44.    ## Which will fire 2 times every second  
  45.    $timer.Interval = [TimeSpan]"0:0:0.50" 
  46.    ## And will invoke the $updateBlock  
  47.    $timer.Add_Tick( $updateBlock )  
  48.    ## Now start the timer running  
  49.    $timer.Start()  
  50.    if(! $timer.IsEnabled ) {  
  51.       $clock.Close()  
  52.    }  
  53. } )  
  54.  
  55. $clock.Add_MouseLeftButtonDown( {   
  56.    $_.Handled = $true 
  57.    $clock.DragMove() # WPF Magic!  
  58. } )  
  59.  
  60. $clock.Add_MouseRightButtonDown( {   
  61.    $_.Handled = $true 
  62.    $timer.Stop()  # we'd like to stop that timer now, thanks.  
  63.    $clock.Close() # and close the windows  
  64. } )  
  65.  
  66. ## Lets go ahead and invoke that update block   
  67. &$updateBlock 
  68. ## And then show the window  
  69. $clock.ShowDialog()  

...and finally the modified clock.xaml file:

  1. <Window xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' 
  2.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  3.         xmlns:system="clr-namespace:System;assembly=mscorlib" 
  4.         WindowStyle='None' AllowsTransparency='True' 
  5.         Topmost='True' Background="Transparent"  ShowInTaskbar='False' 
  6.         SizeToContent='WidthAndHeight' WindowStartupLocation='CenterOwner' > 
  7.    <Window.Resources> 
  8.       <system:String x:Key="Time">12:34.56</system:String> 
  9.    </Window.Resources> 
  10.  
  11.    <Grid Height="2.2in"> 
  12.       <Grid.ColumnDefinitions> 
  13.          <ColumnDefinition/> 
  14.       </Grid.ColumnDefinitions> 
  15.       <Label Name="ClockLabel" Grid.Column="2" Opacity="0.7" Content="{DynamicResource Time}" FontFamily="Impact, Arial" FontWeight="800" FontSize="2in" > 
  16.          <Label.Foreground> 
  17.             <LinearGradientBrush> 
  18.                <GradientStop Color="#CC064A82" Offset="1"/> 
  19.                <GradientStop Color="#FF6797BF" Offset="0.8"/> 
  20.                <GradientStop Color="#FF6797BF" Offset="0.4"/> 
  21.                <GradientStop Color="#FFD4DBE1" Offset="0"/> 
  22.             </LinearGradientBrush> 
  23.          </Label.Foreground> 
  24.       </Label> 
  25.    </Grid> 
  26. </Window> 

Important: you'll need to save all files into the same directory and fix up the path to the clock.xaml file in the start-countdown.ps1 script.

Have fun!

posted on Friday, May 23, 2008 5:04:18 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Wednesday, May 21, 2008

This question has been asked in various ways over the last few years and I don't believe an answer that suits everyone has been proffered yet. I think this is part of a broader problem space that needs to be solved, one that I (and many others) have spent a bit thinking about -- for me personally, it's been mostly in the pub strangely enough, usually with a pint in hand -- and while I don't profess to have the answer, I do spent most of my powershell time tinkering with providers and have some views on this ;-)

Firstly, if you haven't seen this suggestion I've raised on connect (Allow providers other than the filesystem provider to surface commands) then take a gander at it now. It suggests allowing providers other than the FileSystemProvider to surface commands by using a new ProviderCapabilities flag. For those not able to read this suggestion, the bottom line is that currently one can execute a command on the filesystem by using the following syntax:

ps c:\> .\test.bat
hello, world

However, if you had another provider that linked into a mobile device, the amazon s3 service or MSL skydrive (when are they going to release an API?) for example, you would allow execution of commands with the same syntax, e.g.

ps skydrive:\> cd ppts
ps skydrive:\ppts> .\mydemo.ppt
The term '.\mydemo.ppt' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
At line:1 char:8
+ .\mydemo.ppt <<<<

As you can see, this doesn't work.

What's important is having the same experience with similarly capable providers; e.g. those that can host executable content. Yes, you can implement support for invoke-item, but it's a bit discordant. One of the nicest features of powershell (and sometimes the most confusing) is that all providers - variable, function, environment, filesystem etc all hook into the same framework. There are some philosophically irksome differences like the fact that the  variable drive is the "default" provider, since dollar-qualified expressions are assumed to point there if not qualified with a drive name:

ps c:\> $host
Name             : ConsoleHost
Version          : 1.0.0.0
InstanceId       : 9d8a29bf-3d84-4ce6-8651-e0c72afb404b
UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture   : en-CA
CurrentUICulture : en-US
PrivateData      : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy

so let's give a drive name this time:

ps c:\> ${c:test.bat}
@echo hello, world

This is a lot easier to understand once you realise that the '$' prefix is a grammatical shortcut into the IContentReader/IContentWriter interfaces on every provider; Much easier than just blindly committing to memory the method to read a variable and the method to read a file (imho). Once you introduce this capability into other providers, you then have to address ye olde $env:path variable. Currently this variable is imported from the system environment. The system, aka Windows, knows nothing about powershell and its drives. When using Get-Command, the search order for discovering commands is:

Aliases, Functions, Cmdlets, Scripts, Commands located in the directories specified by the Path environment variable, External scripts.

As we can see, Commands (5) are discovered via the $env:path variable. The other items (apart from 3 - cmdlets) all live in a flat namespace, so there's no path involved there.  I'd love if somehow it were possible to add any powershell path to this variable, even if they were limited to drive-qualified paths:

ps c:\> $env:path
c:\windows; c:\windows\system32; ...; s3:\utilities; "mobile:\storage card"; ...

Perhaps this information could be persisted inside PowerShell only so when the shell is exited, the path environment variable remains unchanged when viewed from the external windows system. This might mean that PowerShell paths would have to be appended in your profile at each load, but this isn't a bad thing either, IMO. So finally, on to the crux of the matter: disambiguation of identically named Cmdlets. Ultimately I don't believe there is a magic answer. This is solved in Windows by using the path variable, and so I believe it isn't such a bad idea to solve it with a path variable in powershell too. Behold $env:cmdletpath

ps c:\> $env:cmdletpath
Microsoft.PowerShell.Core; Microsoft.PowerShell.Host; Microsoft.PowerShell.Management; Microsoft.PowerShell.Security; Microsoft.PowerShell.Utility; VMWare.Commands.Utility

It's simple. It's optional. Snap-in qualified commands would continue to work, overriding the cmdlet search path. Instead of having to alias multiple commands when using a snapin that replaces a suite of built-in cmdlets, you can just re-jig the search path. Done. It's not the answer to everything, but it sure would make life a bit easier, no?

posted on Wednesday, May 21, 2008 8:36:39 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Wednesday, April 02, 2008

Now this is barely worth blogging, but one of the things I used a lot when I was confined to cmd.exe (yes, "confined" is the word I would use, 4NT aside) is the wonderfully simple copy con filename.txt then type a few lines and end it all with CTRL+Z and enter. So, if you're a PowerShell noob and yearn for this olden-days simplicity like dead parrots pine for the fjords, then this is for you:

  1. rm function:copy-console -ea 0 # silentlycontinue   
  2. rm alias:cc -ea 0   
  3.   
  4. # encoding can be String, Unicode, Byte, BigEndianUnicode, UTF8, UTF7, Ascii   
  5. function global:copy-console {   
  6.        
  7.     param(   
  8.         [string]$Filename = $(Throw "Need output filename."),   
  9.         $Encoding = "ASCII"  
  10.     )   
  11.        
  12.     $out = [io.path]::combine($pwd$Filename)   
  13.   
  14.     $buffer = @()   
  15.     $crlf = "`r`n"  
  16.        
  17.     do {   
  18.         $line = [console]::readline()   
  19.         if ($line -eq $null) { break; }   
  20.         $buffer += $line  
  21.     } while ($TRUE)   
  22.   
  23.     $buffer | set-content $out -Encoding $Encoding  
  24. }   
  25. new-alias cc copy-console   
  26.   
  27. # Usage:   
  28. #   
  29. # PS> cc test.txt -Encoding utf8   
  30. # bleh   
  31. # moop   
  32. # vlorg   
  33. # ^Z   
  34. # PS> cat test.txt   
  35. # bleh   
  36. # ...   
  37.   

I saved this to copy-console.ps1 and aliased it to "cc." Of course, you can do whatever you want - it's probably easier to just put it into a function in your profile. Just place the script above into your profile and remember: CTRL+Z then enter to save.

UPDATE 2008-04-04: Somehow I completely broke this in my attempts to "clean it up" before posting. I've reposted a better version (imho), and implemented encoding support as suggested out in Jason's comment below ;-)

posted on Wednesday, April 02, 2008 4:28:46 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
# Thursday, March 27, 2008

PowerShell has several "type accelerators" which are used exactly like a casting operation. Examples of these special operators are [xml] and [wmi]. The former is used for quickly converting a string of xml into a fully-fledged System.Xml.XmlDocument object.

Often I find myself converting things to and from hexadecimal using the -f operator, but this always seemed like just a little too much typing for me. Enter the [hex] accelerator type:

image

As you can see from the source below, there's no magic here. This is just a straight cast, but I have no namespace. If I had a namespace, say, like "Nivot.PowerShell", we'd have to cast using [nivot.powershell.hex] instead of just [hex]. All of the trickery is done using operator overloads in C#. These tells .NET (and in turn, powershell) how to behave should someone try to add, subtract, remove or divide our instances.

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Text;  
  4.  
  5. public class Hex  
  6. {  
  7.     private readonly int _value = 0;  
  8.  
  9.     private Hex(int value)  
  10.     {  
  11.         _value = value;  
  12.     }  
  13.  
  14.     private Hex(string value)  
  15.     {  
  16.         _value = Convert.ToInt32(value, 16);  
  17.     }  
  18.  
  19.     public static implicit operator Hex(int value)  
  20.     {  
  21.         return new Hex(value);  
  22.     }  
  23.  
  24.     public static implicit operator int(Hex value)  
  25.     {  
  26.         return value._value;  
  27.     }  
  28.  
  29.     public static explicit operator Hex(string value)  
  30.     {  
  31.         return new Hex(value);  
  32.     }  
  33.  
  34.     public static Hex operator +(Hex op1, Hex op2)  
  35.     {  
  36.         return new Hex(op1._value + op2._value);  
  37.     }  
  38.  
  39.     public static Hex operator -(Hex op1, Hex op2)  
  40.     {  
  41.         return new Hex(op1._value - op2._value);  
  42.     }  
  43.  
  44.     public static Hex operator *(Hex op1, Hex op2)  
  45.     {  
  46.         return new Hex(op1._value * op2._value);  
  47.     }  
  48.  
  49.     public static Hex operator /(Hex op1, Hex op2)  
  50.     {  
  51.         return new Hex(op1._value / op2._value);  
  52.     }  
  53.  
  54.     public override string ToString()  
  55.     {  
  56.         return "0x" + _value.ToString("X");
  57.     }  

The next step is to tell PowerShell's formatter what to do with the new type. Here's a simple format definition that tells the formatter to call ToString() on the Hex instance. This is the method that does the conversion by calling ToString("X") on the integer field. "X" means format the integer as hexadecimal using upper case. A lower-case "x" would output the value using lower-case (if you couldn't guess ;-)).

  1. <Configuration> 
  2.   <ViewDefinitions> 
  3.     <View> 
  4.       <Name>Hex</Name> 
  5.       <ViewSelectedBy> 
  6.         <TypeName>Hex</TypeName> 
  7.       </ViewSelectedBy> 
  8.       <CustomControl> 
  9.         <CustomEntries> 
  10.           <CustomEntry> 
  11.             <CustomItem> 
  12.               <ExpressionBinding> 
  13.                 <ScriptBlock>$_.ToString()</ScriptBlock> 
  14.               </ExpressionBinding> 
  15.             </CustomItem> 
  16.           </CustomEntry> 
  17.         </CustomEntries> 
  18.       </CustomControl> 
  19.     </View> 
  20.   </ViewDefinitions> 
  21. </Configuration> 

If we don't load this format file, PowerShell just emits a couple of blank lines when you try to use it.  You'll notice from the screenshot above that the blank lines still appear. I'm not sure how to remove these - it looks pretty ugly compared to the output of the [int] object. If you want to play with this, you can download the zip file below and unzip the contents into a single folder and run "hex.ps1". I didn't bother with a full Snap-In, it just loads the DLL using reflection. It also loads the format ps1xml too. Have fun.

HexAccelerator1.zip (2.4 KB)
posted on Thursday, March 27, 2008 8:19:15 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, March 06, 2008

Just a minor thing, but this is a filter I've been using for a while now as a replacement for Get-Member. It's always annoyed me that the Get_ and Set_ methods are returned along with the actual properties. The only place that's actual useful is when you're using an object wrapped in the [xml] adapter since those objects do not expose the XmlDocument's properties in the adapted member set.

update: if you use (of course you do!) MoW's PowerTab, you can disable display of the accessor methods with $PowerTabConfig.ShowAccessorMethods = $false.

  1. filter get-memberex {   
  2.     $_ | gm | ? { -not($_.name -match "^[gs]et_.+") }   
  3. }   
  4. new-alias gmx get-memberex  
posted on Thursday, March 06, 2008 11:35:21 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Friday, February 29, 2008
Someone on the PowerShell usenet group asked if it was possible to interact with SharePoint lists through our favourite little shell. Marco Shaw responded and put the pressure on by saying this was my bag of tricks. Who am I to say otherwise? so lets take a look at the recipe.
posted on Friday, February 29, 2008 10:11:39 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, February 14, 2008

My last post got me thinking about the problems experienced when trying to write culture aware software. Yeah, I know it was actually me that was unaware of the culture, but this time it's about the software end of the deal; in particular, the recently updated Microsoft Business Data Catalog Definition Editor for Microsoft's popular SharePoint 2007 server. If you read some of the comments on the blog, you'll see that various people (using a non US English version of Windows) have installed it and have come across a problem where the tool cannot find the local security group called "Builtin\Users." Oops. In the world of cutting-edge technology, people often install software that doesn't match the installed language of their O/S. The fact of the world is that all major symbolic computer languages are based around English, and the most popular software gets written in English first. Here in Quebec, Canada, French is the primary language with English coming second (Canada is officially bilingual - although most of the country only speaks English). Localization of software takes a fair amount of time. It's not just translating a resources file - there are hot-keys to reassign (the Bold shotcut in French MSWord is CTRL+G for example, bold being Gras in French) dialog boxes to resize, labels and controls to reposition etc. Some languages are more verbose than others and end up with text that won't fit. However, there are things you do to avoid certain problems -- lets take the issue above as an example.

Logins and Group names are just an abstraction in the Windows security subsystem. These things are actually represented by value called a SID ( system.security.principal.securityidentifier ). No matter what version of Windows you use, the SIDs for built-in accounts and groups are the same:

First using an en-US system:

  1. PS > $acc = new-object System.Security.Principal.NTAccount "Users" 
  2. PS > $acc.Translate( [System.Security.Principal.SecurityIdentifier] ).value  
  3. S-1-5-32-545 

and a French (fr-FR) system:

  1. PS > $acc = new-object System.Security.Principal.NTAccount "Utilisateurs" 
  2. PS > $acc.Translate( [System.Security.Principal.SecurityIdentifier] ).value  
  3. S-1-5-32-545 

As you can see, the SID is the same: S-1-5-32-545. An example of this is shown below - a simple If-Elevated function that takes two Scriptblocks: the first is executed if the user is running as an administrator, the second is running if the user is just a plain well, user:

  1. # Usage:  
  2. #  
  3. # If-Elevated { .. admin code .. } { "sorry, need admin" }   
  4. #  
  5.  
  6. function If-Elevated {  
  7.   param(  
  8.     [scriptblock]$AsAdmin = $(Throw "Missing 'as admin' script"),  
  9.     [scriptblock]$AsUser= $(Throw "Missing 'as user' script")  
  10.   )  
  11.    
  12.   $identity = [security.principal.windowsidentity]::Getcurrent()  
  13.   $principal = new-object  security.principal.windowsprincipal $identity 
  14.   $adminsRole =  [system.security.principal.securityidentifier]"S-1-5-32-544" 
  15.                   
  16.   if ($principal.IsInRole($adminsRole)) {  
  17.     & $AsAdmin 
  18.   } else {  
  19.     & $AsUser 
  20.   }  
  21. }  
So ok, it doesn't have localized messages, but at least it will execute correctly on other locales ;-) Have fun.
posted on Thursday, February 14, 2008 6:36:06 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Monday, January 14, 2008

I'm happy to say that the powers that be in Microsoft have deemed me MVP worthy - I am now an official Microsoft Most Valued Professional in Windows Server Admin Frameworks for 2008, more specifically for my open source work and public support of Microsoft's most excellent object-oriented interactive shell, PowerShell during the last 18 months.

MVP_Horizontal_BlueOnly

I've just returned from an extended computational absence, so hopefully I can get back to churning out code and the odd blog post.

posted on Monday, January 14, 2008 10:53:30 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2] Trackback
# Friday, December 14, 2007

In case anyone is interested, here are my slides in PowerPoint PPTX format from the most recent PS Virtual User Group. It covers the new Path handling infrastructure for PscxCmdlets in the upcoming PowerShell Community Extensions 1.2 and some brief information on my PowerShell Eventing snap-in for PowerShell.

psvug2_oisin_grehan.zip (152 KB)

(updated to a zip: it appears DasBlog will not serve pptx files?)

posted on Friday, December 14, 2007 11:04:07 AM (Eastern Standard Time, UTC-05:00)  #    Comments [2] Trackback
# Friday, December 07, 2007

As knowledge of PowerShell increases for those new to .NET, there comes a point when people start to notice some shortcomings of the Assembly loading/unloading mechanisms of the 2.0 CLR. Namely, once you load an assembly into PowerShell to use it, you can't unload it again. The only way to remove it from memory is to restart PowerShell. Eventually, you might read something about how Assemblies can be loaded into AppDomains, and AppDomains themselves can be unloaded. This is true, but for the most part it is not much use in PowerShell unless the Types in question where specifically designed with this in mind. For those of you who understand enough of what I'm talking about to get this far without going "huh?", the following script will demonstrate some of the issues at hand:

Before you run this script, please disable PowerTab or any other SnapIns that may load the WinForms assembly into the current AppDomain. In short, this script creates a Form object in a child AppDomain, examines the current AppDomain for the WinForms assembly. It then attempts to manipulate the Form and again examines the current AppDomain for the WinForms assembly.

  1. # full qualified display name to WinForms assembly   
  2. $assembly = "System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"  
  3.   
  4. function IsWinFormsLoaded() {   
  5.     $loaded = [appdomain]::currentdomain.getassemblies()   
  6.     $winforms = $loaded | ? { $_.fullname -like "system.windows*" }   
  7.     return ($winforms -ne $null)       
  8. }   
  9.   
  10. if (-not (IsWinFormsLoaded)) {   
  11.     "Creating child AppDomain..."  
  12.     $child = [appdomain]::Createdomain("child",$null,$null)   
  13.   
  14.     # create a remote instance of a WinForms Form in a child AppDomain   
  15.     "Creating remote WinForms Form in child AppDomain... "  
  16.     $handle = $child.CreateInstance($assembly"System.Windows.Forms.Form")   
  17.   
  18.     # examine returned ObjectHandle   
  19.     "Returned object is a {0}" -f $handle.GetType()   
  20.     $handle | gm # dump methods   
  21.   
  22.     # Did WinForms get pulled into our AppDomain?   
  23.     "Is Windows Forms loaded in this AppDomain? {0}" -f (IsWinFormsLoaded)   
  24.   
  25.     # attempt to manipulate remote object, so unwrap   
  26.     "Unwrapping, examining methods..."  
  27.     $form = $handle.Unwrap()   
  28.     $form | gm | select -first 10   
  29.   
  30.     # is Windows Forms loaded now?   
  31.     "Is Windows Forms loaded in this AppDomain? {0}" -f (IsWinFormsLoaded)   
  32.   
  33. else {   
  34.     write-warning "System.Windows.Forms is already loaded. Please disable PowerTab or other SnapIns that may load System.Windows.Forms and restart PowerShell."  
  35. }  

Hopefully this will clear up any outstanding questions. I'll post more information about this later, or possibly add to this post.

appdomains.ps1 (1.33 KB)

posted on Friday, December 07, 2007 6:40:37 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2] Trackback
# Wednesday, December 05, 2007

Here's another interesting use for my PowerShell Eventing Snap-In, where I'm simulating unix-style foreground/background tasks. In this case, the task is a large download using System.Net.WebClient. Just dot-source the script below and start a download using the Start-Download function, passing the url to the large file and the local path where to save your file (be sure to fully qualify the save path). The download will start immediately and show a progress bar with bytes remaining and a percentage, however you can hit Ctrl+C at any time and the download will continue in the background. You can get back to PowerShell tasks, and bring back up the progress bar by invoking Show-Progress at any time. Use Stop-Download to cancel the currently active download. Only one download can be active at a time, but this could easily be extended to support a pool of downloads (using multiple WebClient objects).

downloads.ps1 (1.85 KB)

  1. #requires -pssnapin pseventing  
  2.  
  3. function Stop-Download {  
  4.     $wc.CancelAsync()  
  5. }  
  6.  
  7. function Start-Download {  
  8.     param(  
  9.         [uri]$Url = $(throw "need Url."),  
  10.         [string]$Path = $(throw "Need save path.")  
  11.     )  
  12.  
  13.     # initialise webclient  
  14.     if (-not $global:wc) {  
  15.         $global:wc = New-Object System.Net.WebClient  
  16.         $var = get-variable wc  
  17.         Connect-EventListener -Variable $var `  
  18.             -EventName DownloadProgressChanged, DownloadFileCompleted         
  19.     }  
  20.  
  21.     if ($wc.IsBusy) {  
  22.         Write-Warning "Currently busy: Please cancel current download or wait until it is completed." 
  23.         return 
  24.     }  
  25.       
  26.     $wc.DownloadFileAsync($url, $path, $path) # last is userstate  
  27.     Write-Host "Download started. Ctrl+C to continue in background." 
  28.     Show-Progress  
  29. }  
  30.  
  31. function Show-Progress {  
  32.     if (-not $wc.IsBusy) {  
  33.         # possibly already completed, clear queue  
  34.         get-event | Out-Null 
  35.         "No active download." 
  36.         return 
  37.     }  
  38.       
  39.     Start-KeyHandler -CaptureCtrlC  
  40.       
  41.     trap [exception] {  
  42.         Stop-KeyHandler  
  43.         Write-Warning "error" 
  44.         $_ 
  45.         return 
  46.     }  
  47.       
  48.     $exiting = $false 
  49.       
  50.       
  51.     while (-not $exiting) {       
  52.         $events = @(Get-Event -Wait)          
  53.         $event = $null;  
  54.           
  55.         # skip to last event  
  56.         foreach ($event in $events) {  
  57.             if ($event.Name -eq "CtrlC") {  
  58.                 break;  
  59.             }  
  60.         }         
  61.           
  62.         switch ($event.Name) {  
  63.             "DownloadProgressChanged" {  
  64.                 $r = $event.args.BytesReceived  
  65.                 $t = $event.args.TotalBytesToReceive  
  66.                 $activity = "Downloading $($event.args.userstate)" 
  67.                 $p = [math]::Ceiling(([double]$r / $t) * 100)  
  68.                 Write-Progress -Activity $activity -PercentComplete $p `  
  69.                     -Status ("{0} of {1} byte(s)" -f $r,$t)  
  70.             }  
  71.             "DownloadFileCompleted" {  
  72.                 Write-Progress -Activity DownloadComplete -Completed  
  73.                 "Complete." 
  74.                 $exiting = $true 
  75.             }  
  76.             "CtrlC" {  
  77.                 "Switching to background downloading. Show-Progress to move to foreground." 
  78.                 $exiting = $true 
  79.             }  
  80.         }         
  81.     }  
  82.     Stop-KeyHandler  
posted on Wednesday, December 05, 2007 7:47:42 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Tuesday, December 04, 2007

Hmmm... that old typographical oddity "the quick brown fox jumps over the lazy dog," does it really use all letters from "A" to "Z?"

  1. [char]"a" .. [char]"z" | ? { "the quick brown fox jumps over the lazy dog".getenumerator() -contains $_ } | % { [char]$_

I guess it's true then.

posted on Tuesday, December 04, 2007 8:13:55 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Sunday, December 02, 2007

Using Windows Forms controls in PowerShell is a tricky thing, because it only supports very simple event listboxhandling. However, some time ago I wrote a Snap-In to allow anyone to work with .NET events, even asynchronous ones. You can download it from my PSEventing CodePlex project; it comes with full help via the normal PowerShell mechanisms of -? and get-help. Examples are also available online on how to use the project for more complex scenarios. Here's simple demonstration of autogenerating a listbox control filled with choices given as arguments to a simple script:

  1. # as simple as 1,2,3 :-)  
  2. $choice = .\get-choice.ps1 "one","two","three" 

And here is the source to the get-choice.ps1 script itself. This requires PSEventing 1.0 or 1.1 Beta.

  1. #requires -pssnapin pseventing  
  2.  
  3. # http://www.codeplex.com/pseventing (1.0 or 1.1)  
  4.  
  5. param([string[]]$choices = $(throw "please supply a string array of choices"))  
  6.  
  7. if ($choices.length -eq 0) {  
  8.     Write-Warning "cannot be a zero length array." 
  9.     return 
  10. }  
  11.  
  12. # initialize form  
  13. $form = new-object windows.forms.form  
  14. $form.Text = "Choose..." 
  15. $form.MinimizeBox = $false 
  16. $form.MaximizeBox = $false 
  17. $form.AutoSize = $true 
  18. $form.AutoSizeMode = "GrowAndShrink" 
  19.  
  20. # initialize listbox  
  21. $listbox = new-object windows.forms.listbox  
  22. $choices | % { [void]$listbox.items.add($_) }  
  23.  
  24. $form.controls.Add($listbox)  
  25.  
  26. # catch a choice in the listbox (remove -verbose for quiet mode)  
  27. Connect-EventListener -VariableName listbox -EventName SelectedIndexChanged -Verbose  
  28.  
  29. # catch someone closing the form (remove -verbose for quiet mode)  
  30. Connect-EventListener -VariableName form -EventName Closed -Verbose  
  31.  
  32. $form.Show()  
  33.  
  34. # wait for an event while performing sendmessage pumping (or ctrl+c to exit)  
  35. $event = Get-Event -Wait  
  36.  
  37. # don't pollute pipeline (remove in production)  
  38. write-host ($event | ft -auto | out-string)  
  39.  
  40. $form.Dispose()  
  41.  
  42. $choice = $listbox.SelectedItem  
  43.  
  44. # clean up; event listeners will be automatically unhooked ;-)  
  45. $form = $null 
  46. $listbox = $null 
  47.  
  48. # return chosen item, or $null if the form was closed  
  49. if ($event.Name -eq "SelectedIndexChanged") {  
  50.     $choice 
  51. } else {  
  52.     $null # cancelled  
Have fun!
posted on Sunday, December 02, 2007 4:48:09 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback

It's very easy sometimes to look at the PowerShell grammar and partition it into two distinct styles: pipeline and traditional imperative script. In fact, it took a number of weeks of playing around before I realised that this is leaving out a large portion of patterns that employ a fusion of these two styles. For example, Let's iterate over an array of numbers, skipping null elements; first, using the pipeline style:

  1. $arr = @(1,2,3,$null,5)  
  2.  
  3. $arr | where-object { $_ -ne $null } | foreach-object { $_

Then, using a traditional VBScript style, using the foreach keyword, as opposed to the foreach-object cmdlet:

  1. $arr = @(1,2,3,$null,5)  
  2.  
  3. foreach ($elem in $arr) {  
  4.     if ($elem -ne $null) {  
  5.         $elem 
  6.     }  

And finally, a fusion of the two styles whereby I insert a pipeline into the expression, and also take advantage of the fact that $null will evaluate to $false, thereby skipping the need to test with the -eq operator (update: Keith Hill reminded me that both 0 and "" will also evaluate to $false, so I added an explicit -ne $null):

  1. $arr = @(1,2,3,$null,5)  
  2.  
  3. foreach ($elem in $arr | where-object {$_ -ne $null} ) {  
  4.     $elem 

As Mr. Snover is fond of saying, this is a great example of PowerShell's pithiness. PithyShell at its best!

posted on Sunday, December 02, 2007 12:58:14 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
# Wednesday, November 21, 2007

Well, this is not exactly rocket science, but it helped me out today with some spreadsheets tomfoolery. It generates PSCustomObjects with properties named after the header row in the Excel sheet (you need a header row for this to work), just like Import-Csv. The parameters should be self-explanatory: filename and worksheet.

param (
    [
string]$filename = $(throw "need a filename, e.g. c:\temp\test.xls"),
   
[string]$worksheet
)

if (-not (Test-Path $filename)) {
    throw "Path '$filename' does not exist."
    exit
}

if (-not $worksheet) {
   
Write-Warning "Defaulting to Sheet1 in workbook."
   
$worksheet = "Sheet1"
}

# resolve relative paths
$filename = Resolve-Path $filename

# assume header row (HDR=YES)
$connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data" +
    "
Source=${filename};Extended Properties=`"Excel 8.0;HDR=YES`"";

$connection = New-Object data.OleDb.OleDbConnection $connectionString;
$connection.Open();
$command = New-Object data.OleDb.OleDbCommand "select * from [$worksheet`$]"

$command.connection = $connection
$reader
= $command.ExecuteReader("CloseConnection")

if ($reader.HasRows) {
   
# cache field names
   
$fields = @()
   
$count = $reader.FieldCount

    for ($i = 0; $i -lt $count; $i++) {
        $fields += $reader.GetName($i)
    }

    while ($reader.read()) {

        trap [exception] {
            Write-Warning "Error building row."
            break;
        }

        # needs to match field count
        $values = New-Object object[] $count

        # cache row values
        $reader.GetValues($values)

        $row = New-Object psobject
        $fields | foreach-object -begin {$i = 0} -process {
            $row | Add-Member -MemberType noteproperty -Name $fields[$i
                -Value $values[$i]; $i++
        }
        $row # emit row
    }
}

 

posted on Wednesday, November 21, 2007 5:51:47 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2] Trackback
# Tuesday, November 20, 2007

It's been a really busy few weeks in work, but I'm finally starting to find some time to put together some weightier posts on PowerShell. By this stage, everyone is pretty good at hacking together Cmdlets, and to be honest, Cmdlets are where it's at for most applications. However, I think the provider system is pretty powerful and deserves a chance to become a more equal partner to its Cmdlet brethren. The bewildering array of methods and base classes at your disposal is a little intimidating, and let's be honest, not everyone's provider ideas can be compared to an Access Database provider model.

My new blog is reachable via http://oisin.powershell.com/ (thanks Klumsy!) and is a redirect to my spot on the new community site, http://www.powershellcommunity.org/ . The first post will cover PowerShell paths in all their guises.

 

posted on Tuesday, November 20, 2007 10:24:49 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Wednesday, October 24, 2007

Someone on the newsgroup ran into an issue whereby a instance of a non CLS Compliant type was fed to PowerShell's default formatter. The type in question had two properties with the same name, but in different casing. I'm pretty much just going to carbon-copy the qestion and answer here as I feel this is worth preserving in the annals of blogdom.

> I am in the process of writing a script to document SharePoint
> installations.  When I try to get information about the content database
> I get this error.

> "out-lineoutput : The field/property: "Id" for type:
> "Microsoft.SharePoint.Administration.SPContentDatabase" differs only in
> case from the field/property: "ID". Failed to use non CLS compliant type."

> Here is some of the PowerShell Script that I am using

> [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
> $site = New-Object Microsoft.SharePoint.SPSite("http://localhost")
> $site.ContentDatabase

> It is the final line that is giving me the error.  I can get other
> properties without error.  Can someone enlighten me on this error
> message and let me know if there is a way to work around it.

> Thanks
> Jerry

Hi Jerry,

This is a tricky one for people very new to powershell without any real experience of the .NET framework. One of the rules of a so-called "CLS compliant type" (cls = common lanaguage specification) is that you should not expose any public members that only differ in casing. The SPContentDatabase type has two properties, "Id" and "ID." This is perfectly legal in C# since case in important in that language and the compiler sees them as different as chalk and cheese. However, in VB.NET, case is not important and as such it [vb] has no native mechanism to differentiate between the two. The same goes for PowerShell's grammar. So, how do we get around this?

First, you grab a handle to the method directly by asking for it from the Type itself:

PS> $idMethod1 = [Microsoft.SharePoint.Administration.SPContentDatabase].getmethod("get_ID")

and for the second version,

PS> $idMethod2 = [Microsoft.SharePoint.Administration.SPContentDatabase].getmethod("get_Id")

Note the differing case for get_Id and get_ID. Btw, Properties like ID in .NET are actually served by special accessor methods prefixed with get_ and set_, hence the prefixes. Next, you need a way to invoke those methods on the instance itself: this is done by using the Invoke method on the MethodInfo object representing the method itself. You pass the instance to the Invoke method telling it that the method is "instance" (e.g. not static/shared) and "public" and it will call that method for you on the instance, returning the value, if any.

PS> $result = $idMethod1.Invoke($site.ContentDatabase, "instance,public", $null, $null, $null)

I know from the SharePoint documenation, that this property returns a guid. Also, due to another oversight, Posh does not know how to format Guids, so you must call ToString() on the result to get the value back or you'll be greeted by a blank line from powershell's formatter.

PS> $result.ToString()
1ade149d-2049-4668-b555-4b7be9374c65

Bizarrely, these two properties return the same guid, but hey, I'll let someone from the sharepoint team answer that one. Anyway, you should have enough information here to extract the properties that you need.

So, that's the interesting portion out of the way. Another way to do this might be to create a custom view for the SPContentDatabase type omitting the duplicated property and use the Update-FormatData cmdlet to activate it. This may not work though, as the non-cls compliancy issue may rear it's ugly head before posh's formatter gets this far.

Hope this helps,

- Oisin / x0n.

posted on Wednesday, October 24, 2007 4:18:50 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Thursday, October 11, 2007

A question came up on the PowerShell newsgroup concerning how to use enums, particularly the shortcut form whereby posh will coerce strings. Roman Kuzmin of PowerShell/FarNet fame offered up a quick answer on how to provide multiple values to be "or'd" together for [flags] decorated enums:

$srv.ReplicationServer.Script("Creation,SomeOtherValue")

To which the original poster (moff) replied:

Out of interest, can this syntax be used for xor'ing values together too? 
At the moment my statement looks like this: 


$pub_svr.ReplicationServer.Script(([Microsoft.SqlServer.Replication.scripto­­ptions]::Creation `
    -bor [Microsoft.SqlServer.Replication.scriptoptions]::IncludeAll ` 
    -bxor [Microsoft.SqlServer.Replication.scriptoptions]::IncludeReplicationJobs ))

To which I explained that sure, one way has you casting the operands (using system.attributetargets as the enum example):

$targets = ([attributetargets]"all" -bxor [attributetargets]"event,field")

And if the type name is long, another handy trick is to assign the enum type to a variable, e.g.

$enum = [Microsoft.SqlServer.Replication.ScriptO­ptions] 
$options = ($enum::creation -bor $enum::IncludeAll) -bxor $enum::includereplicationjobs

...and finally, if you want to cast multiple flags using a variable shortcut, use the -as operator:

$options = $enum::all -bxor ("includeall,includereplicationjobs" -as $enum)

because [$enum]"creation,includeall" (or other guessed-at variants) won't work.

posted on Thursday, October 11, 2007 10:30:27 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Saturday, September 22, 2007

Marco Shaw recently asked on the powershell newsgroup:

Anyone have a working example of using "CodeMethod" to define methods
for a custom object?

And funnily enough, not even Microsoft themselves seem to have a decent working example. So, spelunking in the packaged Types.ps1xml that comes with a PowerShell install I found only three examples. Let's look at the ETS definition for one of them (XmlNode.ToString) in Types.ps1xml:

<Type>
  <
Name>System.Xml.XmlNode</Name>
    <
Members>
      <
CodeMethod>
        <
Name>ToString</Name>
        <
CodeReference>
          <
TypeName>Microsoft.PowerShell.ToStringCodeMethods</TypeName>
          <
MethodName>XmlNode</MethodName>
        </
CodeReference>
      </
CodeMethod>
   </
Members>
</
Type>

Using Reflector, I took a look at the managed code providing this service - btw, ETS CodeMethods are always provisioned by static methods on a managed type:

public static class ToStringCodeMethods
{
    ...
    public static string XmlNode(PSObject instance);
    ...
}


The C# 3.0 users among you will see a remarkable similarity to Extension Methods here, except here ETS CodeMethods can also replace a native method if you so desire. To define a new CodeMethod, you need to provide the full type name, the name of the static method and the ETS method name for the extension method on the target type. In this particular example, all instances of XmlNode exposed in PowerShell will now have their native ToString method replaced with this new one. When you invoke ToString on the XmlNode instance, the static method ToStringCodeMethods.XmlNode will be invoked and the instance parameter will be passed the PSObject wrapped XmlNode. Taking the only three examples in Types.ps1xml at face value, one might assume that CodeMethods can only be parameterless, but it appears not to be the case...

ETS Code Methods with additional parameters

A little experimentation verified that to me that we're not stuck with parameterless codemethods; any additional parameters you define on your static method will be bound to parameters provided in a script invocation of the ETS CodeMethod. The first argument is mandatory for any statics providing CodeMethod services: it will always be passed the ETS extended instance. Here are three examples of some multi-argument signatures and the backing managed code to provide ETS with the implementation:

<Types>
 <
Type>
  <
Name>System.String</Name>
  <
Members>
  <
CodeMethod>
   <
Name>Test1</Name>
   <
CodeReference>
    <
TypeName>Nivot.CodeMethods.TestCodeMethods</TypeName>
    <
MethodName>TestNoArguments</MethodName>
   </
CodeReference>
  </
CodeMethod>
  <
CodeMethod>
   <
Name>Test2</Name>
   <
CodeReference>
    <
TypeName>Nivot.CodeMethods.TestCodeMethods</TypeName>
    <
MethodName>TestOneArgument</MethodName>
   </
CodeReference>
  </
CodeMethod>
  <
CodeMethod>
   <
Name>Test3</Name>
   <
CodeReference>
    <
TypeName>Nivot.CodeMethods.TestCodeMethods</TypeName>
    <
MethodName>TestVariableArguments</MethodName>
   </
CodeReference>
  </
CodeMethod>
 </
Members>
 </
Type>
</
Types>

And here is the corresponding backing managed code signatures:

public static class TestCodeMethods
{
    // Methods
    public static string TestNoArguments(PSObject instance) { ... }
    public static string TestOneArgument(PSObject instance, PSObject arg1) { ... }
    public static string TestVariableArguments(PSObject instance, params PSObject[] args) { ... }
}
Have fun!

posted on Saturday, September 22, 2007 4:42:05 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Friday, August 24, 2007

While I absolutely love MoW's PowerTab, there's been one little niggling thing that's been missing since day one; while you can navigate Types using the opening square bracket syntax, e.g. [<tab> , when using the new-object command this method is not totally syntactically compatible. (update: MoW explains in a comment below that there is a special trick for navigating without the square brackets, oops! me wrong!) You have to navigate to your type, then edit out the square brackets which is a little bit annoying. Also, when using this syntax you typically know exactly which type you need but can't remember the namespace. Or perhaps you know the type name, but don't want to have to type it all out (or navigate there).

So, I figured I'd crack open PowerTab and see how easy it was to implement. To cut a long story short, in about 30 minutes it was done. The thing that made it so easy was that MoW already has a DataSet in memory of all types and their namespaces, so the grunt work is done by a simple DataTable.Select call. I modified the function TabExpansion in TabExpansion.ps1 (version 0.96 b12), and put the following snippet just after the switch on $lastWord :

switch -regex ($lastWord) {

    # Handle inline type search, e.g. new-object .identityreference<tab> or .identityre<tab>
    '^\.(\w+)$' {
       
$typeName = $matches[1]
      
$types = $dsTabexpansion.tables["Types"]
       
$rowFilter = "name like '%.${typeName}%'"
       $types.select($rowFilter) | % {$_["name"] } | Invoke-TabItemSelector $lastWord -Select $SelectionHandler
       break;
    }

The type search is initiated by prefixing the full or partial type name with a period (.) and then hitting tab. There you have it, inline type search. Have fun!

posted on Friday, August 24, 2007 3:28:45 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2] Trackback
# Monday, August 20, 2007

Another answer I posted to the NG, and not all that hard once you know the right classes to use from the BCL. But if you didn't know where to look, I can imagine it being a royal pain in the ass.

--- begin ConvertTo-Sid.ps1 ---

param ($account = $(throw "need account in form domain\username or
[ntaccount] object"))

if ($account -is [security.principal.ntaccount]) { 
    $ntaccount = $account

} else {
   
$ntaccount = new-object security.principal.ntaccount $account
}

$ntaccount.translate( [security.principal.securityidentifier] )
-- end ConvertTo-Sid.ps1 ---

and the reverse:

--- begin ConvertTo-NTAccount.ps1 ---

param ($sid = $(throw "need sid string or [securityidentifier] object"))

if ($sid -is [security.principal.securityidentifier]) {
    $securityidentifier  = $sid

} else { 
    $securityidentifier  = new-object security.principal.securityidentifier $sid
}

$securityidentifier.translate( [security.principal.ntaccount] )

--- end ConvertTo-NTAccount.ps1 ---

You can pass strings as args, or their respective native objects. They both output objects. The output of one can be used as the input of the other.

 

posted on Monday, August 20, 2007 5:50:36 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Thursday, August 16, 2007

Then get ready for PS+ !

http://www.powershell.com/plus/

 

posted on Thursday, August 16, 2007 4:32:09 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Tuesday, August 14, 2007

I got tired of typing add-pssnapin blah, especially with Quest's AD snap-in's longer than usual name so I came up with a little script that enumerates registered snap-ins, and lists a simple numbered menu showing those that are not already loaded into memory. You just type the number and hit enter to load it. Enter on an empty line exits the script. Yeah, it's brain-dead, but hey, I think I was brain-dead to continually type long commands in every single session to load stuff ;-) 

UPDATE 2007-08-16: I was even more braindead than I realised - I copied up a broken version without the $notloaded array. Ooops. Fixed.

  1. write-progress "Enumerating Snapins" "Registered..."
  2. $snaps = get-pssnapin -r
  3.  
  4. write-progress "Enumerating Snapins" "Loaded..."
  5. $loaded = get-pssnapin | % {$_.name}
  6. $notloaded  = @()
  7.  
  8. write-progress "Enumerating Snapins" "Complete." -Completed # glitchy
  9.  
  10. $i = 0; "";
  11.  
  12. foreach ($snap in $snaps) {
  13.         if (-not($loaded -contains $snap.name)) {
  14.                 Write-Host -fore cyan -nonewline "${i}) "
  15.                 write-host -fore green $snap.name
  16.                     $notloaded  += $snap
  17.                 $i++
  18.         }
  19. }
  20.  
  21. if ($i -eq 0) {
  22.         Write-Warning "Any eligible snapins are already loaded."
  23.         exit;
  24. }
  25.  
  26. $i--;
  27. Write-Host -fore yellow "<enter> to quit"
  28.  
  29. do {
  30.         write-host -nonewline "Load 0 - ${i}: ";
  31.         $choice = [console]::readline()
  32.         if ($choice -and ($choice -lt 0) -or ($choice -gt $i)) {
  33.                 Write-Warning "'${choice}' is out of range!"
  34.                 continue;
  35.         }
  36.         if ($choice) {
  37.                 "loading $($notloaded[$choice].name) ..."
  38.                 add-pssnapin $notloaded[$choice]
  39.         }
  40. } while ($choice)


Just pop it into a function or a ps1 script.

You'll notice how when this is run from a clear screen, the progress bar disappears, then suddenly reappears and the first few menu items are hidden. This is a bug in the powershell.exe host. Vote on it here:

https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=291380&SiteID=99

 

posted on Tuesday, August 14, 2007 4:05:06 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2] Trackback
# Monday, August 13, 2007

Someone on the PowerShell NG asked recently if there was some kind of equivalent to c#'s "using namespace;" or VB.NET's "Imports namespace" statements. Well, the short answer to that is no, not really. However, you can simulate it by creating functions for each static method on the class:

  1. param([type]$type = $(throw "need a type!"))
  2.  
  3. $type | gm -static | ? {$_.membertype -eq "method" } | % {
  4.         $func = "function:$($_.name)"
  5.         if (test-path $func) { remove-item $func }
  6.         $flags = 'Public,Static,InvokeMethod,DeclaredOnly'
  7.         new-item $func -value "[$($type.fullname)].InvokeMember('$($_.name)', ${flags}, `$null, `$null, `$args[0])"
  8. }

Save this as "import.ps1" for example and use like so:

PS > . .\import.ps1 ([Math])
...

PS > Sin 1
0.841470984807897

Methods that take multiple args must have them passed as a single array:

PS > Max @(1,2)
2

Have fun!

Update: there are some interesting extensions appearing based around my initial post on microsoft.public.windows.powershell.

 



posted on Monday, August 13, 2007 4:27:05 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2] Trackback
# Tuesday, August 07, 2007

I noticed that my last script could not handle more complex webservices like the MSDN ContentService (unlike WSDL.EXE) for example, so I rewrote it from the ground up. This time, it supports Basic Profile 1.1 and can generate proxy instances for simple ASMX services and more complicated multi-schema services like the MSDN/TechNet Publishing System (MTPS) Content Service.

What's New:

  • No need for the .NET 2.0 SDK, just PowerShell
  • Auto discovery of all referenced schema
  • No need to point it at page.asmx?wsdl any more, just the asmx
  • Validation against WS-I Basic Profile 1.1

get-webservice2.ps1.txt (4.28 KB)  (updated 2007/12/19: errant comma in write-progress fixed)

  1. #
  2. # Get-WebService.ps1 (v2.0 Aug 6, 2007)
  3. #
  4. # Oisin Grehan <oising@gmail.com> (x0n)
  5. #
  6. # Usage:
  7. #   $proxy = .\get-webservice2.ps1 [-Url] http://site/service.asmx [-Anonymous] [[-SoapProtocol] <Soap | Soap12>]
  8. #
  9. # to see available webmethods:
  10. # $proxy | gm
  11. #
  12.  
  13. # $url = "http://services.msdn.microsoft.com/contentservices/contentservice.asmx?wsdl"
  14.  
  15. param($url = $(throw "need `$url"), [switch]$Anonymous, [string]$protocol = "Soap")
  16.  
  17. [void][system.Reflection.Assembly]::LoadWithPartialName("system.web.services")
  18.  
  19. trap {
  20.         "Error:`n`n $error";
  21.         break;
  22. }
  23.  
  24. #$request = [System.Net.WebRequest]::Create($url);
  25. $dcp = new-object system.web.services.discovery.discoveryclientprotocol
  26.  
  27. if (! $Anonymous) {
  28.     Write-Progress "Network Credentials" "Awaiting input..."
  29.     $dcp.Credentials = (Get-Credential).GetNetworkCredential()
  30. }
  31.  
  32. Write-Progress "Discovery" "Searching..."
  33. $dcp.AllowAutoRedirect = $true
  34. [void]$dcp.DiscoverAny($url)
  35. $dcp.ResolveAll()
  36.  
  37. # get service name
  38. foreach ($entry in $dcp.Documents.GetEnumerator()) { # needed for Dictionary
  39.     if ($entry.Value -is [System.Web.Services.Description.ServiceDescription]) {
  40.         $script:serviceName = $entry.Value.Services[0].Name
  41.         Write-Verbose "Service: $serviceName"
  42.     }
  43. }
  44.  
  45. Write-Progress "WS-I Basic Profile 1.1" "Validating..."
  46. $ns = new-Object System.CodeDom.CodeNamespace # "WebServices"
  47.  
  48. $wref = new-object System.Web.Services.Description.WebReference $dcp.Documents, $ns
  49. $wrefs = new-object system.web.services.description.webreferencecollection
  50. [void]$wrefs.Add($wref)
  51.  
  52. $ccUnit = new-object System.CodeDom.CodeCompileUnit
  53. [void]$ccUnit.Namespaces.Add($ns)
  54.  
  55. $violations = new-object system.web.Services.Description.BasicProfileViolationCollection
  56. $wsi11 = [system.web.services.WsiProfiles]::BasicProfile1_1
  57.  
  58. if ([system.web.Services.Description.WebServicesInteroperability]::CheckConformance($wsi11, $wref, $violations)) {
  59.     Write-Progress "Proxy Generation" "Compiling..."
  60.    
  61.     $webRefOpts = new-object System.Web.Services.Description.WebReferenceOptions
  62.         $webRefOpts.CodeGenerationOptions = "GenerateNewAsync","GenerateProperties" #,"GenerateOldAsync"
  63.  
  64.         #StringCollection strings = ServiceDescriptionImporter.GenerateWebReferences(
  65.         #       webReferences, codeProvider, codeCompileUnit, parameters.GetWebReferenceOptions());
  66.  
  67.     $csprovider = new-object Microsoft.CSharp.CSharpCodeProvider
  68.         $warnings = [System.Web.Services.Description.ServiceDescriptionImporter]::GenerateWebReferences(
  69.                 $wrefs, $csprovider, $ccunit, $webRefOpts)
  70.        
  71.     if ($warnings.Count -eq 0) {
  72.         $param = new-object system.CodeDom.Compiler.CompilerParameters
  73.         [void]$param.ReferencedAssemblies.Add("System.Xml.dll")
  74.         [void]$param.ReferencedAssemblies.Add("System.Web.Services.dll")       
  75.         $param.GenerateInMemory = $true;
  76.         #$param.TempFiles = (new-object System.CodeDom.Compiler.TempFileCollection "c:\temp", $true)
  77.         $param.GenerateExecutable = $false;
  78.         #$param.OutputAssembly = "$($ns.Name)_$($sdname).dll"
  79.         $param.TreatWarningsAsErrors = $false;
  80.         $param.WarningLevel = 4;
  81.        
  82.         # do it
  83.         $compileResults = $csprovider.CompileAssemblyFromDom($param, $ccUnit);
  84.  
  85.         if ($compileResults.Errors.Count -gt 0) {
  86.             Write-Progress "Proxy Generation" "Failed."
  87.             foreach ($output in $compileResults.Output) { write-host $output }
  88.             foreach ($err in $compileResults.Errors) { write-warning $err }           
  89.         } else {           
  90.             $assembly = $compileResults.CompiledAssembly
  91.  
  92.             if ($assembly) {
  93.                 $serviceType = $assembly.GetType($serviceName)               
  94.                 $assembly.GetTypes() | % { Write-Verbose $_.FullName }
  95.             } else {
  96.                 Write-Warning "Failed: `$assembly is null"
  97.                                 return
  98.             }
  99.            
  100.             # return proxy instance
  101.             $proxy = new-object $serviceType.FullName
  102.             if (! $Anonymous) {
  103.                 $proxy.Credentials = $dcp.Credentials
  104.             }
  105.             $proxy # dump instance to pipeline
  106.         }
  107.     } else {
  108.         Write-Progress "Proxy Generation" "Failed."       
  109.         Write-Warning $warnings
  110.     }
  111.     #Write-Progress -Completed
  112. }
posted on Tuesday, August 07, 2007 12:44:03 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Thursday, August 02, 2007

For me, this opens up a whole new world of PowerShell interaction with remote servers. No more constructing soap packets - just point it at a WSDL url, and you get back a first-class object with methods that you can call corresponding to remote webmethods. PowerShell's [xml] accelerator automatically wraps the responses so you can easily manipulate the results.

Have fun!

UPDATE: newer more robust version 2.0 available!

posted on Thursday, August 02, 2007 1:56:44 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Tuesday, July 17, 2007

PSEventing 1.0 Released

Trap and respond to synchronous & asynchronous .NET events within your powershell scripts with this easy to use suite of cmdlets.

What's new?


  • A PSEvent object's Source property now contains the PSVariable wrapping the original object which generated the event. This resolves scoping issues with 0.5 whereby it wasn't always possible to reach the original sender by variable name (using get-variable).
  • New-Event cmdlet added for inserting user-generated "events" into the queue. This cmdlet allows attaching an abitrary object payload which is available in the PSEvent's Args.Data property.
  • All event handlers are now AUTOMATICALLY unhooked if the original PSVariable goes out of scope! No more phantom events continually generated if you forget to use the explicit "Disconnect-EventListener" and lose your reference. Simply null out a variable or let it disappear into the scopeless void!
  • and finally, some bugfixes, as listed on the release page.

Pick it up from CodePlex, includes source: http://www.codeplex.com/PSEventing/

 

posted on Tuesday, July 17, 2007 9:41:47 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Wednesday, May 16, 2007

I've included some handy wrapper functions to make working with events and scriptblocks a bit easier - download the example script called "event handling wrapper functions" from the Releases page.

  • Add-EventHandler : Automatically run a scriptblock when the provided event occurs on a given variable (when inside Do-Events loop)
    • Add-EventHandler [-Variable] <PSVariable> [-EventName] <String> [-Script] <ScriptBlock>
  • Remove-EventHandler : Remove all bindings for the specific event from the given variable.
    • Remove-EventHandler [-Variable] <PSVariable> [-EventName] <String>
  • Do-Events : Much like VB's DoEvents command, this function puts PowerShell into a waiting state and will call your ScriptBlocks when your configured events occur. Use Ctrl+C to exit.
    • Do-Events [-ExitImmediately] <Boolean>


NOTE: your ScriptBlocks will only be called if you're inside a Do-Events loop.

1# Add-PSSnapin PSEventing
 
2# $fsw = new-object system.io.filesystemwatcher
3# $fsw.Path = "c:\temp"
4# $fsw.EnableRaisingEvents = $true
 
5# Add-EventHandler (get-variable fsw) deleted {
            param([System.Management.Automation.PSVariable]$variable, [EventArgs]$args)
     ... do stuff ... }
 
6# Do-Events $false

an example how to wire up an event using the wrapper scripts

posted on Wednesday, May 16, 2007 7:02:10 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Sunday, May 13, 2007

Nrgghg,  I feel another PowerShell project coming on ...  enter PowerShell Eventing 0.5 Beta! With the magic of lightweight codegen, aka LCG, a smidgeon of reflection (well, quite a bit) and some inspiration, I managed to cough up this latest project.

While you cannot directly bind scriptblocks as eventhandlers, you can automatically route events in realtime to a background queue and deal with them in your time with a special Get-Event cmdlet. There is a sample walkthrough on the home page of the Wiki, and you can download a Sql backup script which shows progress reporting, all in script!

PS 1# Add-PSSnapin PSEventing                                                                      
PS 2# $wc = new-object system.net.webclient                                                        
PS 3# get-eventbinding -IncludeUnboundEvents | ft -auto                                            
                                                                                                                        
VariableName   EventName               TypeName  Listening                                                              
------------   ---------               --------  ---------                                                              
wc             Disposed                WebClient     False                                                              
wc             DownloadDataCompleted   WebClient     False                                                              
wc             DownloadFileCompleted   WebClient     False                                                              
wc             DownloadProgressChanged WebClient     False                                                              
wc             DownloadStringCompleted WebClient     False                                                              
wc             OpenReadCompleted       WebClient     False                                                              
wc             OpenWriteCompleted      WebClient     False                                                              
wc             UploadDataCompleted     WebClient     False                                                              
wc             UploadFileCompleted     WebClient     False                                                              
wc             UploadProgressChanged   WebClient     False                                                              
wc             UploadStringCompleted   WebClient     False                                                              
wc             UploadValuesCompleted   WebClient     False                                                              
                                                                                                                                                                                                                                        
PS 4# Connect-EventListener wc disposed -verbose                                                   
VERBOSE: Target is a WebClient                                                                                          
VERBOSE: Now listening for 'disposed' events from $wc                                                                   
PS 5# $wc.Dispose()                                                                                
PS 6# get-event | ft -auto                                                                         
                                                                                                                        
Occurred             Source      Name     Args                                                                          
--------             ------      ----     ----                                                                          
5/13/2007 8:04:20 PM variable:wc Disposed System.EventArgs                                                              
                                                                                                                                                 

Have fun!

posted on Sunday, May 13, 2007 8:09:16 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1] Trackback
# Tuesday, April 17, 2007

I had an interesting problem today, I wanted to temporarily set a powershell variable to "readonly." So, I tried the obvious, and expected it to fail (it did):

PS E:\projects\powershell> $p = 7                                                                                       
PS E:\projects\powershell> (gi variable:p).Options = "readonly"                                                         
PS E:\projects\powershell> (gi variable:p).Options = "none"                                                             
Exception setting "Options": "Cannot overwrite variable p because it is read-only or constant."                         
At line:1 char:17                                                                                                       
+ (gi variable:p).O <<<< ptions = "none"                                                                                

Of course it seems pretty obvious in hindsight that this would fail, but why do they provide both "const" AND "readonly" choices for variables? well, after some spelunking with the excellent Reflector (my first choice these days, I find it easier than navigating around the msdn behemoth), I found the answer:

internal void SetOptions(ScopedItemOptions newOptions, bool force)
{
    using (IDisposable disposable = tracer.TraceMethod(newOptions))
    {
        if (this.IsConstant || (!force && this.IsReadOnly))
        {
            SessionStateUnauthorizedAccessException exceptionRecord = new SessionStateUnauthorizedAccessException(this.name, SessionStateCategory.Variable, "VariableNotWritable");
            tracer.TraceException(exceptionRecord);
            throw exceptionRecord;
        }

E.g, if the variable is not a constant, you can force the change:

PS E:\projects\powershell> set-item variable:p $p -force                                                                
PS E:\projects\powershell> (gi variable:p).Options                                                                      
None                                                                                                                    

This resets the "readonly" bit.

posted on Tuesday, April 17, 2007 9:07:39 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Wednesday, March 07, 2007

Well, we've done it! Head on over to grab the latest release of PowerShell's most advanced and feature-rich snap-in!

http://www.codeplex.com/PowerShellCX

I'm pretty proud of this one. I wrote the archive cmdlets write-zip, write-bzip2, write-tar and write-gzip. I also wrote a general purpose assembly resolver cmdlet, resolve-assembly, and also an AssemblyCache provider; yes, the GAC.

 

posted on Wednesday, March 07, 2007 10:42:21 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Saturday, January 27, 2007

Now that I'm involved in two PowerShell projects, it's probably only fair that I should spend a little time talking about the non-sharepoint one. The PowerShell Community Extensions -- or PSCX for short -- consists of a group of maybe half a dozen people who are passionate about Monad (there! he said it again! stone him!) who are trying to fill in some gaps in the support for everyday operations that we're used to using from the good old fashioned command prompt. Yes, you can use ping.exe from powershell, but it's not native and if you want to use any of the output you must revert to old-school string parsing a la awk or sed. There's nothing wrong with awk or sed, but it's not the "PowerShell Way." Everything should be an object, right Mr Gosling?

For our upcoming release 1.1, in addition to some reimplementations of existing DOS-based tools, there are some pretty cool extras such as an enhanced Tab-completion system. This is implemented in c# for performance -- as opposed to the current script-based approach -- and can tab complete much more things such as static members, types, and can partially evaluate statements. There are also symlink and filesystem commands, and my contribution: bzip2, gzip, tar and zip creation cmdlets.

posted on Saturday, January 27, 2007 11:59:50 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Friday, January 05, 2007

So, happy new year everyone (read: both of you reading this blog) -- fyi, there's a powershell v1.0 binary compatible build of my SharePoint PSProvider up on codeplex now. I also filled out the Wiki with some information that was sorely lacking.

http://www.codeplex.com/PSSharePoint

posted on Friday, January 05, 2007 3:00:46 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Saturday, November 04, 2006

As a sign that I'm trying to ramp up development (especially getting a MOSS 2007 layer working), I've snagged me a spot on the codeplex for this code. Feel free to ask for features, provide ideas, critique whatever on the forums provided. I'm also working on an ArchivePSProvider which will allow navigation of Zip and Tar files as hierarchical stores as well as providing some pipe-friendly cmdlets for tar/gzip/zip, e.g.  get-childitems -inc *.log | out-tar | out-gzip

posted on Saturday, November 04, 2006 8:40:42 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Friday, October 20, 2006

No changes. Too busy at work. Argh. However, it now works with PS RC2. Argh.

(old binary removed) - latest version always at:
http://www.codeplex.com/PSSharePoint

I'm also trying to finish off zip, gzip, tar and bzip2 cmdlets along with an experimental tar/zip provider. Stay tuned, if you've got nothing better to do and a few months to spare. Honestly though, I hope to finish them before the end of the month. There's a warm bed waiting for them up on the CodePlex I'm told...

posted on Friday, October 20, 2006 3:52:28 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Thursday, May 25, 2006

Just released SharePoint SDKs; these look quite meaty -- The wss one weights in at nearly 34MB, compressed.

Windows SharePoint Services V3: SDK

This SDK contains conceptual overviews, programming tasks, samples, and references to guide you in developing solutions based on Microsoft® Windows® SharePoint® Services (version 3).
 
SharePoint Server 2007: SDK

The Microsoft Office SharePoint Server 2007 (Beta) SDK contains conceptual overviews, programming tasks, code samples, and references to guide you in developing solutions based on Microsoft® Office SharePoint® Server 2007.
 

I've also just installed the fresh Beta 2 releases on a virtual machine, so I'll be looking forward to porting my PowerShell SharePoint provider to v3...
posted on Thursday, May 25, 2006 12:15:59 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Friday, May 12, 2006

Hmm, I just realised that RC1 and RC1 Refresh have different build numbers and are not binary compatible. Here's another drop built for rc1 refresh, 1.0.9567.0:

(old binary removed) - latest version always at:
http://www.codeplex.com/PSSharePoint

posted on Friday, May 12, 2006 1:44:05 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Wednesday, May 10, 2006

I've got the bones of a Compressed Folder (think zip files) provider lying around, and I was wondering if people would be interested to see a blog entry on how to put it together? I think I need a bit of a break from the SharePoint provider, so I'd be happy to write up a two-parter on how to get it up and running. Everyone seems obsessed with cmdlets; has anyone else got the psprovider bug, or is the learning curve too steep/documentation too confusing?

posted on Wednesday, May 10, 2006 12:22:48 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [3] Trackback
# Friday, April 28, 2006

Just some minor fixes, and the obvious API changes needed to be RC1 compatible. Source has been pushed the the MSH Community Extensions Workspace. For those of you who just want to play, you can get the latest binary at the bottom of this entry. Installation has changed slightly:

PS c:\temp> InstallUtil Nivot.PowerShell.SharePoint.dll
...
PS c:\temp> Add-PSSnapin Nivot.PowerShell.SharePoint
PS c:\temp> New-PSDrive wss SharePoint
http://mywss/
PS c:\temp> cd wss:
PS wss:\>

Apart from some minor refactoring, I've added some more actions to allow copy/move/delete between users/roles/groups and webs. Here's an example of how easy it is to define the operations for adding and removing users to a SPWeb using my generics-based provider model:

// add SPUser and place in Reader role by default
RegisterAdder<SPUser>(
    new Action<IStoreItem>(
        delegate(IStoreItem item) {
            SPUser user = (SPUser)item.NativeObject;
            NativeObject.Roles["Reader"].AddUser(user);
        }
    )
);

// remove SPUser
RegisterRemover<SPUser>(
    new Action<IStoreItem>(
        delegate(IStoreItem item) {
            SPUser user = (SPUser)item.NativeObject;
            NativeObject.Users.Remove(user.LoginName);
        }
    )
);

The above code allows the following to work:

   PS wss:\subweb> copy ..\!roles\contributor\username .

where . represents wss:\subweb, an SPWeb object.

Have fun!

Build RC1 Only, NOT RC1 Refresh -- for latest version see homepage 
Download: Nivot.PowerShell.SharePoint-0.51.zip (40.94 KB)

(edit: minor bug found -- v0.51 update)

posted on Friday, April 28, 2006 1:12:10 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Tuesday, April 25, 2006

Well, one sign that we're close to a release -- they've let the marketing monkeys out of their boxes for the day. What's the first stinky poop thrown from the cage? the name change: "powershell." I find all this kind of self-congratulatory naming very indulgent or something. I imagine the ultimate extension of this to be renaming a country to "WE ROCK."

I find it hard to believe that the richest company in the world can send a dozen highly paid marketeers into a room with their free soda and the best they can come up with is the "power" prefix. Anyhow, perhaps it fits in with the "powertoys" brand or something. Another thing that makes it seem a bad choice is that there are already several "powershell" products out there in the world. Not to mention "powerdiets," "powershoes," and no doubt several varieties of "power-pants."

Man, I'm spending too much time on this non-issue already. I'm starting to feel psychotic... go grab yerselves a new shell already.

http://www.microsoft.com/downloads/results.aspx?freetext=powershell&DisplayLang=en

 

posted on Tuesday, April 25, 2006 9:08:52 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback