490 lines
17 KiB
C#
490 lines
17 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Management.Automation;
|
|
|
|
using Microsoft.PowerShell.Commands.Internal.Format;
|
|
|
|
namespace Microsoft.PowerShell.Commands
|
|
{
|
|
/// <summary>
|
|
/// </summary>
|
|
[Cmdlet(VerbsData.Compare, "Object", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096605",
|
|
RemotingCapability = RemotingCapability.None)]
|
|
public sealed class CompareObjectCommand : ObjectCmdletBase
|
|
{
|
|
#region Parameters
|
|
/// <summary>
|
|
/// </summary>
|
|
[Parameter(Position = 0, Mandatory = true)]
|
|
[AllowEmptyCollection]
|
|
public PSObject[] ReferenceObject { get; set; }
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
[Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true)]
|
|
[AllowEmptyCollection]
|
|
public PSObject[] DifferenceObject { get; set; }
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
[Parameter]
|
|
[ValidateRange(0, int.MaxValue)]
|
|
public int SyncWindow { get; set; } = int.MaxValue;
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <value></value>
|
|
[Parameter]
|
|
public object[] Property { get; set; }
|
|
|
|
/* not implemented
|
|
/// <summary>
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter IgnoreWhiteSpace
|
|
{
|
|
get { return _ignoreWhiteSpace; }
|
|
|
|
set { _ignoreWhiteSpace = value; }
|
|
}
|
|
|
|
private bool _ignoreWhiteSpace = false;
|
|
*/
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter ExcludeDifferent
|
|
{
|
|
get { return _excludeDifferent; }
|
|
|
|
set { _excludeDifferent = value; }
|
|
}
|
|
|
|
private bool _excludeDifferent /*=false*/;
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter IncludeEqual
|
|
{
|
|
get
|
|
{
|
|
return _includeEqual;
|
|
}
|
|
|
|
set
|
|
{
|
|
_isIncludeEqualSpecified = true;
|
|
_includeEqual = value;
|
|
}
|
|
}
|
|
|
|
private bool _includeEqual /* = false */;
|
|
private bool _isIncludeEqualSpecified /* = false */;
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter PassThru
|
|
{
|
|
get { return _passThru; }
|
|
|
|
set { _passThru = value; }
|
|
}
|
|
|
|
private bool _passThru /* = false */;
|
|
#endregion Parameters
|
|
|
|
#region Internal
|
|
private List<OrderByPropertyEntry> _referenceEntries;
|
|
|
|
private readonly List<OrderByPropertyEntry> _referenceEntryBacklog
|
|
= new();
|
|
|
|
private readonly List<OrderByPropertyEntry> _differenceEntryBacklog
|
|
= new();
|
|
|
|
private OrderByProperty _orderByProperty = null;
|
|
private OrderByPropertyComparer _comparer = null;
|
|
|
|
private int _referenceObjectIndex /* = 0 */;
|
|
|
|
// These are programmatic strings, not subject to INTL
|
|
private const string SideIndicatorPropertyName = "SideIndicator";
|
|
private const string SideIndicatorMatch = "==";
|
|
private const string SideIndicatorReference = "<=";
|
|
private const string SideIndicatorDifference = "=>";
|
|
private const string InputObjectPropertyName = "InputObject";
|
|
|
|
/// <summary>
|
|
/// The following is the matching algorithm:
|
|
/// Retrieve the incoming object (differenceEntry) if any
|
|
/// Retrieve the next reference object (referenceEntry) if any
|
|
/// If differenceEntry matches referenceEntry
|
|
/// Emit referenceEntry as a match
|
|
/// Return
|
|
/// If differenceEntry matches any entry in referenceEntryBacklog
|
|
/// Emit the backlog entry as a match
|
|
/// Remove the backlog entry from referenceEntryBacklog
|
|
/// Clear differenceEntry
|
|
/// If referenceEntry (if any) matches any entry in differenceEntryBacklog
|
|
/// Emit referenceEntry as a match
|
|
/// Remove the backlog entry from differenceEntryBacklog
|
|
/// Clear referenceEntry
|
|
/// If differenceEntry is still present
|
|
/// If SyncWindow is 0
|
|
/// Emit differenceEntry as unmatched
|
|
/// Else
|
|
/// While there is no space in differenceEntryBacklog
|
|
/// Emit oldest entry in differenceEntryBacklog as unmatched
|
|
/// Remove oldest entry from differenceEntryBacklog
|
|
/// Add differenceEntry to differenceEntryBacklog
|
|
/// If referenceEntry is still present
|
|
/// If SyncWindow is 0
|
|
/// Emit referenceEntry as unmatched
|
|
/// Else
|
|
/// While there is no space in referenceEntryBacklog
|
|
/// Emit oldest entry in referenceEntryBacklog as unmatched
|
|
/// Remove oldest entry from referenceEntryBacklog
|
|
/// Add referenceEntry to referenceEntryBacklog.
|
|
/// </summary>
|
|
/// <param name="differenceEntry"></param>
|
|
private void Process(OrderByPropertyEntry differenceEntry)
|
|
{
|
|
Diagnostics.Assert(_referenceEntries != null, "null referenceEntries");
|
|
|
|
// Retrieve the next reference object (referenceEntry) if any
|
|
OrderByPropertyEntry referenceEntry = null;
|
|
if (_referenceObjectIndex < _referenceEntries.Count)
|
|
{
|
|
referenceEntry = _referenceEntries[_referenceObjectIndex++];
|
|
}
|
|
|
|
// If differenceEntry matches referenceEntry
|
|
// Emit referenceEntry as a match
|
|
// Return
|
|
// 2005/07/19 Switched order of referenceEntry and differenceEntry
|
|
// so that we cast differenceEntry to the type of referenceEntry.
|
|
if (referenceEntry != null && differenceEntry != null &&
|
|
_comparer.Compare(referenceEntry, differenceEntry) == 0)
|
|
{
|
|
EmitMatch(referenceEntry);
|
|
return;
|
|
}
|
|
|
|
// If differenceEntry matches any entry in referenceEntryBacklog
|
|
// Emit the backlog entry as a match
|
|
// Remove the backlog entry from referenceEntryBacklog
|
|
// Clear differenceEntry
|
|
OrderByPropertyEntry matchingEntry =
|
|
MatchAndRemove(differenceEntry, _referenceEntryBacklog);
|
|
if (matchingEntry != null)
|
|
{
|
|
EmitMatch(matchingEntry);
|
|
differenceEntry = null;
|
|
}
|
|
|
|
// If referenceEntry (if any) matches any entry in differenceEntryBacklog
|
|
// Emit referenceEntry as a match
|
|
// Remove the backlog entry from differenceEntryBacklog
|
|
// Clear referenceEntry
|
|
matchingEntry =
|
|
MatchAndRemove(referenceEntry, _differenceEntryBacklog);
|
|
if (matchingEntry != null)
|
|
{
|
|
EmitMatch(referenceEntry);
|
|
referenceEntry = null;
|
|
}
|
|
|
|
// If differenceEntry is still present
|
|
// If SyncWindow is 0
|
|
// Emit differenceEntry as unmatched
|
|
// Else
|
|
// While there is no space in differenceEntryBacklog
|
|
// Emit oldest entry in differenceEntryBacklog as unmatched
|
|
// Remove oldest entry from differenceEntryBacklog
|
|
// Add differenceEntry to differenceEntryBacklog
|
|
if (differenceEntry != null)
|
|
{
|
|
if (SyncWindow > 0)
|
|
{
|
|
while (_differenceEntryBacklog.Count >= SyncWindow)
|
|
{
|
|
EmitDifferenceOnly(_differenceEntryBacklog[0]);
|
|
_differenceEntryBacklog.RemoveAt(0);
|
|
}
|
|
|
|
_differenceEntryBacklog.Add(differenceEntry);
|
|
}
|
|
else
|
|
{
|
|
EmitDifferenceOnly(differenceEntry);
|
|
}
|
|
}
|
|
|
|
// If referenceEntry is still present
|
|
// If SyncWindow is 0
|
|
// Emit referenceEntry as unmatched
|
|
// Else
|
|
// While there is no space in referenceEntryBacklog
|
|
// Emit oldest entry in referenceEntryBacklog as unmatched
|
|
// Remove oldest entry from referenceEntryBacklog
|
|
// Add referenceEntry to referenceEntryBacklog
|
|
if (referenceEntry != null)
|
|
{
|
|
if (SyncWindow > 0)
|
|
{
|
|
while (_referenceEntryBacklog.Count >= SyncWindow)
|
|
{
|
|
EmitReferenceOnly(_referenceEntryBacklog[0]);
|
|
_referenceEntryBacklog.RemoveAt(0);
|
|
}
|
|
|
|
_referenceEntryBacklog.Add(referenceEntry);
|
|
}
|
|
else
|
|
{
|
|
EmitReferenceOnly(referenceEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InitComparer()
|
|
{
|
|
if (_comparer != null)
|
|
return;
|
|
|
|
List<PSObject> referenceObjectList = new(ReferenceObject);
|
|
_orderByProperty = new OrderByProperty(
|
|
this, referenceObjectList, Property, true, _cultureInfo, CaseSensitive);
|
|
Diagnostics.Assert(_orderByProperty.Comparer != null, "no comparer");
|
|
Diagnostics.Assert(
|
|
_orderByProperty.OrderMatrix != null &&
|
|
_orderByProperty.OrderMatrix.Count == ReferenceObject.Length,
|
|
"no OrderMatrix");
|
|
if (_orderByProperty.Comparer == null || _orderByProperty.OrderMatrix == null || _orderByProperty.OrderMatrix.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_comparer = _orderByProperty.Comparer;
|
|
_referenceEntries = _orderByProperty.OrderMatrix;
|
|
}
|
|
|
|
private OrderByPropertyEntry MatchAndRemove(
|
|
OrderByPropertyEntry match,
|
|
List<OrderByPropertyEntry> list)
|
|
{
|
|
if (match == null || list == null)
|
|
return null;
|
|
Diagnostics.Assert(_comparer != null, "null comparer");
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
OrderByPropertyEntry listEntry = list[i];
|
|
Diagnostics.Assert(listEntry != null, "null listEntry " + i);
|
|
if (_comparer.Compare(match, listEntry) == 0)
|
|
{
|
|
list.RemoveAt(i);
|
|
return listEntry;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#region Emit
|
|
private void EmitMatch(OrderByPropertyEntry entry)
|
|
{
|
|
if (_includeEqual)
|
|
Emit(entry, SideIndicatorMatch);
|
|
}
|
|
|
|
private void EmitDifferenceOnly(OrderByPropertyEntry entry)
|
|
{
|
|
if (!ExcludeDifferent)
|
|
Emit(entry, SideIndicatorDifference);
|
|
}
|
|
|
|
private void EmitReferenceOnly(OrderByPropertyEntry entry)
|
|
{
|
|
if (!ExcludeDifferent)
|
|
Emit(entry, SideIndicatorReference);
|
|
}
|
|
|
|
private void Emit(OrderByPropertyEntry entry, string sideIndicator)
|
|
{
|
|
Diagnostics.Assert(entry != null, "null entry");
|
|
|
|
PSObject mshobj;
|
|
if (PassThru)
|
|
{
|
|
mshobj = PSObject.AsPSObject(entry.inputObject);
|
|
}
|
|
else
|
|
{
|
|
mshobj = new PSObject();
|
|
if (Property == null || Property.Length == 0)
|
|
{
|
|
PSNoteProperty inputNote = new(
|
|
InputObjectPropertyName, entry.inputObject);
|
|
mshobj.Properties.Add(inputNote);
|
|
}
|
|
else
|
|
{
|
|
List<MshParameter> mshParameterList = _orderByProperty.MshParameterList;
|
|
Diagnostics.Assert(mshParameterList != null, "null mshParameterList");
|
|
Diagnostics.Assert(mshParameterList.Count == Property.Length, "mshParameterList.Count " + mshParameterList.Count);
|
|
|
|
for (int i = 0; i < Property.Length; i++)
|
|
{
|
|
// 2005/07/05 This is the closest we can come to
|
|
// the string typed by the user
|
|
MshParameter mshParameter = mshParameterList[i];
|
|
Diagnostics.Assert(mshParameter != null, "null mshParameter");
|
|
Hashtable hash = mshParameter.hash;
|
|
Diagnostics.Assert(hash != null, "null hash");
|
|
object prop = hash[FormatParameterDefinitionKeys.ExpressionEntryKey];
|
|
Diagnostics.Assert(prop != null, "null prop");
|
|
string propName = prop.ToString();
|
|
PSNoteProperty propertyNote = new(
|
|
propName,
|
|
entry.orderValues[i].PropertyValue);
|
|
try
|
|
{
|
|
mshobj.Properties.Add(propertyNote);
|
|
}
|
|
catch (ExtendedTypeSystemException)
|
|
{
|
|
// this is probably a duplicate add
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mshobj.Properties.Remove(SideIndicatorPropertyName);
|
|
PSNoteProperty sideNote = new(
|
|
SideIndicatorPropertyName, sideIndicator);
|
|
mshobj.Properties.Add(sideNote);
|
|
WriteObject(mshobj);
|
|
}
|
|
#endregion Emit
|
|
#endregion Internal
|
|
|
|
#region Overrides
|
|
|
|
/// <summary>
|
|
/// If the parameter 'ExcludeDifferent' is present, then the 'IncludeEqual'
|
|
/// switch is turned on unless it's turned off by the user specifically.
|
|
/// </summary>
|
|
protected override void BeginProcessing()
|
|
{
|
|
if (ExcludeDifferent)
|
|
{
|
|
if (_isIncludeEqualSpecified && !_includeEqual)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_includeEqual = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
protected override void ProcessRecord()
|
|
{
|
|
if (ReferenceObject == null || ReferenceObject.Length == 0)
|
|
{
|
|
HandleDifferenceObjectOnly();
|
|
return;
|
|
}
|
|
else if (DifferenceObject == null || DifferenceObject.Length == 0)
|
|
{
|
|
HandleReferenceObjectOnly();
|
|
return;
|
|
}
|
|
|
|
if (_comparer == null && DifferenceObject.Length > 0)
|
|
{
|
|
InitComparer();
|
|
}
|
|
|
|
List<PSObject> differenceList = new(DifferenceObject);
|
|
List<OrderByPropertyEntry> differenceEntries =
|
|
OrderByProperty.CreateOrderMatrix(
|
|
this, differenceList, _orderByProperty.MshParameterList);
|
|
|
|
foreach (OrderByPropertyEntry incomingEntry in differenceEntries)
|
|
{
|
|
Process(incomingEntry);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
protected override void EndProcessing()
|
|
{
|
|
// Clear remaining reference objects if there are more
|
|
// reference objects than difference objects
|
|
if (_referenceEntries != null)
|
|
{
|
|
while (_referenceObjectIndex < _referenceEntries.Count)
|
|
{
|
|
Process(null);
|
|
}
|
|
}
|
|
|
|
// emit all remaining backlogged objects
|
|
foreach (OrderByPropertyEntry differenceEntry in _differenceEntryBacklog)
|
|
{
|
|
EmitDifferenceOnly(differenceEntry);
|
|
}
|
|
|
|
_differenceEntryBacklog.Clear();
|
|
foreach (OrderByPropertyEntry referenceEntry in _referenceEntryBacklog)
|
|
{
|
|
EmitReferenceOnly(referenceEntry);
|
|
}
|
|
|
|
_referenceEntryBacklog.Clear();
|
|
}
|
|
#endregion Overrides
|
|
|
|
private void HandleDifferenceObjectOnly()
|
|
{
|
|
if (DifferenceObject == null || DifferenceObject.Length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<PSObject> differenceList = new(DifferenceObject);
|
|
_orderByProperty = new OrderByProperty(
|
|
this, differenceList, Property, true, _cultureInfo, CaseSensitive);
|
|
List<OrderByPropertyEntry> differenceEntries =
|
|
OrderByProperty.CreateOrderMatrix(
|
|
this, differenceList, _orderByProperty.MshParameterList);
|
|
|
|
foreach (OrderByPropertyEntry entry in differenceEntries)
|
|
{
|
|
EmitDifferenceOnly(entry);
|
|
}
|
|
}
|
|
|
|
private void HandleReferenceObjectOnly()
|
|
{
|
|
if (ReferenceObject == null || ReferenceObject.Length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitComparer();
|
|
Process(null);
|
|
}
|
|
}
|
|
}
|