I've been using PowerShell 5.0 and 5.1 for a long time now and sometimes realise I'm still thinking of the 2.0 and 3.0 era. I wanted to revisit some of the more forgettable improvements.

PowerShell 2.0

#Requires

Allegedly this was out in PowerShell 1.0 but 2.0 added the about_requires help topic. For some reason I never used this because I remember it erroring out somewhere incorrectly.

The rules are:

  • It must be at the start of a line.
  • You can have more than one in a file.
  • If it's a function it must be outside of the function declaration.
  • It supports parameters like -Version x[.y].

    It also supported -ShellId (which is $ShellId and which from my testing is always Microsoft.PowerShell so not very useful) and -PSSnapin (remember those).

In PowerShell 3.0 the more useful -Modules <Module-Name> | <Hashtable> and -RunAsAdministrator switches were added. That's probably what I had tried and failed to use in PowerShell 2.0 all those years back.

Fun fact: #Requires -Version works in a module's .psd1 and .psm1, but -RunAsAdministrator gets ignored. It acts as expected in any dot-sourced files however.


PowerShell 3.0

$PSItem

Wherever you used $_ in a pipeline you could now use $PSItem instead.

Get-ChildItem . | Where { $PSItem.BaseName -eq "Contacts" }

-in and -notin

For the longest time I've been using -contains when it turns out -in and -notin were added ages ago:

Get-ChildItem . | Where-Object { "Contacts", "Desktop" -contains $_.BaseName }
Get-ChildItem . | Where-Object { $_.BaseName -in "Contacts", "Desktop" }

Conversions

I use this one a lot but often forget the syntax. Ordered hash tables and ordered custom objects - but they only work as intended if you're using literals.

[ordered] @{ Something = 1; SomethingElse = 2 }
[PSCustomObject] @{ Something = 1; SomethingElse = 2 }
Name                           Value
----                           -----
Something                      1
SomethingElse                  2

Something SomethingElse
--------- -------------
        1             2

One cool improvement I often don't use is calling a constructor of an object with a hash table to set its properties.

[System.Drawing.Point] @{X=1; Y=2}

Simple member enumeration, with some catches

Previously to retrieve an array of an object's properties it would be done like this:

Get-ChildItem . | Select-Object -ExpandProperty FullName

Now you could do this:

(Get-ChildItem .).FullName

But I always work in Strict Mode which makes this feature unsafe to use as demonstrated below. (Too bad it's very common to see it used in DSC, but at least it's in places which shouldn't be $null).

($null).FullName # Also @($null).FullName

The property 'FullName' cannot be found on this object. Verify that the property exists.
At line:1 char:1
+ ($null).FullName
+ ~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : PropertyNotFoundStrict

Similar features were added with [0] and .Count being able to reference single objects as if they were in arrays. These do not appear to work while in Strict Mode.

ForEach and $null

Back in PowerShell 2 life was a little confusing when dealing with empty arrays and null arrays.

Get-ChildItem . | ForEach-Object { "Never triggered in an empty directory" }
$items = Get-ChildItem .
foreach ($item in $items) {
    "Always triggered in an empty directory"
}

So you often had to do a lot of $null checks. PowerShell 3.0 took this away so that $null will not be iterated on and so will not trigger the above code.

The good part about this is that there is also a simplified Where-Object and ForEach-Object syntax which, combined with the $null handling, lets us use use some of that member enumeration even in combination with Strict Mode. Note however that it must be a property name, and not a property of a property name.

# Old
Get-Service | Where-Object { $_.Name -eq "bits" }
# New
Get-Service | Where-Object Name -eq "bits"

# Old
Get-ChildItem . | Select-Object -ExpandProperty FullName
# New
Get-ChildItem . | ForEach-Object FullName

# No!
Get-ChildItem . | ForEach-Object BaseName.Length

Other minor changes

Get-ChildItem secretly changed under the hood to have slightly different wildcard handling, and in new .psd1 module manifests the ModuleToProcess tag was switched to the RootModule tag. Updating these in old modules is a bit of a hobby of mine.


PowerShell 4.0

-PipelineVariable

Now for any functions supporting the pipeline, you can specify your own pipeline variable on top of $_ and $PSItem. This is useful if you're chaining a long pipeline together and need to reach back further in the pipeline (though I've never done it myself).

Get-ChildItem . * -PipelineVariable Parent | Get-ChildItem -PipelineVariable Child | ForEach-Object {
    "$Parent\$Child"
}

Where and ForEach member on arrays

This was a weird add-on that lets you use a method on arrays. A small caveat is that this doesn't support specifying just the property name, you must use the full $_.PropertyName or $PSItem.PropertyName syntax. I've checked and it handles Strict Mode and $null properly by not iterating it.

# Old
Get-ChildItem | Where-Object { $_.Name -like "C*" }
Get-ChildItem | ForEach-Object { $_.Name }

# New
(Get-ChildItem).Where({ $PSItem.Name -like "C*" })
(Get-ChildItem).ForEach({ $PSItem.Name })

# This also works correctly
$null.Where({ $PSItem.Name -like "C*" })
$null.ForEach({ Name })

PowerShell 5.0

Classes and enums, along with hidden properties. I haven't used any of these yet so I will refer you to the best source I could find.

PowerShell 5.1

Nothing. Poor thing. You should still use it because pipelines are 50% faster.