How To: Create A Word Document In Powershell - Part 2 - Adding and Manipulating Tables, Adding Images
Table of Contents
- Opening the Word Document, Writing Some Text, Using Styles
- Adding and Manipulating Tables, Adding Images
- Document Properties, Table of Contents, Changing Page Properties
- Moving around the Document
In the first part of this series, I talked about the first steps you should take if you want to start creating and manipulating Word documents with Powershell - you can find the link to that blog above - and here we’re going to look at what is likely to be the next stage for you; adding and manipulating tables and images.
Tables
Tables is one of the first things I looked at when learning how to create Word documents in Powershell, as I needed to be able to tabularise data collected from a number of APIs as well as fill in cells of pre-existing tables. We’ll be talking about both, so that should cover most use cases!
I’m going to be using a dummy set of data for the purposes of this tutorial; the below script will create an object with multiple entries that we’ll be able to use to populate our table.
$randomObj = for ($i = 1; $i -lt 10; $i++) {
New-Object PsObject -Property @{
"index" = $i
"name" = "Object $i"
"value" = "Value $i"
}
}
Creating a new table
I’m going to assume that you’ve already followed Part 1, and so have the script at the end to start working with!
The first thing we need to do is define our table - how many columns, how many rows, what style to use, and what name to give it.
Rows
We’ll usually need N+1 rows, where N is the number of rows in our object. I don’t need to know how many this is ahead of time, we’ll just count the number of objects and add one to it:
$count = $randomObj.Count
$rowsNo = $count + 1
Columns
You’ll need to know and define this in advance; in my case, we’ve got three columns
$colsNo = 3
And with those we can create the table:
$table = $text.Tables.Add(
$text.Range, $rowsNo, $colsNo
)
#$text.Range is the current position of the text 'selector' object within the Word document; this is just creating it wherever we've left our imaginary cursor
Style and Title
Once we’ve created the table, we can then start applying and modifying it’s properties - for the purposes of this blob I’m only going to modify it’s style and title (that will be important later):
$table.style = "Plain Table 3" #this is a built in style; you may have custom styles available, and these can also be referenced by their name
$table.title = "randomObjectTable"
Content
I can now start adding content! My table is going to have a header row, so I can define that as a static object:
$table.cell(1,1).range.text = "Index"
$table.cell(1,2).range.text = "Name"
$table.cell(1,3).range.text = "Value"
#I can also apply a style to these cells, to differentiate the header row from the rest of the table
$table.cell(1,1).range.style = "Strong"
$table.cell(1,2).range.style = "Strong"
$table.cell(1,3).range.style = "Strong"
But I want to be able to iterate through my object and add the data iteratively; so we need to define a variable to track what row we’re on, and move through the document using a foreach-object
loop:
$r = 2 #we start at 2 as we're going to be populating our table from Row 2
$randomObj | foreach-object {
$table.cell($r,1).range.text = $($_ | Select-Object -expandProperty index).ToString() #All items added to Word must be a string; you will get an error if you try to enter an Int or other object type
$table.cell($r,2).range.text = $_ | Select-Object -expandProperty name
$table.cell($r,3).range.text = $_ | Select-Object -expandProperty value
$r++ #we need to iterate our $r object by 1 each time to move onto the next row
}
Finishing up the table
Once you’ve entered all your data, you’ll want to format your table to fit your page and also then move the ‘cursor’ onto the next line - this took a little trial and error on my part, but eventually I got to this that has proven effective in many different scenarios:
$table.AutoFitBehavior(2) #This autofits the table to the window; if you want to autofit to content, use 1 instead
$range = $doc.Range($table.Range.End, $table.Range.End) #this defines a range within the document at the end of the table
$range.select() #this moves the selection/cursor to the end of the table
$text = $word.selection #this sets our $text object to now be in the same place as $range.select()
$text.TypeParagraph() #this creates a new paragraph for new text or anything else!
Which should leave you with a table like this:
You can of course do plenty more with the formatting; alignment, font size, font colour etc. but I’ll leave that for a future blog!
Modifying an existing table
Lets say you’re working with a templated document, and you have particular fields within a table that you need to update. Or you’ve predefined a table for positioning or formatting, and you now want to add data to it. Or you’ve got more data you need to add to an existing table. This next section is going to show you how to reference an existing table and make modifications to it, and we’re going to use the table we just created.
Referencing the table
First, lets get a list of all the tables in our document and then reference the object that is the table we’ve just created:
$tables = $doc.Tables
$randomObjTable = $tables | Where-Object {$_.Title -eq "randomObjectTable"}
Modifying the table
We can now start manipulating it; my scenario is that I’m going to have some new data that I want to add to the table:
$randomObjCount = $randomObj.count #we'll want this later so that we can count how many rows have been added
$randomObj += New-Object PsObject -Property @{
"index" = 10
"name" = "Object 10"
"value" = "Value 10"
}
We’re then going to define the number of rows we need to add, and add them to the table; in my case it’s only one but I’m going to write it as if there were multiple:
$rowsToAdd = $($randomObj.count) - $randomObjCount
for ($s = 1; $s -le $rowsToAdd; $s++) {
$randomObjTable.Rows.Add() | Out-Null
}
Which adds a new empty row (or rows) to the table:
Adding data to the table
From which, we are then able to add data - the first point will be to determine our start row, and then iterate from there in another for loop:
$r = $randomObjCount + 1 + 1 #we take the count of our object before we added new data, add 1 for the header row, and then add 1 for the new row
for ($r; $r -le $($randomObj.count + 1); $r++){
$index = $r - 2 #we need to define our index value for referencing that specific item in our object
$randomObjTable.cell($r, 1).range.text = $($randomObj[$index] | Select-Object -expandProperty index).ToString()
$randomObjTable.cell($r, 2).range.text = $randomObj[$index] | Select-Object -expandProperty name
$randomObjTable.cell($r, 3).range.text = $randomObj[$index] | Select-Object -expandProperty value
}
Which then leaves you with the data inserted!
Images
So you’ve added your tables, but now you want to impress with including some images; perhaps pulled automatically from the internet even? For the purposes of this I’m going to use a local file, but the possiblities are endless!
The image I’m going to add, courtesy of my friendly Copilot, is this one:
Very topical, I’m sure you’ll agree. And it’s really quite simple to then add the image to the document:
$imagePath = "C:\Temp\copilotImageForDemo.jpeg"
$insertedImage = $text.InlineShapes.AddPicture($imagePath, $false, $true)
#$false means "Do not link to the file", so it embeds the image
#$true means "Save with Document"
And that’s it! Running it immediately after everything we’ve already done should leave you with this:
But that’s pretty big, right - I don’t need it to be that big. No problem, all we need to do is modify the Width and Height of the image using the $insertedImage
object we’ve created:
#I want to reduce the size by about 50%, so th easiest way to do that is using the existing values
$newWidth = $insertedImage.width / 2
$newHeight = $insertedImage.height / 2
$insertedImage.width = $newWidth
$insertedImage.height = $newHeight
Much better:
And that’s it! You’ve got the starting point of being able to manipulate tables and images now.
In our next part, we’ll look at modifying the document properties, updating page attributes like orientation, and utilising a table of contents. For now, find below our complete script for everything up until this point:
$randomObj = for ($i = 1; $i -lt 10; $i++) {
New-Object PsObject -Property @{
"index" = $i
"name" = "Object $i"
"value" = "Value $i"
}
}
$word = New-Object -ComObject Word.Application
$word.visible = $true
#If you're starting with a blank document
$doc = $word.Documents.Add()
$text = $word.Selection
$text.style = "Heading 1"
$text.TypeText("This is Heading 1")
$text.TypeParagraph()
$text.style = "Body Text"
$text.TypeText("This is a section of text immediately under Heading 1. This might be where you add some dynamically generated information")
$text.TypeParagraph()
$text.style = "Heading 2"
$text.TypeText("This is Heading 2")
$text.TypeParagraph()
$count = $randomObj.Count
$rowsNo = $count + 1
$colsNo = 3
$table = $text.Tables.Add(
$text.Range, $rowsNo, $colsNo
)
#$text.Range is the current position of the text 'selector' object within the Word document; this is just creating it wherever we've left our imaginary cursor
$table.style = "Plain Table 3" #this is a built in style; you may have custom styles available, and these can also be referenced by their name
$table.title = "randomObjectTable"
$table.cell(1,1).range.text = "Index"
$table.cell(1,2).range.text = "Name"
$table.cell(1,3).range.text = "Value"
#I can also apply a style to these cells, to differentiate the header row from the rest of the table
$table.cell(1,1).range.style = "Strong"
$table.cell(1,2).range.style = "Strong"
$table.cell(1,3).range.style = "Strong"
$r = 2 #we start at 2 as we're going to be populating our table from Row 2
$randomObj | foreach-object {
$table.cell($r,1).range.text = $($_ | Select-Object -expandProperty index).ToString() #All items added to Word must be a string; you will get an error if you try to enter an Int or other object type
$table.cell($r,2).range.text = $($_ | Select-Object -expandProperty name)
$table.cell($r,3).range.text = $($_ | Select-Object -expandProperty value)
$r++ #we need to iterate our $r object by 1 each time to move onto the next row
}
$table.AutoFitBehavior(2) #This autofits the table to the window; if you want to autofit to content, use 1 instead
$range = $doc.Range($table.Range.End, $table.Range.End) #this defines a range within the document at the end of the table
$range.select() #this moves the selection/cursor to the end of the table
$text = $word.selection #this sets our $text object to now be in the same place as $range.select()
$text.TypeParagraph() #this creates a new paragraph for new text or anything else!
$tables = $doc.Tables
$randomObjTable = $tables | Where-Object {$_.Title -eq "randomObjectTable"}
$randomObjCount = $randomObj.count #we'll want this later so that we can count how many rows have been added
$randomObj += New-Object PsObject -Property @{
"index" = 10
"name" = "Object 10"
"value" = "Value 10"
}
$rowsToAdd = $($randomObj.count) - $randomObjCount
for ($s = 1; $s -le $rowsToAdd; $s++) {
$randomObjTable.Rows.Add() | Out-Null
}
$r = $randomObjCount + 1 + 1 #we take the count of our object before we added new data, add 1 for the header row, and then add 1 for the new row
for ($r; $r -le $($randomObj.count + 1); $r++){
$index = $r - 2 #we need to define our index value for referencing that specific item in our object
$randomObjTable.cell($r, 1).range.text = $($randomObj[$index] | Select-Object -expandProperty index).ToString()
$randomObjTable.cell($r, 2).range.text = $randomObj[$index] | Select-Object -expandProperty name
$randomObjTable.cell($r, 3).range.text = $randomObj[$index] | Select-Object -expandProperty value
}
$imagePath = "C:\Temp\copilotImageForDemo.jpeg"
$insertedImage = $text.InlineShapes.AddPicture($imagePath, $false, $true)
#$false means "Do not link to the file", so it embeds the image
#$true means "Save with Document"
#I want to reduce the size by about 50%, so th easiest way to do that is using the existing values
$newWidth = $insertedImage.width / 2
$newHeight = $insertedImage.height / 2
$insertedImage.width = $newWidth
$insertedImage.height = $newHeight
$doc.SaveAs([Path To Save File To])
$doc.Close
$word.Quit