about_PSItem
Short description
The automatic variable that contains the current object in the pipeline object.
Long description
PowerShell includes the $PSItem
variable and its alias, $_
, as
automatic variables in scriptblocks that process the current object, such
as in the pipeline. This article uses $PSItem
in the examples, but $PSItem
can be replaced with $_
in every example.
You can use this variable in commands that perform an action on every object in a pipeline.
There are a few common use cases for $PSItem
:
- in the scriptblock for the Process parameter of the
ForEach-Object
cmdlet - in the scriptblock for the FilterScript parameter of the
Where-Object
cmdlet - in the intrinsic methods ForEach and Where
- with delay-bind scriptblock parameters
- in a
switch
statement's conditional values and associated scriptblocks - in the
process
block of a function - in a
filter
definition - in the scriptblock of the ValidateScript attribute
The rest of this article includes examples of using $PSItem
for these use
cases.
ForEach-Object Process
The ForEach-Object cmdlet is designed to operate on objects in the pipeline, executing the Process parameter's scriptblock once for every object in the pipeline.
You can use $PSItem
in the Process parameter's scriptblock but not in the
Begin or End parameter scriptblocks. If you reference $PSItem
in the
Begin or End parameter scriptblocks, the value is $null
because those
scriptblocks don't operate on each object in the pipeline.
$parameters = @{
Begin = { Write-Host "PSItem in Begin is: $PSItem" }
Process = {
Write-Host "PSItem in Process is: $PSItem"
$PSItem + 1
}
End = { Write-Host "PSItem in End is: $PSItem" }
}
$result = 1, 2, 3 | ForEach-Object @parameters
Write-Host "Result is: $result"
PSItem in Begin is:
PSItem in Process is: 1
PSItem in Process is: 2
PSItem in Process is: 3
PSItem in End is:
Result is: 2 3 4
Where-Object FilterScript
The Where-Object cmdlet is designed to filter objects in the pipeline.
You can use $PSItem
in the scriptblock of the FilterScript parameter,
which executes once for each input object in the pipeline.
1, 2, 3 | Where-Object -FilterScript { ($PSItem % 2) -eq 0 }
2
In this example, the FilterScript checks to see if the current object is
even, filtering out any odd values, and returns only 2
from the original
list.
ForEach and Where methods
Both the ForEach and Where intrinsic methods for arrays take a
scriptblock as an input parameter. You can use the $PSItem
in those
scriptblocks to access the current object.
@('a', 'b', 'c').ForEach({ $PSItem.ToUpper() }).Where({ $PSItem -ceq 'B' })
B
In this example, the scriptblock of the ForEach method uppercases the
current object. Then the scriptblock of the Where method returns only B
.
Delay-bind scriptblock parameters
Delay-bind scriptblocks let you use $PSItem
to define parameters for a
pipelined cmdlet before executing it.
dir config.log | Rename-Item -NewName { "old_$($_.Name)" }
Switch statement scriptblocks
In switch statements, you can use $PSItem
in both action scriptblocks
and statement condition scriptblocks.
$numbers = 1, 2, 3
switch ($numbers) {
{ ($PSItem % 2) -eq 0 } { "$PSItem is even" }
default { "$PSItem is odd" }
}
1 is odd
2 is even
3 is odd
In this example, the statement condition scriptblock checks whether the current object is even. If it's even, the associated action scriptblock outputs a message indicating the current object is even.
The action scriptblock for the default
condition outputs a message indicating
the current object is odd.
Function process blocks
When you define a function, you can use $PSItem
in the process
block
definition but not in the begin
or end
block definitions. If you
reference $PSItem
in the begin
or end
blocks, the value is $null
because those blocks don't operate on each object in the pipeline.
When you use $PSItem
in the process
block definition, the value is the
value is the current object if the function is called in the pipeline and
otherwise $null
.
function Add-One {
process { $PSItem + 1 }
}
1, 2, 3 | Add-One
2
3
4
Tip
While you can use $PSItem
in advanced functions, there's little
reason to do so. If you intend to receive input from the pipeline,
it's best to define parameters with one of the ValueFromPipeline*
arguments
for the Parameter attribute.
Using the Parameter attribute and cmdlet binding for advanced functions makes the implementation more explicit and predictable than processing the current object to get the required values.
One good use of $PSItem
in advanced functions is to inspect the current
object itself for debugging or logging when the function has multiple
parameters that take input from the pipeline.
function Write-JsonLog {
[CmdletBinding()]
param(
[parameter(ValueFromPipelineByPropertyName)]
[string]$Message
)
begin {
$entries = @()
}
process {
$entries += [pscustomobject]@{
Message = $Message
TimeStamp = [datetime]::Now
}
if ($PSItem) {
$props = $PSItem | ConvertTo-Json
$number = $entries.Length
Write-Verbose "Input object $number is:`n$props"
}
}
end {
ConvertTo-Json -InputObject $entries
}
}
This example function outputs an array of JSON objects with a message and timestamp. When called in a pipeline, it uses the Message property of the current object for each entry. It also writes the JSON representation of the current object itself to the verbose stream, so you can see the actual input compared to the output logs.
$Items = @(
[pscustomobject]@{
Name = 'First Item'
Message = 'A simple note'
}
[pscustomobject]@{
Name = 'Item with extra properties'
Message = 'Missing message, has info instead'
Info = 'Some metadata'
Source = 'Where this came from'
}
[pscustomobject]@{
Name = 'Last Item'
Message = 'This also gets logged'
}
)
$Items | Write-JsonLog -Verbose
VERBOSE: Input object 1 is:
{
"Name": "First Item",
"Message": "A simple note"
}
VERBOSE: Input object 2 is:
{
"Name": "Item with extra properties",
"Message": "Missing message, has info instead",
"Info": "Some metadata",
"Source": "Where this came from"
}
VERBOSE: Input object 3 is:
{
"Name": "Last Item",
"Message": "This also gets logged"
}
[
{
"Message": "A simple note",
"TimeStamp": "\/Date(1670344068257)\/"
},
{
"Message": "Missing message, has info instead",
"TimeStamp": "\/Date(1670344068259)\/"
},
{
"Message": "This also gets logged",
"TimeStamp": "\/Date(1670344068261)\/"
}
]
Filter definitions
You can use $PSItem
in the statement list of a filter's definition.
When you use $PSItem
in a filter
definition, the value is the current
object if the filter is called in the pipeline and otherwise $null
.
filter Test-IsEven { ($PSItem % 2) -eq 0 }
1, 2, 3 | Test-IsEven
False
True
False
In this example, the Test-IsEven
filter outputs $true
if the current object
is an even number and $false
if it isn't.
The ValidateScript attribute scriptblock
You can use $PSItem
in the scriptblock of a ValidateScript attribute.
When used with ValidateScript, $PSItem
is the value of the current object
being validated. When the variable or parameter value is an array, the
scriptblock is called once for each object in the array with $PSItem
as the
current object.
function Add-EvenNumber {
param(
[ValidateScript({ 0 -eq ($PSItem % 2) })]
[int[]]$Number
)
begin {
[int]$total = 0
}
process {
foreach ($n in $Number) {
$total += $n
}
}
end {
$total
}
}
Add-EvenNumber -Number 2, 4, 6
Add-EvenNumber -Number 1, 2
12
Add-EvenNumber : Cannot validate argument on parameter 'Number'. The
" 0 -eq ($PSItem % 2) " validation script for the argument with value "1"
did not return a result of True. Determine why the validation script
failed, and then try the command again.
At line:1 char:24
+ Add-EvenNumber -Number 1, 2
+ ~~~~
+ CategoryInfo : InvalidData: (:) [Add-EvenNumber],
ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,
Add-EvenNumber
In this example the scriptblock for the ValidateScript attribute runs once for each value passed to the Number parameter, returning an error if any value isn't even.
The Add-EvenNumber
function adds the valid input numbers and returns the
total.