Powershell - 在 XML 文件中创建新条目 [英] Powershell - Creating new entries into an XML File

查看:40
本文介绍了Powershell - 在 XML 文件中创建新条目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 PowerShell 加载一个 XML 文件,我想向所选节点添加新条目.

I am using PowerShell to load an XML File and I would like to add new entries into the selected nodes.

这是 XML 文档:

    <?xml version="1.0"?>
<MDMPolicyMappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.microsoft.com/MdmMigrationAnalysisTool">
  <Computer>
    <PolicyMap xsi:type="OptionalPolicyMap">
      <Name>Accounts: Administrator account status</Name>
      <CspUri>./Device/Vendor/MSFT/Policy/Config/LocalPoliciesSecurityOptions/Accounts_EnableAdministratorAccountStatus</CspUri>
      <CspName>Policy</CspName>
      <Version>16299</Version>
    </PolicyMap>
  </Computer>
  <User>
    <PolicyMap xsi:type="AdmxPolicyMap">
      <Name>Control Panel/Printers/Point and Print Restrictions</Name>
      <CspUri>./User/Vendor/MSFT/Policy/Config/Printers/PointAndPrintRestrictions_User</CspUri>
      <CspName>Policy</CspName>
      <Version>15063</Version>
    </PolicyMap>
  </User>
</MDMPolicyMappings>

我使用这个 Powershell 命令:

I use this Powershell command:

[xml]$XmlMmat = GC "C:\MDMPolicyMapping.xml"

但我不确定如何将新记录添加到计算机和用户节点.

But I am not sure how I can add new records to the Computer and User nodes.

例如我想将此添加到计算机:

For example I would like to add this to Computer:

 <PolicyMap xsi:type="OptionalPolicyMap">
          <Name>Test Policy</Name>
          <CspUri>./Device/Vendor/MSFT/Policy/Config/Test</CspUri>
          <CspName>Policy</CspName>
          <Version>0000</Version>
        </PolicyMap>

有人知道我需要做什么才能将这个 XML 块添加到主文件中吗?

Does anyone know what I would need to do to get this block of XML added into the main file?

谢谢.

推荐答案

[xml] 转换结构是一个 System.Xml.XmlDocument 实例.

What the [xml] cast constructs is a System.Xml.XmlDocument instance.

您可以使用其方法来操作 XML DOM,并且您可以使用 PowerShell 对该 DOM 的改编 来轻松钻取直到感兴趣的父元素.

You can use its methods to manipulate the XML DOM, and you can use PowerShell's adaptation of that DOM to easily drill down to the parent element of interest.

在您的情况下,XML 命名空间的参与使得新元素的插入具有挑战性.

In your case, the involvement of XML namespaces makes the insertion of the new elements challenging.

更新:现在通过辅助文档片段构建新元素(使用.CreateDocumentFragment()) 而不是通过辅助.第二个文档,第一次显示在 fpmurphy 的有用回答中.

Update: The new element is now constructed via an auxiliary document fragment (constructed with .CreateDocumentFragment()) rather than via an aux. second document, as first shown in fpmurphy's helpful answer.

# Read the file into an XML DOM ([System.Xml.XmlDocument]; type accelerator [xml]).
# NOTE: You should use Get-Content to read an XML file ONLY if
#       you know the document's character encoding (see bottom section).
#       The robust alternative, where the XML declaration is analyzed
#       for the encoding actually used, is:
#        [xml] $xmlMmat = [xml]::new()
#        $xmlMat.Load('C:\MDMPolicyMapping.xml') # !! Always use a FULL path.
[xml] $xmlMmat = Get-Content -Encoding utf8 -Raw C:\MDMPolicyMapping.xml

# Create the element to insert.
# An aux. XML document fragment *with the same namespace declarations* as the target
# document must be used, and these are attached to a dummy *document node*
# enclosing the actual element, so that the namespace declarations don't become
# part of the element itself.
$xmlFragment = $xmlMmat.CreateDocumentFragment()
$xmlFragment.InnerXml =
@'
<aux xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.microsoft.com/MdmMigrationAnalysisTool">
  <PolicyMap xsi:type="OptionalPolicyMap">
    <Name>Test Policy</Name>
    <CspUri>./Device/Vendor/MSFT/Policy/Config/Test</CspUri>
    <CspName>Policy</CspName>
    <Version>0000</Version>
  </PolicyMap>
</aux>
'@

# Use dot notation to drill down to the <Computer> element and
# append the new element as a new child node.
$null = $xmlMmat.MDMPolicyMappings.Computer.AppendChild(
          $xmlFragment.aux.PolicyMap
        )

