加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

22. PowerShell -- 处理XML

发布时间:2020-12-16 09:09:49 所属栏目:百科 来源:网络整理
导读:XML 结构 加载和处理XML文件 浏览扩展类型系统 PowerShell处理XML --XML结构 之前原始信息存储在逗号分隔的记录文件或者.ini文件中,但是近几年XML标准占了上风。XML是”可扩展标记语言“的缩写,是一种对于任意结构化的信息的可描述性语言。过去处理XML还是
  1. XML 结构

  2. 加载和处理XML文件

  3. 浏览扩展类型系统

  • PowerShell处理XML --XML结构

之前原始信息存储在逗号分隔的记录文件或者.ini文件中,但是近几年XML标准占了上风。XML是”可扩展标记语言“的缩写,是一种对于任意结构化的信息的可描述性语言。过去处理XML还是相当麻烦的,但是现在PowerShell中,对XML有了非常优秀的支持。通过它的帮助,你既可以非常容易的在XML中包装数据,也可以非常舒服的访问已有的XML文件。

XML 结构

XML使用标签来唯一标识信息片段,一个标签像网站中的HTML文档使用的一样,是一对尖括号。通常,一条信息被一个起始和结束标记所分割。结束标记前面使用“/”,其结果谓之结点,在下面的例子就应当叫做Name结点。

1
<Name>Tobias Weltner</Name>

另外,结点拥有它自身相关信息的属性(attributes)。这些信息位于开始标签。

1
<staff branch="Hanover" Type="sales">...</staff>

如果一个结点为空,它的开始结点和结束结点可以折叠起来。结束符号“/”指向标签结束。例如,在Hanover的子公司没有任何员工从事销售工作,那这个标签可以像这样描述。

1
<staff branch="Hanover" Type="sales"/>

通常,如果一个标签不空,包含更多信息,这些信息应当包含在标签中。这就允许按照你喜欢的深度产生信息结构。下面XML结构描述Hanover子公司销售部门工作的两位员工。

1
2
3
4
5
6
7
8
9
10
11
12
<staff branch="Hanover" Type="sales">
<employee>
<Name>Tobias Weltner</Name>
<function>management</function>
<age>39</age>
</employee>
<employee>
<Name>Cofi Heidecke</Name>
<function>security</function>
<age>4</age>
</employee>
</staff>

为了让XML文件被识别,通常在它们的开始有一个非常简单类似下面例子中的一个头声明。

1
<?xml version="1.0" ?>

头声明声明了紧跟其后的XML符合1.0 版本规范。被称为”schema”的东西也可以在这里被指定。具体来说,Schema有个XSD文件的形式,它用来描述XML文件结构应当遵循确定的目的。在上一个例子中,Schema可能会指定必须包含“staff”结点作为员工的信息,进而指定多个命名为“staff”的子结点为必须的。Schema也可以指定相关的信息,例如每个员工的名称和定义他们具体的职能。

因为XML文件有纯文本构成,你可以使用编辑器创建他们,也可以直接使用PowerShell。让我们将前面定义的员工信息保存为一个XML文件吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$xml = @'
<?xml version="1.0" standalone="yes"?>
<staff branch="Hanover" Type="sales">
<employee>
<Name>Tobias Weltner</Name>
<function>management</function>
<age>39</age>
</employee>
<employee>
<Name>Cofi Heidecke</Name>
<function>security</function>
<age>4</age>
</employee>
</staff>
'@ | Out-File employee.xml
  • PowerShell处理XML --加载和处理XML文件

如果你想将XML文件按照实际的XML来处理,而不是纯文本。文件的内容必须转换成XML类型。类型转换在第六章已经提到,只须一行。

$xmldata=[xml](Get-Contentemployee.xml)

Get-Content从之前保存的xml文件中读取xml内容,然后使用[xml]将xml内容转换成真正的XML。你可以将xml文本直接转换成XML,然后保存在变量$xml中:

$xmldata=[xml]$xml

然而,转换只会在指定的xml文本合法并不包含解析错误时有效。当你尝试转换的xml文件结构不合法,会报错。

用来描述XML的结构和信息现在被存放在变量$xmldata。从现在开始,获取一小段信息将会变得非常容易,因为XML对象将每个结点转换成了属性。你可以像这样获取员工信息:

