Tuesday, November 16, 2010

PowerShell: Making it all happen

I was fielding some basic questions about ‘how do I’ in PowerShell when became aware of some things that seem to be happening around PowerShell.


First, it seems that many PowerShell users tend to disappear and may only reappear months later with dramatically newer and more sophisticated questions.  Where they go in the interim is still a mystery.  Perhaps they have gone to another dimension temporarily.


Second, the PowerShell user community on most Forums was very large a year ago.  The excitement was very high.  Now only the newest, most novice newbies to PowerShell seem to show up at the Forums. What could be causing this?


Third, and last, there are more than a few dozen sites that produce products that add to PowerShell functionality.  Why is this? The user community seems to be disappearing into some sort of black hole.


At first I thought that PowerShell was unacceptable to the community of Windows Administrators but that wouldn’t explain the large and growing number of companies producing value added products.


Next I thought that it might be due to the economy.  Perhaps these users were being laid off and just going home to wait it out.  I looked at the vendor support sites and saw a large number of questions specific to the vendor’s product showing that the users were there but just not visiting the scripting help forums.


Why would this be?  I scanned through all of the scripting support forums for PowerShell.  Some were fielding almost no PowerShell questions.  The ones that did have posts showed that the posts were nearly all from first time users.   Where are all of the more advanced PowerShell users?


Then it hit me.  PowerShell has succeeded in changing the users.  Once they see how it works they find that it can be easily used at any level of expertise.  Use it like a DOS command line or like a VBScript host.  It doesn’t matter.  Use the help system to search for things to make your job easier.  When DOS commands don’t work, use the PowerShell environment to capture the output of existing scripts and commands for further processing.


Once a user discovers these basic elements, PowerShell pretty much automatically teaches them what to do.  Users, then, only need to come back to the forums to post neat solutions or to ask for help with one of the few tough to solve issues; like changing the ownership of an object.  The need to return is lessened by things like PowerShell Community Extensions and ActiveRoles Management Shell for Active Directory.


The way things look from here, after only a few years of PowerShell, the whole computer industry may be put out of business by just a few smart Admins and their PowerShell magic.  Think of PowerShell like using Twitter to talk to computers.  Just text it and they will come!  Power! Ha!

Sunday, November 07, 2010

PowerShell: Advanced Functions (Binding Behavior)


PowerShell binds in a variety of ways.  The pipeline attempts to connect by inspecting the Attributes. of the Function Parameters statement. Explicit binding by name takes precedence whenever the type is compatible.
Here are two variations in behavior:
function Test-MyCmdLetA(
        [parameter(
            ValueFromPipeline=$true
            )]$Name
        ){
    process{
        $Name
    }
}
# test pipeline for 'function A'
dir c:\|Test-MyCmdLetA

function Test-MyCmdLetB(
        [parameter(
            ValueFromPipeline=$true
            )][string]$Name
        ){
    process{
        $Name
    }
}

# test pipeline for function 'B'
dir c:\Test-MyCmdLetB
In the first function above notice that the output is the full object even though we have a property called ‘Name’ on the objects returned from the DIR CmdLet. There is NOT sufficient match to bind by property name.
In the second function I have added a ‘type’ declaration or converter to the parameter statement in the form of [string]$name.  When we test this function the output is the value of the ‘Name’ property of the objects sent through the pipeline.
Now try this:
function Test-MyCmdLetB(
        [parameter(
            ValueFromPipeline=$true
            )][string]$junk
        ){
    process{
        $junk
    }
}

# test pipeline for function 'C'
dir c:\Test-MyCmdLetC
Notice that we still get the name string output. So how is this binding?  What if we don’t want the Name string bound to $junk?
How about if we try and fool the code by putting in a second parameter named 'Name’?
function Test-MyCmdLetB(
        [parameter(
            ValueFromPipeline=$true
            )][string]$junk="Not bound",
        [parameter(
            ValueFromPipeline=$true
            )][string]$Name="Not bound"
        ){
    process{
        $Name
    }
}
dir c:\Test-MyCmdLetB
Now we are getting the strings output on the ‘Name’ parameter just like we want but what is happening on the other parameter ‘$junk’?
function Test-MyCmdLetE(
        [parameter(
            ValueFromPipeline=$true
            )][string]$junk="Not bound",
        [parameter(
            ValueFromPipeline=$true
            )][string]$Name="Not bound"
        ){
    process{
        Write-Host "Name=$Name and Junk=$junk"
    }
}
dir c:\|Test-MyCmdLetE
It appears that we are getting the same output on both arguments…hmmm???
Now try this:
function Test-MyCmdLetF(
        [parameter(
            ValueFromPipelineByPropertyName=$true
            )][string]$junk="Not bound",
        [parameter(
            ValueFromPipeline=$true
            )][string]$Name="Not bound"
        ){
    process{
        Write-Host "Name=$Name and Junk=$junk"
    }
}
dir c:\Test-MyCmdLetF
Now we are only getting the name.  $junk is reported as ‘Not bound’.  Now rey the following:
function Test-MyCmdLetG(
        [parameter(
            ValueFromPipelineByPropertyName=$true
            )][string]$junk="Not bound",
        [parameter(
            ValueFromPipeline=$true
            )][string]$junk2="Not bound",
        [parameter(
            ValueFromPipeline=$true
            )][string]$Name="Not bound"
        ){
    process{
        Write-Host "Name=$Name and Junk=$junk and Junk2=$junk2"
    }
}
dir c:\Test-MyCmdLetG
Now only the property called ‘Name’ is being bound. Why is the first argument being bound even though the named argument (parameter) is being bound?  What is really happening here?
This is the way the properties are discovered and bound.  IN the absence of anexplicit request by name the properties are bound and discovered in order applying any type conversions possible. 
Here is another bit that will help in understanding what is going on.
function Test-MyCmdLetB(
        [parameter(
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true
            )][string]$junk="Not bound",
        [parameter(
            ValueFromPipeline=$true
            )][string]$junk2="Not bound",
        [parameter(
            ValueFromPipeline=$true
            )][string]$Name="Not bound"
        ){
    process{
        Write-Host "Name=$Name and Junk=$junk and Junk2=$junk2"
    }
}
dir c:\Test-MyCmdLetB
I have added the ValueFromPipeline statement in along with the ValueFromPipelineByPropertyName statement and now the output, once again, binds to ALL parameters. Adding both of these statements to the same parameter causes the behavior to ignore the value by name version.  Use only one of these in any parameter.
Here are some references to articles that will help to clarify what is happening and why PowerShell s designed this way:
PowerShell Team Blog: ValuFromPipeLineByPropertyName
Microsoft Connect:ValueFromPipelineByPropertyName overridden by ValueFromPipeline
Previous:
Script CmdLets
The Pipeline
Next:

