11

我有 2 个要比较的 Excel 电子表格:

$OleDbAdapter = New-Object System.Data.OleDb.OleDbDataAdapter “Select * from [Report$]“,”Provider=Microsoft.ACE.OLEDB.12.0;Data Source=S:\FIS-BIC Reporting\Report Output Files\Product-Marketing\TEST_XI\ECM - Pipeline by LOB_04182013_040544.xls;Extended Properties=”"Excel 12.0 Xml;HDR=YES”";”
$RowsReturned = $OleDbAdapter.Fill($DataTable)

$OleDbAdapter2 = New-Object System.Data.OleDb.OleDbDataAdapter “Select * from [Report$]“,”Provider=Microsoft.ACE.OLEDB.12.0;Data Source=S:\FIS-BIC Reporting\Report Output Files\Product-Marketing\ECM - Pipeline by LOB_04182013_074004.xls;Extended Properties=”"Excel 12.0 Xml;HDR=YES”";”
$RowsReturned2 = $OleDbAdapter2.Fill($DataTable2)

Compare-Object $DataTable $DataTable2 

它什么也不返回。我知道在第 6 列中,它们是不同的。如果我指定“-property F6”,它会返回差异。知道为什么它不会,除非我指定属性吗?列数可能会有所不同(尽管比较中的每个文件都相同),因此专门指定属性将不起作用。

4

2 回答 2

30

如果不指定-Property参数,Compare-Object不会比较所有属性,它会比较在两个对象上调用.ToString()方法的结果。因此,Compare-Object $DataTable $DataTable2$DataTable1.ToString()$DataTable1.ToString()进行比较。.ToString()方法在DataTable 对象上调用时返回一个空字符串,因此报告没有区别。

例如:

$file1 = Get-Item somefilename
$file1 = Get-Item anotherfilename
Compare-Object $file1 $file2

这将返回两个文件的完整路径之间的差异,如下所示:

InputObject              SideIndicator
-----------              -------------
<path>\anotherfilename   =>
<path>\somefilename      <=

这是因为在 FileInfo 对象上调用.ToString()会返回其FullName属性,因此您正在比较文件的完整路径名。

虽然-Property参数接受多个属性,但列出所有属性并不是解决方案。除了非常乏味之外,它不会给你想要的结果。如果列出多个属性,Compare-Object会比较所有属性的组合,如果列出的任何一个属性不同,则返回显示所有列出的属性的结果(相同的和不同的)作为一个单一的区别。

您需要做的是遍历属性列表,并为每个属性调用一次Compare-Object :

$properties = ($DataTable | Get-Member -MemberType Property | Select-Object -ExpandProperty Name)
foreach ($property in $properties) {
  Compare-Object $DataTable $DataTable2 -Property "$property" | Format-Table -AutoSize
}
  • 在大多数情况下,当比较两个对象的所有属性时,您会想要使用Get-Member -MemberType Properties, 以涵盖所有属性类型。但是,如果要比较 DataTable 对象,最好Get-Member -MemberType Property只比较与数据字段对应的属性,而不是与数据无关的 DataTable 对象的其他属性。

  • 正如您所说,这是假设列数相同,或者至少$DataTable2中的列数不超过 $DataTable 中的列

    如果您不能可靠地假设这一点,请通过比较和使用哪个更大的属性,从具有更多列的那个中派生$ properties数组($DataTable | Get-Member -MemberType Property).Count($DataTable2 | Get-Member -MemberType Property).Count

  • 使用Format-Table很重要,它不仅仅是为了让事情看起来更漂亮。如果您列出多个相同类型的对象(在本例中为数组),PowerShell 会记住第一个对象的格式,并将其用于所有后续对象,除非您明确指定格式。由于每个属性的第一列名称不同(即电子表格中的每一列),因此除了遇到的第一个差异之外,第一列将为空。

    -AutoSize开关是可选的。那只是为了让事情看起来很漂亮。但是您必须将结果通过管道传送到格式过滤器。如果您愿意,也可以使用Format-List 。

于 2013-08-20T23:10:45.900 回答
5

Adi Inbar 的有用答案包含有关如何Compare-Object工作的良好背景信息。