$xmldata.staff.employee
Name		Function	Age
----		-----		-----
Tobias	Weltner	management	39
CofiHeidecke	security	4

访问和更新单个结点

如果一个结点在xml中是唯一的,你可以像前面的例子一样输入一个句号来访问它。然而,多数情况下XML文档包含了许多类似的结点(被称为兄弟结点),像前面最后一个例子中一样,包含了多个独立的员工。比如,你可以使用管道获得特定的员工,然后来更新它的数据。

$xmldata.staff.employee|
Where-Object{$_.Name-match"TobiasWeltner"}
Namefunctionage
---------------
TobiasWeltnermanagement39
$employee=$xmldata.staff.employee|
Where-Object{$_.Name-match"TobiasWeltner"}
$employee.function="vacation"
$xmldata.staff.employee|ft-autosize
Namefunctionage
---------------
TobiasWeltnervacation39
CofiHeideckesecurity4

使用SelectNodes()来选择Nodes

SelectNodes()方法是Xpath查询语言支持的方法,也允许你选择结点。XPath指的是一个结点‘路径名称’:

$xmldata=[xml](Get-Contentemployee.xml)
$xmldata.SelectNodes("staff/employee")
Namefunctionage
---------------
TobiasWeltnermanagement39
CofiHeideckesecurity4

结果看起来像前面直接通过属性访问一样,但是XPath支持在方括号中使用通配符访问。下面的语句只会返回第一个员工结点。

PS>$xmldata.SelectNodes("staff/employee[1]")

Namefunctionage
---------------
TobiasWeltnermanagement39

如果你想,你还可以获取一个年龄小于18岁的员工列表:

PS>$xmldata.SelectNodes("staff/employee[age<18]")

Namefunctionage
---------------
CofiHeideckesecurity4

类似的方式,查询语言也支持获取列表中的最后一位员工信息,所以也可以指定位置:

$xmldata.SelectNodes("staff/employee[last()]")
$xmldata.SelectNodes("staff/employee[position()>1]")

或者,你可以使用所谓的XpathNavigator,从中获取许多从XML文本的类型转换。

#创建一个XML定位:
$xpath=[System.XML.XPath.XPathDocument]`
[System.IO.TextReader][System.IO.StringReader]`
(Get-Contentemployee.xml|out-string)
$navigator=$xpath.CreateNavigator()

#输出Hanover子公司的最后一位员工
$query="/staff[@branch='Hanover']/employee[last()]/Name"
$navigator.Select($query)|Format-TableValue

Value
-----
CofiHeidecke

#输出Hanover子公司的除了TobiasWeltner之外的所有员工,
$query="/staff[@branch='Hanover']/employee[Name!='Tobias
Weltner']"
$navigator.Select($query)|Format-TableValue

Value
-----
CofiHeideckesecurity4

荔非苔注:如果你的XML文档包含命名空间,SelectNodes时稍有不同,可以参考这篇文章PowerShell 基于Namespace来SelectNode

访问属性

属性是定义在一个XML标签中的信息,如果你想查看结点的属性,可以使用get_Attributes()方法:

$xmldata.staff.get_Attributes()
#text
-----
Hanover
sales

使用GetAttribute()方法来查询一个特定的属性:

$xmldata.staff.GetAttribute("branch")
Hanover

使用SetAttribute()方法来指定新的属性,或者更新(重写)已有的属性。

$xmldata.staff.SetAttribute("branch","NewYork")
$xmldata.staff.GetAttribute("branch")
NewYork

添加新结点

如果你想在员工结点列表中添加新的员工。首先,使用CreateElement()创建一个员工元素,然后定制员工的内部结构。最后,就可以在XML结构中你期望的位置插入这个元素。

