When using Where-Object
in script block form, e.g Where-Object {$_.LastWriteTime -gt (Get-Date).AddHours(-1)}
, it is inefficient because the Get-Date
command will be executed for every single pipeline element.
In general it’s recommended to use precomputed values for filtering in script block form:
$OneHourAgo = (Get-Date).AddHours(-1)
Get-ChildItem -File | Where-Object { $_.LastWriteTime -gt $OneHourAgo }
However, in the newer form that doesn’t require the use of $psitem
/ $_
, the command will only run once:
# test function which logs when it gets called
PS C:\> function test { write-verbose -Verbose -Message $(get-date) ; get-date }
# parameter form, it logs being called once
PS C:\> gci | where LastWriteTime -gt (test).addhours(-1)
VERBOSE: 14/01/2020 00:08:52
# scriptblock form, it logs being called many times
PS C:\> gci | where {$_.LastWriteTime -gt (test).addhours(-1)}
VERBOSE: 14/01/2020 00:09:15
VERBOSE: 14/01/2020 00:09:15
VERBOSE: 14/01/2020 00:09:15
[..]
Explanation
A script block is a bit like a function which doesn’t have a name. When passed to a Where-Object
cmdlet it is called in its entirety once for each input object, and so everything inside it is evaluated for each input.
The other form looks like a script block:
Where-Object LastWriteTime -gt (Get-Date).AddHours(-1)
…but it’s not. This is just a cmdlet being called with two positional arguments and one switch. The first argument is the string 'LastWriteTime'
, it binds to the first positional parameter (which is named -Property
). This is no different than running Get-ChildItem name*
, where the string 'name*'
is bound to its first positional parameter (named -Path
).
The second portion looks like the -gt
operator, but it’s actually a cmdlet switch named -gt
.
The last argument is evaluated once, the same as any cmdlet argument (and in this case into a [DateTime]
object), and then bound to the second positional parameter (which is named -Value
).
The Where-Object
call above is no different from:
Where-Object -Property LastWriteTime -gt -Value (Get-Date).AddHours(-1)
Since PowerShell doesn’t care about the order of named arguments, a sadist could also write:
Where-Object -Value (Get-Date).AddHours(-1) -gt LastWriteTime
to unexpectedly produce the same results as the first two.