class Person {
[String]$Name
[String]$LastName
[int]$Age
[Person[]]$Parents
Person($Name, $LastName, $Age) {
$this.Name = $Name
$this.LastName = $LastName
$this.Age = $Age
}
}
$psmith = [Person]::new('Paul', 'Smith', 46)
$msmith = [Person]::new('Mary', 'Smith', 35)
$nsmith = [Person]::new('Nigel', 'Smith', 11)
$nsmith.Parents = $psmith, $msmith
$pdoe = [Person]::new('Jon', 'Doe', 46)
$ldoe = [Person]::new('Mary', 'Doe', 51)
$adoe = [Person]::new('Aidan', 'Doe', 23)
$adoe.Parents = $pdoe, $ldoe
Compare-Object $nsmith $adoe
Compare-Object $nsmith $adoe -Property Parents
Compare-Object $nsmith $adoe -Property Parents -IncludeEqual
$nsmith.Parents.ToString()
$adoe.Parents.ToString()
Compare-Object $nsmith $adoe -Property {$_.Parents.Name}
Compare-Object $nsmith $adoe -Property { $_.Parents.Age }, { $_.Parents.Name }
Compare-Object $nsmith $adoe -Property { $_.Parents[1].Age }, { $_.Parents[1].Name }
$customObj = [PSCustomObject][ordered]@{Prop='value'}
$customObj.GetType()
$customObjArray = ,$customObj, $customObj
'---------'
$customObjArray -is [Array]
'---------'
$adoe.GetType()
function Compare-Stuff($refObject,$diffObject,$props){
foreach ($prop in ($props)) {
$ref = $refObject.$prop
$diff = $diffObject.$prop
if (!$diff){ $diff = 'null'}
if (!$ref){ $ref = 'null' }
$out = Compare-Object $ref $diff | select @{n='Property';e={$prop}}, InputObject, SideIndicator
$out | Group-Object Property,{$_.SideIndicator -eq '=='} | foreach {
if ($_.Group[0].SideIndicator -eq '==') {
[PSCustomObject][Ordered]@{
Property = $_.Group.Property
ReferenceValue = $_.Group.InputObject
DifferenceValue = $_.Group.InputObject
}
}
else {
[PSCustomObject][Ordered]@{
Property = $_.Group[0].Property
ReferenceValue = ($_.Group.where{ $_.SideIndicator -eq '<=' }).InputObject
DifferenceValue = ($_.Group.where{ $_.SideIndicator -eq '=>' }).InputObject
}
}
}
}
}
$builtinType = 'test'.GetType()
$personType = $adoe.GetType()
$excludeProps = 'DeclaredFields','Assembly','CustomAttributes','DeclaredConstructors','DeclaredMembers','DeclaredMethods','AssemblyQualifiedName','DeclaredProperties','Attributes','GUID','ImplementedInterfaces'
$props = ($builtinType | gm -MemberType Property).Name.where{$_ -notin $excludeProps}
Compare-Stuff $builtinType $personType $props
$customType = ([pscustomobject][ordered]@{prop='value'}).GetType()
Compare-Stuff $customType $personType $props
function Compare-Stuff($ReferenceObject, $DifferenceObject, $MaxDepth = -1, $__Property, $__Depth = 0, [switch]$IncludeEqual, [switch]$ExcludeDifferent, [switch]$PassThru, [switch]$Compact) {
if ($MaxDepth -eq -1 -or $__Depth -le $MaxDepth) {
#check for arrays of PSCustomObjects or arrays of custom class and iterate over those
if (($ReferenceObject -is [array]) -and ($ReferenceObject[0] -is [PSCustomObject] -or $null -eq $ReferenceObject[0].GetType().Namespace)) {
$__Depth++
for ($i = 0; $i -lt $ReferenceObject.Count; $i++) {
#recurse carrying the current Property name + index and Depth values forward
Compare-Stuff $ReferenceObject[$i] $DifferenceObject[$i] -__Property ($__Property + "[$i]") -__Depth $__Depth -IncludeEqual:$IncludeEqual -ExcludeDifferent:$ExcludeDifferent -PassThru:$PassThru -Compact:$Compact
}
}
#check for custom classes or PSCutomObjects and iterate over their properties.
elseif ($ReferenceObject -is [PSCustomObject] -or $null -eq $ReferenceObject.GetType().Namespace) {
$__Depth++
foreach ($prop in $ReferenceObject.PSObject.properties.name) {
#build up the property name hiarachry
$newProp = $prop
if ($__Property) {
$newProp = $__Property + '.' + $prop
}
# handle ref. or diff. objects equal null
$refValue = $ReferenceObject.$prop
$diffValue = $DifferenceObject.$prop
if ($Null -eq $refValue) {
$refValue = ''
}
if ($null -eq $diffValue) {
$diffValue = ''
}
#recurse carrying the current Property and Depth values forward
Compare-Stuff $refValue $diffValue -__Property $newProp -__Depth $__Depth -IncludeEqual:$IncludeEqual -ExcludeDifferent:$ExcludeDifferent -PassThru:$PassThru -Compact:$Compact
}
}
else {
#if we reach here we are dealing with a scalar or array of scalars that the built-in cmdlet can already deal with
$output = Compare-Object $ReferenceObject $DifferenceObject -IncludeEqual:$IncludeEqual -ExcludeDifferent:$ExcludeDifferent -PassThru:$PassThru |
Select-Object @{n = 'Property'; e = { $__Property } }, @{n = 'Value'; e = { $_.InputObject } }, SideIndicator
if ($Compact) {
$output | Group-Object Property,{$_.SideIndicator -eq '=='} | ForEach-Object {
if ($_.Group[0].SideIndicator -eq '==') {
[PSCustomObject][Ordered]@{
Property = $_.Group.Property
ReferenceValue = $_.Group.Value
DifferenceValue = $_.Group.Value
}
}
else {
[PSCustomObject][Ordered]@{
Property = $_.Group[0].Property
ReferenceValue = ($_.Group.where{ $_.SideIndicator -eq '<=' }).Value
DifferenceValue = ($_.Group.where{ $_.SideIndicator -eq '=>' }).Value
}
}
}
}
else {
$output
}
}
}
}
Compare-Stuff $nsmith $adoe -Compact -IncludeEqual
$peter = [PSCustomObject][ordered]@{Name='Peter'; Colors=('red','black')}
$lisa = [PSCustomObject][ordered]@{Name='Lisa'; Colors=('red','black','green','pink')}
Compare-Stuff $peter $lisa -IncludeEqual -Compact
$Metadata = New-Object System.Management.Automation.CommandMetaData (Get-Command Compare-Object)
$proxyCmd = [System.Management.Automation.ProxyCommand]::Create($Metadata) | Set-Clipboard
function Compare-Object{
[CmdletBinding(HelpUri='https://go.microsoft.com/fwlink/?LinkID=113286', RemotingCapability='None', DefaultParameterSetName='Compact')]
param(
[Parameter(Mandatory=$true, Position=0)]
[AllowEmptyCollection()]
${ReferenceObject},
[Parameter(Mandatory=$true, Position=1, ValueFromPipeline=$true)]
[AllowEmptyCollection()]
${DifferenceObject},
[ValidateRange(0, 2147483647)]
[int]
${SyncWindow},
[System.Object[]]
${Property},
[ValidateRange(-1, 20)]
[int]
${MaxDepth} = -1,
[int]
$__Depth = 0,
[string]
$__Property,
[switch]
${ExcludeDifferent},
[switch]
${IncludeEqual},
[Parameter(ParameterSetName='PassThru')]
[switch]
${PassThru},
[string]
${Culture},
[switch]
${CaseSensitive},
[Parameter(ParameterSetName='Compact')]
[switch]
$Compact
)
begin
{
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Compare-Object', [System.Management.Automation.CommandTypes]::Cmdlet)
###########################################################
if ($MaxDepth -eq -1 -or $__Depth -le $MaxDepth) {
#check for array of objects or custom class
if (($ReferenceObject -is [array]) -and ($ReferenceObject[0] -is [PSCustomObject] -or $null -eq $ReferenceObject[0].GetType().Namespace)) {
$__Depth++
for ($i = 0; $i -lt $ReferenceObject.Count; $i++) {
if ($PassThru){
Compare-Object $ReferenceObject[$i] $DifferenceObject[$i] -__Property ($__Property + "[$i]") -__Depth $__Depth -IncludeEqual:$IncludeEqual -ExcludeDifferent:$ExcludeDifferent -PassThru:$PassThru
}
else{
Compare-Object $ReferenceObject[$i] $DifferenceObject[$i] -__Property ($__Property + "[$i]") -__Depth $__Depth -IncludeEqual:$IncludeEqual -ExcludeDifferent:$ExcludeDifferent -Compact:$Compact
}
}
}
elseif ($ReferenceObject -is [PSCustomObject] -or $null -eq $ReferenceObject.GetType().Namespace) {
$__Depth++
foreach ($prop in $ReferenceObject.PSObject.properties.name) {
$newProp = $prop
if ($__Property) {
$newProp = $__Property + '.' + $prop
}
#recurse
# handle ref. or diff. objects equal null
$refValue = $ReferenceObject.$prop
$diffValue = $DifferenceObject.$prop
if ($Null -eq $refValue) {
$refValue = ''
}
if ($null -eq $diffValue) {
$diffValue = ''
}
if ($PassThru){
Compare-Object $refValue $diffValue -__Property $newProp -__Depth $__Depth -IncludeEqual:$IncludeEqual: -ExcludeDifferent$ExcludeDifferent -PassThru:$PassThru
}
elseif ($Compact){
Compare-Object $refValue $diffValue -__Property $newProp -__Depth $__Depth -IncludeEqual:$IncludeEqual -ExcludeDifferent:$ExcludeDifferent -Compact:$Compact
}
else{
Compare-Object $refValue $diffValue -__Property $newProp -__Depth $__Depth -IncludeEqual:$IncludeEqual -ExcludeDifferent:$ExcludeDifferent
}
}
}
else {
if($PSBoundParameters['__Depth']){
$null = $PSBoundParameters.Remove('__Depth')
}
if($PSBoundParameters['MaxDepth']){
$null = $PSBoundParameters.Remove('MaxDepth')
}
if($PSBoundParameters['__Property']){
$prop = $PSBoundParameters['__Property']
$null = $PSBoundParameters.Remove('__Property')
}
if($PSBoundParameters['Compact']){
$compact = $PSBoundParameters['Compact']
$null = $PSBoundParameters.Remove('Compact')
}
$scriptCmd = { & $wrappedCmd @PSBoundParameters }
if ($prop) {
$scriptCmd = {
& $wrappedCmd @PSBoundParameters |
Select-Object @{n = 'Property'; e = { $prop } }, @{n = 'Value'; e = { $_.InputObject } }, SideIndicator
}
if ($compact) {
$scriptCmd = {
& $wrappedCmd @PSBoundParameters |
Select-Object @{n = 'Property'; e = { $prop } }, @{n = 'Value'; e = { $_.InputObject } }, SideIndicator |
Group-Object Property,{$_.SideIndicator -eq '=='} | ForEach-Object {
if ($_.Group[0].SideIndicator -eq '==') {
[PSCustomObject][Ordered]@{
Property = $_.Group.Property
ReferenceValue = $_.Group.Value
DifferenceValue = $_.Group.Value
}
}
else {
[PSCustomObject][Ordered]@{
Property = $_.Group[0].Property
ReferenceValue = ($_.Group.where{ $_.SideIndicator -eq '<=' }).Value
DifferenceValue = ($_.Group.where{ $_.SideIndicator -eq '=>' }).Value
}
}
}
}
}
}
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
}
#############################################################
} catch {
throw
}
}
process
{
try {
$steppablePipeline.Process($_)
} catch {
}
}
end
{
try {
$steppablePipeline.End()
} catch {
}
}
<#
.ForwardHelpTargetName Microsoft.PowerShell.Utility\Compare-Object
.ForwardHelpCategory Cmdlet
#>
}
Compare-Object $nsmith $adoe -Compact -IncludeEqual
Compare-Object $nsmith $adoe -IncludeEqual
WordPress conversion from compareObject.ipynb by nb2wp v0.3.1

Tree Stock photos by Vecteezy
One thought on “Extending PowerShell’s Compare-Object to handle custom classes and arrays”