#加载XML文本文件:
$xmldata=[xml](Get-Contentemployee.xml)
#创建新的结点:
$newemployee=$xmldata.CreateElement("employee")
$newemployee.set_InnerXML(`
"<Name>BerndSeiler</Name><function>expert</function>")
#插入新结点:
$xmldata.staff.AppendChild($newemployee)

#保存结果到原文件中:
$xmldata.save(".employeexml")

#验证结果:
$xmldata.staff.employee
Namefunctionage
---------------
TobiasWeltnermanagement39
CofiHeideckesecurity4
BerndSeilerexpert
#输出为纯文本:
$xmldata.get_InnerXml()
<?xmlversion="1.0"?><Branchofficestaff="Hanover"Type="sales">
<employee><Name>TobiasWeltner</Name><function>management</function>
<age>39</age></employee><employee><Name>CofiHeidecke</Name>
<function>security</function><age>4</age></employee><employee>
<Name>BerndSeiler</Name><function>expert</function></employee></staff>
  • PowerShell处理XML --扩展类型系统

PowerShell扩展类型系统(ETS)确保了对象可以被转换成有意义的文本。此外,它还可以传递额外的属性和方法给对象。这些操作的精确定义被存放在扩展名为.ps1xml的文件中。

扩展类型系统的XML数据

不论何时,PowerShell要将对象转换成文本,都会搜索它自己的内置纪录中关于对象的描述和转换。正确的文件包含了XML:它的文件名以.format.ps1xml收尾,这些文件位于PowerShell的根目录$pshome下。

PS>Dir$pshome*.format.ps1xml

Directory:C:WindowsSystem32WindowsPowerShellv1.0

ModeLastWriteTimeLengthName
---------------------------
-a---2013/6/1822:5027338Certificate.format.ps1xml
-a---2013/6/1822:5027106Diagnostics.Format.ps1xml
-a---2013/6/1822:50147702DotNetTypes.format.ps1xml
-a---2013/6/1822:5014502Event.Format.ps1xml
-a---2013/6/1822:5021293FileSystem.format.ps1xml
-a---2013/6/1822:50287938Help.format.ps1xml
-a---2013/6/1822:5097880HelpV3.format.ps1xml
-a---2013/6/192:30105230PowerShellCore.format.ps1xml
-a---2013/6/1822:5018612PowerShellTrace.format.ps1xml
-a---2013/6/1822:5013659Registry.format.ps1xml
-a---2013/6/1822:5017731WSMan.Format.ps1xml

所有的这些文件包含了众多的视图,你可以使用PowerShell 支持的XML方法来检验它。

1
2
[xml]$file = Get-Content "$pshomedotnettypes.format.ps1xml"
$file.Configuration.ViewDefinitions.View
NameViewSelectedByListControl
-----------------------------
System.CodeDom.Compiler.CompilerErrorViewSelectedByListControl
System.Reflection.AssemblyViewSelectedBy
System.Reflection.AssemblyNameViewSelectedBy
System.Globalization.CultureInfoViewSelectedBy
System.Diagnostics.FileVersionInfoViewSelectedBy
System.Management.ManagementObject#rootcimv2Win32_PingStatusViewSelectedBy
Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatusViewSelectedBy
System.Management.ManagementObject#rootdefaultSystemRestoreViewSelectedBy
Microsoft.Management.Infrastructure.CimInstance#root/default/SystemRestoreViewSelectedBy
Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ProductViewSelectedBy
System.Net.NetworkCredentialViewSelectedBy
System.Management.Automation.PSMethodViewSelectedBy
Microsoft.Management.Infrastructure.CimInstance#__PartialCIMInstanceViewSelectedBy

查找预定义的视图

预定义的视图非常有意思,因为你可以在像format-table和format-list这样的格式化命令中使用-View参数来对指定的结果做出丰富的调整和更改。

1
2
Get-Process | Format-Table -view Priority
Get-Process | Format-Table -view StartTime

很遗憾,没人告诉你像Priority和 StartTime这样的视图或者其它视图已经存在。你可以查看相关的XML文件,视图文件显示每一个视图结点包含了子结点:Name,ViewSelectedBy,TableControl。但是开始,视图的原始XML数据可能看起来费解,不清楚。

使用一行命令即可重新格式化文本,让它方便阅读:

1
2
3
4
5
$xmldata.get_OuterXML().Replace("<","`t<").Replace(">",">`t").Replace(">`t`t<",">`t<").Split("`t") |
ForEach-Object {$x=0}{ If ($_.StartsWith("</")) {$x--} `
ElseIf($_.StartsWith("<")) { $x++}; (" " * ($x)) + $_; `
if ($_.StartsWith("</")) { $x--} elseif `
($_.StartsWith("<")) {$x++} }
<View>
<Name>
System.CodeDom.Compiler.CompilerError
</Name>
<ViewSelectedBy>
<TypeName>
System.CodeDom.Compiler.CompilerError
</TypeName>
</ViewSelectedBy>
<ListControl>
<ListEntries>
<ListEntry>
<ListItems>
<ListItem>
<PropertyName>
ErrorText
</PropertyName>
</ListItem>
<ListItem>
<PropertyName>
Line
</PropertyName>
</ListItem>
<ListItem>
<PropertyName>
Column
</PropertyName>
</ListItem>
<ListItem>
<PropertyName>
ErrorNumber
</PropertyName>
</ListItem>
<ListItem>
<PropertyName>
LineSource
</PropertyName>
</ListItem>
</ListItems>
</ListEntry>
</ListEntries>
</ListControl>
</View>

