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?
Perhaps instead of
PowerShell 2 might work better with:
PetSerAl, as he routinely does, has provided the crucial pointer in a comment on the question:
Member 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.
Member 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 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 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 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 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 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 of mine):