• Likes

Summary: Microsoft MVP, Richard Siddaway, shares an excerpt from his book, PowerShell in Practice.

Microsoft Scripting Guy, Ed Wilson, is here. This week we will not have our usual PowerTip. Instead we have excerpts from seven books from Manning Press. In addition, each blog will have a special code for 50% off the book being excerpted that day. Remember that the code is valid only for the day the excerpt is posted. The coupon code is also valid for a second book from the Manning collection.

Today, the excerpt is from PowerShell in Practice
By Richard Siddaway

It’s a fair assumption to say that the Microsoft Office applications will be found on almost every desktop computer in work environments. It’s possible to work with most of the Office applications by using Windows PowerShell. There are COM objects representing most of them. In this technique from PowerShell in Practice, author Richard Siddaway shows how to create an Excel spreadsheet and add data to it, and how to open a CSV file in Excel, from anywhere you are without triggering a pre-2007 bug.

In this set of tips, we’ll concentrate on using Excel because this is one of the applications we’re most likely to use as administrators. The Microsoft Script Center has a lot of VBScript script examples of using Excel that can be converted to Windows PowerShell. The first thing we need to do is to create an Excel spreadsheet, and spreadsheets seem much more useful when they have data in them.

Creating an Excel spreadsheet should be a simple act, in theory. But if you don’t happen to be in the U.S., there’s a slight issue in the shape of a bug in versions of Excel 2007 and earlier that can prevent this from working. After reading this, it won’t matter where you live. If you’re using Excel 2010, the first version in Listing 1 can be used wherever you live and work.

### Problem

We need to create an Excel spreadsheet from within a Windows PowerShell script.

### Solution

The Excel.application COM object can be used to create a spreadsheet.

$xl = New-Object -ComObject "Excel.Application" 1$xl.visible = $true$xlbooks =$xl.workbooks.Add()$xl = New-Object -ComObject "Excel.Application"    2

$xl.visible =$true

$xlbooks =$xl.workbooks

$newci = [System.Globalization.CultureInfo]"en-US"$xlbooks.PSBase.GetType().InvokeMember("Add",

[Reflection.BindingFlags]::

InvokeMethod, $null,$xlbooks, $null,$newci)

1 U.S. version

2 International version

### Discussion

If you live in the U.S. or are using a computer that’s configured to the U.S. locale, you can use the first option in Listing 1. Otherwise, you have to use the second, international option. (See the Regional and Language settings in Control Panel, as shown in the following screenshot.)

If you want to remain with Windows PowerShell rather than succumbing to the GUI, you can check the culture by typing $psculture (in Windows PowerShell 2.0). If en-US isn’t returned, you need to use the second option in Listing 1. The simple way to create a spreadsheet starts by creating the COM object by using New-Object. We make it visible. Administrators are clever people, but working on an invisible spreadsheet may be a step too far…especially on a Monday morning. At this point, we have only the Excel application open. We need to add a workbook to enable us to use the spreadsheet. If the computer isn’t using the U.S. culture (I live in England so$psculture returns en-GB), we have two options. The first option is to change the culture on the computer to en-US, which isn’t convenient. Otherwise, we have to use the second option given in the listing.

We start in the same way by creating the COM object and making the spreadsheet visible. A variable $xlbooks, which represents the workbooks in the spreadsheet is created. A second variable$newci, which represents the culture is created. Note that we’re forcing the culture used to create the workbook to be U.S. English. The last line is a bit complicated, but we’re dropping down into the base workbook object and invoking the Add method using the U.S. English culture. If you don’t want to see the long list of data on screen when this last line is run, add | Out-Null to the end of the line. This is awkward, but it does get us past the bug. The good news is that, once we’ve created our workbook, we can add data into it.

A spreadsheet without data isn’t much use to us, so we need to investigate how we can add data into the spreadsheet and perform calculations on that data.

### Problem

We need to populate our spreadsheet with some data.

### Solution

Expanding on the previous script, we can create a worksheet to hold the data. The starting point is to remove any previous versions of spreadsheet #1, as shown in Listing 2. We use Test-Path to determine whether the file exists and Remove-Item to delete it. The Confirm parameter could be used with Remove-Item as an additional check if required. This is useful if working with important data.

$sfile = "C:\test\test.xlsx" if(Test-Path$sfile){Remove-Item $sfile} 1$xl = New-Object -comobject "Excel.Application"

$xl.visible =$true

$xlbooks =$xl.workbooks

$newci = [System.Globalization.CultureInfo]"en-US"$wkbk = $xlbooks.PSBase.GetType().InvokeMember("Add", [Reflection.BindingFlags] ::InvokeMethod,$null, $xlbooks,$null, $newci)$sheet = $wkbk.WorkSheets.Item(1) 2$sheet.Cells.Item(1,1).FormulaLocal = "Value"        3

$sheet.Cells.Item(1,2).FormulaLocal = "Square" 3$sheet.Cells.Item(1,3).FormulaLocal = "Cube"         3

$sheet.Cells.Item(1,4).FormulaLocal = "Delta" 3$row = 2                                             4

for ($i=1;$i -lt 25; $i++){ 5$f = $i*$i

$sheet.Cells.Item($row,1).FormulaLocal = $i$sheet.Cells.Item($row,2).FormulaLocal =$f

$sheet.Cells.Item($row,3).FormulaLocal = $f*$i

$sheet.Cells.Item($row,4).FormulaR1C1Local = "=RC[-1]-RC[-2]"

$row++ } [void]$wkbk.PSBase.GetType().InvokeMember("SaveAs",

[Reflection.BindingFlags]

::InvokeMethod, $null,$wkbk, $sfile,$newci)                  6

