Beyond Export-Csv: Export-Xls
Warning: this post has no “virtual” content !
This time I post a function that allows you to export your data to a “real” spreadsheet (XLS format) instead of a CSV file.
The reason for posting this function was a series of threads in the PowerCLI Community by Suresh. Over several threads he has been collecting scripts that create various reports on his vSphere environment. Ultimately he wanted to have a spreadsheet where each report would be stored on a worksheet.
PowerShell has the very handy Export-Csv cmdlet to create CSV files but afaik nothing for creating XLS files
That’s when I decided to “pimp” my XLS creating function I already had to something more suited for general use.
The function uses Excel through a COM object. So yes, you do need to have Excel installed on the client where you use the function. The function first exports the data to a CSV file with the Export-Csv cmdlet. And then does a copy/paste to the worksheet in the XLS file.
Update June 16th 2010: the function has a new parameter -ChartType which allows the addition of a chart on the worksheet.
Update November 2nd 2010: a complete overhaul of the function
- The function now uses the clipboard, instead of a temporary CSV file, to hold the data that needs to be written to the worksheet.
- The function places the array on the clipboard in such a way that it can be correctly pasted into the Worksheet. Thanks to my colleague Géry for his algorithm.
- The function uses the Textbox trick as described in Copy and Paste with Clipboard from PowerShell. With this method we don’t need to start an new PowerShell thread with the STA attribute. And this method is considerably faster !
The script
#requires -version 2
function Export-Xls{
<#
.SYNOPSIS
Saves Microsoft .NET Framework objects to a worksheet in an XLS file
.DESCRIPTION
The Export-Xls function allows you to save Microsoft .NET Framework objects
to a named worksheet in an Excel file (type XLS). The position of the
worksheet can be specified.
.NOTES
Author: Luc Dekens
.PARAMETER InputObject
Specifies the objects to be written to the worksheet. The parameter accepts
objects through the pipeline.
.PARAMETER Path
Specifies the path to the XLS file.
.PARAMETER WorksheetName
The name for the new worksheet. If not specified the name will
be "Sheet" followed by the "Ticks" value
.PARAMETER SheetPosition
Specifies where the new worksheet will be inserted in the series of
existing worksheets. You can specify "begin" or "end". The default
is "begin".
.PARAMETER ChartType
Specifies the type of chart you want add to the worksheet.
All types in the [microsoft.Office.Interop.Excel.XlChartType]
enumeration are accepted.
.PARAMETER NoTypeInformation
Omits the type information from the worksheet. The default is to
include the "#TYPE" line.
.PARAMETER AppendWorksheet
Specifies if the worksheet should keep or remove the existing
worksheet in the spreadsheet. The default is to append.
.EXAMPLE
PS> $data = Get-Process | Select-Object Name, Id, WS
PS> Export-Xls $data C:\Reports\MyWkb.xls -WorksheetName "WS" -AppendWorksheet:$false
.EXAMPLE
PS> $data = Get-Process | Select-Object Name, Id, WS
PS> Export-Xls $data C:\Reports\MyWkb.xls -SheetPosition "end"
.EXAMPLE
PS> $data = Get-Process | Select-Object Name, Id, WS
PS> Export-Xls $data C:\Reports\MyWkb.xls -WorksheetName "WS" -ChartType "xlColumnClustered"
#>
param(
[parameter(ValueFromPipeline = $true,Position=1)]
[ValidateNotNullOrEmpty()]
$InputObject,
[parameter(Position=2)]
[ValidateNotNullOrEmpty()]
[string]$Path,
[string]$WorksheetName = ("Sheet " + (Get-Date).Ticks),
[string]$SheetPosition = "begin",
[PSObject]$ChartType,
[switch]$NoTypeInformation = $true,
[switch]$AppendWorksheet = $true
)
begin{
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Interop.Excel")
if($ChartType){
[microsoft.Office.Interop.Excel.XlChartType]$ChartType = $ChartType
}
function Set-ClipBoard{
param(
[string]$text
)
process{
Add-Type -AssemblyName System.Windows.Forms
$tb = New-Object System.Windows.Forms.TextBox
$tb.Multiline = $true
$tb.Text = $text
$tb.SelectAll()
$tb.Copy()
}
}
function Add-Array2Clipboard {
param (
[PSObject[]]$ConvertObject,
[switch]$Header
)
process{
$array = @()
if ($Header) {
$line =""
$ConvertObject | Get-Member -MemberType Property,NoteProperty,CodeProperty | Select -Property Name | %{
$line += ($_.Name.tostring() + "`t")
}
$array += ($line.TrimEnd("`t") + "`r")
}
else {
foreach($row in $ConvertObject){
$line =""
$row | Get-Member -MemberType Property,NoteProperty | %{
$Name = $_.Name
if(!$Row.$Name){$Row.$Name = ""}
$line += ([string]$Row.$Name + "`t")
}
$array += ($line.TrimEnd("`t") + "`r")
}
}
Set-ClipBoard $array
}
}
$excelApp = New-Object -ComObject "Excel.Application"
$originalAlerts = $excelApp.DisplayAlerts
$excelApp.DisplayAlerts = $false
if(Test-Path -Path $Path -PathType "Leaf"){
$workBook = $excelApp.Workbooks.Open($Path)
}
else{
$workBook = $excelApp.Workbooks.Add()
}
$sheet = $excelApp.Worksheets.Add($workBook.Worksheets.Item(1))
if(!$AppendWorksheet){
$workBook.Sheets | where {$_ -ne $sheet} | %{$_.Delete()}
}
$sheet.Name = $WorksheetName
if($SheetPosition -eq "end"){
$nrSheets = $workBook.Sheets.Count
2..($nrSheets) |%{
$workbook.Sheets.Item($_).Move($workbook.Sheets.Item($_ - 1))
}
}
$sheet.Activate()
$array = @()
}
process{
$array += $InputObject
}
end{
Add-Array2Clipboard $array -Header:$True
$selection = $sheet.Range("A1")
$selection.Select() | Out-Null
$sheet.Paste()
$Sheet.UsedRange.HorizontalAlignment = [microsoft.Office.Interop.Excel.XlHAlign]::xlHAlignCenter
Add-Array2Clipboard $array
$selection = $sheet.Range("A2")
$selection.Select() | Out-Null
$sheet.Paste() | Out-Null
$selection = $sheet.Range("A1")
$selection.Select() | Out-Null
$sheet.UsedRange.EntireColumn.AutoFit() | Out-Null
$workbook.Sheets.Item(1).Select()
if($ChartType){
$sheet.Shapes.AddChart($ChartType) | Out-Null
}
$workbook.SaveAs($Path)
$excelApp.DisplayAlerts = $originalAlerts
$excelApp.Quit()
Stop-Process -Name "Excel"
}
}
Annotations
Line 65-77: This short function place a string in the Textbox control and then uses the Copy method to send the string to the Clipboard. Function by Brian Reiter.
Line 79-107: This concept for this function came from my colleague Géry. The trick is to separate the properties from the array by the TAB character. That way a paste in Excel will place the properties in different cells. The first part of the function handles the property names (or the column headers in Excel), the second part prepares the actual data.
Line 110-111,155: The Excel alerts are temporarily disabled to avoid pop-up messages. The original setting is restored just before leaving the function.
Line 112-117: The function tests if the XLS file exists or not.
Line 118 A new worksheet is added in position 1.
Line 119-121: If the AppendWorksheet switch is $false, all the other worksheets are removed.
Line 123-128: These lines move the worksheet to the end of the list if the SheepPosition parameter states “end”.
Line 134: All the objects from the InputObject are copied to the local $array variable.
Line 138: The property names of the $array variable are copied to the clipboard
Line 139-141: The column headers (the property names) are pasted to the Excel worksheet
Line 142: The column headers are aligned.
Line 143: The data in the $array variable is copied to the clipboard
Line 144-146: The data is pasted into the Excel worksheet 1 row below the headers.
Line 147: Unselect the range that was just copied.
Line 150: The width of all columns in the worksheet is changed so all cell values are visible.
Line 158: Although the Quit method is called, the Excel process stays active. The only way to get rid of it is through a Stop-Process cmdlet.
Examples
Some sample runs I did with Excel 2003.
$data = Get-Process | Select-Object Name, Id, WS Export-Xls $data D:\Reports\MyWkb.xls
This produces the following
In this installation Excel was configured to start a new spreadsheet with 3 worksheets.
But we can get rid of those by using the -AppendWorksheet switch.
$data = Get-Process | Select-Object Name, Id, WS Export-Xls $data D:\Reports\MyWkb.xls -AppendWorksheet:$false
Now we only have the worksheet we just created.
You can use the function in a pipeline
Get-Process | Select-Object Name, Id, WS | Export-Xls -Path D:\Reports\MyWkb.xls -WorksheetName "My new sheet"
This gives
And you can specify that the new worksheet should be at the end
Get-Process | Select-Object Name, Id, WS | ` Export-Xls -Path D:\Reports\MyWkb.xls -WorksheetName "Another sheet" -SheetPosition "end"
This gives
Charts
With the -ChartType parameter you can create a chart on the worksheet.
The accepted values for this parameter are all the values present in the microsoft.Office.Interop.Excel.XlChartType enumtype. If you want to produce a list with the acceptable values you can do something like this.
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Interop.Excel") | Out-Null
[microsoft.Office.Interop.Excel.XlChartType] | gm -Static -MemberType Property | %{$_.Name}
It will produce a rather long list of available chart types.

Some sample runs
$data = Get-Process | Sort-Object -Property WorkingSet -Descending | `
Select-Object Name, @{N="WS";E={$_.WorkingSet/1MB}} -First 10
Export-Xls $data C:\Temp\MyWkb.xls -AppendWorksheet:$false -WorksheetName "WS" -ChartType "xlColumnClustered"
Produces
This short script selects the working set and the peak working set and add a line graph.
$data = Get-Process | Select-Object -First 10 Name, @{N="WS";E={$_.WorkingSet/1MB}},@{N="PM";E={$_.PeakWorkingSet/1MB}}
Export-Xls $data C:\Temp\MyWkb.xls -AppendWorksheet:$false -WorksheetName "WS" -ChartType "xlLine"
And this produces







