One of the things I really like about PowerShell is how through the use of script blocks and locally scoped functions, you can create interesting script using perfectly valid, albeit odd looking PowerShell script. I had first done something like this without script blocks using the pipeline which was pretty ugly. It was mostly just a way of quickly building up objects using functions that wrapped New-Object and Add-Member.

New-PSObject |
    Field FirstName 'Josh' |
    Field LastName 'Einstein'

That was pretty handy but as a C# guy it still looked ugly. I got the idea from Bruce Payette’s book, PowerShell in Action to use script blocks and locally-scoped functions so that I didn’t have to pollute the runspace with a bunch of functions that were only valid within the context of another function.

New-PSObject {

    Field FirstName 'Josh'
    Field LastName 'Einstein'

    Property FullName { "$($this.FirstName) $($this.LastName)" }

    Method SayHello {
        param($to)
        "$($this.FirstName) says 'Hello' to $to!"
    }

}

How does it work? Well, like most things in PowerShell, functions have scope. So a function defined within another function is only valid during the execution of the outer function. This makes it possible to trample on existing functions and cmdlets too.

In Bruce Payette’s book he calls them "little languages".

This technique allows for some really clean and concise syntax similar to how some DSL’s are being used in .NET.

# Global functions used by New-PSObject
function Field([String]$Name,$Value,[String]$Format) {
    @{ MemberType='NoteProperty'; Name=$Name; Value=$Value; Format=$Format }
}
function Property([String]$Name,[ScriptBlock]$GetDefinition,[ScriptBlock]$SetDefinition) {
    @{ MemberType='ScriptProperty'; Name=$Name; Value=$GetDefinition; SecondValue=$SetDefinition }
}
function Method([String]$Name,[ScriptBlock]$MethodDefinition) {
    @{ MemberType='ScriptMethod'; Name=$Name; Value=$MethodDefinition }
}

##############################################################################
#.SYNOPSIS
# Creates a PSObject and initializes it with fields, properties, and methods
# using a ScriptBlock written in a mini-language.
#
#.DESCRIPTION
# The ScriptBlock passed to the Definition parameter can contain commands
# that are private to this function, such as Field, Property, and Method
# which define members on the new PSObject.
#
#.PARAMETER Definition
# The ScriptBlock that builds up the PSObject by using special commands such
# as Field, Property, and Method. See the example for more information.
#
#.PARAMETER InputObject
# A HashTable that contains key/value pairs that will be turned into
# NoteProperties on the new PSObject.
#
#.EXAMPLE
# New-PSObject {
#     Field FirstName 'Josh'
#     Field LastName 'Einstein'
#     Property FullName { "$($this.FirstName) $($this.LastName)" }
#     Method GetFullName { "$($this.FirstName) $($this.LastName)" } 
# }
#
#.EXAMPLE
# @{FirstName='Josh';LastName='Einstein'} | New-PSObject
##############################################################################
function New-PSObject {

    [CmdletBinding(DefaultParameterSetName='Definition')]
    param ( 

        [Parameter(ParameterSetName='Definition', Position=1)]
        [ScriptBlock]$Definition,
        
        [Parameter(ParameterSetName='Hashtable', Mandatory=$true, ValueFromPipeline=$true)]
        [HashTable]$InputObject
        
    )

    begin { }
    
    process {

        $Object = New-Object PSObject
        
        if ( $InputObject ) {        
            foreach ( $k in $InputObject.Keys ) {
                Add-Member -InputObject $Object -MemberType NoteProperty -Name $k -Value $InputObject[$k]
            }
        }
        elseif ( $Definition ) {

            $Members = &$Definition
            
            foreach ( $Member in $Members ) {
                if ( $Member.Format ) { $Member.Value = [String]::Format([String]$Member.Format,[String]$Member.Value) }
                if ( $Member.ContainsKey('SecondValue') ) { 
                    Add-Member -InputObject $Object `
                                -MemberType $Member.MemberType `
                                -Name $Member.Name `
                                -Value $Member.Value `
                                -SecondValue $Member.SecondValue
                }
                else {
                    Add-Member -InputObject $Object `
                                -MemberType $Member.MemberType `
                                -Name $Member.Name `
                                -Value $Member.Value
                }
            }

        }
        
        $Object
        
    }
    
    end { }

}

2 Responses to “Clever use of PowerShell syntax”

Comments (2)
  1. Hi Josh,

    How’s it going? Came across your blog while searching for some AD Powershell stuff. It seems like you’ve read Bruce’s book? If so, would you recommend it?

    Thanks, Nathan

  2. Highly. But there’s a second edition coming out in July. You can get the “Early Access Edition” on manning.com.

Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>