The most popular answer for this question involves the following Windows powershell code (edited to fix a bug):
$file1 = Get-Content C:\temp\file1.txt
$file2 = Get-Content C:\temp\file2.txt
$Diff = Compare-Object $File1 $File2
$LeftSide = ($Diff | Where-Object {$_.SideIndicator -eq '<='}).InputObject
$LeftSide | Set-Content C:\temp\file3.txt
I always get a zero byte file as the output, even if I remove the $Diff line.
Why is the output file always null, and how can it be fixed?
PetSerAl, as he routinely does, has provided the crucial pointer in a comment on the question:
Member-access enumeration - the ability to access a member (a property or a method) on a collection and have it implicitly applied to each of its elements, with the results getting collected in an array, was introduced in PSv3.[1]
Member-access enumeration is not only expressive and convenient, it is also faster than alternative approaches.
A simplified example:
Applying
.Mode
to the collection that the(...)
-enclosed command outputs causes the.Mode
property to be accessed on each item in the collection, with the resulting values returned as an array (a regular PowerShell array, of type[System.Object[]]
).Caveats: Member-access enumeration handles the resulting array like the pipeline does, which means:
If the array has only a single element, that element's property value is returned directly, not inside a single-element array:
If the property values being collected are themselves arrays, a flat array of values is returned:
Also, member-access enumeration only works for getting (reading) property values, not for setting (writing) them. This asymmetry is by design, to avoid potentially unwanted bulk modification; in PSv4+, use
.ForEach('<property-name', <new-value>)
as the quickest workaround (see below).This convenient feature is NOT available, however:
For instance, even in PSv3+ the following does NOT perform member-access enumeration:
In such cases - and in PSv2 in general - a different approach is needed:
foreach
statement, assuming that the entire collection fits into memory as a whole (which is implied when using member-access enumeration)..ForEach()
, also operating on the collection as a whole:Note: If applicable to the input collection, you can also set property values with
.ForEach('<prop-name>', <new-value>)
, which is the fastest workaround to not being able to use.<prop-name> = <new-value>
, i.e. the inability to set property values with member-access enumeration.Note: Use of the pipeline is only memory-efficient if you process the items one by one, in isolation, without collecting the results in memory as well.
Using the
ForEach-Object
cmdlet, as in Burt Harris' helpful answer:For properties only (as opposed to methods),
Select-Object -ExpandProperty
is an option; it is conceptually clear and simple, and virtually on par with theForEach-Object
approach in terms of performance (for a performance comparison, see the last section of this answer):[1] Previously, the feature was semi-officially known as just member enumeration, introduced in this 2012 blog post along with the feature itself. A decision to formally introduce the term member-access enumeration was made in early 2022.