搜索xml文件并替换其中的字符串

时间:2015-12-24 01:55:33

标签: xml powershell

我正在尝试在Windows 7中创建一个批处理文件,以帮助我删除Skype广告。

这是我到目前为止所得到的:

@echo off

echo Editing '%WINDIR%\System32\Drivers\Etc\Hosts' file
echo 127.0.0.1       apps.skype.com >> %WINDIR%\System32\Drivers\Etc\Hosts
echo 127.0.0.1       g.msn.com >> %WINDIR%\System32\Drivers\Etc\Hosts

echo Editing '%APPDATA%\Skype' config
powershell -Command "(Get-ChildItem %APPDATA%\Skype -Filter "config.xml" -Recurse) | ForEach-Object { $_ -replace '<AdvertPlaceholder>1</AdvertPlaceholder>', '<AdvertPlaceholder>0</AdvertPlaceholder>' } | Set-Content $_"

ipconfig /flushdns

pause

powershell命令会抛出一个错误:

Editing 'C:\Users\user\AppData\Roaming\Skype' config
Set-Content : Cannot bind argument to parameter 'Path' because it is null.
At line:1 char:218
+ (Get-ChildItem C:\Users\user\AppData\Roaming\Skype -Filter config.xml -Recurse) | ForEach-Object { $_ -replace '<AdvertPlaceholder>1</AdvertPlaceholder>
', '<AdvertPlaceholder>0</AdvertPlaceholder>' } | Set-Content <<<<  $_
    + CategoryInfo          : InvalidData: (:) [Set-Content], ParameterBinding
   ValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SetContentCommand

我做错了什么?如何将内容设置为Get-ChildItem找到的文件?如果你有另一种方法,我也想听听。非常感谢。

编辑:

以下是我最终使用的内容:

@echo off

echo Editing '%WINDIR%\System32\Drivers\Etc\Hosts' file
echo 127.0.0.1       apps.skype.com >> %WINDIR%\System32\Drivers\Etc\Hosts
echo 127.0.0.1       g.msn.com >> %WINDIR%\System32\Drivers\Etc\Hosts

echo Editing '%APPDATA%\Skype' config
powershell -noprofile -c "$files = Get-ChildItem $env:AppData\Skype -Recurse config.xml; foreach($file in $files) { (Get-Content $file.fullname) -replace '<(AdvertPlaceholder)>1</\1>', '<$1>0</$1>' | Set-Content $file.fullname }"

ipconfig /flushdns

echo Done. Please restart Skype for changes to take effect.

pause 

在这篇文章发表时确认工作。

2 个答案:

答案 0 :(得分:2)

你有几个问题,我认为你正在走错路。 Nathan Tuggy had one part rightSet-Content获取管道输入并将其发送到文件。在大多数情况下,需要使用-Path(或位置)指定其路径。之前的另一个问题是您实际上并未阅读您找到的文件。您当前的代码正在尝试操作文件名本身,这不是您想要的。

更接近你正在寻找的东西是:

powershell -Command "$file = Get-ChildItem %APPDATA%\Skype -Filter 'config.xml' -Recurse | Select -First 1 -ExpandProperty FullName; (Get-Content $file) | ForEach-Object { $_.replace('<AdvertPlaceholder>1</AdvertPlaceholder>', '<AdvertPlaceholder>0</AdvertPlaceholder>')} | Set-Content $file"

我还没有完全按批次测试,因为我只是想试着告诉你哪里出错了。

获取文件名,假设该目录结构中只有一个config.xml文件。使用-First 1确保只返回一个文件名。然后将该文件名提供给Get-Content以获取该文件。然后在循环中处理文件以替换所需的文本。还使用了.Replace()而不是-replace,因为后者支持正则表达式而你不是。

我认为,等待测试,它会解决您的问题。

但是,您不是以最有效和最可靠的方式编辑XML文件。您也可以在单个PowerShell脚本中拥有所有这些逻辑。让我们看一下:

# Append some loopback entries into the host file. Make sure you are running as admin else this would fail. 
"apps.skype.com","g.msn.com" | Foreach-Object{"127.0.0.1`t$_"} | Add-Content "$env:windir\System32\Drivers\Etc\Hosts"
# Beware that multiple executions of this would keep adding more entries as we do not check the existing ones. 

# Locate and read the config.xml document. Use object notation to find the node we need to change to 0 then save the file. 
$configPath = Get-Item "$($env:appdata)\Skype\*\config.xml" | Select-Object -ExpandProperty FullName
$xml = [xml](Get-Content $configPath)
$xml.config.UI.General.AdvertPlaceholder = "0"
$xml.Save($configPath)

如果您需要帮助运行脚本here is a good post to get you started

我认为只有config.xml存在。如果您想编辑所有配置文件,那么很少需要更改。这次虽然我们需要确保您要编辑的值退出。由于config.xml不是唯一的名称。

# Locate and read the config.xml documents. Use object notation to find the node we need to change to 0 then save the file. 
$configPaths = Get-ChildItem "$($env:appdata)\Skype" -Filter "config.xml"  -Recurse | Select-Object -ExpandProperty FullName

# Cycle each file found
$configPaths | ForEach-Object{
    $xml = [xml](Get-Content $_)
    if($xml.config.UI.General.AdvertPlaceholder){
        # Check to be sure this value actually is present before we try to change it. 
        $xml.config.UI.General.AdvertPlaceholder = "0"
        # Only need to save if change was made. 
        $xml.Save($_)
    }
}

答案 1 :(得分:2)

首先,我建议在PowerShell中执行 all ,这最终会简化问题:
处理2个非常不同的环境的复杂性,调用带有powershell -command的PowerShell命令和包含要执行的命令的命令字符串引入了引用复杂性,这可能很难解决。

其次,您的问题是您的管道尝试同时执行 2个不同的事情:

  • 通过
  • 传递文件的路径
  • 同时还会转换该文件的内容

您的尝试混淆了两个任务:它传入文件对象(在字符串上下文中解析为路径),然后错误地将它们视为内容,然后神奇地期望修改后的内容返回到文件对象(路径),告诉Set-Content要写回哪些文件。

虽然 可以使用单个管道执行此操作,但它实际上是不可读的,因此我建议分别执行这两项任务:

注意:我知道您的Get-ChildItem命令实际上只会产生 1 文件,但可能它会产生 multiple < / em>那些,所以我试着解决这个问题 此外,我避免使用cmd.exe变量(%AppDate%),而只使用PowerShell($env:AppData)。 另外,请注意在下面的正则表达式中使用反向引用\1),这样就无需重复AdvertPlaceholder,也无需使用{{ 1}}使用替换表达式来引用正则表达式中的第一个捕获组($1

如果我们现在只专注于PowerShell,我们会得到:

AdvertPlacholder

注意:

  • 就字符编码而言,$files = Get-ChildItem $env:AppData\Skype -Recurse config.xml foreach($file in $files) { (Get-Content $file.fullname) -replace '<(AdvertPlaceholder)>1</\1>', '<$1>0</$1>' | Set-Content $file.fullname } 默认为系统的默认代码页。如有疑问,请使用显式编码,例如Set-Content

  • 以上假设有问题的XML元素位于行。如果不是,事情会变得更复杂:虽然您可以使用-Encode UTF8(PowerShell v3 +)一次读取整个输入文件,但Get-Content -Raw会添加 extra 输出的换行符;另外,您必须调整传递给上面Set-Content的正则表达式以考虑换行空格。

    • Matt's helpful answer通过-replace类型(完全限定名称:[xml])使用正确的XML解析,显示了更多强大的处理值替换的方法。

如果您仍想从批处理文件中调用上述PowerShell命令,请按以下步骤操作:

请注意,需要使用单行文字传递给System.Xml.XmlDocument,这会使整个事情变得不可读;此外,添加powershell.exe -command通常是一个好主意,以便禁止加载配置文件,该文件仅用于交互式使用。

-noprofile