Cleaning Viruses In Public Folders Using OOM

In my previous post, I provided an example script that used EWS to delete items out of public folders. Of course, you can only use EWS if your public folders are on Exchange 2007 or 2010.

The example script in this post uses Outlook Object Model instead. This means it has to be run from a machine with Outlook installed (it should work with Outlook 2007 or Outlook 2010) as well as Powershell 2.0.

You might notice that this script is a little simpler than the EWS example. Most of that is due to the fact that Outlook Object Model doesn’t give us much control over how to retrieve the contents of a folder. For folders with lots of items, you may find that this script will run very slowly, if it works at all. The EWS version won’t have that problem since it is able to use ranged retrieval, whereas this one has to get the items in one big chunk.

# Delete-PublicFolderItems.ps1
#
# Syntax:
#
# .\Delete-PublicFolderItems \TopFolder\Subfolder $false
# This example runs against a single folder and does not recurse.
#
# .\Delete-PublicFolderItems "" $true
# This example runs against the entire public folder hierarchy.

param([string]$folderPath, [bool]$recurse)

# By default, this script only reports what it would have deleted.
# If you flip $readOnly to $false, it WILL DELETE ITEMS OUT OF YOUR
# PUBLIC FOLDERS. Make sure you've tested it first and you have a
# backup.
$readOnly = $true

$outlook = new-object -com Outlook.Application
$mapi = $outlook.GetNamespace("MAPI")
$pfStore = $null
foreach ($store in $mapi.Stores)
{
if ($store.ExchangeStoreType -eq 2)
{
$pfStore = $store
}
}

if ($pfStore -eq $null)
{
"Couldn't find public folder store."
return
}

$pfRoot = $pfStore.GetRootFolder()

# Define the function we'll use to traverse
# the PF tree if a path was specified.
function GetNamedFromCollection($name, $collection)
{
foreach ($item in $collection)
{
if ($item.Name -eq $name -or $item.DisplayName -eq $name)
{
return $item
}
}
return $null
}

$allPFs = GetNamedFromCollection "All Public Folders" $pfRoot.Folders
if ($allPFs -eq $null)
{
"Couldn't find All Public Folders folder."
return
}

$folderPath = $folderPath.Trim("\")
$folderPathSplit = $folderPath.Split("\")
$folder = $allPFs
if ($folderPath.Length -gt 0)
{
"Traversing folder path..."
for ($x = 0; $x -lt $folderPathSplit.Length; $x++)
{
$folder = GetNamedFromCollection $folderPathSplit[$x] $folder.Folders
}

     if ($folder -eq $null)
{
("Could not find folder: " + $folderPath)
return
}

     ("Found folder: " + $folder.FolderPath)
}

# Now let's define the function that'll do the real work

function DoFolder($folder)
{
("Scanning folder: " + $folder.FolderPath)

try
{
foreach ($item in $folder.Items)
{
##########################################################
# Here's the most important part. This is where we
# decide whether to delete the item.
#
# By default this script will delete any item that
# contains the phrase "Here you have" in the subject.
# If that isn't what you want, you need to edit
# the criteria here.
if ($item.Subject.Contains("Here you have"))
{
if ($readOnly)
{
(" Would have deleted item: " + $item.Subject)
}
else
{
(" Deleting item: " + $item.Subject)
$item.Delete()
}
}
}
}
catch { "Error processing folder." }

if ($recurse)
{
foreach ($subfolder in $folder.Folders)
{
try
{
DoFolder $subfolder
}
catch { "Error processing folder: " + $subfolder.FolderPath }
}
}
}

DoFolder $folder

"Done!"