在PowerShell中操作Excel - 创建一个富Excel文档

让我们继续《在PowerShell中操作Excel》系列文章。上一次我们掩饰了如何用Microsoft Excel和Windows PowerShell来创建一个基本的报表。从某些方面来讲,我们上次创建的东西和创建CSV并在Excel中打开差不了多少。所以,如果您希望用Excel,让我们彻彻底底地使用它!在今天的文章中,我将沿用上次的演示脚本,但是创建一个更“富(richer)”的Excel文档。下一步,在第三部分中,我将为您演示如何从Excel文件中读取数据。

创建一个富Excel文档

和之前一样,我们将通过WMI获取磁盘信息,并创建一个Excel应用程序对象。

$disks=Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computer -Filter "DriveType=3"
$xl=New-Object -ComObject "Excel.Application"

和Microsoft Word类似,Excel自动化很大程度依赖于使用类似 xlDown 等内置常量。我可以记下一个常量值并创建一个变量,或者可以读取包含所需常量的合适的类。在我印象中,我将需要提取以下信息:

$xlConditionValues=[Microsoft.Office.Interop.Excel.XLConditionValueTypes]
$xlTheme=[Microsoft.Office.Interop.Excel.XLThemeColor]
$xlChart=[Microsoft.Office.Interop.Excel.XLChartType]
$xlIconSet=[Microsoft.Office.Interop.Excel.XLIconSet]
$xlDirection=[Microsoft.Office.Interop.Excel.XLDirection]

将来当我希望使用 xlDown 时,我可以通过 $xlDirection::xlDown 来指定它。您等等会看到一些这样的代码。现在,我将像第一部分那样写入磁盘数据,但先让我们加入一些样式。另一种引用电子表格的一部分是使用工作簿对象的 Range 属性。您既可以通过类似的方式 A1 引用一个单元格,或者通过类似 A1:A10 的方式引用一个范围。范围(Range)对象有一个样式(Style)属性。我将把 A1 单元格的样式设置成“Title”,并且把我的表头样式设置成“Heading 2”。

$range=$ws.range("A1")
$range.Style="Title"
#或者用这种方法
$ws.Range("A3:F3").Style = "Heading 2"

另一个常见的格式化选项是调整列宽。我们可以设置列宽为固定值或者为自动调整列宽。

$ws.columns.item("C:C").columnwidth=15
$ws.columns.item("D:F").columnwidth=10.5
$ws.columns.item("B:B").EntireColumn.AutoFit() | out-null

顺便提一句,我将某些方法,比如 AutoFit() 输出到管道 Out-Null 来禁止不需要的输出。以下是很有意思的地方:我想如果能用上Excel的条件格式功能将会很酷。具体来说,我想用交通灯图标集来反映每个驱动器的使用量。如我之前所说,既然我们要创建一个Excel文件,那么尽量做到极致。我将为您演示这些代码,不用紧张:)

$start=$ws.range("F4")
#获取最后一个单元格
$Selection=$ws.Range($start,$start.End($xlDirection::xlDown))
#增加图标集
$Selection.FormatConditions.AddIconSetCondition() | Out-Null
$Selection.FormatConditions.item($($Selection.FormatConditions.Count)).SetFirstPriority()
$Selection.FormatConditions.item(1).ReverseOrder = $True
$Selection.FormatConditions.item(1).ShowIconOnly = $False
$Selection.FormatConditions.item(1).IconSet = xlIconSet::xl3TrafficLights1
$Selection.FormatConditions.item(1).IconCriteria.Item(2).Type = xlConditionValues::xlConditionValueNumber
$Selection.FormatConditions.item(1).IconCriteria.Item(2).Value = 0.8
$Selection.FormatConditions.item(1).IconCriteria.Item(2).Operator = 7
$Selection.FormatConditions.item(1).IconCriteria.Item(3).Type = xlConditionValues::xlConditionValueNumber
$Selection.FormatConditions.item(1).IconCriteria.Item(3).Value = 0.9
$Selection.FormatConditions.item(1).IconCriteria.Item(3).Operator = 7

我并不是一夜之间突然知道怎么用PowerShell来做这些事情。相反地,我创建了一个Excel宏,然后应用样式,然后将代码翻译成PowerShell脚本。我希望我可以为您提供一系列翻译的规则,但是碰到一系列障碍和错误。请注意常量的使用?
(译者注:原文为I wish I could give you a set of translation rules, but it just takes trial and error and experience. Notice the use of the constant values?)

