Search code examples
powershellcompareobject

Powershell compare-object output 1 line per item


I am attempting to put the output of compare-object. I am new to Powershell and unfortunately don't know the ins and outs yet.

My command is as follows:

Compare-Object -referenceObject $(Get-Content "c:\temp\mek\123-first.txt") -differenceObject $(Get-Content "c:\temp\mek\123-second.txt") | %{$_.Inputobject} | sort-object | out-file "c:\temp\mek\results.txt"

The contents of my files are as follows (simply comparing Windows services):

systemname name                                         state   startmode
---------- ----                                         -----   ---------
D7MCYP     AdobeARMservice                              Stopped Auto     
D7MCYP     AdobeFlashPlayerUpdateSvc                    Stopped Manual   
D7MCYP     AeLookupSvc                                  Stopped Manual   

My results of the compare-object are as follows:

BL3C4V     wudfsvc                                      Stopped Auto
BL3C4V     wudfsvc                                      Stopped Manual   
D7MCYP     AdobeARMservice                              Running Auto     
D7MCYP     AdobeARMservice                              Stopped Auto     

Now if anyone could help output to keep the first 2 columns per server and the different values of columns 3,4 to new columns (5,6). It would also be nice if I get titles too. For example:

Server      Service             Before State    Before Mode     After State  After Mode
BL3C4V      wudfsvc             Stopped         Auto            Stopped         Manual
D7MCYP      AdobeARMservice     Running         Auto            Stopped         Auto     

Solution

  • Note: The code below is an exercise in parsing plain-text data into objects for more robust, flexible handling.
    Ideally, however, processing should start out with objects rather than plain text, which is why starting with PowerShell cmdlets such as Get-Service rather than the text output from external utilities is preferable.

    Assuming that all entries in each input file have a matching server + service-name entry in the respective other file:

    $f1, $f2 = "c:\temp\mek\123-first.txt", "c:\temp\mek\123-second.txt"
    Compare-Object (Get-Content $f1) (Get-Content $f2) | ForEach-Object {
        $i = 0; $ht = @{}; $vals = -split $_.InputObject
        foreach($col in 'Server', 'Service', 'State', 'Mode') {
          $ht.$col = $vals[$i++]
        }
        $ht.Before = $_.SideIndicator -eq '<='
        [pscustomobject] $ht
      } | Group-Object Server, Service | ForEach-Object {
          $ndxBefore, $ndxAfter = if ($_.Before) { 0, 1 } else { 1, 0 }
          [pscustomobject] @{
            Server = $_.Group[0].Server
            Service = $_.Group[0].Service
            'State Before' = $_.Group[$ndxBefore].State
            'Mode Before' = $_.Group[$ndxBefore].Mode
            'State After' = $_.Group[$ndxAfter].State
            'Mode After' = $_.Group[$ndxAfter].Mode
          }
        } | Sort-Object Server, Service |
          Format-Table
    

    Note:

    • The above formats the output for display (using Format-Table), without sending it to a file.
      You can append | Out-File "c:\temp\mek\results.txt" to save the same representation to a file.

    • However, note that the command - before Format-Table is applied - returns objects with individual properties, so you can output to a file in a variety of formats, such as by using Export-Csv, for instance.

    Example output:

    Server Service                   State Before Mode Before State After Mode After
    ------ -------                   ------------ ----------- ----------- ----------
    D7MCYP AdobeFlashPlayerUpdateSvc Stopped      Manual      Stopped     Auto      
    D7MCYP AeLookupSvc               Stopped      Manual      Started     Manual    
    

    Explanation:

    A single, long pipeline is used, which makes the code concise and memory-efficient.
    The pipeline breaks down as follows:

    • Comparison:

      • Compare-Object compares the array of lines from the two input files returned by the Get-Content calls, and outputs [pscustomobject] instances representing the differences found, with string property .SideIndicator indicating whether the line at hand (accessible via .InputObject) is unique to the LHS (the 1st input file) - <= - or the RHS (2nd input file) - >=
    • Transformation to custom objects:

      • The script block ({ ... }) passed to ForEach-Object is executed for each input object (represented as $_).

      • -split $_.InputObject splits the "difference line" at hand into fields by runs of whitespace and stores the resulting fields as an array in $vals.

      • $ht is an auxiliary hashtable that is used to map the field values to field names.

      • $ht.Before adds a Boolean entry to indicate whether the difference line at hand is from the "before file" (the 1st input file) or not.

      • [pscustomobject] $ht converts the aux. hashtable into a custom object and outputs it (sends it through the pipeline).

    • Grouping:

      • Group-Object is used to group the resulting objects can by shared Server and Service property values, resulting in a [Microsoft.PowerShell.Commands.GroupInfo] instance representing each grouping.
    • Transformation to combined custom objects:

      • Again, ForEach-Object is used to perform per-input object processing.

      • [pscustomobject] @{ ... } is used to construct each combined output object, again using an auxiliary hashtable.

      • $_.Group contains the input objects that form each group - in our case, $_.Group[0] and $_.Group[1] are the converted-to-objects input lines representing a given server-service combination.

      • By definition, both input objects have the same .Server and .Service values, so blindly using $_.Group[0]'s values for the combined output object will do.

      • By contrast, the * Before and * After properties the appropriate input object (whether from the 1st or 2nd file), which is why the array indices $ndxBefore and $ndxAfter are chosen accordingly, via the previously added .Before property

    • Sorting:

      • Sort-Object sorts the resulting objects by the specified properties.
    • Output formatting:

      • Output-formatting cmdlet Format-Table ensures that the sorted objects are presented as a table.