August 12th, 2011

Use PowerShell to Work with Pictures in Microsoft Excel

Doctor Scripto
Scripter

Summary: Guest Blogger Robert Robelo shows how to use Windows PowerShell to work with images in Microsoft Excel.

 

Microsoft Scripting Guy Ed Wilson here. Our guest blogger today is Robert Robelo. First, I want to give Robert a chance to tell us a little bit about himself.

I live in Nicaragua. I have been an incomplete quadriplegic (paralyzed from the chest down, but have limited mobility in upper limbs) for more than twenty years. Because of that I have not had a job since then. Before I got shot, I was an officer in the Nicaraguan Resistance Army, better known as Contras. One of my multiple responsibilities was to train, support, and maintain the IT structure (guess that would be a chief information officer), which was no more than twenty individual machines. My other responsibilities included intelligence, counterintelligence, and friendly forces liaison officer; obviously, before becoming an officer, I was an infantry and artillery soldier. I later became a sniper.

Robert is going to talk to us about working with images in Microsoft Excel via Windows PowerShell. Take it away, Robert!

 

Inserting an image in an Excel spreadsheet with Windows PowerShell is not an everyday chore for IT pros, but there very well may come a time where it would be useful to know how to do such a task.

An Excel spreadsheet has a Shapes property; it holds a collection of objects in the drawing layer, such as AutoShapes, freeforms, OLE objects, and images. To insert an image in the spreadsheet, use the Shapes collection’s AddPicture method. This method takes seven required arguments, which are shown in the following figure.

Image of seven required arguments

The AddPicture method returns a Shape object, which should be assigned to a variable. The Shape can then be relocated, resized, rotated, transformed, cropped, formatted, duplicated, and so on.

Let’s fire up an Excel instance, get a new workbook, and grab the ever-present Sheet1. Before we do that, let’s set up a few Constant variables. First, the MsoTriState constants that apply to the AddPicture method and then a couple Constant variables of our own. These will hold the default width and height of a cell; they can be very useful to place and size the image we are going to insert. However, they are not necessary if you prefer to place and size the image with greater accuracy. In this sample, we will place the image’s upper left corner in cell C3’s upper left corner—that is two cells from the left and two cells from the top of the sheet, and we will make the image two cells wide and four cells tall. (Note   The image path is set to C:\Ensign.jpg; you can substitute this with your own image or download the .zip file that contains that image as part of a test kit. Read more below.)

# Excel Constants

# MsoTriState

Set-Variable msoFalse 0 -Option Constant -ErrorAction SilentlyContinue

Set-Variable msoTrue 1 -Option Constant -ErrorAction SilentlyContinue

 

# own Constants

# cell width and height in points

Set-Variable cellWidth 48 -Option Constant -ErrorAction SilentlyContinue

Set-Variable cellHeight 15 -Option Constant -ErrorAction SilentlyContinue

 

$xl = New-Object -ComObject Excel.Application -Property @{

 Visible = $true

 DisplayAlerts = $false

}

$wb = $xl.WorkBooks.Add()

$sh = $wb.Sheets.Item(‘Sheet1’)

 

# arguments to insert the image through the Shapes.AddPicture Method

$imgPath = ‘C:\Ensign.jpg’

$LinkToFile = $msoFalse

$SaveWithDocument = $msoTrue

$Left = $cellWidth * 2

$Top = $cellHeight * 2

$Width = $cellWidth * 2

$Height = $cellHeight * 4

 

# add image to the Sheet

$img = $sh.Shapes.AddPicture($imgPath, $LinkToFile, $SaveWithDocument,

 $Left, $Top, $Width, $Height)

$xl.Speech.Speak(‘Add an image to the Sheet through the Add Picture Method.’)

Pretty easy, isn’t it? You can examine the Shape object with the Get-Member cmdlet to see the different methods and properties it has. There are too many to explore in one blog post anyway.

Now, let’s close the workbook without saving it, quit Excel, and clean up its insubordinate instance with the Remove-ComObject function. Basically, the Remove-ComObject function collects all variables that were assigned System.__ComObject objects within the caller scope and are not Constant or Read-Only variables:

# close without saving the workbook

$wb.Close($false)

$xl.Quit()

Remove-ComObject

There are many techniques that attempt to release these defiant System.__ComObject objects. Some do get the job done eventually, but they look chaotic or involve repeated calls to Runtime.Interopservices.Marshal‘s ReleaseComObject method. Using the Remove-Variable cmdlet the traditional way to get rid of these vestigial System.__ComObject objects will not suffice either, but using it as I do in the Remove-ComObject function, this capricious cmdlet does its job very well with a little help from its friends.

First, the Remove-ComObject function briefly pauses for half a second to let Windows PowerShell mark the System.__ComObject object as Disposed and ready to be collected by the Garbage Collector. Next, a variable is set with the Management.Automation.ScopedItemOptions ReadOnly and Constant enumerations. This variable will be the second half of the filter that will be applied in the following step. Then, the Get-Variable cmdlet fetches all variables in the caller scope (-Scope 1) and pipes them to the Where-Object cmdlet, where a filter is applied to exclude Constant or Read-Only variables, but whose value are of type System.__ComObject. This first half of the filter is done by comparing the value’s PSTypeNames collection against the string System.__ComObject with the Contains operator. The unfortunate and rebellious variables that go through are then piped to the Remove-Variable cmdlet and are slyly shredded to oblivion. Finally, the Garbage Collector gets a call to sanitize the area and collect the scraps.

The Remove-ComObject function is an advanced function because it has common parameters, which permits the user to use its Verbose flag to divulge which variables were removed. I do want to point out a very important fact: the Remove-ComObject function removes all variables declared in the function’s caller scope. It is safer to use it within scripts, functions, or modules, but you can also call it from the command line. Just beware that you may wipe out other System.__ComObject objects. This is the reason the function excludes Constant or Read-Only variables. It is up to you to declare those precious variables as such.

I have set up a test that demonstrates the efficacy of my technique to successfully release System.__ComObject objects before they become ghosts in your machine. You can download the test kit here; the results are displayed in the console. The test will show RAM usage and Excel processes running before and after the execution of two scripts, one that cleans up with a Remove-ComObject call and another that does not. These scripts also demonstrate other image manipulation that you might find interesting. Unzip the contents to your favorite test directory, run TestExcelDemo.ps1, watch and listen. It is less than five minutes. Excel 2010 or Excel 2007 is required.

 

Robert, I want to thank you for writing a really cool script, and for sharing your Windows PowerShell goodness with us.

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

 

 

Author

The "Scripting Guys" is a historical title passed from scripter to scripter. The current revision has morphed into our good friend Doctor Scripto who has been with us since the very beginning.

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • Luca Spolidoro

    Hi Dr Scripto,
    I followed your instructions but with this code:

    $ExcelObject=New-Object -ComObject excel.application -Property @{
    Visible = $false
    DisplayAlerts = $false
    }

    $Workbook = $ExcelObject.Workbooks.Open("C:\test\test.xlsx")
    $Worksheet = $Workbook.Sheets.Item("Sheet1")
    $Worksheet.Shapes.AddPicture("C:\test\logo.png",$msoFalse,$msoTrue,1,1,100,100)

    $Workbook.Save()
    $Workbook.Close($false)
    $ExcelObject.Quit()
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelObject)

    I get a weird error:
    "The specified value is out of range.
    At C:\test\Untitled1.ps1:8 char:1"

    but I couldn't find how to fix it... can you help me?

    Read more