XML 结构
加载和处理XML文件
浏览扩展类型系统
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 = @'
'@ | Out-File employee.xml
|
PowerShell处理XML --加载和处理XML文件
如果你想将XML文件按照实际的XML来处理,而不是纯文本。文件的内容必须转换成XML类型。类型转换在第六章已经提到,只须一行。
$xmldata = [xml](Get-Content employee.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 Cofi Heidecke security 4
访问和更新单个结点
如果一个结点在xml中是唯一的,你可以像前面的例子一样输入一个句号来访问它。然而,多数情况下XML文档包含了许多类似的结点(被称为兄弟结点),像前面最后一个例子中一样,包含了多个独立的员工。比如,你可以使用管道获得特定的员工,然后来更新它的数据。
$xmldata.staff.employee | Where-Object { $_.Name -match "Tobias Weltner" }
Name function age ---- -------- --- Tobias Weltner management 39
$employee = $xmldata.staff.employee | Where-Object { $_.Name -match "Tobias Weltner" } $employee.function = "vacation" $xmldata.staff.employee | ft -autosize
Name function age ---- -------- --- Tobias Weltner vacation 39 Cofi Heidecke security 4
使用SelectNodes()来选择Nodes
SelectNodes()方法是Xpath查询语言支持的方法,也允许你选择结点。XPath指的是一个结点‘路径名称’:
$xmldata = [xml](Get-Content employee.xml) $xmldata.SelectNodes("staff/employee")
Name function age ---- -------- --- Tobias Weltner management 39 Cofi Heidecke security 4
结果看起来像前面直接通过属性访问一样,但是XPath支持在方括号中使用通配符访问。下面的语句只会返回第一个员工结点。
PS> $xmldata.SelectNodes("staff/employee[1]") Name function age ---- -------- --- Tobias Weltner management 39
如果你想,你还可以获取一个年龄小于18岁的员工列表:
PS> $xmldata.SelectNodes("staff/employee[age<18]") Name function age ---- -------- --- Cofi Heidecke security 4
类似的方式,查询语言也支持获取列表中的最后一位员工信息,所以也可以指定位置:
$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-Content employee.xml | out-string) $navigator = $xpath.CreateNavigator() # 输出Hanover子公司的最后一位员工 $query = "/staff[@branch='Hanover']/employee[last()]/Name" $navigator.Select($query) | Format-Table Value Value ----- Cofi Heidecke # 输出Hanover子公司的除了Tobias Weltner之外的所有员工, $query = "/staff[@branch='Hanover']/employee[Name!='Tobias Weltner']" $navigator.Select($query) | Format-Table Value Value ----- Cofi Heideckesecurity 4荔非苔注:如果你的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", "New York") $xmldata.staff.GetAttribute("branch") New York
添加新结点
如果你想在员工结点列表中添加新的员工。首先,使用CreateElement()创建一个员工元素,然后定制员工的内部结构。最后,就可以在XML结构中你期望的位置插入这个元素。
# 加载XML文本文件: $xmldata = [xml](Get-Content employee.xml) # 创建新的结点: $newemployee = $xmldata.CreateElement("employee") $newemployee.set_InnerXML( ` "Bernd Seiler expert ") # 插入新结点: $xmldata.staff.AppendChild($newemployee) # 保存结果到原文件中: $xmldata.save(".\employeexml") # 验证结果: $xmldata.staff.employee
Name function age ---- -------- --- Tobias Weltner management 39 Cofi Heidecke security 4 Bernd Seiler expert
# 输出为纯文本:
$xmldata.get_InnerXml()
Tobias Weltner management 39 Cofi Heidecke security 4 Bernd Seiler expert
PowerShell处理XML --扩展类型系统
PowerShell扩展类型系统(ETS)确保了对象可以被转换成有意义的文本。此外,它还可以传递额外的属性和方法给对象。这些操作的精确定义被存放在扩展名为.ps1xml的文件中。
扩展类型系统的XML数据
不论何时,PowerShell要将对象转换成文本,都会搜索它自己的内置纪录中关于对象的描述和转换。正确的文件包含了XML:它的文件名以.format.ps1xml收尾,这些文件位于PowerShell的根目录$pshome下。
PS> Dir $pshome\*.format.ps1xml Directory: C:\Windows\System32\WindowsPowerShell\v1.0 Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/6/18 22:50 27338 Certificate.format.ps1xml -a--- 2013/6/18 22:50 27106 Diagnostics.Format.ps1xml -a--- 2013/6/18 22:50 147702 DotNetTypes.format.ps1xml -a--- 2013/6/18 22:50 14502 Event.Format.ps1xml -a--- 2013/6/18 22:50 21293 FileSystem.format.ps1xml -a--- 2013/6/18 22:50 287938 Help.format.ps1xml -a--- 2013/6/18 22:50 97880 HelpV3.format.ps1xml -a--- 2013/6/19 2:30 105230 PowerShellCore.format.ps1xml -a--- 2013/6/18 22:50 18612 PowerShellTrace.format.ps1xml -a--- 2013/6/18 22:50 13659 Registry.format.ps1xml -a--- 2013/6/18 22:50 17731 WSMan.Format.ps1xml
所有的这些文件包含了众多的视图,你可以使用PowerShell 支持的XML方法来检验它。
1
2
|
[xml] $file = Get-Content "$pshome\dotnettypes.format.ps1xml"
$file .Configuration.ViewDefinitions.View
|
Name ViewSelectedBy ListControl ---- -------------- ----------- System.CodeDom.Compiler.CompilerError ViewSelectedBy ListControl System.Reflection.Assembly ViewSelectedBy System.Reflection.AssemblyName ViewSelectedBy System.Globalization.CultureInfo ViewSelectedBy System.Diagnostics.FileVersionInfo ViewSelectedBy System.Management.ManagementObject#root\cimv2\Win32_PingStatus ViewSelectedBy Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus ViewSelectedBy System.Management.ManagementObject#root\default\SystemRestore ViewSelectedBy Microsoft.Management.Infrastructure.CimInstance#root/default/SystemRestore ViewSelectedBy Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Product ViewSelectedBy System.Net.NetworkCredential ViewSelectedBy System.Management.Automation.PSMethod ViewSelectedBy Microsoft.Management.Infrastructure.CimInstance#__PartialCIMInstance ViewSelectedBy
查找预定义的视图
预定义的视图非常有意思,因为你可以在像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 ++} }
|
System.CodeDom.Compiler.CompilerError System.CodeDom.Compiler.CompilerError ErrorText Line Column ErrorNumber LineSource
每一个视图由一个Name,一个ViewSelectedBy中的.NET类型组成,作为视图合格的条件,TableControl结点也一样,指定对象被支持转换成文本。如果你想在一列中输出定义在XML文件中的所有的视图,Format-Table命令足矣,然后选择你想在摘要中显示的属性。
1
2
3
|
[xml] $file = Get-Content "$pshome\dotnettypes.format.ps1xml"
$file .Configuration.ViewDefinitions.View |
Format-Table Name, { $_ .ViewSelectedBy.TypeName}
|
Name $_.ViewSelectedBy.TypeName ---- -------------------------- System.CodeDom.Compiler.CompilerError System.CodeDom.Compiler.CompilerError System.Reflection.Assembly System.Reflection.Assembly System.Reflection.AssemblyName System.Reflection.AssemblyName System.Globalization.CultureInfo System.Globalization.CultureInfo System.Diagnostics.FileVersionInfo System.Diagnostics.FileVersionInfo System.Diagnostics.EventLogEntry System.Diagnostics.EventLogEntry System.Diagnostics.EventLog System.Diagnostics.EventLog System.Version System.Version System.Drawing.Printing.PrintDocument System.Drawing.Printing.PrintDocument Dictionary System.Collections.DictionaryEntry ProcessModule System.Diagnostics.ProcessModule process System.Diagnostics.Process ProcessWithUserName System.Diagnostics.Process#IncludeUserName DirectoryEntry System.DirectoryServices.DirectoryEntry PSSnapInInfo System.Management.Automation.PSSnapInInfo PSSnapInInfo System.Management.Automation.PSSnapInInfo Priority System.Diagnostics.Process StartTime System.Diagnostics.Process service System.ServiceProcess.ServiceController System.Diagnostics.FileVersionInfo System.Diagnostics.FileVersionInfo System.Diagnostics.EventLogEntry System.Diagnostics.EventLogEntry System.Diagnostics.EventLog System.Diagnostics.EventLog System.TimeSpan System.TimeSpan System.TimeSpan System.TimeSpan System.TimeSpan System.TimeSpan System.AppDomain System.AppDomain System.ServiceProcess.ServiceController System.ServiceProcess.ServiceController System.Reflection.Assembly System.Reflection.Assembly System.Collections.DictionaryEntry System.Collections.DictionaryEntry process System.Diagnostics.Process DateTime System.DateTime System.Security.AccessControl.ObjectSecurity System.Security.AccessControl.ObjectSecurity System.Security.AccessControl.ObjectSecurity System.Security.AccessControl.ObjectSecurity System.Management.ManagementClass System.Management.ManagementClass Microsoft.Management.Infrastructure.CimClass Microsoft.Management.Infrastructure.CimClass System.Guid System.Guid System.Management.ManagementObject#root\cimv2\Win32_Ping... System.Management.ManagementObject#root\cimv2\Win32_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, WideControl 和 CustomControl。这些结点在第一次概览时,不会被显示。原因是每一个视图只允许出现一个这样的结点。TableControl被输出时或多或少带有随机性,因为在转换第一条记录时,PowerShell是基于未知对象的转换。
接下来我们会提取出XML文件中所有的必须的信息。首先对视图按照ViewSelectedBy.TypeName排序,接着根据criterion(标准)来分组。你也可以按照只匹配出一次确定的对象类型来排序。你只需要那些值得在-view参数中指定的,存在多个对象类型的视图。
1
2
3
4
5
6
7
8
9
10
|
[xml] $file = Get-Content "$pshome\dotnettypes.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.TypeName Type ---- -------------------------- ---- System.Collections.DictionaryEntry System.Collections.DictionaryEntry List Dictionary System.Collections.DictionaryEntry Table System.Diagnostics.EventLog System.Diagnostics.EventLog Table System.Diagnostics.EventLog System.Diagnostics.EventLog List System.Diagnostics.EventLogEntry System.Diagnostics.EventLogEntry List System.Diagnostics.EventLogEntry System.Diagnostics.EventLogEntry Table System.Diagnostics.FileVersionInfo System.Diagnostics.FileVersionInfo Table System.Diagnostics.FileVersionInfo System.Diagnostics.FileVersionInfo List Priority System.Diagnostics.Process Table StartTime System.Diagnostics.Process Table process System.Diagnostics.Process Wide process System.Diagnostics.Process Table PSSnapInInfo System.Management.Automation.PSSnapInIn List fo PSSnapInInfo System.Management.Automation.PSSnapInIn Table fo System.Reflection.Assembly System.Reflection.Assembly List System.Reflection.Assembly System.Reflection.Assembly Table System.Security.AccessControl.ObjectSec System.Security.AccessControl.ObjectSec Table urity urity System.Security.AccessControl.ObjectSec System.Security.AccessControl.ObjectSec List urity urity service System.ServiceProcess.ServiceController Table System.ServiceProcess.ServiceController System.ServiceProcess.ServiceController List System.TimeSpan System.TimeSpan Wide System.TimeSpan System.TimeSpan List System.TimeSpan System.TimeSpan Table
记住这些包含格式化信息的XML文件。你只有在生成了所有格式化XML文件列表时,才可以得到一个完整的概览。
参考:
http://powershell.com/cs/blogs/ebookv2/archive/2012/03/21/chapter-14-xml.aspx
http://www.pstips.net/powershell-online-tutorials/