cormanaz has asked for the wisdom of the Perl Monks concerning the following question:

Good day Bros. I have written a script in an attempt to delete attachments (but not messages to which they're attached) from my Outlook Sent Items folder. The code I have is
#!/usr/bin/perl -w use strict; use Win32::OLE; use Win32::OLE::Variant; use Win32::OLE::Const 'Microsoft Outlook'; use Date::Manip; # use existing instance if Outlook is already running, or launce a new + one my $ol; eval {$ol = Win32::OLE->GetActiveObject('Outlook.Application')}; die "Outlook not installed" if $@; unless (defined $ol) { $ol = Win32::OLE->new('Outlook.Application', sub {$_[0]->Quit;}) or die "Oops, cannot start Outlook"; } my $cutoffdate = ParseDate("May 1, 2014"); my $mailbox = seekFolder($ol->Session, 'foo@bar.com'); my $folder = seekFolder($mailbox, 'Sent Items'); my $end = $folder->Items->Count; for my $i (1..$end) { my $msg = $folder->Items->Item($i); my $msgdate = getTimeStamp($msg->{ReceivedTime}); if (Date_Cmp($cutoffdate,$msgdate) == 1) { my $count = $msg->{Attachments}->Count; if ($count > 0) { for (my $i = $count; $i > 0; $i--) { if (defined($msg->{Attachments}->Items($i))) { $msg->{Attachments}->Items($i)->Delete; } } print UnixDate($msgdate,"%Y-%m-%d %i:%M:%S %p")," ",substr +($msg->{Subject},0,30)."... $count attachments deleted\n"; } } } Win32::OLE->FreeUnusedLibraries(); sub seekFolder { my $obj = shift; my $target = shift; for (my $i = 1; $i <= $obj->Folders->Count; $i++) { if ( $obj->Folders->Item($i)->Name eq $target ) { return $obj->Folders->Item($i); } } } sub getTimeStamp { my ($var) = @_; my $timestamp = Win32::OLE::Variant->new(VT_DATE,$var); return ParseDate($timestamp->Date("yyyyMMdd").$timestamp->Time("HH +mmss")); }
When I run this, it seems to delete the attachments in some cases but not all. When it does delete them, if I run it again on the same message it will show an attachment count, but there are no Items in the attachment object (why I had to add the defined clause in line 29).

I couldn't find any existing Perl code to do this job, so I am basing the code on this VB example. But obviously I'm not doing something right. Anyone know how to fix?

Replies are listed 'Best First'.
Re: Outlook OLE Delete Attachments
by Lotus1 (Vicar) on Oct 17, 2014 at 01:43 UTC

    The approach I would take to debug this is to sprinkle print statements to log to csv in all those nested for and if blocks. Print maybe the subject, date, and attachment filename. Maybe simplify your code to just list all the attachments and start with a small group of emails, some with and some without attachments. Compare your logged output.

    Also I would get rid of the C style for loops and do Perl style foreach loops over lists of attachments. In a quick search I found use Win32::OLE qw(in with); to import in() to let you get a list of objects. Something like foreach my $msg (in $Folder->{Items}){.

    Here is an example of a similar question. Here is an even better example here at Perlmonks: Re: Win32::OLE Examples?.

      It's looping through the messages and attachments fine, and I guess I could use a different statement to loop. But the problem is that it's not actually deleting the attachments like it's supposed to. On some further investigation I found that I was not saving the message after I modified it to get rid of the attachment, so I added $msg->Save after the delete operation, but still the same result.

        This morning I realized that you are deleting elements of an array that you are looping over. So the foreach loop approach I suggested is not a good idea.

        Then I found this node from ten years ago where you were deleting attachments in Outlook using a method called Remove(). Did that not work for this?

        Your code:

        my $count = $msg->{Attachments}->Count; if ($count > 0) { for (my $i = $count; $i > 0; $i--) { if (defined($msg->{Attachments}->Items($i))) { $msg->{Attachments}->Items($i)->Delete; } } print UnixDate($msgdate,"%Y-%m-%d %i:%M:%S %p")," ",substr +($msg->{Subject},0,30)."... $count attachments deleted\n"; }

        One thing I notice here is that you are setting $count before the for loop and then assuming it should decrement each time you call delete. However you are decrementing $count each time whether or not you delete! This is the problem and some print statements would have clearly shown this.

Re: Outlook OLE Delete Attachments
by Anonymous Monk on Oct 17, 2014 at 13:46 UTC
    Ha. Well I remembered that I had written such a script a long time ago but had lost the code. Turns out I posted here for help on it in 2004, and I found the node in a dusty corner of the Monastery basement. Problem was that you have to use a Remove method rather than a Delete method. The working code is below for anyone else who's interested (sans the C-style loop, which really seems to bother some of the Brothers).

    Now the odd thing is that the purpose of running this was to free up mailbox space, but after running it and deleting a couple hundred attachments, some large, my mailbox size didn't change at all. Need to consult the Exchange admin on that one.

    #!/usr/bin/perl -w use strict; use Win32::OLE; use Win32::OLE::Variant; use Win32::OLE::Const 'Microsoft Outlook'; use Date::Manip; my $ol; eval {$ol = Win32::OLE->GetActiveObject('Outlook.Application')}; die "Outlook not installed" if $@; unless (defined $ol) { $ol = Win32::OLE->new('Outlook.Application', sub {$_[0]->Quit;}) or die "Cannot start Outlook"; } my $cutoffdate = ParseDate("December 31, 2012"); my $mailbox = seekFolder($ol->Session, 'foo@bar.com'); my $folder = seekFolder($mailbox, 'Sent Items'); my $end = $folder->Items->Count; for my $i (1..$end) { my $msg = $folder->Items($i); my $msgdate = getTimeStamp($msg->{ReceivedTime}); if (Date_Cmp($cutoffdate,$msgdate) == 1) { my $atch = $msg->Attachments; my $deleted = 0; if ($atch->Count) { while ($atch->Count) { $atch->Remove(1); $deleted++; } $msg->Save; } if ($deleted) { print UnixDate($msgdate,"%Y-%m-%d %i:%M:%S %p")," ",substr +($msg->{Subject},0,30)."... $deleted attachments deleted\n"; } } else { last; } } Win32::OLE->FreeUnusedLibraries(); sub seekFolder { my $obj = shift; my $target = shift; for (my $i = 1; $i <= $obj->Folders->Count; $i++) { if ( $obj->Folders->Item($i)->Name eq $target ) { return $obj->Folders->Item($i); } } } sub getTimeStamp { my ($var) = @_; my $timestamp = Win32::OLE::Variant->new(VT_DATE,$var); return ParseDate($timestamp->Date("yyyyMMdd").$timestamp->Time("HH +mmss")); }
      ... the C-style loop, which really seems to bother some of the Brothers).

      C-style for loops are wonderful and powerful when there is a purpose for them. But when Perl provides such powerful ways to simplify your code such as the foreach loop it is a shame not to use them.