下一步,我将为插入一个柱形图到工作表:

$chart=$ws.Shapes.AddChart().Chart
$chart.chartType=$xlChart::xlBarClustered

我又一次采用了创建一个宏来观察其中的方法并修正其中的值的方法。接下来,我需要为图表选择数据源。

$start=$ws.range("A3")
#获取最后一个单元格
$Y=$ws.Range($start,$start.End($xlDirection::xlDown))
$start=$ws.range("F3")
#获取最后一个单元格
$X=$ws.Range($start,$start.End($xlDirection::xlDown))

驱动器名称将作为Y轴,%Used将作为X轴。我将用这个区域的集合来定义图表的数据。

$chartdata=$ws.Range("A$($Y.item(1).Row):A$($Y.item($Y.count).Row),F$($X.item(1).Row):F$($X.item($X.count).Row)")
$chart.SetSourceData($chartdata)

我希望对这个图表做的最后一件事是增加数据标题和图表标题。

$chart.seriesCollection(1).Select() | Out-Null
$chart.SeriesCollection(1).ApplyDataLabels() | out-Null
$chart.ChartTitle.Text = "Utilization"

Excel很可能并不会按您所希望的位置摆放这个图表,所以您可以使用以下代码来定位它:

$ws.shapes.item("Chart 1").top=40
$ws.shapes.item("Chart 1").left=400

TopLeft 是从Excel窗口开始计算的顶边距和左边距。可能会在获取右边距的时候遇到一些障碍和错误,但请注意在多台计算机上进行测试。最后一步是将工作表以计算机名来命名。

$xl.worksheets.Item("Sheet1").name=$name

当您明白所有这些Excel的魔法师如何工作的,那么要为您希望查看的每台计算机增加一个工作表也不是那么难了。以下截图显示最终的结果:

增强的Excel报表

结论

您可以下载我的演示脚本并且自己进行测试。如果您的确需要写数据到Excel,我建议您完整地操作一遍。也许需要掌握一些基础知识,但这方面的努力是值得的。下一步我们将演示如何从Excel文件中读取数据。

用PowerShell操作Office系列文章

PowerShell 技能连载 - PowerShell函数的详细输出

若要为您的PowerShell函数增加指定的详细输出信息(verbose output),请增加 CmdletBinding 属性到您的函数,使它支持公共参数。
(译者注:公共参数例如 -Verbose-Debug 等)

function test
{
    [CmdletBinding()]
    param()
}

下一步,添加 Write-Verbose 来输出文本信息。它们仅当用户指定了 -Verbose 参数时才有效:

function test
{
    [CmdletBinding()]
    param()

    Write-Verbose "Starting"
    "Doing Something"
    Write-Verbose "Shutting Down"
}

所以当您按以下方式运行它时,您会见到正常的输出信息:

PS > test

Doing Something

然而,如果您增加了 -Verbose 参数,您将会看到您输出的详细信息:

PS > test -Verbose

Starting
Doing Something
Shutting Down

PowerShell 技能连载 - 将结果复制到剪贴板

要将 CmdLet 的输出结果复制到别的应用程序,只需要简单地将它们通过管道传输到 clip.exe。然后,将结果粘贴到您所要的应用程序即可:

Get-Service | clip

译者注 - CLIP 命令的帮助信息:

C:\>clip /?

CLIP

描述:
    将命令行工具的输出重定向到 Windows 剪贴板。这个文本输出可以被粘贴
    到其他程序中。

参数列表:
    /?                  显示此帮助消息。

示例:
    DIR | CLIP          将一份当前目录列表的副本放入 Windows 剪贴板。

    CLIP < README.TXT   将 readme.txt 的一份文本放入 Windows 剪贴板。

PowerShell 技能连载 - 将Out-GridView改造为选择对话框

在PowerShell 3.0中,Out-GridView 终于可以被改为一个多功能选择对话框——只要增加一个新的参数 -PassThru 就可以看到效果:

属性窗口

$Title = 'Select one or more files to open'

Get-ChildItem -Path $env:windir -Filter *.log |
  Out-GridView -PassThru -Title $title |
  ForEach-Object {
    notepad $_.FullName
  }

您可以通过管道将任何对象传给 Out-GridView。用户可以从界面中选择输出结果的一部分,或者用关键词过滤结果,然后选择结果的一个或多个元素。点击 OK 之后,选中的元素将传输到下一个命令。

理解PowerShell的类型转换魔法

