0

I am writing this script to delete any Exchange quarantined emails that have already been replied to by another technician.

It "works" but it only is deleting the first set of emails that it finds that have been replied to, the original plus the reply.

If I run the script a second time it then deletes the next set and so on.

I am not seeing why it is not looping through and deleting all of the emails that have been replied to instead of just the first set.

#connect to outlooks
$outlook = new-object -comobject “Outlook.Application”
$mapi = $outlook.getnamespace(“mapi”)

#connect to outlook inbox
$inbox = $mapi.GetDefaultFolder(6)

#find the subfolder named Exchange Quarantined
$subfolder = $inbox.Folders | Where-Object {$_.Name -eq “Exchange Quarantined”}

#loop through emails and if someone already replied to the email then delete all emails with that users name in it
ForEach ($email in $subfolder.Items) {
    $subject = $email.Subject

    #Get the users name out of the subject line
    $user = $subject.Split("(")
    $name = $user[0] -replace ".*device that belongs to "
    $name = $name.Trim()
    Write-host $name
    if($subject -like "*RE: A device that belongs to*") {
        ForEach ($emailDelete in $subfolder.Items) {
            $subjectDelete = $emailDelete.Subject

            if($subjectDelete -like "*$name*") {
                Write-Host "Delete $subjectDelete"
                $emailDelete.Delete()
            }
        }
    }
}

When I first ran the script the folder had five emails in it, 3 original quarantined emails and 2 replies. Here is the output on three runs which on each run it deleted only the first set of replies it found.

PS H:\> C:\Users\todd.welch\Downloads\Exchange Quarantined.ps1
Lan Fill
Adam Pac
Adam Pac
Delete A device that belongs to Adam Pac (adam.pac) has been quarantined. Exchange ActiveSync will be blocked until you take action.
Delete RE: A device that belongs to Adam Pac (adam.pac) has been quarantined. Exchange ActiveSync will be blocked until you take action.

PS H:\> C:\Users\todd.welch\Downloads\Exchange Quarantined.ps1
Lan Fill
Antonia Gonz
Antonia Gonz
Delete A device that belongs to Antonia Gonz (antonia.gonz) has been quarantined. Exchange ActiveSync will be blocked until you take action.
Delete RE: A device that belongs to Antonia Gonz (antonia.gonz) has been quarantined. Exchange ActiveSync will be blocked until you take action.

PS H:\> C:\Users\todd.welch\Downloads\Exchange Quarantined.ps1
Lan Fill
Todd Welch
  • 1,776
  • 2
  • 17
  • 23
  • Why do you have the second inner loop? That does not seem required. If you have a subject match you should just be able to `$email.Delete()` no? – Matt Jul 03 '19 at 16:18
  • Because it needs to delete both the reply and also the original and since I have no way to know what order it will process the emails the only way I could think to do it is to run through all of the emails again deleting any with that users name on the inner loop. – Todd Welch Jul 03 '19 at 16:24
  • Ah ok. I understand. – Matt Jul 03 '19 at 16:25
  • My guess is that you are changing the members of the outer loop asyou are deleting them in the inner. So the outer loop would possibly try to access a mail you have since deleted. I think you might need to query all the mail at once then delete in a separate action... or requery `$subfolder.Items` to make sure you get the updated list – Matt Jul 03 '19 at 16:28
  • Hey Matt that is one thought I had but I thought having the seperate ForEach loop would solve that. I even tried having a separate $subfolder variable but perhaps the pointer is on a higher level at a higher level in the outlook connection. So perhaps I need to loop through one time to create an array of RE: emails user names and then loop through in a separate loop to delete all emails containing the users name. – Todd Welch Jul 03 '19 at 16:53

1 Answers1

1

Never delete collection items in a "foreach" loop - you are modifying the collection, and that causes your code to skip at least some items. Use a down "for" loop (from Items.Count down to 1).

That being said, you should never be matching items explicitly in your own code - use Items.Find/FindNext or Items.Restrict. Let the store provider do the job.

Dmitry Streblechenko
  • 62,942
  • 4
  • 53
  • 78
  • 1
    Thanks for the reply and I did research but I am just not following what you mean. – Todd Welch Jul 03 '19 at 17:34
  • So this is what I think you meant but I get the error $messages = $subfolder.Items For($i = $messages.count; $i -ge 1; $i--) { Write-Host $i $subject = $messages[$i].Subject Write-host "Subject: $subject" if($subject -like "*RE: A device that belongs to*") { $user = $subject.Split("(") $name = $user[0] -replace ".*device that belongs to " $name = $name.Trim() $deleteMessage = $subfolder.Find("[Subject] = '.*$name.*'") While ($deleteMessage) { $deleteMessage.Delete() } } } – Todd Welch Jul 03 '19 at 18:08
  • Method invocation failed because [System.__ComObject] does not contain a method named 'Find'. At C:\Users\todd.welch\Downloads\Exchange Quarantined.ps1:34 char:9 + $deleteMessage = $subfolder.Find("[Subject] = '.*$name.*'") + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (Find:String) [], RuntimeException + FullyQualifiedErrorId : MethodNotFound – Todd Welch Jul 03 '19 at 18:08
  • Find/FindNext/Restrict methods are implemented by the Items object (returned from MAPIFolder.Items), not MAPIFolder - see https://learn.microsoft.com/en-us/office/vba/api/outlook.items.find – Dmitry Streblechenko Jul 03 '19 at 18:10
  • yes I see my mistake I changed $deleteMessage = $subfolder.Items.Find("[Subject] = '.*$name.*'") but $deleteMessage is empty it does not find any email. – Todd Welch Jul 03 '19 at 18:12
  • You are using an exact match. Did you mean Find("@SQL=Subject Like '%some value%' ") – Dmitry Streblechenko Jul 03 '19 at 18:14
  • Ok so I got it working but similar problem to original. It finds and deletes only the first email with the users name in it of the emails that have been replied to so it seems that the FindNext() is not actually finding the next email with the users name. – Todd Welch Jul 03 '19 at 19:13
  • $messages = $subfolder.Items For($i = $messages.count; $i -ge 1; $i--) { $subject = $messages[$i].Subject if($subject -like "*RE: A device that belongs to*") { $user = $subject.Split("(") $name = $user[0] -replace ".*device that belongs to " $name = $name.Trim() $deleteMessage = $subfolder.Items.Find("@SQL=http://schemas.microsoft.com/mapi/proptag/0x0037001E Like '%$name%'") While ($deleteMessage) { $deleteMessage.Delete() $deleteMessage = $subfolder.Items.FindNext() } } } – Todd Welch Jul 03 '19 at 19:13
  • Every time you call MAPIFolder.Items, you get back a brand new Items object that has no knowledge of any other instances. Store $subfolder.Items in a dedicated variable and call Find / FindNext on that variable. – Dmitry Streblechenko Jul 03 '19 at 20:12