每一个视图由一个Name,一个ViewSelectedBy中的.NET类型组成,作为视图合格的条件,TableControl结点也一样,指定对象被支持转换成文本。如果你想在一列中输出定义在XML文件中的所有的视图,Format-Table命令足矣,然后选择你想在摘要中显示的属性。

1
2
3
[xml]$file = Get-Content "$pshomedotnettypes.format.ps1xml"
$file.Configuration.ViewDefinitions.View |
Format-Table Name,{$_.ViewSelectedBy.TypeName}
Name$_.ViewSelectedBy.TypeName
------------------------------
System.CodeDom.Compiler.CompilerErrorSystem.CodeDom.Compiler.CompilerError
System.Reflection.AssemblySystem.Reflection.Assembly
System.Reflection.AssemblyNameSystem.Reflection.AssemblyName
System.Globalization.CultureInfoSystem.Globalization.CultureInfo
System.Diagnostics.FileVersionInfoSystem.Diagnostics.FileVersionInfo
System.Diagnostics.EventLogEntrySystem.Diagnostics.EventLogEntry
System.Diagnostics.EventLogSystem.Diagnostics.EventLog
System.VersionSystem.Version
System.Drawing.Printing.PrintDocumentSystem.Drawing.Printing.PrintDocument
DictionarySystem.Collections.DictionaryEntry
ProcessModuleSystem.Diagnostics.ProcessModule
processSystem.Diagnostics.Process
ProcessWithUserNameSystem.Diagnostics.Process#IncludeUserName
DirectoryEntrySystem.DirectoryServices.DirectoryEntry
PSSnapInInfoSystem.Management.Automation.PSSnapInInfo
PSSnapInInfoSystem.Management.Automation.PSSnapInInfo
PrioritySystem.Diagnostics.Process
StartTimeSystem.Diagnostics.Process
serviceSystem.ServiceProcess.ServiceController
System.Diagnostics.FileVersionInfoSystem.Diagnostics.FileVersionInfo
System.Diagnostics.EventLogEntrySystem.Diagnostics.EventLogEntry
System.Diagnostics.EventLogSystem.Diagnostics.EventLog
System.TimeSpanSystem.TimeSpan
System.TimeSpanSystem.TimeSpan
System.TimeSpanSystem.TimeSpan
System.AppDomainSystem.AppDomain
System.ServiceProcess.ServiceControllerSystem.ServiceProcess.ServiceController
System.Reflection.AssemblySystem.Reflection.Assembly
System.Collections.DictionaryEntrySystem.Collections.DictionaryEntry
processSystem.Diagnostics.Process
DateTimeSystem.DateTime
System.Security.AccessControl.ObjectSecuritySystem.Security.AccessControl.ObjectSecurity
System.Security.AccessControl.ObjectSecuritySystem.Security.AccessControl.ObjectSecurity
System.Management.ManagementClassSystem.Management.ManagementClass
Microsoft.Management.Infrastructure.CimClassMicrosoft.Management.Infrastructure.CimClass
System.GuidSystem.Guid
System.Management.ManagementObject#rootcimv2Win32_Ping...System.Management.ManagementObject#rootcimv2Win32_Ping...
...............

你所看到是XML文件中定义的所有的视图了,第二列显示的就是视图定义的对象类型。其中的Priority和StartTime就是我们之前在上一个例子中使用的两个视图。看了第二列应当就会非常清楚,该视图是针对System.Diagnostics.Process对象,恰恰就是命令Get-Process获取的对象。

1
2
(Get-Process | Select-Object -first 1).GetType().FullName
System.Diagnostics.Process