毫无疑问地,PowerShell中的类型转换是它最有用的“魔法”功能之一。如果您执行一个需要特定参数类型(例如:DateTimeTimeSpan)的命令,情况似乎“一切正常”。

例如,Twitter上的这条问题:

PowerShell 的 TimeSpan 参数将10理解为10个计时周期,10:00理解为10小时,”10”理解为10天。

译者注:计时周期的概念请参见 TimeSpan.Ticks 属性

但是,这是怎么做到的呢?

以下是PowerShell根据您的需要将输入值转换为所需要的类型的步骤——例如 TimeSpan。和许多事情一样,并不是什么魔法——只是一堆繁琐的工作。

1. 直接赋值

如果您的输入值是可直接赋值的,那么直接将您的输入值转型为该类型。

2. 基于语言的转换

这些基于语言的转换适用于目标类型是 voidBooleanStringArrayHashtablePSReference(例如:[ref])XmlDocument(例如:[xml])Delegate(用于支持代码块转换到Delegate)Enum 类型。

3. 解析转换

如果目标类型定义了一个 Parse() 方法接受该输入值,那么将使用它来做转换。

4. 静态创建转换

如果目标类型定义了一个静态的 ::Create() 方法并接受该输入值,那么将使用它来转做换。

5. 构造函数转换

如果目标类型定义了一个构造函数接受该输入值,那么将用它来做转换。

6. 强制类型转换

如果目标类型针对源类型 隐式或显示地 定义了一个强制类型转换操作符,那么将用它来做转换。如果源类型定义了一个隐式或显式转换到目标类型的前置类型转换操作符,那么使用它来做转换。

7. IConvertible转换

如果源类型定义了一个知道如何转换为目标类型的 IConvertible 的实现,那么将使用它来做转换。

8. IDictionary转换

如果源类型是一个 IDictionary(例如:Hashtable) 类型,则尝试用目标类型的缺省构造函数创建一个实例,并且使用 IDictionary 中的同名键值对为源对象的属性赋值。

9. PSObject属性转换

如果源类型是 PSObject,那么用目标类型的缺省构造函数创建一个实例,然后用 PSObject 中的属性名和属性值为源对象赋值。如果某个名字对应的是一个方法而不是一个属性,则以该值作为实参执行该方法。

10. TypeConverter 转换

如果注册了一个可以处理该转换的 TypeConverterPSTypeConverter,则用它来做转换。您可以在 types.ps1xml 文件中注册一个 TypeConverter(参见:$pshome\Types.ps1xml),或通过 Update-TypeData 来注册。

好了,现在考考您能不能指出这些分别是什么类型的转换,以及为什么?

[TimeSpan] 10
[TimeSpan] "10"
[TimeSpan] "0:10"

希望本文对您有所帮助!

Lee Holmes [MSFT]
Windows PowerShell开发团队

PowerShell 技能连载 - 3个最有用的ISE技巧

如果您在使用PowerShell 3.0和ISE编辑器,那么您必须知道以下3个最有用的技巧:

  1. CTRL+J 打开一系列包含 loopsconditionsfunctions 以及 workflows 等内置代码片段的清单。这个技巧可以帮助您快速起步。

  2. 当您选取一段代码后,按下 F8 将执行选中部分的代码。这个技巧在被注释的区域里也可以使用。所以您可以用 <##> 将一些代码注释掉,当需要执行的时候,选中它们并按下 F8 键。

  3. 若要获得cmdlet的上下文帮助,点击脚本中的cmdlet并按 F1 键。

PowerShell 技能连载 - 竖排的网格视图

效果图:

属性窗口

您总是可以将多个对象通过管道输出到 Out-GridView 并且得到一个美观的窗口,窗口中含有一个表格,表格中的每一行对应对象所有属性。当您需要显示很多对象的时候这种做法十分有效。

如果您只是希望显示单个对象的所有属性,那么显示为一个竖排的表格则更为美观。实际上您可以通过名为 PropertyGrid 的控件来实现。以下是相应的方法:

Function Show-Object
{
    param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [Object]
        $InputObject,

        $Title
    )

    Add-Type -AssemblyName system.Windows.Forms
    Add-Type -AssemblyName system.Drawing

    if (!$Title) { $Title = "$InputObject" }
    $Form = New-Object "System.Windows.Forms.Form"
    $Form.Size = New-Object System.Drawing.Size @(600,600)
    $PropertyGrid = New-Object System.Windows.Forms.PropertyGrid
    $PropertyGrid.Dock = [System.Windows.Forms.DockStyle]::Fill
    $Form.Text = $Title
    $PropertyGrid.SelectedObject = $InputObject
    $PropertyGrid.PropertySort = 'Alphabetical'
    $Form.Controls.Add($PropertyGrid)
    $Form.TopMost = $true
    $null = $Form.ShowDialog()
}