但是,一种方法可以-Property通用地比较所有列值- 假设两个输入表具有相同的列结构,或者这些表只能通过第一个表的列进行比较

# The original collection.
$coll1 = [pscustomobject] @{ one = '1a'; two = '2a'; three = '3a' },
         [pscustomobject] @{ one = "1b"; two = "2b"; three = '3b' }

# The other collection to compare the original to.
# Note the difference in the 2nd object in column 'two'
$coll2 = [pscustomobject] @{ one = '1a'; two = '2a'; three = '3a' },
         [pscustomobject] @{ one = "1b"; two = "2b!"; three = '3b' }

# PSv3+: Get the array of all property names to compare 
#        from the original collection.
# Note: 
#      * The assumption is that both collections have the same set of 
#        properties (or that the collections should only be compared by
#        the *first* collection's properties).
#      * In PSv2-, use the following instead:
#         $propsToCompare = $coll1[0].psobject.properties | % { $_.name }
$propsToCompare = $coll1[0].psobject.properties.name

# Compare the 2 collections by all property values.
# -PassThru means that any input object in which a difference is found
# is passed through as-is.
Compare-Object $coll1 $coll2 -Property $propsToCompare -PassThru

以上产生:

one two three SideIndicator
--- --- ----- -------------
1b  2b! 3b    =>           
1b  2b  3b    <=           

请注意如何=>告诉您所选对象是右侧独有的,反之亦然<=

需要注意的是,这Compare-Object,因为它不能对输入数据进行排序假设,因此必须完整地读取和比较两个输入集合。

使用排序输入,您可以-SyncWindow <Int32>加快速度,但这需要提前了解在发现每个差异后两个输入集合之间最多可以有多少项目不同,如果-SyncWindow值太小,则会报告虚假差异。


如果将对象作为一个整体进行比较(无-Property参数),PowerShell 使用以下比较方法

笔记:

  • 下面,LHS指的是参考集合中的一个对象 ( -ReferenceObject),而RHS指的是差异集合中的一个对象( -DifferenceObject)

  • 简而言之:除非所涉及的类型实现IComparable(对于字符串和所有原始 .NET 类型(CLR 的数字类型和[char])都是如此)或具有可以推断相等性的实例特定返回值的自定义实现,否则,.ToString()整体-对象比较没有意义即使对象不相等,它们也会被视为相等

以下逻辑在引擎方法中实现,每当必须比较两个值时都会使用该方法,而与上下文无关。TryCompare()

  • 如果LHSstring,则进行字符串比较,默认不区分大小写和区域性;和参数允许您更改它。-CaseSensitive-Culture

  • 如果 LHS 和 RHS 都是(可能不同的).NET 基本类型的数字,则执行数字比较。

  • 否则,尝试将 RHS is 转换为 LHS 的类型,如果类型支持,则调用System.IComparable其方法来确定相等性。.CompareTo()

  • 否则,LHS 和 RHS 通过Object.Equals()所有对象继承或实现的方法进行比较,或者,如果类型实现 interface IEquatable<T>,则比较IEquatable<T>.Equals()

    • 如果System.Object.Equals()被调用,并且给定类型没有覆盖它,那么只有struct仅由其他值类型实例组成的值类型 (s) 才会有意义地进行比较;对于引用类型,只有对同一个对象实例的两个引用被认为是相等的。

    • 警告:从 PowerShell Core 7.1.0-preview.2 开始,仅当.Equals()调用返回时才true使用结果。原因是比较代码也用于排序(排序)值,其中仅确定相等性是不够的。在上下文中Compare-Object使用false结果实际上对于实现(并且不实现)的类型是不合适的- 请参阅此 GitHub 问题IEquatable<T>IComparable

剩下的逻辑来自ObjectCommandComparer.Compare()(也被Get-Uniqueand使用Select-Object -Unique):

  • 如果上述方法均不适用,则通过对象的.ToString()表示来比较对象(使用上述相同的字符串比较细节)。

    • 请注意,对于许多类型,这些不会导致有意义的比较,因为默认.ToString()实现只是返回完整的类型名称(例如,'System.IO.FileInfo'),因此该类型的所有实例都比较相同
于 2017-07-23T22:45:12.493 回答