PowerShell更改返回对象的类型

18

我正在使用PowerShell v3和Windows PowerShell ISE。我有以下这个函数,它很好用:

function Get-XmlNode([xml]$XmlDocument, [string]$NodePath, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.')
{
    # If a Namespace URI was not given, use the Xml document's default namespace.
    if ([string]::IsNullOrEmpty($NamespaceURI)) { $NamespaceURI = $XmlDocument.DocumentElement.NamespaceURI }   

    # In order for SelectSingleNode() to actually work, we need to use the fully qualified node path along with an Xml Namespace Manager, so set them up.
    [System.Xml.XmlNamespaceManager]$xmlNsManager = New-Object System.Xml.XmlNamespaceManager($XmlDocument.NameTable)
    $xmlNsManager.AddNamespace("ns", $NamespaceURI)

    [string]$fullyQualifiedNodePath = Get-FullyQualifiedXmlNodePath -NodePath $NodePath -NodeSeparatorCharacter $NodeSeparatorCharacter

    # Try and get the node, then return it. Returns $null if the node was not found.
    $node = $XmlDocument.SelectSingleNode($fullyQualifiedNodePath, $xmlNsManager)
    return $node
}

现在,我将创建一些类似的函数,因此我想将前三行代码拆分成一个新的函数,这样我就不必在每个地方都复制粘贴它们了,所以我已经这样做了:

function Get-XmlNamespaceManager([xml]$XmlDocument, [string]$NamespaceURI = "")
{
    # If a Namespace URI was not given, use the Xml document's default namespace.
    if ([string]::IsNullOrEmpty($NamespaceURI)) { $NamespaceURI = $XmlDocument.DocumentElement.NamespaceURI }   

    # In order for SelectSingleNode() to actually work, we need to use the fully qualified node path along with an Xml Namespace Manager, so set them up.
    [System.Xml.XmlNamespaceManager]$xmlNsManager = New-Object System.Xml.XmlNamespaceManager($XmlDocument.NameTable)
    $xmlNsManager.AddNamespace("ns", $NamespaceURI)
    return $xmlNsManager
}

function Get-XmlNode([xml]$XmlDocument, [string]$NodePath, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.')
{
    [System.Xml.XmlNamespaceManager]$xmlNsManager = Get-XmlNamespaceManager -XmlDocument $XmlDocument -NamespaceURI $NamespaceURI
    [string]$fullyQualifiedNodePath = Get-FullyQualifiedXmlNodePath -NodePath $NodePath -NodeSeparatorCharacter $NodeSeparatorCharacter

    # Try and get the node, then return it. Returns $null if the node was not found.
    $node = $XmlDocument.SelectSingleNode($fullyQualifiedNodePath, $xmlNsManager)
    return $node
}
问题在于执行 "return $xmlNsManager" 时会抛出以下错误:
Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Xml.XmlNamespaceManager".
因此,即使我已经明确将 $xmlNsManager 变量转换为 System.Xml.XmlNamespaceManager 类型,但当它从 Get-XmlNamespaceManager 函数返回时,PowerShell 会将其转换为对象数组。
如果我不将从 Get-XmlNamespaceManager 函数返回的值显式转换为 System.Xml.XmlNamespaceManager,则由于传递到函数的第二个参数的数据类型错误,.SelectSingleNode() 函数将引发以下错误。
Cannot find an overload for "SelectSingleNode" and the argument count: "2".

由于某些原因,PowerShell没有保持返回变量的数据类型。我真的很想从一个函数中让它工作,这样我就不必在各个地方复制粘贴那3行代码了。欢迎提出任何建议,谢谢。


2
那么你对内置的 Select-Xml 命令有什么意见呢?使用它的命名空间就像传递一个前缀到命名空间映射的哈希表一样简单,例如 $xml | Select-Xml -XPath '//dns:foo' -namespace @{dns='http://schema.foo.org'} - Keith Hill
谢谢Keith,我实际上没有听说过Select-Xml cmdlet。我宁愿完全忽略Xml Namespace,因为我不关心它,但不幸的是.SelectSingleNode()需要它,正如我在我的博客文章中所讨论的那样(http://blog.danskingdom.com/powershell-functions-to-get-an-xml-node-and-get-and-set-an-xml-elements-value-even-when-the-element-does-not-already-exist/)。使用Select-Xml会解决这个问题吗? - deadlydog
1
Select-Xml 只是在底层调用 XmlDocumentSelectSingleNodeSelectNodes 方法,因此仍然容易受到 XML 命名空间的影响,这就是为什么 @Keith 使用了 -namespace 参数的原因。 - Andy Arismendi
2
顺便提一下,有一种方法可以完全忽略XML命名空间,你只需要以不同的方式编写你的Xpath。这个SO问题展示了如何实现。 - Andy Arismendi
2个回答

34

发生的是PowerShell将你的命名空间管理器对象转换为字符串数组。

我认为这与PowerShell“展开”集合的本质有关,当向管道发送对象时,PowerShell会为任何实现IEnumerable(具有GetEnumerator方法)的类型执行此操作。

作为一种解决方法,您可以使用逗号技巧来防止这种行为,并将对象作为整个集合发送。

function Get-XmlNamespaceManager([xml]$XmlDocument, [string]$NamespaceURI = "")
{
    ...
    $xmlNsManager.AddNamespace("ns", $NamespaceURI)
    return ,$xmlNsManager
}

是的,XmlNamespaceManager 实现了 IEnumerable 接口,因此 PowerShell 将其视为一系列项目。使用逗号运算符是解决这个特定问题的方法,但在这种情况下使用 return 是真的不必要的(因为它是函数的最后一行,所以不需要提前返回)。 - Keith Hill
谢谢你们的解释。Keith,我传统上是一名C#和.Net程序员,最近才开始接触PowerShell,所以我仍然有使用return;的习惯,而且我喜欢它能明确/显而易见地表明函数正在返回什么。 - deadlydog
3
除了这个展开问题之外,另一个要注意的地方是任何返回值没有被赋值给变量或通过管道传送到null的内容都将从函数中返回。例如,如果您调用XmlDocument.AppendChild方法,它将返回一个XmlNode对象,并且除非您进行防止,否则该对象将与您的命名空间管理器对象一起发送。 - Andy Arismendi
@AndyArismendi +1。这就是为什么我通常避免使用return,除非我需要提前退出函数。即使在这种情况下,我也不倾向于使用return <expr>形式。它会给人一种虚假的感觉,让人误以为从函数中返回了什么。 - Keith Hill
@AndyArismendi 你也可以将其转换为 void 来抑制方法或 Cmdlet 的输出,例如 [void] $xmlNsManager.AddNamespace("ns", $NamespaceURI) - JamieSee
@JamieSee [void]Out-Null可以去除不必要的函数输出,但是这里发生的是$xmlNsManager(一个System.Xml.XmlNamespaceManager类型)实现了IEnumerable(具有GetEnumerator()),因此Powershell返回一个对象数组([System.Object[])中枚举的内容,而不是OP想要的XmlNamespaceManager对象本身。 - Andy Arismendi

1
更具体地说,这里正在发生的是您编码时对$fullyQualifiedModePath进行强类型化的习惯尝试将 Get(对象列表)的结果转换为字符串。

[string] $foo

将限制变量$foo只能是字符串,无论返回了什么。在本例中,您的类型约束是微妙地破坏了返回并使其成为Object[]。

此外,查看您的代码,我个人建议您使用Select-Xml(内置于 V2 及更高版本),而不是手动编写大量的 XML 展开代码。您可以使用-Namespace@{x="..."}在 Select-Xml 中进行命名空间查询。


1
$fullyQualifiedModePath 这一行不是他遇到问题的那一行。问题出在这一行:[System.Xml.XmlNamespaceManager]$xmlNsManager = - Andy Arismendi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接