# For illustration, pretty-print the resulting XML to the screen.
# Note: The LINQ-related [Xml.Linq.XDocument] type offers pretty-printing by 
#       default, so casting the XML text to it and calling .ToString() on
#       the resulting instance pretty-prints automatically, but note that
# while this is convenient, it is *inefficient*, because the whole document
#       is parsed again.
([System.Xml.Linq.XDocument] $xmlMmat.OuterXml).ToString()

# Now you can save the modified DOM to a file, e.g., to save back to the
# original one, but WITHOUT PRETTY-PRINTING:
#
#    $xmlMmat.Save('C:\MDMPolicyMapping.xml') # !! Always use a FULL path.

# To save PRETTY-PRINTED XML, more work is needed, using 
# a [System.Xml.XmlTextWriter] instance:
#
#   # !! Always specify the output file as a *full path*.
#   $xmlWriter = [System.Xml.XmlTextWriter] [System.IO.StreamWriter] 'C:\MDMPolicyMapping.xml'
#   $xmlWriter.Formatting = 'indented'; $xmlWriter.Indentation = 2
#   $xmlMMat.WriteContentTo($xmlWriter)
#   $xmlWriter.Dispose()

注意:如果您不介意再次解析 XML 的额外开销,请转换为 System.Xml.Linq.XDocument 实例,保存到打印精美的文件就像:

Note: If you don't mind the extra overhead of parsing the XML again, into a System.Xml.Linq.XDocument instance, saving to a pretty-printed file is as easy as:

# Inefficient, but convenient way to save an [xml] instance pretty-printed.
# !! Always use a *full path*:
([System.Xml.Linq.XDocument] $xmlMmat.OuterXml).Save('C:\MDMPolicyMapping.xml')


重新 XML 漂亮打印:

请注意,通过设置 [xml] 实例的 ,您可以选择在 加载 XML 文件时保留无关紧要的空格(用于漂亮打印)="https://docs.microsoft.com/en-US/dotnet/api/System.Xml.XmlDocument.PreserveWhitespace" rel="nofollow noreferrer">.PreserveWhitepace 属性$true 在调用 .Load() 之前:

Note that you can opt to preserve insignificant whitespace (as used in pretty-printing) on loading an XML file, by setting an [xml] instance's .PreserveWhitepace property to $true before calling .Load():

  • 如果输入文件一开始是漂亮打印的并且您没有进行结构更改.Save() 将保留漂亮打印的格式文件.

  • If the input file was pretty-printed to begin with and you make no structural changes, .Save() will preserve the pretty-printed format of the document.

如果您确实进行了结构更改,则保留漂亮打印格式的唯一方法是手动匹配预期的格式空白以使其适合与现有的漂亮印刷.

If you do make structural changes, the only way to preserve the pretty-printed format is to manually match the expected formatting whitespace so that it fits in with the existing pretty-printing.