现在,您可以将任何对象通过管道输出至 Show-Object,它将显示一个竖排的属性网格(PropertyGrid)。更有趣的是,所有可写的对象都被加粗,并且您的的确确可以在网格中修改这些值(注意,改变值有可能很危险)。并且许多对象,当您选择一个属性,将在状态条上显示详细的描述信息:

Get-Process -id $pid | Show-Object
$host | Show-Object
Get-Item -Path $pshome\powershell.exe | Show-Object

PowerShell 技能连载 - 通过F12键跳转到函数定义

如果您将要编写又长又复杂,有很多函数的PowerShell代码,那么这篇文章对您有所帮助。在其它开发环境中,当您将光标停在一个函数上,并按下F12键,编辑器将跳转到函数的定义处。而PowerShell ISE并不会那么做。

然而,您可以为ISE增加这个功能。以下代码将在 AddOns 菜单处增加一个新的 Find Definition 命令,并且为其绑定键盘的 F12 热键。

下一次您在一大段脚本中点击某个函数,ISE将直接导航到该函数的定义处(当函数在脚本内定义时)。

function Find-Definition {
    $e = $psISE.CurrentFile.Editor
    $Column = $e.CaretColumn
    $Line = $e.CaretLine

    $AST = [Management.Automation.Language.Parser]::ParseInput($e.Text,[ref]$null,[ref]$null)
    $AST.Find({param($ast)
            ($ast -is [System.Management.Automation.Language.CommandAst]) -and
            (($ast.Extent.StartLineNumber -lt $Line -and $ast.Extent.EndLineNumber -gt $line) -or
            ($ast.Extent.StartLineNumber -eq $Line -and $ast.Extent.StartColumnNumber -le $Column) -or
            ($ast.Extent.EndLineNumber -eq $Line -and $ast.Extent.EndColumnNumber -ge $Column))}, $true) |
            Select-Object -ExpandProperty CommandElements |
            ForEach-Object {
                $name = $_.Value
                $AST.Find({param($ast)
                        ($ast -is [System.Management.Automation.Language.FunctionDefinitionAst]) -and
                        ($ast.Name -eq $name)}, $true) |
                        Select-Object -Last 1 |
                        ForEach-Object {
                            $e.SetCaretPosition($_.Extent.StartLineNumber,$_.Extent.StartColumnNumber)
                    }
            }
}

$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Goto Definition",{Find-Definition},'F12')

PowerShell 技能连载 - 增加新的类型加速器

如果您发现您常常使用某些.NET类型,您可能会希望创建一些快捷方式,使您的生活变得更简单。

例如,System.IO.Path .NET类型有许多常用的路径功能:

[System.IO.Path]::GetExtension('c:\test.txt')
[System.IO.Path]::ChangeExtension('c:\test.txt', 'bak')

如果您觉得每次为了这个.NET类型敲入长长的代码太辛苦,只需要用这种方式增加一个快捷方式:

[PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::Add('Path', [System.IO.Path])

现在,您可以通过 Path 快捷方式获得完全一样的功能:

[Path]::GetExtension('c:\test.txt')
[Path]::ChangeExtension('c:\test.txt', 'bak')

要查看一个类型所支持的所有方法和属性,用以下的代码:

[Path] | Get-Member -Static

PowerShell 技能连载 - 查找类型加速器

PowerShell维护着一系列.NET类型的缩写,使您编写代码更加自如。例如要将一个字符串转换成DateTime类型,您可以这样写:

[DateTime] '2013-07-02'

它的幕后机制只是一个名为 System.DateTime 类型的缩写。您可以通过 FullName 属性查看这些缩写实际上代表的类型:

[DateTime].FullName

若要获取所有支持的“类型加速器”(缩写),您可以使用以下代码。这段代码返回PowerShell实现的所有加速器。这段代码十分有用,因为它列出了PowerShell开发者认为十分重要的所有.NET内部类型。

[PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::Get |
    Sort-Object -Property Value

当您将结果通过管道输出到一个grid view窗口时,您可以方便地搜索类型加速器。只需要在grid view窗口顶部的搜索框内键入类型名的一部分即可:

[PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::Get |
    Sort-Object -Property Value |
    Out-GridView