Trouble with .Close() Method -- Help converting VB to PS?

Answered Trouble with .Close() Method -- Help converting VB to PS?

  • Wednesday, November 14, 2012 11:05 PM
     
     

    Hello all! 

    I'm still a bit new to PS and I'm attempting to use a command to read text out of the body of 4000 emails.  Unfortunately, Exchange MAPI has a memory limit on 250 open e-mails at a time.  I need to read a block of e-mail, close each e-mail, and then loop. 


    I've been trying to get this .close() method to work and can't seem to figure it out.  The method is described in these pages:
         http://weblogs.foxite.com/simonarnold/2006/03/08/outlook-automation-part-4-mailitem-method-finally-arrives/
         http://msdn.microsoft.com/en-us/library/microsoft.office.interop.outlook._mobileitem.close.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1
         http://msdn.microsoft.com/en-us/library/microsoft.office.interop.outlook.olinspectorclose.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1


    I've tried lines of code like

    • $MailItem.close
    • $MailItem.close() Exception calling "Close" with "0" argument(s): "The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)"
    • $MailItem.close(olDiscard) Missing ')' in method call. + $eAlertMail.close( <<<< SaveMode)
          + CategoryInfo          : ParserError: (CloseParenToken:TokenId) [], ParentContainsErrorRecordException
          + FullyQualifiedErrorId : MissingEndParenthesisInMethodCall
    • $MailItem.close(1)

    with no success--the items are still open.


    Get-Member:

    $eMails | get-member -membertype method
       TypeName: System.__ComObject#{00063026-0000-0000-c000-000000000046}
    Name                 MemberType Definition                      
    Close                Method     void Close (OlInspectorClose)  


    So I'm not sure what I'm not doing.  Do I need to create a new item to use this method?  If so, could someone give some assistance with that?

    Thanks for the help!