总的来说,更强大的方法是:

  • 加载时忽略无关紧要的空格,[xml] 默认情况下会这样做(也就是说,任何原始的漂亮打印都会丢失).
  • 如果需要,在保存时显式地打印漂亮(不幸的是,[xml] 并不容易 - 与 [System.Xml.Linq.XDocument]).
  • Ignore insignificant whitespace on loading, which [xml] does by default (that is, any original pretty-printing is lost).
  • Explicitly pretty-print on saving, if needed (which [xml] doesn't make easy, unfortunately - unlike [System.Xml.Linq.XDocument]).

Tomalak 建议将关于此快捷方式的警告表述如下:

除非文件损坏并且 XmlDocument.Load() 失败,否则您应该永远不要使用 Get-Content 来读取 XML 文件.

You should use NEVER use Get-Content to read an XML file unless the file is broken and XmlDocument.Load() fails.

原因是诸如 [xml] $xmlDoc = Get-Content -Raw file.xml[1] 之类的语句(或者,没有类型约束变量,$xmlDoc = [xml] (Get-Content -Raw some.xml)) 可能会误解文件的字符编码:

The reason is that a statement such as [xml] $xmlDoc = Get-Content -Raw file.xml[1] (or, without type-constraining the variable, $xmlDoc = [xml] (Get-Content -Raw some.xml)) may misinterpret the file's character encoding:

  • XML 文件的默认字符编码是 UTF-8;也就是说,在没有 BOM(并且 XML 声明中没有 encoding 属性,见下文)的情况下,应该假定为 UTF-8.

  • The default character encoding for XML files is UTF-8; that is, in the absence of a BOM (and without an encoding attribute in the XML declaration, see below) UTF-8 should be assumed.

  • 但是,在 Windows PowerShellGet-Content 假定 ANSI 编码(系统的活动 ANSI 代码页由系统区域设置(语言用于非 Unicode 程序)) 在没有 BOM 的情况下.幸运的是,PowerShell (Core) v6+ 现在始终默认为(无 BOM)UTF-8.
  • However, in Windows PowerShell Get-Content assumes ANSI encoding (the system's active ANSI code page as determined by the system locale (language for non-Unicode programs)) in the absence of a BOM. Fortunately, PowerShell (Core) v6+ now consistently defaults to (BOM-less) UTF-8.

虽然您可以在 Windows PowerShell 中使用 -Encoding ut8 解决这个问题,但这还不够,因为 XML 文档可以自由指定其实际编码作为文档内容的一部分,即通过 XML 声明的 encoding 属性;例如:

While you can address this problem with -Encoding ut8 in Windows PowerShell, this is not sufficient, because an XML document is free to specify its actual encoding as part of the document's content, namely via the XML declaration's encoding attribute; e.g.:
<?xml version="1.0" encoding="ISO-8859-1"?>

  • 由于 Get-Content 仅根据 BOM 的缺失/存在来决定使用哪种编码,它不是遵守声明的编码.

  • Since Get-Content decides what encoding to use solely based on the absence / presence of a BOM, it does not honor the declared encoding.

相比之下,[xml] (System.Xml.XmlDocument) 类型的 .Load() 方法 确实如此,这是为什么它是唯一可靠读取 XML 文档的方法.[2]

By contrast, the [xml] (System.Xml.XmlDocument) type's .Load() method does, which is why it is the only robust way to read XML documents.[2]

同样,您应该使用 .Save() 方法,用于将 [xml] 实例正确保存到文件中.

Similarly, you should use the .Save() method for properly saving an [xml] instance to a file.

不幸的是,与 Get-Content 快捷方式相比,这种健壮的方法是 (a) 更冗长,更不流畅(您需要显式构造一个 [xml] 实例,然后在其上调用 .Load() 和 (b) treacherous(由于 .NETAPI 通常看到与 PowerShell 不同的工作目录,相对文件路径故障):

It is unfortunate that the robust method, compared to the Get-Content shortcut is (a) more verbose and less fluid (you need to construct an [xml] instance explicitly and then call .Load() on it) and (b) treacherous (due to .NET APIs typically seeing a different working dir. than PowerShell, relative file paths malfunction):

# The ROBUST WAY to read an XML file:

# Construct an empty System.Xml.XmlDocument instance.
$xmlDoc = [xml]::new()

# Determine the input file's *full path*, as only that
# guarantees that the .Load() call below finds the file.
$fullName = Convert-Path ./file.xml

# Load from file and parse into an XML DOM.
$xmlDoc.Load($fullName)

你可以缩短成这个成语,但它仍然很别扭:

You can shorten to this idiom, but it is still awkward:

($xmlDoc = [xml]::new()).Load((Convert-Path file.xml))


未来的潜在改进:

  • 如果 Get-Content 本身被设为可识别 XML(以便遵守 XML 声明中指定的编码),则快捷方式将正常工作.

  • If Get-Content itself were to be made XML-aware (so as to honor an encoding specified in the XML declaration), the shortcut would work robustly.

  • Tomalak has suggested this in GitHub issue #14508.

或者,可以引入新的语法糖来支持转换System.IO.FileInfo 实例到[xml],如Get-Item返回Get-ChildItem:

Alternatively, new syntactic sugar could be introduced to support casting a System.IO.FileInfo instance to [xml], such as returned by Get-Item and Get-ChildItem:

# WISHFUL THINKING - cast a FileInfo instance to [xml]
[xml] $xmlDoc = Get-Item ./file.xml

[1] -Raw 不是绝对必要的,但可以大大加快操作速度.

[1] -Raw isn't strictly necessary, but greatly speeds up the operation.

[2] 更一般地说,任何合适的 XML 解析器都应该正确处理通过 XML 声明指定的编码;例如,[System.Xml.Linq.XDocument] 类型的 .Load 也可以工作,但与 [System.Xml.XmlDocument] 不同的是,这种类型不是与 PowerShell 集成良好(无法通过方便的点符号访问元素和属性).

[2] More generally, any proper XML parser should handle an encoding specified via the XML declaration correctly; for instance, The [System.Xml.Linq.XDocument] type's .Load works too, but, unlike [System.Xml.XmlDocument], this type isn't well integrated with PowerShell (no access to elements and attributes by convenient dot notation).

这篇关于Powershell - 在 XML 文件中创建新条目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