How to release a COM object after being used in a loop (or pipe)?

260 Views Asked by At

I have the following Powershell code which should send an email in the background and should wait till that email with the special attribute was also sent successfully. All works fine, but for any reason I cannot release the COM-object $sent after I used it in a loop or pipe. As a result the background process does not terminate as expected. How can I solve that problem?

Here the code-snippet:

cls
remove-variable * -ea 0
$ErrorActionPreference = 'stop'

# connect to existing outlook or start a new instance:
$i = [System.Runtime.Interopservices.Marshal]
Add-Type -AssemblyName "Microsoft.Office.Interop.Outlook"
try {
    $o = $outlook = $i::GetActiveObject('Outlook.Application')
} catch {$o = New-Object -ComObject Outlook.Application}

# create email object:
$m = $o.CreateItem(0)

# set email properties:
$m.Subject = "Test"
$m.Body = "This is a test."
$m.To = "[email protected]"

# add a unique id to the mail:
$guid = [System.Guid]::NewGuid().guid
$id = $m.UserProperties.Add("guid",1)
$id.Value = $guid
$null = $i::ReleaseComObject($id)

# send the email and release the object:
$m.Send()
$null = $i::ReleaseComObject($m)

if ($outlook) {
    # no need to wait here, because outlook is still running:
    write-host "sending email initiated."
} else {
    # wait till email with that id appears in "sent" (or timeout):
    $timeout = 30
    $sent = $o.Session.GetDefaultFolder(5).Items
    do {
        sleep 1
        $sent.Sort("[SentOn]", $true)

        # this loop gets the id of the last sent mail,
        # but it blocks the release of $sent COM-object later.
        # same issue when using a pipe with "select -first 1"

        foreach($s in $sent){
            $id = $s.UserProperties['guid'].value
            break
        }
        $timeout -= 1
    } until ($timeout -eq 0 -or $id -eq $guid)

    # this does NOT release the object because I used it in the loop before:
    $null = $i::ReleaseComObject($sent)

    if ($id -eq $guid) {write-host "email sent."} 
    write-host "closing background process."
    $o.Quit()
}

# release the outlook object
$null = $i::ReleaseComObject($o)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
1

There are 1 best solutions below

0
Carsten On

Based on the good comments here the final solution to successfully close all COM-objects which also terminates the Outlook background process at the end:

cls
remove-variable * -ea 0
$ErrorActionPreference = 'stop'

# connect to existing outlook or start a new instance:
$i = [System.Runtime.Interopservices.Marshal]
Add-Type -AssemblyName "Microsoft.Office.Interop.Outlook"
try {
    $o = $outlook = $i::GetActiveObject('Outlook.Application')
} catch {$o = New-Object -ComObject Outlook.Application}

# create email object:
$m = $o.CreateItem(0)

# set email properties:
$m.Subject = "Test"
$m.Body = "This is a test."
$m.To = "[email protected]"

# add a unique id to the mail:
$guid = [System.Guid]::NewGuid().guid
$id = $m.UserProperties.Add("guid",1)
$id.Value = $guid
$null = $i::ReleaseComObject($id)

# send the email and release the object:
$m.Send()
$null = $i::ReleaseComObject($m)

if ($outlook) {
    # no need to wait here, because outlook is still running:
    write-host "sending email initiated."
} else {
    # start child scope:
    & {
        # wait till email with that id appears in "sent" (or timeout):
        $timeout = 30
        $sent = $o.Session.GetDefaultFolder(5).Items
        do {
            sleep 1
            $sent.Sort("[SentOn]", $true)
            foreach($s in $sent){
                $id = $s.UserProperties['guid'].value
                break
            }
            $timeout -= 1
        } until ($timeout -eq 0 -or $id -eq $guid)
        if ($id -eq $guid) {write-host "email sent."}

    # end of child scope releases all COM dependencies
    }

    write-host "closing background process."
    $o.Quit()
}

# release the outlook object
$null = $i::ReleaseComObject($o)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()