All Replies

  • Thursday, November 15, 2012 12:08 AM
    Moderator
     
     Answered Has Code

    Perhaps something like this? (Courtesy of http://blogs.technet.com/b/heyscriptingguy/archive/2009/01/26/how-do-i-use-windows-powershell-to-work-with-junk-e-mail-in-office-outlook.aspx)


    PS C:\> [Void] [Reflection.Assembly]::LoadWithPartialname("Microsoft.Office.Interop.Outlook")
    PS C:\> $olInspectorClose = "Microsoft.Office.Interop.Outlook.OlInspectorClose" -as [Type]
    PS C:\> $MailItem.Close($olInspectorClose::olDiscard)
    

    Bill

  • Friday, November 16, 2012 3:53 PM
     
     

    Thank you, Bill!  I think that was probably what I needed to load it, but I can't tell--I can't seem to use it to close my messages.  Perhaps I'm not using it properly, or at the right time?

    I have a "While" loop that runs for each section.  I've tried running the close at the start with an "if" several ways at this point, but each fails:
            # $UndvMails.close gives "no close method for that object."  
            # $UndvMail.close gives "attempted to call method on null-valued expression" (makes sense, this is a variable in the ForEach and it is outside of that loop).  
            # ($NDRs.Items).close gives "[System.__ComObject] doesn't contain a method named 'close'" EVEN THOUGH when I Get-Member on $NDRs.Items I see a .close method!
            # $NDRs.Items.close gives the same.
            # $NDRs doesn't have the .close method

    I have a "ForEach" loop that calls a function against each message and adds the result to a csv.  I tried to close the individual message when I am done with it (before the loop closes) but I also get an error.
            # Error: You cannot call a method on a null-valued expression.
                  + CategoryInfo          : InvalidOperation: (close:String) [], RuntimeException
                  + FullyQualifiedErrorId : InvokeMethodOnNull

    I can put this at the end of my "While" loop, but I don't see how that is any different from the beginning with the if.

    Any suggestions as to what I might be doing wrong would be greatly appreciated!

    Roy

    # ==================================================
    #
    # Script Information
    #
    # Title: NDRs.ps1
    # Author: Jacob Saaby Nielsen ()
    # Additions and loop by RCIII
    # Originally created: 06-02-2008 - 11:25:18
    # Description: Extracts e-mail addresses and error-codes from NDR mails, from a
    #              folder in Outlook
    #
    # ==================================================

    #-------- Configurable Settings --------

    # $subjectFilter needs to contain the word/words of the subject you want to match
    $subjectFilter = "Undeliverable: "

    # $NDRFolderName is the name of the folder in which you store the NDRs. Folder
    # needs to be at the folderlevel just beneath Inbox
    $NDRFolderName = "Undeliverable"

    # $NDRResultsFolder is the folder in which you want your results file located. End
    # with a backslash.
    $NDRResultsFolder = "c:\"

    # $NDRResultsFile is the name of the results file
    $NDRResultsFile = "ndr-list.csv"



    #-------- Non-configurable Settings - Do not change anything after this line --------

    # Instantiating Outlook etc.
    $olFolderInbox = 6;
    $outlook = New-Object -com outlook.application;
    $ns = $outlook.GetNameSpace("MAPI");
    $NDRs = $ns.GetDefaultFolder($olFolderInbox).Folders.Item( "$NDRFolderName")
    $BlockTotal = (($NDRs.Items).count)/230
    $BlockCounter = 0
        # Divide the total message .count by 230, and then do an incrementing loop until
        # BlockCount is larger than the messages.  This will rely on being able to
        # .close() the emails in between loops.
    [Void] [Reflection.Assembly]::LoadWithPartialname("Microsoft.Office.Interop.Outlook")
    $olInspectorClose = "Microsoft.Office.Interop.Outlook.OlInspectorClose" -as [Type]



    #
    # COUNTER LOOP
    Write-Host 'Getting total number of undeliverable mails...'
    Write-Host "Total Messages in subfolder $NDRFolderName is ($NDRs.Items.count)"
    Write-Host "Total Messages divided by 230 = $BlockTotal"
    while ($BlockCounter -lt $BlockTotal) {
        Write-Host "Working on section number $BlockCounter"
        # Filter out all the files that are not meeting your subject filter
        # $UndvMails = $NDRs.Items | where {$_.Subject -match $subjectFilter}
            # Above is the original code.  I need to break this into chunks and build a loop.
        if ($BlockCounter -ne 0) {Write-Host "Running NDRs.Items.Close"; $UndvMails.close($olInspectorClose::olDiscard)}
            # Trying to close the messages before running the query again.  Putting at
            # the foreach loop didn't work...
            # Using "if" statement to skip running this line the first iteration of the loop.
            # $UndvMails.close gives "no close method for that object."  
            # $UndvMail.close gives "attempted to call method on null-valued expression" (makes sense, this is outside of the foreach).  
            # ($NDRs.Items).close gives "[System.__ComObject] doesn't contain a method named 'close'"
            # $NDRs.Items.close gives the same.
            # $NDRs doesn't have the .close method
        $UndvMails = $NDRs.Items | Select-Object -index (($BlockCounter * 230)..($BlockCounter * 230 + 230)) | where {$_.Subject -match $subjectFilter}


        #
        # Returns e-mail address from the message body, based on a regular expression
        function get-Email([string]$MailBody)
            {
            $Regex = [regex] "([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})"
            $Match = $Regex.Match($MailBody)
            return $Match.Value
            }

        [int]$MailCounter = 1


        #
        # Calls Function and adds results to CSV
        foreach ($UndvMail in $UndvMails)
            {
            # Create and update the progress bar
            $status = "Processing mail {0} of {1}" -f $MailCounter, $UndvMails.Count
            Write-Progress $status -PercentComplete ((100 / $UndvMails.Count) * $MailCounter) -Activity "Processing e-mails" -ID 1

            # Add the string to the results file
            Add-Content -path $NDRResultsFolder$NDRResultsFile -value (get-Email $UndvMail.Body)

            $UndvMail.close($olInspectorClose::olDiscard)
            # Attempting to close each individual message after it is read.
            # Error: You cannot call a method on a null-valued expression.
            #    + CategoryInfo          : InvalidOperation: (close:String) [], RuntimeException
            #    + FullyQualifiedErrorId : InvokeMethodOnNull

            # Increment $MailCounter
            $MailCounter++
            }


        #
        #
        #
        # Increment BlockCounter - this is the end of the While loop    
        Write-host "Section $BlockCounter completed."
        $BlockCounter++}



    Write-host "Script Completed!"






            
    • Edited by R_C_III Friday, November 16, 2012 3:53 PM for clarity
    •  
  • Friday, November 16, 2012 4:23 PM
    Moderator
     
     

    Hi,

    I don't do any development using Outlook, so I can't offer anything further on this; sorry.

    Bill

  • Friday, November 16, 2012 4:28 PM
     
     

    In general you cannot oopen messages directly but need to open thenm in an inspector.   This cannot be don from VBScritp or PowerShell easily.  Yu should be doing this in VBA which has support for opening messages in qn inspector.

    Try posting you issues in teh Outlook developeers forum.

    As why you think you need to open teh message at all.  A  message is just a structure. It only needs to tbe opened if a human wants to look at it.  It does noit need to beopened to rad the contents.

    Note that received messages cannot be alte5red.  They are readonly.  You can make a copy and alter the copy.


    ¯\_(ツ)_/¯

  • Friday, November 16, 2012 4:37 PM
     
     

    Thanks for your help, Bill and JRV.  I'll give the Outlook Dev forums a shot.

    JRV, I have a mail account that Marketing uses to send notices.  I'm trying to create a script that will pull e-mail addresses from the DNR bouncebacks so we can programatically unregister those addresses.  That's why the message needs to be opened. :)

    The script does work, but only for the first 250 messages (because of a MAPI limit in the registry).  My options are to edit the registry or figure out .close() and make a funky loop.  I chose the option that would let me learn a bit more about PowerShell (and I've learned a lot!)

    Thanks again, guys!

  • Friday, November 16, 2012 7:46 PM
     
     

    Thanks for your help, Bill and JRV.  I'll give the Outlook Dev forums a shot.

    JRV, I have a mail account that Marketing uses to send notices.  I'm trying to create a script that will pull e-mail addresses from the DNR bouncebacks so we can programatically unregister those addresses.  That's why the message needs to be opened. :)

    The script does work, but only for the first 250 messages (because of a MAPI limit in the registry).  My options are to edit the registry or figure out .close() and make a funky loop.  I chose the option that would let me learn a bit more about PowerShell (and I've learned a lot!)

    Thanks again, guys!

    You do not need to open the message.  Just read the message contents as you enumerate the mesages.  Opening is used to display the message in an inspector.  You do not need to do that in script.

    I can access and copy thousands of messages without ever using an open and without using an inspector.

    I will find a link to my code that used to be posted and post it back here.

    The developer forum will also show you how to do this correctly.  You are using methods for the GUI which you do not need.


    ¯\_(ツ)_/¯

  • Monday, November 19, 2012 3:23 PM
     
     
    Thanks, JRV!  I haven't come across anything like that in my searches on reading from a message body--a link to your script would be *exceptionally* helpful!
  • Monday, November 19, 2012 4:47 PM
     
      Has Code
    Thanks, JRV!  I haven't come across anything like that in my searches on reading from a message body--a link to your script would be *exceptionally* helpful!

    Here is a veruy old project that was used to classify, filter and archive emails.  It has many useful techniques for enumerating mail messages.  It does not use inspectors.

    The code is many years old but should still work.  It was a one-off project done overnight in an emergency.  The code searched hundreds of PSTs fro specific phrases and captured the mesages for later forensic analysis.  You are free to use any of it you may find useful.

    http://www.designedsystemsonline.com/upload/ThisOutlookSession.zip

    Her eis on piece of code that shows how to enumerate mail items and perform actions on teh items.  It does not require an open or an inspector and has no limit as to teh number of messages that are being read.

    Function SaveMailItems(folder As Outlook.MAPIFolder)
        Dim oItem As Object
        Dim oMail As MailItem
        Dim oAttachment As Attachment
        Dim sMessagePath As String
        
        For Each oItem In folder.Items
        'Debug.Print folder.Name & ":" & oItem.Class
            If oItem.Class = 43 Then
                Set oMail = oItem
                iMessageNumber = iMessageNumber + 1
                sMessagePath = ARCHIVEPATH & sUserID & "\" & sPSTName & folder.FolderPath & "\"
                sMessagePath = Replace(sMessagePath, "\\", "\")
                sMessagePath = Replace(sMessagePath, "~", "_")
                sMessagePath = CreateFolders(sMessagePath)
                'WriteItemLog ARCHIVEPATH & sUserID & oMail.EntryID & vbTab & oMail.Subject
                smessagefile = sMessagePath & iMessageNumber & ".txt"
                oMail.SaveAs smessagefile, olTXT
                sMessagePath = sMessagePath & iMessageNumber
                SaveAttachments oMail.Attachments, sMessagePath
            End If
            
        Next
    End Function


    ¯\_(ツ)_/¯