你可能会惊讶有些Name是成对出现的,例如System.TimeSpan。其原因正是上一个例子中提到的TableControl结点,一起的还有其它类型转换的结点ListControl,WideControlCustomControl。这些结点在第一次概览时,不会被显示。原因是每一个视图只允许出现一个这样的结点。TableControl被输出时或多或少带有随机性,因为在转换第一条记录时,PowerShell是基于未知对象的转换。

接下来我们会提取出XML文件中所有的必须的信息。首先对视图按照ViewSelectedBy.TypeName排序,接着根据criterion(标准)来分组。你也可以按照只匹配出一次确定的对象类型来排序。你只需要那些值得在-view参数中指定的,存在多个对象类型的视图。

1
2
3
4
5
6
7
8
9
10
[xml]$file = Get-Content "$pshomedotnettypes.format.ps1xml"
$file.Configuration.ViewDefinitions.View |
Sort-Object {$_.ViewSelectedBy.TypeName} |
Group-Object {$_.ViewSelectedBy.TypeName} |
Where-Object { $_.Count -gt 1} |
ForEach-Object { $_.Group} |
Format-Table Name,{$_.ViewSelectedBy.TypeName},`
@{expression={if ($_.TableControl) { "Table" } elseif `
($_.ListControl) { "List" } elseif ($_.WideControl) { "Wide" } `
elseif ($_.CustomControl) { "Custom" }};label="Type"} -wrap

如果对于这几行的格式化命令有任何疑问,可以参考第五章,主要讲格式化。关于像Format-Table与其它这样的格式化命令,其重要性在于可以让你在表格的列中显示特定的对象,属性,或者脚本块。如果你想在表格列中不直接显示属性,而是显示属性的子属性,那么子表达式是必须的。因为你对ViewSelectedBy属性不感兴趣,但是对它的子属性TypeName感兴趣,所以列必须定义在脚本块中。第三列也是脚本块。因为它的长度和列头冲突,一个用于格式化的哈希表应当应用到这里,允许你能选择列的标题。

提供给你的结果是一个可编辑的列表。第一列显示的是所有视图的名称;视图被适用的对象类型位于第二列;第三列展示这些格式化命令Format-Table,Format-List,Format-Wide或者 Format-Custom那个会应用到它。

Name$_.ViewSelectedBy.TypeNameType
----------------------------------
System.Collections.DictionaryEntrySystem.Collections.DictionaryEntryList
DictionarySystem.Collections.DictionaryEntryTable
System.Diagnostics.EventLogSystem.Diagnostics.EventLogTable
System.Diagnostics.EventLogSystem.Diagnostics.EventLogList
System.Diagnostics.EventLogEntrySystem.Diagnostics.EventLogEntryList
System.Diagnostics.EventLogEntrySystem.Diagnostics.EventLogEntryTable
System.Diagnostics.FileVersionInfoSystem.Diagnostics.FileVersionInfoTable
System.Diagnostics.FileVersionInfoSystem.Diagnostics.FileVersionInfoList
PrioritySystem.Diagnostics.ProcessTable
StartTimeSystem.Diagnostics.ProcessTable
processSystem.Diagnostics.ProcessWide
processSystem.Diagnostics.ProcessTable
PSSnapInInfoSystem.Management.Automation.PSSnapInInList
fo
PSSnapInInfoSystem.Management.Automation.PSSnapInInTable
fo
System.Reflection.AssemblySystem.Reflection.AssemblyList
System.Reflection.AssemblySystem.Reflection.AssemblyTable
System.Security.AccessControl.ObjectSecSystem.Security.AccessControl.ObjectSecTable
urityurity
System.Security.AccessControl.ObjectSecSystem.Security.AccessControl.ObjectSecList
urityurity
serviceSystem.ServiceProcess.ServiceControllerTable
System.ServiceProcess.ServiceControllerSystem.ServiceProcess.ServiceControllerList
System.TimeSpanSystem.TimeSpanWide
System.TimeSpanSystem.TimeSpanList
System.TimeSpanSystem.TimeSpanTable

记住这些包含格式化信息的XML文件。你只有在生成了所有格式化XML文件列表时,才可以得到一个完整的概览。

参考:

http://powershell.com/cs/blogs/ebookv2/archive/2012/03/21/chapter-14-xml.aspx

http://www.pstips.net/powershell-online-tutorials/

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读