【问题标题】:Updating a string from XML using PowerShell使用 PowerShell 从 XML 更新字符串
【发布时间】:2018-07-17 15:35:28
【问题描述】:

我正在从一些 XML 数据中检索一个文本字符串,然后尝试用其他内容替换该字符串。我使用的代码是:

$newstring = "mystring"
$bar = Get-Gpo -Name "My GPO"
$gpoid = $bar.Id.ToString()
$drivesxml = New-Object XML
$drivesxml.Load("\\mydomain.co.uk\sysvol\mydomain.co.uk\Policies\{gpoid}\User\Preferences\Drives\drives.xml")
$oldpath = $drivesxml.Drives.Drive.Properties.Path[0]
$oldlabel = $drivesxml.Drives.Drive.Properties.Label[0]
#1
[string]$newtemppath = $oldpath -replace "Oldstring$", "$newstring"
[string]$newtemplabel = $oldlabel -replace "^Oldstring", "$newstring"
#2
$drivesxml.Drives.Drive.Properties.Path[0] = $newtemppath
$drivesxml.Drives.Drive.Properties.Label[0] = $newtemplabel
#3
$drivesxml.Save("\\mydomain.co.uk\sysvol\mydomain.co.uk\Policies\{gpoid}\User\Preferences\Drives\drives.xml")

它很好地从 sysvol 中检索 XML,如果我查询 $oldpath$oldlabel,它们在点 1 处包含来自 XML 的预期文本。

如果我在 #2 查询 $newtemppath$newtemplabel,则字符串按预期返回,并修改了文本,因此“Oldstring”的实例已被“mystring”替换。

在#3,如果我查询$drivesxml.Drives.Drive.Properties.Path[0]$drivesxml.Drives.Drive.Properties.Label[0],我希望它们返回与$newtemppath$newtemplabel 变量相同的内容,但它们会继续返回其原始值。

保存 XML 后,如果我再次查询它,内容并没有改变。

谁能看到我在 #2 和 #3 之间的分配中可能做错了什么?

【问题讨论】:

    标签: xml powershell


    【解决方案1】:

    Powershell 支持用于导航 XML 文件的“点路径”语法,这对于从 XML 文件中读取数据非常方便——大概是为了方便使用 XML 输入或使用 XML 配置文件。

    该语法的缺点是它tries to return strings and arrays whenever it can。在您的情况下,$drivesxml.Drives.Drive.Properties.Path[0] 是一个字符串,不再是 XML 节点,因此您分配给该值的任何内容都将丢失。

    诀窍是使用 XML 节点。实现这一点的最简单方法是use XPath for navigation(无论如何它比点路径语法更强大):

    $path = $drivesxml.SelectSingleNode('/Drives/Drive/Properties/Path')
    $path.InnerText = $path.InnerText -replace "Oldstring$",$newstring
    
    # ...
    
    $drivesxml.Save($xmlPath)
    

    您还可以使用.SelectNodesforeach 循环进行多项更改。


    另外,Powershell 将在双引号字符串中进行变量插值。这可能与正则表达式发生冲突,其中$ 有其自身的含义。在上面的例子中没有歧义,但最好养成使用单引号字符串作为正则表达式的习惯:

    $path.InnerText = $path.InnerText -replace 'Oldstring$',$newstring
    

    关于 XML 命名空间的说明:如果您的 XML 文件使用命名空间(在元素上由 xmlns 声明),则需要在使用 XPath 之前知道这些命名空间:Using PowerShell, how do I add multiple namespaces (one of which is the default namespace)?

    【讨论】:

    • 请注意,如果 XML 具有命名空间(我几乎可以保证 Microsoft 产品生成的任何类型的 XML),使用这种方法您需要一个命名空间管理器。
    • 好点子,我将添加一个指向详细说明其工作原理的答案的链接。
    • 太棒了,谢谢!幸运的是,GPO .xml 文件似乎没有使用命名空间。原来“.path”不是一个实际的节点,它可能与点路径无关,但与 XPath 无关,所以最终使用 SelectNodes('/Drives/Drive/Properties'),然后是 $path[0 ].path 和 $path[0].label,这是可行的,但如果没有你指出我正确的方向,就不会到达那里。
    • @KeithLangmead 感谢您的反馈。下次带走,将 XML 文件(有意义的示例)添加到问题中,这样我们就不必猜测答案了。 (说到这里,我猜path 是一个属性,它们在XPath 中使用@ 寻址,即/Drives/Drive/Properties/@path。请注意,XPath 区分大小写,而Powershell 通常不区分。)
    • 啊哈,很高兴知道,谢谢。上网打猎,但网上很多人把节点和属性混为一谈! :) 关于包含 XML 的公平点,如果读取的部分没有工作,我肯定会这样做,但既然这样做了,我天真地认为没有必要。
    【解决方案2】:

    为了完整起见,我实现了 Tomalak 的答案后的工作代码是。

    $newstring = "mystring"
    $bar = Get-Gpo -Name "My GPO"
    $gpoid = $bar.Id.ToString()
    $drivesxml = New-Object XML
    $drivesxml.Load("\\mydomain.co.uk\sysvol\mydomain.co.uk\Policies\{gpoid}\User\Preferences\Drives\drives.xml")
    $path=$drivesxml.SelectNodes('/Drives/Drive/Properties')    
    $path[0].path = $path[0].path -replace 'Oldstring$',$newstring
    $path[0].label = $path[0].label -replace '^Oldstring',$newstring
    $drivesxml.Save("\\mydomain.co.uk\sysvol\mydomain.co.uk\Policies\{gpoid}\User\Preferences\Drives\drives.xml")
    

    以及它正在查询以供参考的 XML:

    <?xml version="1.0" encoding="utf-8"?>
    <Drives clsid="{8FDDCC1A-0C3C-43cd-A6B4-71A6DF30DA8C}">
      <Drive clsid="{935D1B74-9CB8-4e3c-9914-7DD559C7A417}" name="S:" status="S:" image="0" changed="2010-07-15 15:01:51" uid="{ACCCC2A9-809B-4CE3-9F2D-F4B4643B02F5}" bypassErrors="1">
        <Properties action="C" thisDrive="SHOW" allDrives="SHOW" userName="" path="\\mydomain.co.uk\GroupData$\Companies\mystring"     label="mystring - Shared drive" persistent="1" useLetter="1" letter="S" />
      </Drive>
      <Drive clsid="{935D1B74-9CB8-4e3c-9914-7DD559B8A417}" name="U:" status="U:" image="0" changed="2010-07-15 15:04:20" uid="{FA3F7F3C-A1A0-4618-89E1-88191C780A67}" bypassErrors="1">
        <Properties action="C" thisDrive="SHOW" allDrives="NOCHANGE" userName="" path="\\mydomain.co.uk\data\users\%username%" label="Personal drive (My documents)" persistent="1" useLetter="1" letter="U" />
      </Drive>
    </Drives>
    

    【讨论】:

    • 我会使用类似$drive_S = $drivesxml.SelectSingleNode("/Drives/Drive/Properties[@name = 'S:']") 然后$drive_S.path = $drive_S.path -replace 'Oldstring$',$newstring 的东西,这比使用.SelectNodes() 和盲目地用[0] 选择第一个更直接 - 而且它没有当&lt;Drive&gt; 元素的顺序由于某种原因突然相反时中断。
    猜你喜欢
    • 1970-01-01
    • 2018-12-25
    • 1970-01-01
    • 2011-04-25
    • 2013-12-19
    • 1970-01-01
    • 2010-11-19
    • 2012-12-02
    • 1970-01-01
    相关资源
    最近更新 更多