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-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( `
"<Name>Bernd Seiler</Name><function>expert</function>")
# 插入新结点:
$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()
<?xml version="1.0"?><Branch office staff="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><employee>
<Name>Bernd Seiler</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:\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++} }
<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 "$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/