$MindWarpingPower = $Cmdlets + $ScriptBlock_Parameters

$MindWarpingPower = $Cmdlets + $ScriptBlock_Parameters

  • Comments 11

A while ago I blogged about the power of Flexible Pipeling Scriptblock parameters.  The mechanics of this are quite simple:  In a pipeline environment, if you provide a SCRIPTBLOCK to a parameter which does not take a SCRIPTBLOCK or an OBJECT, the PowerShell engine assigns the current pipeline object to the variable "$_", runs the scriptblock and uses whatever value it returns for the value of that parameter. 

 

Looking back at this, I don't think we've done a good enough job explaining it or promulgating its use because it is incredibly powerful and I just don't see it being used enough.  Let me give a couple of simple examples of how I used it this morning.  It is a technique that is well worth learning because you can do MIND-WARPING things once you figure out how to leverage it's power.

 

This morning I wanted to email a set of PS1 files to someone.  Exchange blocks PS1 files so what I do is to rename these by added ".txt" to them.  I've see number of people sharing scripts that do something similar using the following technique:

 

foreach ($file in dir *.ps1) {
    $newName = $file.Name + ".TXT"
    copy-item -Path $file.Name -Destination $newName
}

 

That works but heavens, why do all that when you could just use ScriptBlock Parameters?  Now when you start using scriptblocks, you might what to leverage our friend -WHATIF until you are comfortable with how it works (isn't -WHATIF da bomb?).

Dir *.ps1 | Copy-Item -Destination {$_.Name + ".TXT"} -Whatif

 

On the way back, you can do this:

Dir *.ps1.txt |Rename-item -NewName {$_.Name -replace ".txt"} -whatif

 

Now the scriptblock can be as large as you like you can do anything you want in it so feel free to get creative.  Imagine that you wanted to normalize/serialize the names of a set of scripts.  You could do it this way:

$Global:x=0; dir *.ps1 |copy-Item -Destination {"ScriptFile{0:000}" -f $global:x++} -whatif

Here is an fun one:

dir *.ps1 |copy-Item -Destination {if ($_.length -ge 100) {$_.Name + ".BIG"} else {$_.Name + ".SMALL"}} -whatif

 

Experiment and Enjoy!

Jeffrey Snover [MSFT]
Windows Management Partner Architect
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

Leave a Comment
  • Please add 1 and 4 and type the answer here:
  • Post
  • PingBack from http://microsoftnews.askpcdoc.com/?p=3373

  • It is a great idea to promulgate this. It seems to me that most of PowerShell users including fanatics never heard about this technique.

    Will it work in the same way for script cmdlets?

  • > Will it work in the same way for script cmdlets?

    It doesn't right now but it should.

    Jeffrey Snover [MSFT]

    Windows Management Partner Architect

    Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell

    Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

  • Pingback from http://blog.sapien.com/index.php/2008/04/22/scriptblocks-as-cmdlet-parameter-values/

  • this here therefore sets the parameter to a different value each time an item comes through the pipeline despite the fact that the cmdlet gets called only once?

    Thus it will only behave correctly for cmdlets that read the parameter each item, rather than just reading it in the beginprocess section?

    -Karl

  • I have been personally evangelizing PowerShell at work and also on my blog for a while now. The more I dig into PowerShell, the more I realize how powerful it is and it will change the face of shell world.

    So I made a PowerShell cap.

    http://www.devslife.com/2008/04/20/powershell帽/

    Though I blog in Japanese, I live near Seattle. If you see me wearing the cap, please say hi to me! :)

  • @karl

    I haven't tried it, but I don't think scriptblock parameters are much different from pipline parameters. So I guess you have to re-read the parameter for each item in the pipline.

    The alternative would be that your CmdLets Begin/Bla/Process methods are called for each pipline item in the case of scriptblock parameters being present. But then your CmdLet would behave rather different in those two scenarios (think about measure-object), so I don't think it works that way.

  • uh.. never heard of this.. madness. Does this work for v1.0 ? (only have v2 ctp here).

  • > Does this work for v1.0 ?

    Yes.

    Jeffrey Snover [MSFT]

    Windows Management Partner Architect

    Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell

    Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

  • This sounds a lot like what I did with my AD mail enabled object dump scripts.  Being an old crusty Exchange admin I've learned that it's nice to have a directory dump that you can refer back to from time to time, I came up with something like this when Powershell came out and I started using it (abbreviated and sanitized):

    $date = get-date -format yyyyMMdd

    $fname = "userDump-" + $date + ".csv"

    $domain = New-Object System.DirectoryServices.DirectoryEntry

    $DN1 = (get-content Z:\Directory\UserDump\DN-import.txt)

    $DN2 = $DN1 | foreach {

    $domain.PSBase.Children.find($_)

    }

    filter Format-DumpADUser {

    $_ | select @{name='distinguishedName';Expression={$_.distinguishedName} },

    @{ name='homeMDB'; Expression={$_.homeMDB} },

    @{ name='displayName'; Expression={$_.displayName} },

    @{ name='mail'; Expression={$_.mail} },

    @{ name='proxyAddresses'; Expression={$_.proxyAddresses} },

    @{ name='memberOf'; Expression={$_.memberOf} },

    @{ name='whenChanged'; Expression={$_.whenChanged} },

    @{ name='whenCreated'; Expression={$_.whenCreated} },

    @{ name='extensionAttribute1'; Expression={$_.extensionAttribute1} },

    @{ name='mailNickname'; Expression={$_.mailNickname} },

    @{ name='mDBUseDefaults'; Expression={$_.mDBUseDefaults} },

    @{ name='mDBOverQuotaLimit'; Expression={$_.mDBOverQuotaLimit} },

    @{ name='mDBStorageQuota'; Expression={$_.mDBStorageQuota} }

    }

    $DN2 | foreach {

    $_.PSBase.Children

    } | Format-DumpADUser | export-csv -NoTypeInformation Z:\Directory\Archive\$fname

  • I have a case where a scriptblock parameter is evaluated literally:

    Get-DistributionGroup | Set-DistributionGroup -DisplayName {"~$($_.name)"}

    The name of the groups are updated with: "~$($_.name)".

    Could it be because -DisplayName is a dynamic parameter?

    Here is the The relevant trace-command output:

    DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args

    [Set-DistributionGroup]

    DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args

    [Set-DistributionGroup]

    DEBUG: ParameterBinding Information: 0 : BIND cmd line args to DYNAMIC

    parameters.

    DEBUG: ParameterBinding Information: 0 : DYNAMIC parameter object:

    [Microsoft.Exchange.Data.Directory.Management.DistributionGroup]

    DEBUG: ParameterBinding Information: 0 : BIND NAMED args to DYNAMIC

    parameters

    DEBUG: ParameterBinding Information: 0 : BIND arg ["~$($_.name)"]

    to parameter [DisplayName]

    DEBUG: ParameterBinding Information: 0 : COERCE arg type

    [System.Management.Automation.ScriptBlock]

    to

    [System.String]

    DEBUG: ParameterBinding Information: 0 : CONVERT arg type

    to param type using

    LanguagePrimitives.ConvertTo

    DEBUG: ParameterBinding Information: 0 : CONVERT SUCCESSFUL

    using LanguagePrimitives.ConvertTo:

    ["~$($_.name)"]

    DEBUG: ParameterBinding Information: 0 : BIND arg

    ["~$($_.name)"]

    to param [DisplayName] SUCCESSFUL

    -----

    Shay Levi

    $cript Fanatic

    http://scriptolog.blogspot.com

Page 1 of 1 (11 items)