Quantcast
Viewing all articles
Browse latest Browse all 16

Compare-Object workaround

One thing that’s frequently frustrated me about PowerShell is that using the Compare-Object cmdlet doesn’t always yield useful results.

For instance, consider the following command:

Compare-Object (Get-ADUser UserA -Properties * ) `
(Get-ADUser UserB -Properties *) -IncludeEqual -ExcludeDifferent

This should return all the properties that are equal for these two users but it returns nothing when I know for a fact that many properties are identical. (Note: You need the Active Directory module loaded to call Get-ADUser.) Why doesn’t this work? Well, I’m not entirely sure but I believe the objects/properties don’t have meaningful comparison methods defined at the .Net level. As far as I can tell, out of the box, there is no meaningful way to use Compare-Object on many types of objects.

I considered that most of these objects do have structured output to the console and why not compare that? So, I proceeded to play around with the various cmdlets with Out- verbs. Out-String seemed the likely candidate but when I tried it I got no results from the following:

Compare-Object (Get-ADUser UserA -Properties * | Out-String ) `
   (Get-ADUser UserB -Properties * | Out-String) -IncludeEqual -ExcludeDifferent

Other Out- cmdlets fail as well. So, I looked at what was generated by piping to Out-String:

PS C:\> Get-Member -InputObject (Get-ADUser UserA -Properties * | Out-String)

   TypeName: System.String

Hmm,  PowerShell usually outputs multi-line strings as an array of strings with each element representing a single line. This spits out a string with line breaks in it, which Compare-Object can’t handle. Get-Help on Out-String yielded the following interesting information:

“By default, Out-String accumulates the strings and returns them as a single string, but you can use the stream parameter to direct Out-String to return one string at a time.”

So what about:

Compare-Object (Get-ADUser UserA -Properties * | Out-String -Stream) `
   (Get-ADUser UserB -Properties * | Out-String -Stream) -IncludeEqual -ExcludeDifferent

InputObject                                                                     SideIndicator
-----------                                                                     -------------
                                                                                ==
                                                                                ==
AccountExpirationDate              :                                            ==
accountExpires                     : 0                                          ==
AccountLockoutTime                 :                                            ==
AccountNotDelegated                : False                                      ==
AllowReversiblePasswordEncryption  : False                                      ==
BadLogonCount                      : 0                                          ==

Aaand there we have it, useful results. But, that’s a lot of typing so, how about we generalize what we did there into a function? The Scripting Guys blog has been running a series on “splatting.” It’s a really neat feature that comes in handy for this kind of thing. Using some stuff I’ve learned from there and various other places, we can write a really nice, full featured and simple wrapper for Compare-Object like so:

function Compare-ObjectOutput {
    [CmdletBinding()]
    param (
        [parameter(
            Position=0,
            Mandatory=$true
        )]
        [AllowEmptyCollection()]
        $ReferenceObject,
        [parameter(
            Position=1,
            Mandatory=$true,
            ValueFromPipeline=$true
        )]
        [AllowEmptyCollection()]
        $DifferenceObject,
        [int]$SyncWindow,
        [array]$Property,
        [switch]$ExcludeDifferent,
        [switch]$IncludeEqual,
        [string]$Culture,
        [switch]$CaseSensitive
    )

    $newCompareParameters = @{} + $psBoundParameters

    if ($Property) {
        $newCompareParameters.Remove("Property")
        $newCompareParameters.ReferenceObject = $ReferenceObject | Select-Object -Property $Property | fl | Out-String -Stream
        $newCompareParameters.DifferenceObject = $DifferenceObject | Select-Object -Property $Property | fl | Out-String -Stream
    }
    else {
        $newCompareParameters.ReferenceObject = $ReferenceObject | fl | Out-String -Stream
        $newCompareParameters.DifferenceObject = $DifferenceObject | fl | Out-String -Stream
    }

    Compare-Object @newCompareParameters
}

The function takes all of the same arguments as Compare-Object (except PassThru) and should work in most instances where Compare-Object fails. If the objects don’t support nice Format-List (fl) output, then something might go awry but most do.


Viewing all articles
Browse latest Browse all 16

Trending Articles