上一篇文章主要介绍了powershell中通过COM操作Word,本篇文章中将会着重介绍powershell中通过COM操作Excel达到自动化管理的目的。
Microsoft Excel的ProgID是Excel.Application,用其创建对象后即可获取Workbook对象,使用文档的Workbooks集合。Workbooks有多个WorkSheet对象通过Worksheets属性起作用,在操作工作表时的主要对象即单元格。
与Word中的文字区域一样,单元格区域指向部分单元格,可以修改其共有的格式和属性。Range对象包含一个或多个单元格,当包含多个单元格时可以程序化地访问它们。
(1) 从文档中读取单元格内容
Excel自动化中的最重要操作是从任意一个文档中读取其中某一个单元格的内容,可以通过Worksheet对象的Cells属性来实现。Excel中包含COM集合,可以通过行列的索引值获取单元格内容,一般通过Item方法索引。在PowerShell中处理这个方法使用索引标记下的集合,要注意COM集合中对于Visual Basic数组的下标索引是用圆括号包含并从1开始编号,而典型的.NET和PowerShell中的集合是用方括号包含从0开始的索引编号。
下面是一个从工作表中读取单元格值的实例,这个工作表包含的两列分别存放水果名和售价,如图1所示。
图1 Excel文档的内容
在不知道包含记录的条数的情况下可以一直循环直到获取两个字段其中有任意一个为空的情况下退出,代码如下:
$excelApp = New-Object -COM Excel.Application
$file = (dir Sale.xls).FullName
$book = $excelApp.Workbooks.Open($file)
$sheet = $book.Worksheets.Item(1)
$row = 1
while($true)
{
$productName = $sheet.Cells.Item($row,1).Value2
$Price = $sheet.Cells.Item($row,2).Value2
if(!$productName -or !$Price)
{
break;
}
"$productName $Price"
$row++
}
$excelApp.Quit()
$book = $null
$sheet = $null
$excelApp = $null
[GC]::Collect()
在代码中需要提供打开目标文档的全路径,其中打开文档并获取第1个工作表。需要强调的是工作表从索引值1开始编号,该脚本的执行结果如图2所示。
图2 执行结果
在读取行的过程中需要一个循环,通过工作表的Cells属性获取单元格对象,可以用Item方法获取特定索引值行和列的单元格。获取单元格对象中内容的最简单方法是使用Value2属性。单元格可保存多种类型的值,如果需要取回这些值,可以使用Value属性并转换为需要的类型;如果只是需要单元格的内容,则使用Value2属性。
需要强调的是在操作Excel之后需要清理现场,通过调用Quit()方法通知程序退出。并显式地将所有可能指向Excel COM对象变量值设为$null,最后通过调用[GC]::Collect()显式触发垃圾回收机制退出excel进程。
【提示】
在执行脚本后,由于COM的内存管理机制,因此对象将会持续存在内存中,直到没有客户端保持指向其引用。在.NET的对象的封装中PowerShell保持指向对象的引用,而且会阻止Excel进程退出,甚至是在调用程序的Quit()方法也有可能不退出。这是Excel自动化对象的一个局限性,可以查看微软知识库文章(KB317109)了解此问题的详细情况。
为了确保Excel在脚本完成后正确退出,需要在使用后将指向Excel对象的变量都设置为$null。没有好的方法能找出未执行该操作,因为将会挂起Excel的任何操作。处理这种情况最好的方法就是保存从COM对象中传递的数据,并将指向Excel的所有变量设置为$null。即使如此,也要采用强制的垃圾回收机制[GC]::Collect()回收内存。它会释放对象的封装,如果之前将变量设置为$null,Excel将会正常退出。
【注意】
对于Excel来说,Windows系统的区域设置非常重要,因为其中的很多特性需要根据这些设置获取信息,如数字和日期的格式,以及排序规则等。Excel依靠多语言用户界面包(Multilingual user interface packs)的组件提供不同语言习惯下的不同规则设定,以自动化形式启动的Excel会从控制面板中指定的区域选项,并加载相应的多语言用户界面包。如果系统中没有安装与操作系统对应语言包,则提示“Old format or invalid type library”,而且脚本也会中止执行。这是Excel本身的bug,当本地系统环境被设置为非英文,而Excel为英文时就会出现这个问题,这个问题可访问在微软的知识库http://support.microsoft.com/default.aspx?scid=kb;en-us;320369。如果要解决这个问题,需要重新安装非英文版的多语言用户界面包或者从控制面板中系统的语言改为英语;另外一种方法是用脚本临时将系统环境设为英文,代码如下:
System.Globalization.CultureInfo CurrentCI = System.Threading.Thread.CurrentThread.CurrentCulture;
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
很多时候需要精确定位需要的数据,但是大数据量的工作表不可能逐个遍历其中的每个单元格,直到获取所需值。在前面提到的Sale工作表中可以知道需要的数据只是A1:B4之间的区域,所以这里可以传递该Range()方法并获取Range对象,然后遍历区域中的单元格以获取其值。作为示范,下面创建名为“Get-CellInRange.ps1”的脚本,代码如下:
$excelApp = New-Object -COM Excel.Application
$file = (dir Sale.xls).FullName
$book = $excelApp.Workbooks.Open($file)
$sheet = $book.Worksheets.Item(1)
$cellRange = $sheet.Range("A1:B4")
$cells = $cellRange.Cells
$cells | select Value2
$excelApp.Quit()
$cellRange = $null
$cells = $null
$book = $null
$sheet = $null
$excelApp = $null
[GC]::Collect()
其中传递所需单元格区域给工作表的Range()方法,返回包含目标单元格的区域对象。然后将集合内容逐个传递给select cmdlet,并获取其中的Value2属性值。最后设置所有变量为$null,触发垃圾回收机制。该脚本的执行结果如图3所示。
图3 执行结果
(2)修改工作簿的内容
在将数据导入工作簿时一般不需要启动Excel自动化,PowerShell可以使用Export-Csv cmdlet将任何对象导出为逗号分隔的文档。如果需要修改已有文档或者单元格格式,则必须使用COM来实现。
为了演示使用数据填充工作表,创建一个名为“Export-ProcessToExcel.ps1”脚本。用其获取当前系统中所有的进程ID与进程名,并分别对应填充到两列中。可以通过更改Value2属性设置单元格内容,其代码如下:
$excelApp = New-Object -COM Excel.Application
$file = Join-Path (Get-Location) "Processes.xls"
$book = $excelApp.Workbooks.Add()
$sheet = $book.Worksheets.Item(1)
$processes = Get-Process
#set cell contents
for ($i = 1; $i -le $processes.Count; $i++)
{
$processIdCell = $sheet.Cells.Item($i, 1)
$processIdCell.Value2 = $processes[$i].Id
$processNameCell = $sheet.Cells.Item($i, 2)
$processNameCell.Value2 = $processes[$i].Name
}
$processIdCell = $null
$processNameCell = $null
#bold the A and B columns and make their text red
$columnsABRange = $sheet.Range("A:B")
$columnsABRange.Font.Bold = $true
$columnsABRange.Font.ColorIndex = 3
$columnsABRange = $null
$book.SaveAs($file)
$excelApp.Quit()
$book = $null
$sheet = $null
$excelApp = $null
[GC]::Collect()
可以使用Get-Process cmdlet获取进程信息,在代码中使用A:B的区域语法来创建包含前两行的区域。修改该区域对象的字体设置会在单元格填充时生效。需要强调的是,当前代码中将所有变量置为$null和手动垃圾回收的代码占了代码中的很大部分,编写如此复杂的释放资源和手动垃圾回收的代码是因为Excel的COM本身对资源回收做的并不好。如果Excel能够有效解决资源释放问题,PowerShell脚本的代码量将会大大减少。
运行上面的脚本后生成的Processes.xls工作簿如图4所示。
Excel是个功能强大的应用程序,可以用其自动化机制完成很多有用的功能。与Word类似,向PowerShell开放的对象也可在VBA代码中用到。《Visual Basic Reference》中涉及的对象在PowerShell中也可用,完全可以将其作为参考手册,在MSDN上网址是http://msdn.microsoft.com/zh-cn/express/sh9ywfdk(en-us,VS.85).aspx/
图4 生成的Processes.xls工作簿
作者: 付海军
版权:本文版权归作者所有
转载:欢迎转载,为了保存作者的创作热情,请按要求【转载】,谢谢
要求:未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
个人网站: http://txj.shell.tor.hu/