[void]$wkbk.PSBase.GetType().InvokeMember("Close", [Reflection.BindingFlags] ::InvokeMethod,$null, $wkbk, 0,$newci)       7

$xl.Quit() 8 1 Delete previous files 2 Create spreadsheet 3 Set headers 4 Row counter 5 Create data 6 Save 7 Close 8 Quit The next step is to create the spreadsheet. In this case, I’ve used the international method. After the workbook is created, we can create a worksheet (#2). Worksheet cells are referred to by the row and column, as shown by creating the column headers (#3). A counter is created (#4) for the rows. A for loop (#5) is used to calculate the square and the cube of the loop index. This is a simple example to illustrate the point. In reality, the data could be something like the number of rows exported compared to the number of rows imported for each table involved in a database migration. Note that the difference between the square and the cube is calculated by counting back from the current column. We save the spreadsheet when all of the data has been written to it (#6), and close the workbook (#7). Note that we have to use a similar construction to adding a workbook, in Excel 2007 and earlier, to get around the culture issue. If we were using the en-US culture, those lines would become:$wkbk.SaveAs("$file")$wkbk.Close()

The last action is to quit the application (#8).

### Discussion

There are numerous reasons why you would want to record data into a spreadsheet but the performance implications must be understood.

Note   Adding data into an Excel spreadsheet in this manner can be extremely slow. In fact, painfully slow if a lot of data needs to be written into the spreadsheet. I strongly recommend creating a CSV file with the data and manually importing it into Excel instead of working directly with the spreadsheet.

This technique could be used to create reports, for instance from some of the WMI-based scripts we saw earlier. The computer name and relevant information could be written into the spreadsheet. Alternatively, we can write the data to a CSV file and then open it in Excel.

## Opening a CSV file in Excel

We have seen how writing data directly into a spreadsheet is slow. Slow tends to get frustrating, so we need another way to get the data into a spreadsheet. If we can write the data to a CSV file, we can open that file in Excel. It’s much faster and more satisfying.

### Problem

Having decided that we need to speed up creating our spreadsheet, we need to open a CSV file in Excel.

### Solution

The Open method will perform this action, as shown in Listing 3.

Listing 3: Open a CXV file

$xl = New-Object -comobject "excel.application"$xl.WorkBooks.Open("C:\Scripts\Office\data.csv")

$xl.visible =$true

### Discussion

As in the previous examples, we start by creating an object to represent the Excel application. We can then use the Open method of the workbooks to open the CSV file. The only parameter required is the path to the file. The full path has to be given. We then make the spreadsheet visible so we can work with it. Alternatively we could use:

Invoke-Item data.csv

This depends on the default action in the file associations to open the file in Excel. Hal Rottenberg graciously reminded me of this one.

The Microsoft Office applications are extremely widespread in the Windows environment. We can create and access documents using these applications in Windows PowerShell. This enables us to produce a reporting and documentation system for our computers based on using Windows PowerShell with WMI and COM.

~Richard

Here is the code for the discount offer today at www.manning.com: scriptw3
Valid for 50% off PowerShell in Practice and Learn Windows PowerShell in a Month of Lunches
Offer valid from April 3, 2013 12:01 AM until April 4 midnight (EST)

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
• I found a lot of useful information in the book "PowerShell in Practice". I learned more about how to use PowerShell in the real world from this book than anywhere else.

• Great posting!

Strange, I still get errors with Excel 2010 and Powershell 3.0.

Does this really work any longer?

Thanks for any hints!

Method 1 error:

Exception calling "Add" with "0" argument(s): "Old format or invalid type library. (Exception from HRESULT: 0x80028018 (TYPE_E_INVDATAREAD))"

At C:\path\to\script.ps1:5 char:1

+ $xlbooks =$xl.workbooks.Add()

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo          : NotSpecified: (:) [], MethodInvocationException

+ FullyQualifiedErrorId : ComMethodTargetInvocation

Method 2 error:

Exception calling "InvokeMember" with "6" argument(s): "Object does not match target type."

At C:\path\to\script.ps1:5 char:1

+ $xlbooks.PSBase.GetType().InvokeMember("Add",[Reflection.BindingFlags]::InvokeMe ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : TargetException • @RogOTM This works perfectly on 2007,2010,2013 and PowerShell V2 and V3.$xl = New-Object -ComObject Excel.Application

$xlbooks =$xl.workbooks.Add()

Perhaps you have a typo.

• @jrv, Thanks for your hint!

I really checked the syntax and I cannot see anything wrong. However, after Googling for similar C#/VB issues, I found a workaround involving setting the current thread culture. Now, Powershell and Excel work like a charm together! :-)

$xl = New-Object -ComObject "Excel.Application"$xl.visible = $true$newci = [System.Globalization.CultureInfo]"en-US"

[system.threading.Thread]::CurrentThread.CurrentCulture = $newci # this works!$xlbooks =$xl.Workbooks.Add() • @RogOTM That is likely because you are running US Office on a non-US platform. You should get the correct install disks for your culture. I believe you need to use the MUI version if you are not on US -en Windows. • Hi, How does one manipulate an existing spreadsheet. i.e. I don't want to write the code to create a completely new spreadsheet? i.e. not useing "New-Object" Thanks • You can connect to an existing instance of Excel without having to create a new one with New-Object by executing the following code. Keep in mind that this will connect to the first instance of Excel that is running which may not be defined so it is really only going to be reliable if only one copy of Excel is running. Once you have this handle to a running version of Excel the rest of the code examples work the same way as written.$xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')

• $xl=[Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')$xl.Workbooks.item('mybook.xlsx') All books are available to those who know.

• Learn http://www.computerteachpro.com/ms_excel.php">Excel training NY and you can get brilliant information about the Ms Excel.