In this blog post, i try to give simple idea about how to create, write and save excel files using PowerShell. This will help you in many reporting automation process.
First of all we have to understand what is PowerShell. PowerShell is is a shell developed by Microsoft for purposes of task automation and configuration management . This powerful shell is based on the .NET framework and it includes a command-line shell and a scripting language.
Let’s start, what am try to do is, import 3 “txt” files with some data and write those details in to one excel file.
1.How to import .txt files ?
I have 3 txt files with some numeric data. import those files into our script using Get-Content cmdlet.
$Time = Get-Content 'C:InputFilesTIME.txt' $Current = Get-Content 'C:InputFilesCUR.txt' $volt = Get-Content 'C:InputFilesVOLT.txt'
2.How to Create Excel file ?
Create Excel file by creating powershell objects. Following Line create object using excel.
$excel = New-Object -ComObject excel.application
The next line hold visiblity of excel application. if you use $False it will run as hidden process and if you use $True it will show you the writing process as well. I use it as $False.
$excel.visible = $False
Now I add workbook to my excel.
$workbook = $excel.Workbooks.Add()
Then add worksheet to my workbook and rename it as “Data Set”. You can replace any name with it.
$diskSpacewksht= $workbook.Worksheets.Item(1) $diskSpacewksht.Name = "Data Set"
Now add column names and the Header for our excel. we use (row number,col number) format.
$diskSpacewksht.Cells.Item(2,8) = 'Header - Data Set Excel' $diskSpacewksht.Cells.Item(3,1) = 'Time' $diskSpacewksht.Cells.Item(3,2) = 'Current' $diskSpacewksht.Cells.Item(3,3) = 'Volt'
Then i try to use some styles for our excel, to get some attractive look. in this case change the font family, font size, font color and etc.
$diskSpacewksht.Cells.Item(2,8).Font.Size = 18 $diskSpacewksht.Cells.Item(2,8).Font.Bold=$True $diskSpacewksht.Cells.Item(2,8).Font.Name = "Cambria" $diskSpacewksht.Cells.Item(2,8).Font.ThemeFont = 1 $diskSpacewksht.Cells.Item(2,8).Font.ThemeColor = 4 $diskSpacewksht.Cells.Item(2,8).Font.ColorIndex = 55 $diskSpacewksht.Cells.Item(2,8).Font.Color = 8210719
3.How to Write Data to Excel file ?
Following code show you to how 3 txt files data write into a one excel file using powershell.
$col = 4 $col1 = 4 $col2 = 4 foreach ($timeVal in $Time){ $diskSpacewksht.Cells.Item($col,1) = $timeVal $col++ } foreach ($currentVal in $Current){ $diskSpacewksht.Cells.Item($col1,2) = $currentVal $col1++ } foreach ($voltVal in $volt){ $diskSpacewksht.Cells.Item($col2,3) = $voltVal $col2++ }
4.How to Save Excel file ?
Finally we have to save our excel file. This whole process run as hidden. so we have to off Display alerts. Then file extension has to be .xlsx and we have to set the file path to save the file and quit from the excel.
$excel.DisplayAlerts = 'False' $ext=".xlsx" $path="C:CSVfilesDataSetE$ext" $workbook.SaveAs($path) $workbook.Close $excel.DisplayAlerts = 'False' $excel.Quit()
Following Source code shows the complete scenario.
$Time = Get-Content 'C:InputFilesTIME.txt' $Current = Get-Content 'C:InputFilesCUR.txt' $volt = Get-Content 'C:InputFilesVOLT.txt' $excel = New-Object -ComObject excel.application $excel.visible = $False $workbook = $excel.Workbooks.Add() $diskSpacewksht= $workbook.Worksheets.Item(1) $diskSpacewksht.Name = "Data Set" $diskSpacewksht.Cells.Item(2,8) = 'Header - Data Set Excel' $diskSpacewksht.Cells.Item(3,1) = 'Time' $diskSpacewksht.Cells.Item(3,2) = 'Current' $diskSpacewksht.Cells.Item(3,3) = 'Volt' $diskSpacewksht.Cells.Item(2,8).Font.Size = 18 $diskSpacewksht.Cells.Item(2,8).Font.Bold=$True $diskSpacewksht.Cells.Item(2,8).Font.Name = "Cambria" $diskSpacewksht.Cells.Item(2,8).Font.ThemeFont = 1 $diskSpacewksht.Cells.Item(2,8).Font.ThemeColor = 4 $diskSpacewksht.Cells.Item(2,8).Font.ColorIndex = 55 $diskSpacewksht.Cells.Item(2,8).Font.Color = 8210719 $col = 4 $col1 = 4 $col2 = 4 foreach ($timeVal in $Time){ $diskSpacewksht.Cells.Item($col,1) = $timeVal $col++ } foreach ($currentVal in $Current){ $diskSpacewksht.Cells.Item($col1,2) = $currentVal $col1++ } foreach ($voltVal in $volt){ $diskSpacewksht.Cells.Item($col2,3) = $voltVal $col2++ } $excel.DisplayAlerts = 'False' $ext=".xlsx" $path="C:CSVfilesDataSetE$ext" $workbook.SaveAs($path) $workbook.Close$excel.DisplayAlerts = 'False' $excel.Quit()
You have to copy this code and save as any name with “.ps1” file extension. Also change all file paths related to your file locations. I have attached my .txt files with this post, and you can test the script using those files.
Download TXT files :https://drive.google.com/open?id=0Bw-UUdtr10I8RW50X0NpZERPS1E
Author : Malith Oshan Lankarathne
Join
- Home
- Software
- Microsoft Office
- How-tos
2 Minute Read
-
Spice
-
Reply (3)
-
Subscribe
-
Share
Opens a new window
-
Facebook
Opens a new window -
Twitter
Opens a new window -
Reddit
Opens a new window -
LinkedIn
Opens a new window
-
- Contests |
- PowerShell |
- Windows 10 |
- General IT Security |
- General Hardware
Sign Up
Load More
Об инвентаризации не писал, наверное, только ленивый. Вот и я, чтобы не казаться ленивым, тоже решил взяться за это дело. Поводом для написания стало появление нескольких статей на эту тему. Меня даже заинтересовала не сама инвентаризация (что там инвентаризировать – дёргай нужные объекты, смотри их свойства), а работа с Excel’ем, так как всё руки не доходили попробовать. С Word’ом сталкиваться уже приходилось, а вот с Excel’ем ещё нет. Можно, конечно, не заморачиваться, и вывести всё в CSV-файл, но повторюсь – меня интересовала именно работа с Excel: заполнение и форматирование ячеек, раскраска, диаграммы и т.д. Но обо всём по порядку 🙂
Итак, прежде всего нужно создать объект Excel и сделать его видимым, чтоб видеть всю дальнейшую магию 🙂
# Созадём объект Excel $Excel = New-Object -ComObject Excel.Application # Делаем его видимым $Excel.Visible = $true
Это равносильно запуску Excel. Далее необходимо создать файл (в терминологии Excel рабочую книгу):
# Добавляем рабочую книгу $WorkBook = $Excel.Workbooks.Add()
В оригинале статьи автор говорит, что этой операцией добавляется три листа, и если остальные не нужны их можно/нужно удалить, и показывает как это сделать. Но у меня добавляется только один лист, не знаю с чем это связано, возможно в разных версиях офиса по разному, поэтому я на этом останавливаться не буду.
Начинаем работать с первым листом. Для простоты обращения к нему создаём соответствующую переменную:
$LogiclDisk = $WorkBook.Worksheets.Item(1)
Далее переименовываем лист (чтобы было не Лист1, Лист2 и т.д., а “человеческие” названия) и заполняем шапку таблицы:
# Переименовываем лист $LogiclDisk.Name = 'Логические диски' # Заполняем ячейки - шапку таблицы $LogiclDisk.Cells.Item(1,1) = 'Буква диска' $LogiclDisk.Cells.Item(1,2) = 'Метка' $LogiclDisk.Cells.Item(1,3) = 'Размер (ГБ)' $LogiclDisk.Cells.Item(1,4) = 'Свободно (ГБ)'
Как (наверное) понятно здесь мы пишем в каждую ячейку по очереди, первая цифра в скобках – номер строки, вторая – номер столбца.
Уже можно наслаждаться первыми результатами работы 🙂
Главное окно Excel
Пока смотрится криво из-за того, что надписи не влазят в ячейки, и хочется растянуть ячейки, но ничего страшного, мы это потом поправим.
Переходим на следующую строку, возвращаемся в первый столбец и в цикле заполняем таблицу данными по логическим дискам, после каждого диска переводим курсор (или как правильно назвать текущую ячейку?) на следующую строку и возвращаемся в первый столбец:
# Переходим на следующую строку... $Row = 2 $Column = 1 # ... и заполняем данными в цикле по логическим разделам Get-WmiObject Win32_LogicalDisk | ForEach-Object ` { # DeviceID $LogiclDisk.Cells.Item($Row, $Column) = $_.DeviceID $Column++ # VolumeName $LogiclDisk.Cells.Item($Row, $Column) = $_.VolumeName $Column++ # Size $LogiclDisk.Cells.Item($Row, $Column) = ([Math]::Round($_.Size/1GB, 2)) $Column++ # Free Space $LogiclDisk.Cells.Item($Row, $Column) = ([Math]::Round($_.FreeSpace/1GB, 2)) # Переходим на следующую строку и возвращаемся в первую колонку $Row++ $Column = 1 }
Размеры дисков переводятся в гигабайты, и чтобы много цифр не сбивали с толку, округляются до двух знаков после запятой.
Смотрим результат:
Логические диски в Excel
мдя… многовато дисков, надо-бы их немножко пообъединять, создавались когда-то временно для тестовых целей, но как известно нет ничего более постоянного чем временное 🙂
Диски с нулевыми размерами это два DVD-привода и один виртуальный.
Осталось немного приукрасить внешний вид – выделим шапку таблицы (первая строка) жирным, и отрегулируем ширину ячеек по ширине текста (до этого момента я даже не подозревал, что Excel такое умеет:)):
# Выделяем жирным шапку таблицы $LogiclDisk.Rows.Item(1).Font.Bold = $true # Выравниваем для того, чтобы их содержимое корректно отображалось в ячейке $UsedRange = $LogiclDisk.UsedRange $UsedRange.EntireColumn.AutoFit() | Out-Null
Переменная $UsedRange содержит все занятые ячейки (эквивалентно однократному нажатию Ctrl+A)
Смотрим, что получилось:
Готовая таблица
Красота да и только 🙂
С логическими дисками разобрались, переходим к физическим.
Создадим для них отдельный лист:
# Добавляем лист $WorkBook.Worksheets.Add()
Тут есть один нюанс, заключающийся в том, что листы добавляются в обратном порядке, т.е. только что добавленный лист будет иметь номер 1, а предыдущий станет номером 2. Поэтому выделяем только что созданный лист, и делаем всё то же самое, только с физическими дисками:
$PhysicalDrive = $WorkBook.Worksheets.Item(1) # Переименовываем лист $PhysicalDrive.Name = 'Физические диски' # Заполняем ячейки - шапку таблицы $PhysicalDrive.Cells.Item(1,1) = 'Модель' $PhysicalDrive.Cells.Item(1,2) = 'Размер (ГБ)' $PhysicalDrive.Cells.Item(1,3) = 'Кол-во разделов' $PhysicalDrive.Cells.Item(1,4) = 'Тип' # Переходим на следующую строку... $Row = 2 $Column = 1 # ... и заполняем данными в цикле по физическим дискам Get-WmiObject Win32_DiskDrive | ForEach-Object ` { # Model $PhysicalDrive.Cells.Item($Row, $Column) = $_.Model $Column++ # Size $PhysicalDrive.Cells.Item($Row, $Column) = ([Math]::Round($_.Size /1GB, 1)) $Column++ # Partitions $PhysicalDrive.Cells.Item($Row, $Column) = $_.Partitions $Column++ # InterfaceType $PhysicalDrive.Cells.Item($Row, $Column) = $_.InterfaceType # Переходим на следующую строку и возвращаемся в первую колонку $Row++ $Column = 1 } # Выделяем жирным шапку $PhysicalDrive.Rows.Item(1).Font.Bold = $true # Выравниваем для того, чтобы их содержимое корректно отображалось в ячейке $UsedRange = $PhysicalDrive.UsedRange $UsedRange.EntireColumn.AutoFit() | Out-Null
Смотрим, что получилось:
Логические диски
Осталось сохранить полученный отчёт и выйти из Excel:
$WorkBook.SaveAs('C:tempReport.xlsx') $Excel.Quit()
На сегодня всё :). В следующих частях мы научимся объединять и раскрашивать ячейки, а также строить диаграммы.
Creating Excel Reports using PowerShell is very important since Excel as the application is widely used especially in out of the IT world and sometimes a very useful way of presenting the data.
I have written my own Save-ToExcel PowerShell CmdLet and after reading this article you can expect the following result presented with screenshots.
In the following syntax result of Get-ErrorFromEventLog CmdLet (another of my own functions) has been sent down the PowerShell Pipeline as input to the Save-ToExcel CmdLet which will create Excel file in the folder.
Get-ErrorFromEventLog -computers "localhost" -errorlog -client "OK" -solution "FIN" -days 3 -Verbose | Save-ToExcel -ExcelFileName "Get-ErrorsInEventLogs" -title "Get errors from Event Logs on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Errors from Event Logs" -sendemail -client "OK" -solution "FIN"
Here is the resulting Excel Report that shows all the errors from Event Logs on the machine:
We can even send this Excel file as an attachment to some email account right from the Save-ToExcel CmdLet.
Save-ToExcel Function Explained
Save-ToExcel Function is part of the Efficiency Booster PowerShell Project. This project is the library of PowerShell CmdLets that helps us to accomplish our everyday IT tasks in a more efficient way so we can have more time doing some other tasks.
In order to follow me along please download the source code from here. Save-ToExcel Function is part of the Utils module and the script file is in the 02utils folder.
An important feature of the Save-ToExcel function is that accepts the result of some PowerShell CmdLet sent down the PowerShell Pipeline as input in order to create the Excel file for that resultset.
Excel file is saved in the PSreports folder in [My] Documents for the user that runs the code.
INFO: I have thoroughly explained the PowerShell Pipeline concept in the article How PowerShell Pipeline Works, so please read it for more info.
Input Parameters Of Save-ToExcel Function
Save-ToExcel Function has many input parameters and I will try to give a short description for each of them.
Here is the code for input parameters:
Function Save-ToExcel {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
HelpMessage="Input rows to be saved as Excel file.")]
[PSobject[]]$InputObject,
[Parameter(Mandatory=$true,
HelpMessage="Excel file name.")]
[string]$ExcelFileName,
[Parameter(Mandatory=$true,
HelpMessage="Excel workbook title.")]
[string]$title,
[Parameter(Mandatory=$true,
HelpMessage="Excel workbook author.")]
[string]$author,
[Parameter(Mandatory=$true,
HelpMessage="Excel worksheet name.")]
[string]$WorkSheetName,
[Parameter(HelpMessage="Write to error log file or not.")]
[switch]$errorlog,
[Parameter( HelpMessage="Send email.")]
[switch]$sendemail,
[Parameter(Mandatory=$true,
HelpMessage="Client for example OK = O client, BK = B client")]
[string]$client,
[Parameter(Mandatory=$true,
HelpMessage="Solution, for example FIN = Financial solution, HR = Humane Resource solution")]
[string]$solution
)
}
INFO: Creation of input parameters for every function is a very important decision and I have written an article with many examples of parameters How To Create Parameters In PowerShell.
Begin Block Of Save-ToExcel Function
The BEGIN input processing method (block) runs only one time and it is very useful for pre-processing initialization.
In Save-ToExcel Function we do two things in BEGIN block:
- Create an empty array of objects.
- Test if the folder, where we want to save our Excel file, exists and if not we create the folder.
BEGIN {
#Creat an empty array
$objects = @()
$reportsfolder = "$homeDocumentsPSreports"
if ( !( Test-Path -Path $reportsfolder -PathType "Container" ) ) {
Write-Verbose "Create reports folder in: $reportsfolder"
New-Item -Path $reportsfolder -ItemType "Container" -ErrorAction Stop
}
}
INFO: If you want to know more about Begin, Proces, and End input processing methods I have written article PowerShell Function Begin Process End Blocks Explained With Examples.
Process Block Of Save-ToExcel Function
The PROCESS input processing method (block) runs on a record-by-record basis for each object sent through PowerShell Pipeline.
Implementation of PROCESS block in Save-ToExcel function is very simple:
- We just add objects sent to function from PowerShell Pipeline to the array of objects created in the BEGIN block.
PROCESS {
$objects += $InputObject
}
INFO: PowerShell Pipeline is one of the essential concepts in Windows PowerShell and I highly encourage you to read about it in the article How PowerShell Pipeline Works.
End Block Of Save-ToExcel Function
The END input processing method (block) runs only one time and it is very useful for post-processing clean up.
Interestingly enough implementation of END block in Save-ToExcel Function is the main processing rather than post-processing since here we have most of the functionality. This approach to process all data in one batch in END block rather than row by row in PROCESS block is because Excel is very slow for record-by-record processing using PowerShell. In order, to avoid the performance issues I have decided to put the main functionality in the END block and do as one batch.
I will explain the code in steps and at the end show the whole END block code.
Step 1: Set up the culture to EN-US
TIP: This is a very important step for us who use other than English culture settings. I live in Norway and my all culture settings are for Norwegians and this was the main cause of my code breaking until I fixed it with the line of code that follows.
[System.Threading.Thread]::CurrentThread.CurrentCulture = "en-US"
Step 2: Create Temporary CSV file
We create a temporary CSV file using Export-Csv CmdLet with data from the objects sent through the PowerShell Pipeline. We will use this temporary CSV file when we create an Excel workbook just to past the data into the worksheet.
#Temporary .csv file with GUID name has been created first.
$temporaryCsvFile = ($env:temp + "" + ([System.Guid]::NewGuid()).ToString() + ".csv")
#Delimiter ";" helps that result is parsed correctly. Comma delimiter parses incorrectly.
$objects | Export-Csv -ErrorAction Stop -path $temporaryCsvFile -noTypeInformation -Delimiter ";"
Write-Verbose "Temporary csv file saved: $temporaryCsvFile"
TIP: Use as delimiter “;” semicolon instead of “,” comma in Export-CSV CmdLet in order to get data parsed correctly.
Step 3: Create An Excel Object
Now is the time to create Excel Object using COM.
$excelObject = New-Object -ComObject Excel.Application -ErrorAction Stop
IMPORTANT: We need Excel installed on the server that will process this function.
Step 4: Optionally, make Excel object visible/invisible
Since I like to automate my CmdLets and run them scheduled there was no need for me to make Excel workbook visible. However, while I was developing the code I would turn on the visibility just for testing or debugging purposes.
$excelObject.Visible = $false
Step 5: Create Excel Workbook
We create the excel workbook passing the temporary CSV file created in Step 2. In addition, we give a title to our Workbook and Author.
NOTE: Since we use the Open method and pass the temporary CSV file with data, Excel will create a workbook with one worksheet instead workbook with empty 3 worksheets if we use the Add method.
$workbookObject = $excelObject.Workbooks.Open($temporaryCsvFile)
$workbookObject.Title = ("$title " + (Get-Date -Format D))
$workbookObject.Author = "$author"
Step 6: Create Excel Worksheet
Now we can create our first Excel Worksheet object.
$worksheetObject = $workbookObject.Worksheets.Item(1)
Step 7: Customize and Style Excel Worksheet as needed
Now we can customize our Excel Worksheet to give it a name, autofit the columns, and use some predefined Excel table style for the better-formatted result.
#Method TextToColumns is important to convert .csv file data into right columns in Excel file.
$colA=$worksheetObject.range("A1").EntireColumn
$colrange=$worksheetObject.range("A1")
$xlDelimited = 1
$xlTextQualifier = 1
$colA.TextToColumns($colrange,$xlDelimited,$xlTextQualifier,$false,$false,$false,$true)
$worksheetObject.UsedRange.Columns.Autofit() | Out-Null
$worksheetObject.Name = "$WorkSheetName"
#Style of table in Excel worksheet.
$xlSrcRange = 1
$XlYes = 1
#Syntax - expression.Add(SourceType, Source, LinkSource, HasHeaders, Destination)
$listObject = $worksheetObject.ListObjects.Add($xlSrcRange, $worksheetObject.UsedRange, $null, $XlYes, $null)
$listObject.Name = "User Table"
$listObject.TableStyle = "TableStyleMedium6" # Style Cheat Sheet in French/English: http://msdn.microsoft.com/fr-fr/library/documentformat.openxml.spreadsheet.tablestyle.aspx
Step 8: Save Excel File and Close Excel Workbook
We can save our Excel File and close Excel Workbook.
$workbookObject.SaveAs($excelFile,51) # http://msdn.microsoft.com/en-us/library/bb241279.aspx
$workbookObject.Saved = $true
$workbookObject.Close()
Step 9: Clean up open Excel Application and Workbook
Now is the part that is typical for END block and that is clean up of used resources. We clean up Excel Application and Workbook and send information to Garbage Collector to do that for us.
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbookObject) | Out-
$excelObject.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excelObject) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
TIP: While I was writing and debugging this code I have used Process Explorer from Sysinternals Tools Suite in order to see what is happening with the Excel process created in the background in order to be able to release all the resources after processing finished.
INFO: If you want to know more about PowerShell Debugging please read this article with many useful examples How To Debug PowerShell Scripts.
Step 10: Delete Temporary CSV file created in Step 2
Do not forget to delete the temporary CSV file created in step 2. This step is another use of END block in post-processing clean up of the resources fashion.
if(Test-Path -path $temporaryCsvFile) {
Remove-Item -path $temporaryCsvFile -ErrorAction Stop
Write-Verbose "Temporary csv file deleted: $temporaryCsvFile"
}
Step 11: Optionally, send Excel file as a zipped attachment to some email account
I have implemented the possibility to send email using my own Send-Email CmdLet.
if ( $sendemail ) {
$errorlogfile = "$homeDocumentsPSlogsError_Log.txt"
$attachments = "$errorlogfile","$excelFile"
Write-Verbose "Sending email."
Send-Email -Attachments $attachments -Priority "Normal" -errorlog -client $client -solution $solution
Write-Verbose "Email sendt."
}
DISCLAIMER: This feature will not work until Send-Email CmdLet is customized to your own SMTP server settings.
Step 12: Error Handling
All the code that we have discussed in previous steps has been wrapped in a try block in order to have the possibility of trapping the errors. In the catch block, we handle the errors writing them in an external Error Log text file. For writing errors in an external file, we can use my own Write-ErrorLog CmdLet.
} catch {
Write-Warning "SaveToExcel function failed"
Write-Warning "Error message: $_"
if ( $errorlog ) {
$errormsg = $_.ToString()
$exception = $_.Exception
$stacktrace = $_.ScriptStackTrace
$failingline = $_.InvocationInfo.Line
$positionmsg = $_.InvocationInfo.PositionMessage
$pscommandpath = $_.InvocationInfo.PSCommandPath
$failinglinenumber = $_.InvocationInfo.ScriptLineNumber
$scriptname = $_.InvocationInfo.ScriptName
Write-Verbose "Start writing to Error log."
Write-ErrorLog -hostname "SaveToExcel has failed" -errormsg $errormsg -exception $exception -scriptname $scriptname -failinglinenumber $failinglinenumber -failingline $failingline -pscommandpath $pscommandpath -positionmsg $pscommandpath -stacktrace $stacktrace
Write-Verbose "Finish writing to Error log."
}
}
INFO: Error handling is important in each programming language since no one likes programs with bugs. PowerShell is no exception to that and I have written an article on the subject with an example function that writes errors in external text file How To Log PowerShell Errors And Much More.
Regions Of Save-ToExcel Function
I like to organize my code in regions using region and end region tags so when the code is collapsed I can see what the code is all about.
Save-ToExcel is no exception to that rule and consist of three regions:
- No region since this breaks functionality for comment-based Help content.
- Save-ToExcel function “region”.
- Execution examples region which contains different calls to the function.
INFO: If you want to learn how to write your own function help content please read How To Write PowerShell Help (Step by Step).
How To Use Excel Reports – Examples
I am sure everyone has their own needs regarding usage of Excel reports created using PowerShell but let me just quickly give you my few examples.
- For documentation purposes, I was creating Excel reports with different information about the environment working:
- CPU properties of all servers in the environment
- OS information
- Network information
- Memory information
- Printer information
- Application installation
- For maintenance purposes:
- Errors from Event Logs
- For capacity planning:
- Free Disk Space
Save-ToExcel Function Source Code
DISCLAIMER: Save-ToExcel function is part of the Efficiency Booster PowerShell Project and as such utilize other CmdLets that are part of the same project. So the best option for you in order for this function to work without any additional customization is to download the source code of the whole project from here.
Here is the source code of Save-ToExcel function:
<#
.SYNOPSIS
Save Powershell output objects as Excel file.
.DESCRIPTION
Save Powershell cmdlet output objects as Excel file.
In order to overcome performance issue of writting to Excel row by row in Powershell.
This function uses trick to improve performance and that is to save Powershell objects from pipe into temporary .csv file first and later to use that file to create and format Excel file fast.
.PARAMETER InputObject
Any object that will be saved as Excel file. Send through PowerShell pipeline.
.PARAMETER ExcelFileName
Prefix of Excel file name. File name has Time stamp at the and in name with date and time.
.PARAMETER title
Title property of Excel workbook.
.PARAMETER author
Author property of Excel woorkbook.
.PARAMETER WorkSheetName
Name of Excel Worksheet.
.PARAMETER errorlog
write to error log or not.
.PARAMETER client
OK - O client
BK - B client
.PARAMETER solution
FIN - Financial
HR - Humane Resource
.PARAMETER sendemail
Turns on or off sending of emails from this CmdLet.
.EXAMPLE
Get-OSInfo -filename "OKFINservers.txt" -errorlog -client "OK" -solution "FIN" -Verbose | Save-ToExcel -ExcelFileName "OS_Info" -title "OS Info for servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "OS Info" -client "OK" -solution "FIN"
.EXAMPLE
Get-Printer -filename "OKFINservers.txt" -errorlog -client "OK" -solution "FIN" -Verbose | Save-ToExcel -ExcelFileName "Printers_With_Error" -title "Printers with errors on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Printers with errors" -client "OK" -solution "FIN"
.EXAMPLE
Get-Printer -filename "OKFINservers.txt" -errorlog -client "OK" -solution "FIN" -Verbose | Save-ToExcel -ExcelFileName "Printers_With_Error" -title "Printers with errors on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Printers with errors" -sendemail -client "OK" -solution "FIN"
.EXAMPLE
Get-DiskFreeSpace -filename "OKFINservers.txt" -client "OK" -solution "FIN" -Verbose -errorlog | Save-ToExcel -ExcelFileName "Free_disk_space" -title "Free disk space on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Free disk space" -sendemail -client "OK" -solution "FIN"
.EXAMPLE
Get-ErrorFromEventLog -filename "OKFINservers.txt" -errorlog -client "OK" -solution "FIN" -days 3 -Verbose | Save-ToExcel -ExcelFileName "Get-ErrorsInEventLogs" -title "Get errors from Event Logs on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Errors from Event Logs" -sendemail -client "OK" -solution "FIN"
.EXAMPLE
Get-ErrorFromEventLog -filename "OKFINkursservers.txt" -errorlog -client "OK" -solution "FIN" -days 3 -Verbose | Save-ToExcel -ExcelFileName "Get-ErrorsInEventLogs" -title "Get errors from Event Logs on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Errors from Event Logs" -client "OK" -solution "FIN"
.INPUTS
System.Management.Automation.PSCustomObject
InputObjects parameter pipeline both by Value and by Property Name value.
.OUTPUTS
System.Boolen
.NOTES
FunctionName : Save-ToExcel
Created by : Dejan Mladenovic
Date Coded : 10/31/2018 19:06:41
More info : https://improvescripting.com/
.LINK
Export-Csv
New-Object -ComObject
#>
Function Save-ToExcel {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
HelpMessage="Input rows to be saved as Excel file.")]
[PSobject[]]$InputObject,
[Parameter(Mandatory=$true,
HelpMessage="Excel file name.")]
[string]$ExcelFileName,
[Parameter(Mandatory=$true,
HelpMessage="Excel workbook title.")]
[string]$title,
[Parameter(Mandatory=$true,
HelpMessage="Excel workbook author.")]
[string]$author,
[Parameter(Mandatory=$true,
HelpMessage="Excel worksheet name.")]
[string]$WorkSheetName,
[Parameter(HelpMessage="Write to error log file or not.")]
[switch]$errorlog,
[Parameter( HelpMessage="Send email.")]
[switch]$sendemail,
[Parameter(Mandatory=$true,
HelpMessage="Client for example OK = O client, BK = B client")]
[string]$client,
[Parameter(Mandatory=$true,
HelpMessage="Solution, for example FIN = Financial solution, HR = Humane Resource solution")]
[string]$solution
)
BEGIN {
#Creat an empty array
$objects = @()
$reportsfolder = "$homeDocumentsPSreports"
if ( !( Test-Path -Path $reportsfolder -PathType "Container" ) ) {
Write-Verbose "Create reports folder in: $reportsfolder"
New-Item -Path $reportsfolder -ItemType "Container" -ErrorAction Stop
}
}
PROCESS {
$objects += $InputObject
}
END {
$date = Get-Date -UFormat "%Y-%m-%d_%H-%M-%S"
$excelFile = "$homeDocumentsPSreports$ExcelFileName" + "-" + "$client" + "-" + "$solution" + "-" + $date + ".xlsx"
try {
#Write Excel file only if there are some Input objects!!!!!
if ( $objects ) {
[System.Threading.Thread]::CurrentThread.CurrentCulture = "en-US"
#Temporary .csv file with GUID name has been created first.
$temporaryCsvFile = ($env:temp + "" + ([System.Guid]::NewGuid()).ToString() + ".csv")
#Delimiter ";" helps that result is parsed correctly. Comma delimiter parses incorrectly.
$objects | Export-Csv -ErrorAction Stop -path $temporaryCsvFile -noTypeInformation -Delimiter ";"
Write-Verbose "Temporary csv file saved: $temporaryCsvFile"
$excelObject = New-Object -ComObject Excel.Application -ErrorAction Stop
Write-Verbose "Excel sheet created."
$excelObject.Visible = $false
$workbookObject = $excelObject.Workbooks.Open($temporaryCsvFile)
$workbookObject.Title = ("$title " + (Get-Date -Format D))
$workbookObject.Author = "$author"
$worksheetObject = $workbookObject.Worksheets.Item(1)
#Method TextToColumns is important to convert .csv file data into right columns in Excel file.
$colA=$worksheetObject.range("A1").EntireColumn
$colrange=$worksheetObject.range("A1")
$xlDelimited = 1
$xlTextQualifier = 1
$colA.TextToColumns($colrange,$xlDelimited,$xlTextQualifier,$false,$false,$false,$true)
$worksheetObject.UsedRange.Columns.Autofit() | Out-Null
$worksheetObject.Name = "$WorkSheetName"
#Style of table in Excel worksheet.
$xlSrcRange = 1
$XlYes = 1
#Syntax - expression.Add(SourceType, Source, LinkSource, HasHeaders, Destination)
$listObject = $worksheetObject.ListObjects.Add($xlSrcRange, $worksheetObject.UsedRange, $null, $XlYes, $null)
$listObject.Name = "User Table"
$listObject.TableStyle = "TableStyleMedium6" # Style Cheat Sheet in French/English: http://msdn.microsoft.com/fr-fr/library/documentformat.openxml.spreadsheet.tablestyle.aspx
$workbookObject.SaveAs($excelFile,51) # http://msdn.microsoft.com/en-us/library/bb241279.aspx
$workbookObject.Saved = $true
$workbookObject.Close()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbookObject) | Out-Null
$excelObject.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excelObject) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
Write-Verbose "Excel application process deleted from the system."
if(Test-Path -path $temporaryCsvFile) {
Remove-Item -path $temporaryCsvFile -ErrorAction Stop
Write-Verbose "Temporary csv file deleted: $temporaryCsvFile"
}
if ( $sendemail ) {
$errorlogfile = "$homeDocumentsPSlogsError_Log.txt"
$attachments = "$errorlogfile","$excelFile"
Write-Verbose "Sending email."
Send-Email -Attachments $attachments -Priority "Normal" -errorlog -client $client -solution $solution
Write-Verbose "Email sendt."
}
}
} catch {
Write-Warning "SaveToExcel function failed"
Write-Warning "Error message: $_"
if ( $errorlog ) {
$errormsg = $_.ToString()
$exception = $_.Exception
$stacktrace = $_.ScriptStackTrace
$failingline = $_.InvocationInfo.Line
$positionmsg = $_.InvocationInfo.PositionMessage
$pscommandpath = $_.InvocationInfo.PSCommandPath
$failinglinenumber = $_.InvocationInfo.ScriptLineNumber
$scriptname = $_.InvocationInfo.ScriptName
Write-Verbose "Start writing to Error log."
Write-ErrorLog -hostname "SaveToExcel has failed" -errormsg $errormsg -exception $exception -scriptname $scriptname -failinglinenumber $failinglinenumber -failingline $failingline -pscommandpath $pscommandpath -positionmsg $pscommandpath -stacktrace $stacktrace
Write-Verbose "Finish writing to Error log."
}
}
}
}
#region Execution examples
#Get-OSInfo -filename "OKFINservers.txt" -errorlog -client "OK" -solution "FIN" -Verbose | Save-ToExcel -ExcelFileName "OS_Info" -title "OS Info for servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "OS Info" -client "OK" -solution "FIN" -errorlog -Verbose
#Get-Printer -filename "OKFINservers.txt" -errorlog -client "OK" -solution "FIN" -Verbose | Save-ToExcel -ExcelFileName "Printers_With_Error" -title "Printers with errors on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Printers with errors" -client "OK" -solution "FIN"
#Get-Printer -filename "OKFINservers.txt" -errorlog -client "OK" -solution "FIN" -Verbose | Save-ToExcel -ExcelFileName "Printers_With_Error" -title "Printers with errors on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Printers with errors" -sendemail -client "OK" -solution "FIN"
#Get-DiskFreeSpace -filename "OKFINservers.txt" -client "OK" -solution "FIN" -Verbose -errorlog | Save-ToExcel -ExcelFileName "Free_disk_space" -title "Free disk space on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Free disk space" -sendemail -client "OK" -solution "FIN"
#Get-ErrorFromEventLog -filename "OKFINservers.txt" -errorlog -client "OK" -solution "FIN" -days 3 -Verbose | Save-ToExcel -ExcelFileName "Get-ErrorsInEventLogs" -title "Get errors from Event Logs on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Errors from Event Logs" -sendemail -client "OK" -solution "FIN"
#Get-ErrorFromEventLog -filename "OKFINkursservers.txt" -errorlog -client "OK" -solution "FIN" -days 3 -Verbose | Save-ToExcel -ExcelFileName "Get-ErrorsInEventLogs" -title "Get errors from Event Logs on servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "Errors from Event Logs" -client "OK" -solution "FIN"
#Get-CPUInfo -filename "OKFINservers.txt" -errorlog -client "OK" -solution "FIN" -Verbose | Save-ToExcel -ExcelFileName "Get-CPUinfo" -title "Get CPU info of servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "CPU Info" -client "OK" -solution "FIN"
#Get-CPUInfo -computers "localhost" -errorlog -client "OK" -solution "FIN" -Verbose | Save-ToExcel -ExcelFileName "Get-CPUinfo" -title "Get CPU info of servers in Financial solution for " -author "ImproveScripting.com With Dejan" -WorkSheetName "CPU Info" -client "OK" -solution "FIN"
#endregion
Table of Contents
- Problem Exploration
- The Excel XLSX file format
- .NET classes to create real Excel XLSX file from scratch
- Anatomy of a minimal Excel XLSX package file
- Minimal package structure
- Minimal package parts
- Required the document “start part”: workbook.xml
- Required: one (main) relationship part: .rels
- Required one worksheet: sheet1.xml
- Required: workbook relationship part
- Worksheet content
- The PowerShell code
- Links:
- Office Open XML Format Links:
- PowerShell and the Excel COM Object Model:
- Tips:
- See Also
Problem Exploration
I had the need to store data into a Microsoft Excel compatible file.
Attempt 1: Use the Excel COM object model.
This is not a good solution because:
PowerShell runs very often on Servers or clients without a Microsoft Office / Excel installation.
The use of the Excel COM Object can cause errors inside a Scheduled Task.
Excel can read and store CSV data.
Attempt 2: Use CSV data (with Export-CSV)
This is not a good solution either because:
CSV is not just another type of Excel file. On opening a CSV data file, Microsoft Excel converts data automatically. This is not acceptable.
If Microsoft Excel outputs an Excel worksheet into a CSV file, the output does not always follow the CSV format rules. Excel only places quotes around certain fields, not in all fields. This leads to unreadable CSV files.
I had the following requirements:
- The solution that works in PowerShell 2.0 and 3.0 (and later)
- Create an Excel compatible file without having Excel
- (do not use the Excel COM object model)
- The solution which works without 3rd party tools
- Should work similar like the Export-CSV Cmdlet
- Should have the possibility to append a worksheet with data (-append parameter)
My Internet research shows no solution which fits these requirements.
But I found a C# code to do the Job. So here is my Translation of this code into PowerShell.
For C# code see here:
How to use the Office XML file format and the packaging components from the .NET Framework 3.0 to create a simple Excel 2007 workbook or a simple Word 2007 document
http://support.microsoft.com/kb/931866/en-us%20
The Excel XLSX file format
Starting with the Microsoft Office Version of 2007 Microsoft has changed the default application file formats from old, proprietary, closed formats (DOC, XLS, PPT) to new, open and standardized Open XML formats (DOCX, XLSX, and PPTX).
The Office Open XML (also informally known as OOXML or OpenXML) is a zipped, XML-based file format. To represent spreadsheets, charts, presentations, and word processing documents.
Office Open XML is standardized by the European Computer Manufacturers Association (ECMA) where they became ECMA-376 and, in later versions, by ISO and IEC (as ISO/IEC 29500).
Every Open XML file is a zip file (a package) typical containing a number of UTF-8 encoded XML files («parts»).
Inside the XML parts of the package Multipurpose Internet Mail Extensions (MIME) types and Namespaces are used as metadata.
The XML parts (files) of the package are encoded in specialized markup languages. In the case of Microsoft Excel, this is the markup language called SpreadsheetML.
The package also contains relationship files (part). The relationship parts have the extension .rels. They can be found in a folder with the name _rels.
The relationship parts define the relationships between the parts inside the package (internal) and to resources outside of the package (external).
The package may also contain other (binary) media files such as sounds or images.
The structure of the package is organized according to the Open Packaging Conventions as outlined in the OOXML standard.
You can look at the file structure and the files that comprise an XLSX file by simply unzipping the .xlsx file.
.NET classes to create real Excel XLSX file from scratch
With .Net 3.0 Microsoft has introduced the System.IO.Packaging namespace which lives inside the WindowsBase.dll
WindowsBase.dll is one of the core Assemblies used for Windows Presentation Foundation WPF.
(The Windows Presentation Foundation WPF is Microsoft’s next generation UI framework to create applications with a rich user experience even for the new Windows 8 tiles GUI.)
So you don’t have to worry that WindowsBase.dll moves around or goes away.
WindowsBase.dll can be found in:
C:Program FilesReference AssembliesMicrosoftFrameworkv3.0WindowsBase.dll
The System.IO.Packaging namespace provides classes that support Office Open XML Zip compressed containers and other formats, which store multiple data objects in a single container.
System.IO.Packaging contains the ZipPackage class to work with Zip compressed package files.
See the Microsoft developer network (MSDN) documentation for this namespace and classes.
http://msdn.microsoft.com/en-US/library/System.IO.Packaging.aspx%20
PowerShell can use this .NET namespace and can easily deal with XML files, so here is the way to go.
PowerShell code to load the WindowsBase.dll assembly:
$Null = [Reflection.Assembly]::LoadWithPartialName("WindowsBase")
Anatomy of a minimal Excel XLSX package file
The number and types of the XLSX package parts will vary based on what is in the spreadsheet. I will describe the minimal XLSX needs here:
Minimal package structure
Example of a minimal basic structure, of a XLSX package file, with 1 mandatory worksheet:
./[Content_Types].xml
./_rels/.rels
./xl/workbook.xml
./xl/_rels/workbook.xml.rels
./xl/worksheets/sheet1.xml
Minimal package parts
Required is the main file: [Content_Types].xml
Required part for all Open XML documents
- Three content types must be defined:
- 1. SpreadsheetML main document (for the start part)
- 2. Worksheet
- 3. Package relationships (for the required relationships)
The [Content_Types].xml part (file) is generated automatically by the ZipPackage class on creation of the Excel XLSX package file.
Here is the PowerShell code to create the package file on disk:
# create the main package on disk with filemode create
$exPkg = [System.IO.Packaging.Package]::Open
"C:test.xlsx"
, [System.IO.FileMode]::Create)
The [Content_Types].xml file contains definitions of the content types included in the ZIP package, such as the main document, the document theme, and the file properties. This file also stores definitions of the file extensions used in the ZIP package, such
as the file formats like .png or .wav. So you can store pictures or sounds inside a document.
Example of a minimal [Content_Types].xml part the package contains a workbook with one worksheet:
<?xml version=»1.0″ encoding=»UTF-8″ standalone=»yes»?>
<Types xmlns=»http://schemas.openxmlformats.org/package/2006/content-types»>
<Default Extension=»bin» ContentType=»application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings» />
<Default Extension=»rels» ContentType=»application/vnd.openxmlformats-package.relationships+xml» />
<Default Extension=»xml» ContentType=»application/xml» />
<Override PartName=»/xl/workbook.xml» ContentType=»application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml» />
<Override PartName=»/xl/worksheets/sheet1.xml» ContentType=»application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml» />
</Types>
Required the document “start part”: workbook.xml
workbook.xml requires one relationship part workbook.xml.rels which links mainly to the worksheets
Example of a minimal workbook.xml part:
<?xml version=»1.0″ encoding=»UTF-8″ standalone=»yes»?>
<workbook xmlns=»http://schemas.openxmlformats.org/spreadsheetml/2006/main» xmlns:r=»http://schemas.openxmlformats.org/officeDocument/2006/relationships»>
<sheets>
<sheet name=»Table0″ sheetId=»1″ r:id=»rId1″ />
</sheets>
</workbook>
I use the .NET XML classes to create the XML document part from scratch. Here is the PowerShell Code:
# create the Workbook.xml part XML document
# create empty XML Document
$xl_Workbook_xml = New-Object System.Xml.XmlDocument
# Obtain a reference to the root node, and then add the XML declaration.
$XmlDeclaration = $xl_Workbook_xml.CreateXmlDeclaration(
"1.0"
,
"UTF-8"
,
"yes"
)
$Null = $xl_Workbook_xml.InsertBefore($XmlDeclaration, $xl_Workbook_xml.DocumentElement)
# Create and append the workbook node to the document.
$workBookElement = $xl_Workbook_xml.CreateElement(
"workbook"
)
# add the office open xml namespaces to the XML document
$Null = $xl_Workbook_xml.AppendChild($workBookElement)
# Create and append the sheets node to the workBook node.
$Null = $xl_Workbook_xml.DocumentElement.AppendChild($xl_Workbook_xml.CreateElement(
"sheets"
))
The URI is defined as a relative path to the package root. The URI defines the part and the folder(s) to create.
The Namespace in the Create() method declares the type of relationship being defined from the applicable Office Open XML schema.
The GetStream() Method returns the destination file stream to write the XML document.
# create the workbook.xml package part
# create URI for workbook.xml package part
$Uri_xl_workbook_xml = New-Object System.Uri -ArgumentList (
"/xl/workbook.xml"
,
[System.UriKind]::Relative)
# create workbook.xml part
$Part_xl_workbook_xml = $exPkg.CreatePart($Uri_xl_workbook_xml,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
)
# get writeable stream from workbook.xml part
$dest = $part_xl_workbook_xml.GetStream([System.IO.FileMode]::Create,[System.IO.FileAccess]::Write)
# write workbook.xml XML document to part stream
$xl_workbook_xml.Save($dest)
Required: one (main) relationship part: .rels
Must be in a _rels folder.
After you have created the Workbook.xml part, you have to create the relationship from the Main [Content_Types].xml to the document body Workbook.xml.
The .rels file in the _rels folders is the main top-level relationship file in an Office Open XML package file.
This file defines relationships between core files in the ZIP package and the applicable Office Open XML schema.
The main relationship file «.rels» and its folder «_rels» is automatically created by a call to the CreateRelationship() Method from the ZipPackage class.
The Target of a relationship is the location of the referenced file. The target can be within the XLSX ZIP package (internal) or outside (external) of the XLSX ZIP package. We store all files and information’s inside the ZIP package, so we use the Target mode
Internal.
The Namespace declares the type of relationship being defined from the applicable Office Open XML schema. In this case, the file workbook.xml is being defined as type officeDocument. This information tells Excel that the file workbook.xml contains the document
body.
The Relationship Id (rId1 in this case) simply provides a unique identifier for the referenced file.
PowerShell code to create the relationship between the package parts [Content_Types].xml and the main document workbook.xml
# create package general main relationships
Required one worksheet: sheet1.xml
Inside the worksheet XML part, the <sheetdata> node is required, but may be empty
Example of a minimal worksheet XML part:
<?xml version=»1.0″ encoding=»UTF-8″ standalone=»yes»?>
<worksheet xmlns=»http://schemas.openxmlformats.org/spreadsheetml/2006/main» xmlns:r=»http://schemas.openxmlformats.org/officeDocument/2006/relationships»>
<sheetData />
</worksheet>
I use the .NET XML classes to create the XML document part from scratch. The Name of the worksheet part used in the URI, is dynamically generated with the pattern Sheet + number + .xml in the $NewWorkSheetPartName variable. (Example names: Sheet1.xml, Sheet2.xml,
Sheet3.xml and so on …)
# create worksheet XML document
# create empty XML Document
$New_Worksheet_xml = New-Object System.Xml.XmlDocument
# obtain a reference to the root node, and then add the XML declaration.
$XmlDeclaration = $New_Worksheet_xml.CreateXmlDeclaration(
"1.0"
,
"UTF-8"
,
"yes"
)
$Null = $New_Worksheet_xml.InsertBefore($XmlDeclaration, $New_Worksheet_xml.DocumentElement)
# create and append the worksheet node to the document.
$workSheetElement = $New_Worksheet_xml.CreateElement(
"worksheet"
)
# add the Excel related office open xml namespaces to the XML document
$Null = $New_Worksheet_xml.AppendChild($workSheetElement)
# create and append the sheetData node to the worksheet node.
$Null = $New_Worksheet_xml.DocumentElement.AppendChild($New_Worksheet_xml.CreateElement(
"sheetData"
))
The URI is defined as a relative path to the package root. The URI defines the part and the folder(s) to create.
The Namespace in the Create() method declares the type of relationship being defined from the applicable Office Open XML schema.
The GetStream() Method returns the destination file stream to write the XML document.
# create the worksheet package part
# create URI for worksheet package part
$Uri_xl_worksheets_sheet_xml = New-Object System.Uri -ArgumentList (
"/xl/worksheets/$NewWorkSheetPartName"
,
[System.UriKind]::Relative)
# create worksheet part
$Part_xl_worksheets_sheet_xml = $exPkg.CreatePart($Uri_xl_worksheets_sheet_xml,
"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
)
# get writeable stream from part
$dest = $part_xl_worksheets_sheet_xml.GetStream([System.IO.FileMode]::Create,[System.IO.FileAccess]::Write)
# write $New_Worksheet_xml XML document to part stream
$New_Worksheet_xml.Save($dest)
Required: workbook relationship part
Every folder in an XLSX ZIP package can contain his own _rels folder to define relationships within that folder. The main document folder «xl» always contains a «_rels» folder with relationship parts.
The relationship part for the workbook.xml is named workbook.xml.rels.
The workbook.xml.rels part is created by use of the CreateRelationship() Method from the ZipPackage class, the «_rels» Folder which contains this part is created automatically by use of the URI.
So first you have to create the XML package part files and then you can create the relationships between them.
The unique ID of the relationship is determined from the workbook.xml and dynamically generated with the pattern rID + Number in the variable $NewWorkBookRelId (Example: rID1, rID2, rID3 and so on …).
# create workbook to worksheet relationship
Everything else is optional
Worksheet content
If you put data into a Microsoft Excel worksheet, Excel will automatically convert some data into the format that Excel thinks is best.
For example, Excel will remove leading Zeros of Numbers, change Date/Time Formats or uses the scientific number format for large Numbers and others.
This can go unnoticed in large data sets.
To prevent Excel from converting the data, you must tell Excel to import/store the data in Text format.
There are two ways to store data with Type of Text in an Excel XLSX worksheet package part!
1. Inline strings which are stored inside the XML worksheet package part (file)
• Provided for ease of translation/conversion
• Useful in XSLT scenarios
• Excel and other consumers may convert to shared strings
• to export the data programmatically into the worksheet
2. Using a shared-strings XML package part as a table with unique strings
• All worksheets points/links to the strings stored in the shared-strings package part
• Each unique string is stored once (reduced file size, improved performance)
• Cells store the 0-based index of the string
Both approaches may be mixed/combined
I will use the inline string approach here in my PowerShell solution because it is easier to create and maintain.
Example of an Excel XML worksheet part which contains only inline content, formatted as Type of text:
<?xml version=»1.0″ encoding=»UTF-8″ standalone=»yes»?>
<worksheet xmlns=»http://schemas.openxmlformats.org/spreadsheetml/2006/main» xmlns:r=»http://schemas.openxmlformats.org/officeDocument/2006/relationships»>
<sheetData>
<row>
<c t=»inlineStr»>
<is>
<t>Name</t>
</is>
</c>
</row>
<row>
<c t=»inlineStr»>
<is>
<t>acrotray</t>
</is>
</c>
</row>
<row>
<c t=»inlineStr»>
<is>
<t>Name</t>
</is>
</c>
</row>
<sheetData>
A row is represented as <row>-Element.
A cell is represented as <c>-Element. The type of the cell is defined by the «t» attribute here as type of «inlineStr» which means a type of text.
If the cell has a type of «inlineStr» the <c> node must contain a <is> node.
For a simple string (text) without formatting the <is> node contains a <t> node with the value of the string.
Warning:
By default, Excel uses and stores strings into the shared-strings XML package part.
Excel transfers the inline strings into the shared-strings part on save actions!
So, after Excel has converted the data into shared strings, the data cannot easily be accessed!
The PowerShell code
There are several golden rules for the code design.
Two of them are:
A Function should always be concentrated to solve only one task and not being a Swiss army knife.
A Function and scripts should always return well defined and structured Objects
So I have divided my PowerShell code into several functions.
New-XLSXWorkBook
Function to create a new empty Excel .xlsx workbook (XLSX package) without a worksheet
Add-XLSXWorkSheet
Function to append a new empty Excel worksheet to an existing Excel .xlsx workbook
Export-WorkSheet
Function to fill an empty existing Excel worksheet with the string typed data
These functions are only used internally. So best is to hide these functions.
To hide functions you have these options in PowerShell
• nest functions inside other function (in case of advance functions put it inside the begin block)
• nest functions inside the begin block of an advanced script
• Create a module and specify the public module members with the Cmdlet Export-ModuleMember
I don’t want to force the user of my script to import it as a module.
In the fact that a PowerShell script can look and behave like a function, I have decided to nest the functions inside the begin block of the script.
So you can use this script by simply calling it (by its path) and by use of the parameters.
You can Download the full code on the Microsoft Code Repository:
Links:
Office Open XML Format Links:
ISO and IEC standards
http://standards.iso.org/ittf/PubliclyAvailableStandards/index.html%20
Ecma standard 376
http://www.ecma-international.org/publications/standards/Ecma-376.htm%20
Office Open XML Learn resources:
Exploring the Office Open XML Formats
http://office.microsoft.com/en-us/training/office-open-xml-i-exploring-the-office-open-xml-formats-RZ010243529.aspx?section=1%20
Editing Documents in the XML
http://office.microsoft.com/en-us/training/open-xml-ii-editing-documents-in-the-xml-RZ010357030.aspx?CTT=1%20
Good Open XML XLSX Link:
Read and write Open XML files (MS Office 2007)
http://www.developerfusion.com/article/6170/read-and-write-open-xml-files-ms-office-2007/
SpreadsheetML or XLSX
http://officeopenxml.com/anatomyofOOXML-xlsx.php%20
PowerShell and the Excel COM Object Model:
For documentation of the Excel object model search the Microsoft Developer Network (MSDN) for:
» Excel Object Model Reference»
Excel 2003 and 2007:http://msdn.microsoft.com/en-us/library/bb149081%28v=office.12%29.aspx
Excel 2003 and 2007:http://msdn.microsoft.com/en-Us/library/wss56bz7%28v=vs.90%29.aspx
Excel 2013: http://msdn.microsoft.com/en-us/library/office/ff194068.aspx
Article series: Integrating Microsoft Excel with PowerShell by Jeffery Hicks:
http://www.petri.co.il/export-to-excel-with-powershell.htm%20
http://www.petri.co.il/export-to-excel-with-powershell-part-2.htm%20
http://www.petri.co.il/export-to-excel-with-powershell-part-3.htm%20
How Can I Use Windows PowerShell to Automate Microsoft Excel? By Ed Wilson:
http://blogs.technet.com/b/heyscriptingguy/archive/2006/09/08/how-can-i-use-windows-powershell-to-automate-microsoft-excel.aspx
Tips:
WindowsBase.dll can even be used in PowerShell 2.0 and 3.0 to create ZIP Files:
PowerShell-ZIP
http://thewalkingdev.blogspot.de/2012/07/powershellzip.html%20
See Also
- PowerShell Portal
- Wiki: Portal of TechNet Wiki Portals