Per this link:
http://social.technet.microsoft.com/Forums/en-US/ITCG/thread/81dcbbd7-f6cc-47ec-8537-db23e5ae5e2f
the correct way to avoid Excel zombies is to release all ComObjects in the powershell environment / script by invoking:
while ([System.Runtime.InteropServices.Marshal]::ReleaseComObject($comobject)) {} }
on each posh variable holding a reference to a COM object, and to do so in "reverse" order (suitably defined).
"Correct" means specifically"leaves no invisible lurking Excel apps behind".
The recipe works for me when I invoke my scripts as (say):
$ ./script.ps1
But when I invoke the script as:
. ./script.ps1
and then list posh variables:
$ ls variable:
posh throws an error when it encounters the first variable that (previously) held a ComObject reference:
ls : COM object that has been separated from its underlying RCW cannot be used. At line:1 char:1+ ls variable:+ ~~~~~~~~~~~~~~~~~~+ CategoryInfo : NotSpecified: (:) [Get-ChildItem], InvalidComObjectException+ FullyQualifiedErrorId : System.Runtime.InteropServices.InvalidComObjectException,Microsoft.PowerShell.Commands.GetChildItemCommand
I tried rv'ing the posh variables, but when I do so prior to the ReleaseComObject, I no longer have a handle to the Com Object; alternatively when I rv the posh variable after ReleaseComObject, posh throws the same error as above.
How to release both the Com Object and the posh variable in the base posh environment (ie . ./script) while avoiding Excel zombies?
Code excerpts below:
function release($foo) { while ([System.Runtime.InteropServices.Marshal]::ReleaseComObject($foo)) {} } function killExcel() { $xl.displayalerts = $false for ($i=1; $i -lt $wss.count; $i++) { $wss.Item(1).Delete() } release $wss $wb.close($false, $false, $false) release $wb } function quit() { killExcel $xl.Quit() release $xl [gc]::collect() [gc]::WaitForPendingFinalizers() exit } ...
// read xml file, define xmltitle variable
...
// define $count = # workbook pages ... $xl = new-object -comobject Excel.Application $xl.Visible = $True $xl.DefaultFilePath = (Get-Location).ToString() + "SpreadSheets.d\" $xl.DefaultFilePath.ToString() %{$title = $xmltitle.split()} $title[1] = $title[1] -replace "[.]","dot" $filename=$xl.DefaultFilePath.ToString() + $title[1] + "_" + $title[2] + ".xlsx" $filename.ToString() $xl.sheetsInNewWorkbook = $count $wb = $xl.Workbooks.Add() $wss = $wb.worksheets $sstoc = $wss.Item(1) $sstoc.name = "Table of Contents" $sscells = $sstoc.cells $sscells.item(1,1) = $xmltoc.index release $sscells release $sstoc try { $wb.saveas($filename, $xlFileFormat::xlWorkbookDefault) } catch [system.exception] { // ignore it } quit
TIA for the help.
FWIW, I'm running Win7 and Office 2010 in a Workstation 8 VM.
Jim Snyder