Andrei Odegov's helpful answer offers a PowerShell-idiomatic solution that also demonstrates the simplified, argument-based Where-Object
syntax called comparison statement, introduced in PSv3.
In PSv4+ there is another option, using the .Where()
and .ForEach()
collection "operator" methods, which perform better; however, note that it requires the input collection to be in memory in full[1]
(which is the case here):
$data.Where({ $_.name -eq "Item2" }).ForEach({ $_.stock -= 1 })
Or, if you know that there will only be one match, more simply:
$data.Where({ $_.name -eq "Item2" })[0].stock -= 1
Also note how -eq
rather than -contains
is used, because $_.name
is a scalar (single value) rather than a collection, and -contains
is designed for collections.
As for what you tried:
It looks like you were trying to apply something akin to a Python-style list comprehension, which PowerShell doesn't support.
Instead, you must start with the whole collection, filter it down, and then apply the desired operation on the resulting elements, as shown.
I tried with this :
$data.stock = $data.stock - 1 | Where-Object name -contains "item2"
In PSv3+, accessing a property on a collection returns an array ([System.Object[]]
) of the property values collected from the collection's elements.
Therefore, with your sample data, $data.stock - 1
is the equivalent of:
(10, 10, 10) - 1 # !! BROKEN: arrays don't support the "-" operator
Because arrays don't support the -
operator (whose implementation is based on a static op_Subtraction
method), you got the error message you saw.
[1] The PowerShell pipeline vs. all-in-memory processing:
Use of the pipeline is comparatively slow, but memory-throttling, which enables processing of collections that wouldn't fit into memory as a whole; e.g.:
ConvertFrom-Csv in.csv | Where-Object ... | ForEach-Object ... | Export-Csv out.csv
In the example above, each row from in.csv
is processed individually and sent through the pipeline right away, and Export-Csv
creates the output row by row.
That way, there is no need to read the input into memory as a whole.
By contrast, if do collection processing with (array-aware) operators / .Where()/.ForEach()
methods / a foreach
loop, you get better performance, but the input must be collected in memory in full, up-front, which is not always an option.
See the bottom section of this answer of mine for how the approaches compare in terms of performance (execution speed).