PowerShell: Advanced Functions (The Pipeline)

How do we handle items passed to our Advanced Function from the PowerShell pipeline?

When we run a CmdLet or a piece of core that outputs objects we can pass this output to another CmdLet as long as the CmdLet is designed to receive the kind of objects we are passing to it.

Let’s set up our Function to receive any object passed in the pipeline and look at the objects that are received.  First we will inspect teh default object in the pipeline using the $_ variable.

function Test-MyCmdLet{
    Param(
        [cmdletBinding()]
        [parameter(ValueFromPipeline=$true)]$stuff
        )
    process{
        ($_.GetType()).Name
    }
}

What do we get as output when running the following?

1. dir c:\ | Test-MyCmdLet
2. dir c:\ | where-object {!$_.psiscontainer} | Test-MyCmdLet
3. Get-Process | Test-MyCmdLet

In case #1 above we get a mixture of object types.  In case #2 we have limited the output to one type by filtering out objects that are containers (folders).  In case #3 we get only ‘Process’ objects.

You can see that our CmdLet can receive any object and perform an inspection or nay other code task we may need to perform on the object.  In this case we are extracting the object’s type-name and passing that into the pipeline.

What happens if we inspect our parameter $stuff using this new version of the CmdLet?

 function Test-MyCmdLet{
         Param(
         [cmdletBinding()]
         [parameter(ValueFromPipeline=$true)]
         $stuff
     )
     process{
         $stuff
     }
 }

Run the same three test examples from above to see how this acts.  Notice that everything being passed into the CmdLet is just being passed back out. 

What does this look like if we use the following version to inspect the objects type?

 function Test-MyCmdLet{
         Param(
         [cmdletBinding()]
         [parameter(ValueFromPipeline=$true)]
         $stuff
     )
     process{
         $stuff.GetType().Name
     }
 }

This looks the same as using the default object.  What good is that?  Well it can be useful however, we really would like to use the parameters for something more useful since we always have access to the full object. Why don’t we ‘bind’ the parameter to our input object?

function Test-MyCmdLet(
        [parameter(
            ValueFromPipelineByPropertyName=$true
            )]$Name
        ){
    process{
        $Name
    }
}

Now we have changed the attribute to ValueFromPipelineByPropertyName.  This will cause the name parameter to be connected to any object property with the 'name ‘Name’.  (not case sensitive).  Run the three examples again and see what happens.

We can use this to bind to one or more properties by name.  This is one of the main and most powerful features of the pipeline.

Previous: Script CmdLets
Next: Binding Behavior

Thursday, November 04, 2010

PowerShell: Advanced Functions (Script CmdLets)

PowerShell calls ‘Script CmdLets’ Advanced Functions.

At a PowerShell prompt type:  help about_advanced_functions

Here is a minimal advanced function that can be used as a CmdLet.   The minimum requirement is to have either [CmdletBinding()] or at least one [Parameter()] statement.

1 function Test-MyCmdLet( 2 [cmdletBinding()] 3 [parameter( 4 ValueFromPipeline=$true 5 ) 6 ]$whatever 7 ){ 8 9 process{ 10 $whatever 11 } 12 }

Now we are sure that the function will be recognized as a CmdLet and not just a regular function.

What does this buy us in PowerShell?  Not much. We can call the function and be prompted for input if we mark this as a ‘Mandatory’ parameter.

[Parameter(Mandatory=$true)]

Now we will be prompted if we don’t provide an argument.

We can also add ‘help’ to the prompt like this:

[Parameter(
    
Mandatory=$true,
     HelpMessage=”Please enter a value for this parameter
)]

Now we will be prompted with this more specific message.

All of this is very nice but it really doesn’t buy much in the way of processing.  What we would really like to do is have the function accept values directly from the pipeline.  To accomplish this we can just add an attribute:

[Parameter(
    
Mandatory=$true,
     HelpMessage=”Please enter a value for this parameter,
     ValueFromPipeline=$true
)]

Now we can pump items in the pipeline directly through the function.

Get-Process  |Test-MyCmdLet

Get-Process | Select-Object processname, handles | Test-MyCmdLet | ft -auto

This will allow us to build pipeline smart functions that can easily add custom processing to our PowerShell scripts.

Next: The Pipeline