@LucD
Actually, that didn’t work as I planned, it just killed the lowest numbered Excel PID. I had to do this:
$pre = get-process -Name EXCEL
$excelApp = New-Object -ComObject “Excel.Application”
$post = get-process -Name EXCEL
Foreach ($excelproc in $post) {
$test = 0
Foreach ($pred in $pre) {
if ($pred.Id -eq $excelproc.Id) {$test++}
}
if ($test -eq 0) {break}
}
I’m sure there might be a simpler way. But this one actually works.
Thanks for the feedback Dedrick, I’ll update the script.
I modified this a bit to only close the process that it created by including this where you create the Excel process (line 109)
$pre = get-process -Name EXCEL
$excelApp = New-Object -ComObject “Excel.Application”
$post = get-process -Name EXCEL
Foreach ($exlproc in $post) {
if ($pre -notcontains $exlproc) { break }
}
Remove-Variable pre
Remove-Variable post
And this in the place of stop-process:
$exlproc.kill()
Doesn’t force close my current work
That is a very handy improvement.
I’ll update my function accordingly.
one other remark: when saving the files as .xls I get the error “the file (…) is in a different format than specified by the file extension. (…)” when opening the file. When saving as .xlsx this doesn’t occur. So mind the X
hi Luc
great script! But unfortunately it expects your office environment to be in English while mine is Dutch… I worked around it by adding this line
[System.Threading.Thread]::CurrentThread.CurrentCulture = “en-US”
right above
$excelApp = New-Object -ComObject “Excel.Application”
but ideally it shouldn’t matter what language your Office (or Windows) environment is.
Hi there,
big thanks for the function, it’s great and saves some time converting the CSVs to Excel sheets. But I have one problem: I work with arrays in one script and I plan to use them more often after I found out how easy they are to use. The problem with Export-Xls is, that the columns are now sorted in alphabetical order and not the way I defined them.
If you need a script to test with, I can mail it to you, just tell me where to.
2nd, I use Excel 2010 on my computer so I have to change the file extension to xlsx (maybe a warning by the script if version is 2007/12 or above?) and Excel is telling me that it found a previous version of the document. I fixed this error by adding a “sleep -s 3″ into the script right before you kill the process in line 158 to let Excel actually quit.
Hi Luc,
I’m getting the below given error when i try to use this function to export a xls file.
Unable to find type [microsoft.Office.Interop.Excel.XlHAlign]: make sure that the assembly containing this type is loaded.
At :line:101 char:82
+ $Sheet.UsedRange.HorizontalAlignment = [microsoft.Office.Interop.Excel.XlHAlign] <<<< ::xlHAlignCenter
Already @jfrmilner has mentioned this but he has used it for chart i think. But i'm not using chart and just trying to export to xls file and getting this error. I'm running on W2K3 & Excel2007 machine. Please help.
@jfrmilner
Hi Can you pls, let us knw wat exact changes were made for office 2007, or attached the working script itself.
Thanks.
@Harry, I think the change is mentioned in @jfrmilner’s reply.
He changed line 62 from
[microsoft.Office.Interop.Excel.XlChartType]$ChartType = $ChartType
to
$chartType = “microsoft.office.interop.excel.xlChartType” -as [type]
Unfortunately I don’t have an Office 2007 version handy to test this right now.
Hi Luc,
I am getting the following error when executing Export-Xls $data C:\MyWkb.xls -AppendWorksheet:$false -WorksheetName “WS” -ChartType “xlLine”
##################################################
Method invocation failed because [System.__ComObject] doesn’t contain a method named ‘AddChart’.
At C:\Users\d40229g\desktop\Harish\export-xls.ps1:153 char:26
+ $sheet.Shapes.AddChart <<<< ($ChartType) | Out-Null
+ CategoryInfo : InvalidOperation: (AddChart:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
@Harish, which Office version do you have installed on the PC where you run the script ? This error typically appears with some older version of Office.
Does this work on office 2003. If not any chances to modify the script to do so ??
@Harry, I don’t have an Office 2003 to test but I’m afraid it will not work.
I’ll try to find out.
Hi Luc. i’m using your great piece of script to easily create a graph from PowerCLI data but it is possible to fixe the abscissa reference because in your process/workingset example if you replace N=”WS” by N=”AWS” the first column is not “name” anymore and the graphic is mixed up ?
Hi NiTRo, the problem is caused by the fact that the Get-Member cmdlet, which the script uses to get the column headers, always returns them in alphabetical order.
At the moment I have no clue how the order of properties on the Select-Object cmdlet can be retrieved.
The -Property parameter of the New-Object cmdlet has a similar “problem”. You have no control over the order of the properties.
Let me think if I can come up with a bypass.
How do I get this function to work as a cmdlet?
@Harish. There are many ways of doing that (aren’t there always in PowerShell).
You can store the function in one of your profiles, include in the script file, dot-source it from the command prompt…
And then you can call the function/cmdlet in your other statements.
See some examples at the bottom of the post.
Any thoughts on how to tweak the script to work with Office 2003 – shy of upgrading to office 2010?
This is really excellent; I really like the Chart option.
I got a minor issue (Win7 – Office 2007) with “Unable to find type [microsoft.Office.Interop.Excel.XlChartType]: make sure that the assembly containing this type is loaded.”
So i changed that line to this:
$chartType = “microsoft.office.interop.excel.xlChartType” -as [type]
Thanks. I could only test with Office 2010.
Apparently it doesn’t work at all with Office XP
@LucD
No its v1
V2 wont install for some reason …
When i run WindowsXP-KB968930-x86-ENG
it fails with a message : Access is Denied
Current config :
Win Xp sp3
Domain account with Local admin rights
IE 7
@Pcli, this function was written for PowerShell v2.
You could try out some of the suggestions in KB968930 fails at installation with Automatic Updates
Export-Xls
When i run the scrip it Displays the error below
Missing closing ‘)’ in expression.
At :line:45 char:2
+ [ <<<< ValidateNotNullOrEmpty()]
some thing i need to do ?
Are you using PowerShell v2 ?
Hi Luc,
Got it working with the Using-Culture function from Jeffrey. http://blogs.msdn.com/b/powershell/archive/2006/04/25/583235.aspx
using-culture en-US {get-process | select Name, Id, WS | export-xls -path c:\temp\culture.xls}
Regards,
Stefan Stranger
Hi Luc,
Thanks for this great function. I only needed to change my Region and Language format from Dutch to English to get it working.
Have to figure out how I can have it work with my regional settings though.
Regards,
Stefan Stranger
Hi Stefan,
You could try adding the -UseCulture parameter on the Export-Csv cmdlet in the script.
Luc.
Thanks Luc, this is a “real” value function for people who want a combine output’s of multiple related scripts into a single excel sheet with separate worksheet for each script or category.