我们最近在构建服务器上将Powershell版本从4.0更新到5.0。此更改导致我们的一个构建脚本以意外方式启动失败。
该代码用于确定应在我们的产品中包含哪些用户指南。代码处理xml节点列表,这些节点描述具有版本和文化的所有可用文档。我们按文档标题和文化分组,然后选择最合适的版本。
$documents = Get-ListItemsFromSharePoint
$documents = $documents |
Where-Object { $productVersion.CompareTo([version]$_.ows_Product_x0020_Version) -ge 0 } |
Where-Object { -not ($_.ows_EncodedAbsUrl.Contains('/Legacy/')) }
Write-Verbose -Message "Filtered to: $($documents.length) rows"
# Filter to the highest version for each unique title per language
$documents = $documents | Group-Object { $_.ows_Title, $_.ows_Localisation } |
ForEach-Object {
$_.Group | Sort-Object { [version]$_.ows_Product_x0020_Version } -Descending | Select-Object -First 1
}
在Powershell 4中,此代码正确地按标题和文化对文档进行排序,然后选择最合适的版本。在Powershell 5中,此代码将所有文档分组到一个列表中,然后从该列表中选择最合适的版本。鉴于我们有多种语言的文档,这意味着只会出现具有最合适版本的语言。
通过更改
解决了这个问题$documents = $documents | Group-Object { $_.ows_Title, $_.ows_Localisation } |
到
$documents = $documents | Group-Object ows_Title, ows_Localisation |
现在我明白第一种语法在技术上并不符合文档,因为Group-Object期望一组属性名称可以分组,但是在Powershell 4中,代码确实返回了所需的结果。
现在的问题是Powershell 5中的变化是原始代码在Powershell 4中有效但在Powershell 5中失败了。
答案 0 :(得分:6)
看起来Group-Object cmdlet的语法没有改变,因为下面显示了两个版本的相同定义(以及定义方法的DLL):
gcm Group-Object | fl DLL,Definition
DLL : C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerShell.Commands.Utility\v4.0_
3.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.Commands.Utility.dll
Definition :
Group-Object [[-Property] <Object[]>] [-NoElement] [-AsHashTable] [-AsString]
[-InputObject <psobject>] [-Culture <string>] [-CaseSensitive] [<CommonParameters>]
但正如PetSerAL在评论中提到的那样,5.0 DLL处理数组的方式与4.0不同。例如:
$a=[PSCustomObject]@{item=@(1,2)} #Object with an array for the value of the item property
$b=[PSCustomObject]@{item=@(3,3)} #Object with a different array for the value of the item property
$a.item.Equals($b.item) #This deep compare is false, as the two objects are not equal
$a.item.GetType().Equals($b.item.GetType()) #This "shallow" compare is true because both are the array type.
$c=[PSCustomObject]@{item=@{key='value'}} #Similar but this time the item value is a hashtable
$d=[PSCustomObject]@{item=@{anotherkey='anothervalue'}} #again comparing the two items we expect the result to be false if deep compared but true if shallow compared
$e=[PSCustomObject]@{item=get-date} #another test using two datetimes (another common "reference" type)
$f=[PSCustomObject]@{item=[datetime]::MinValue}
$a,$b,$c,$d,$e,$f | group -Property item #now we see what happens when using group-object
#Output in PowerShell 4.0
Count Name Group
----- ---- -----
1 {1, 2} {@{item=System.Object[]}}
1 {3, 3} {@{item=System.Object[]}}
2 {System.Collections.Di... {@{item=System.Collections.Hashtable}, @{item=System.Collections...
1 8/5/2016 9:45:36 PM {@{item=8/5/2016 9:45:36 PM}}
1 1/1/0001 12:00:00 AM {@{item=1/1/0001 12:00:00 AM}}
#Output in PowerShell 5.0
Count Name Group
----- ---- -----
2 {1, 2} {@{item=System.Object[]}, @{item=System.Object[]}}
2 {System.Collections.Di... {@{item=System.Collections.Hashtable}, @{item=System.Collections...
1 8/5/2016 9:45:40 PM {@{item=8/5/2016 9:45:40 PM}}
1 1/1/0001 12:00:00 AM {@{item=1/1/0001 12:00:00 AM}}
请注意,在版本4中,数组值被视为单独的组,但哈希表被视为相等的组。这意味着数组有一个深度比较,但哈希表是一个浅比较(所有哈希表被视为等价)
现在在版本5中,数组被视为等效,这意味着它们是一个浅层比较,类似于哈希表的工作方式。
如果要查看完整的详细信息,则需要使用ilspy或.Net Reflector来反汇编DLL并比较Microsoft.PowerShell.Commands.GroupObjectCommand类的DoGrouping方法。很难说它是否是一个bug,但它绝对是group-object cmdlet的重大变化。
更新:我玩的越多,我认为新代码更正确(除了显示的名称应该只是System.Object)并且旧代码中存在错误。似乎v4正在进行某种基于字符串的比较,因为即使$a.Equals([PSCustomObject]@{item=@(1,2)})
总是为假(它们的GetHashCode方法结果不匹配),即使两个具有相同元素的不同数组也会被组合在一起。我能够获得5.0来对类似数组进行分组的唯一方法是使用group -Property {$_.item -join ','}
,它匹配4.0输出,但名称是1,2而不是{1,2}。此外,如果您想使用哈希表项的键进行分组,您可以使用group -Property {$_.item.somekey}
(假设它们都有某个键的值)