Hey, Scripting Guy! Question

Hey, Scripting Guy! My users seem to think that once they delete an e-mail message, it is gone. Most do not have the concept of going and emptying their Deleted Items folder. Using Windows PowerShell on Exchange, I can easily obtain a report from the server about how much space this stuff is taking up. I would like a script the users can run that would tell them how much space they are using. We have strict quotas set on their mailboxes, and I would like to notify them, perhaps via logon script, of the space used in their Deleted Items folder. I do not want to just arbitrarily empty the contents of that folder because some of the users actually use their deleted items folder for storage and often refer back to those deleted e-mail messages. Can you help me?

- JH

SpacerHey, Scripting Guy! Answer

Hi JH,

Of course we can help you. The Deleted Items folder is just a folder, like any other Office Outlook folder. In fact, here is a VBScript article that talks about obtaining the size and number of items in a folder. Careful examination of that article will reveal several points of similarity between it and today’s script, ReportSpaceUsedByDeletedItems.ps1 script.

This week we are looking at scripting Office Outlook. Interestingly enough, the Office Outlook object model is not as rich as you might suspect. Certainly Office Word and Office Excel have far more capability for automation than Office Outlook. This having been said, a basic familiarity with Office Outlook automation can lend rich results for the enterprising network administrator, consultant, or power user.

If you are interested in VBScript examples of working with Office Outlook, start with the "Hey, Scripting Guy!" archive, and then move on to the Office Space archive. Finally, you would probably like to see some examples of scripts for automating Office Outlook. You are in luck here because we have dozens of well-written scripts in the Community-Submitted Scripts Center. Read on, this is going to be a cool week. If you need help with Windows PowerShell, you can find download links and getting started information in the Windows PowerShell technology hub.

In the ReportSpaceUsedByDeletedItems.ps1 script, we create an instance of the Outlook.Application object, make a collection that points to the Deleted Items folder, and walk through the collection of items while adding up their size. Here is today’s script:

ReportSpaceUsedByDeletedItems.ps1

[Reflection.Assembly]::LoadWithPartialname("Microsoft.Office.Interop.Outlook") | 
out-null
$i = $size = $null
$olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
$outlook = new-object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")
$folder = $namespace.getDefaultFolder($olFolders::olFolderDeletedItems)
$items = $folder.items
Write-Host -ForeGroundColor Green "There are $($items.count) items in the deleted items folder"
$folder.items |
Foreach-Object {
 Write-Progress -Activity "Talleying size of Deleted items" -Status "Adding them up ..." -PercentComplete ($i/$items.count*100)
 $size += $_.size
 $i ++
}

$size = "{0:N2}" -f ($size/1024)
Write-Host -foregroundcolor Red "The deleted items are consuming $Size kilobytes of space"

The first thing we need to do is to load the Office Outlook interop assembly. This will give us the ability to load the Office Outlook folder enumerations for the default folder names. It will also give us the ability to use other things from the Office Outlook automation model. To load the Microsoft.Office.Interop.Outlook assembly, we use the static LoadWithPartialName method from the reflection.assembly .NET Framework class as seen here (in Windows PowerShell 2.0 we will be able to use a cmdlet to load the assembly, and the process will be a bit less cryptic if not actually easier):

[Reflection.Assembly]::LoadWithPartialname("Microsoft.Office.Interop.Outlook") | 
out-null

Now we need to initialize variables. There are actually three variables here. The first two are user defined, $i and $size. We are saying we want them to be equal to the same thing the $null automatic variable is equal to, which of course is null. This is a nice tight syntax that makes it easy to initialize our variables. As a best practice for Windows PowerShell (and even for VBScript), one should always initialize variables before using them. In this way, we know exactly what the value of the variable is when the script runs:

$i = $size = $null

When we are done initializing our variables, we want to load the olDefaultFolders enumeration. The olDefaultFolders enumeration is documented on MSDN. To load the olDefaultFolders enumeration, we place the enumeration name in quotation marks (making it a string) and then we use the –as operator to cast the string as a type. This sounds much worse than it is. Here is the code:

$olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]

Then we create an instance of the Outlook.Application object. The Outlook.Application object is the main object to use when working with Office Outlook automation. The Outlook.Application object is documented on MSDN. To create the object, we use the New-Object cmdlet with the comobject parameter. The program ID is Outlook.Application. We store the returned object in the $outlook variable as seen here:

$outlook = new-object -comobject outlook.application

It is time to create the namespace object. To do this, we use the GetNameSpace method. We tell it to use MAPI because this is the only constructor that will work for this method. And because the GetNameSpace method is a method, we need to use parentheses when we are passing a value to the method. All methods in Windows PowerShell use parentheses. This is seen here:

$namespace = $outlook.GetNameSpace("MAPI")

Now we want to connect to the Deleted Items folder. To connect to a folder, we use the GetDefaultFolder method and give it one of the olDefaultFolders enumeration values. If you are unsure which enumeration values are allowed, you can either look them up on MSDN or use the technique we talked about in yesterday's article. When using one of the olDefaultFolders enumeration values, we use double colons to separate the variable containing the enumeration type from the name of the enumeration (think about accessing a static property):

$folder = $namespace.getDefaultFolder($olFolders::olFolderDeletedItems)

Then we use the items property from the folder object to obtain a collection of items from the Deleted Items folder:

$items = $folder.items

We now print out a status message that tells the user how many items are in the Deleted Items folder. This is easy to do and happens nearly immediately. In this line of code, we are using the Write-Host cmdlet to print to the screen. Normally, we do not need to use Write-Host cmdlet, but we want the text to appear in green. We can use any color that is documented in the System.ConsoleColor enumeration class. If you do not want to look the enumeration up on MSDN, you can use the GetNames static method from the System.Enum .NET Framework class. This is seen here:

PS C:\> [enum]::GetNames("System.ConsoleColor")
Black
DarkBlue
DarkGreen
DarkCyan
DarkRed
DarkMagenta
DarkYellow
Gray
DarkGray
Blue
Green
Cyan
Red
Magenta
Yellow
White

The other thing we do is print out the number of items in the items collection. To do this, we need to evaluate the count property. To force the count property to be evaluated prior to returning the number of items, we use the subexpression $() to surround the property (if we did not use the subexpression, the object would unravel inside our string and fill the screen with gobbledygook):

Write-Host -ForeGroundColor Green "There are $($items.count) items in the deleted items folder"

Next we want to add up the size of all the messages in the Deleted Items folder. This will take a bit of time, but we can use pipelining to help it to go more efficiently. We take the items collection and pipeline it to the next part of the code by using the pipeline symbol ("|"):

$folder.items |

We pipeline the collection of items to the ForEach-Object cmdlet to allow us to work with individual items from the collection:

ForEach-Object {

As this operation is happening, we use the Write-Progress cmdlet to display feedback to the user. Depending on the number of deleted items, the operation could take a substantial amount of time. We specify three parameters for the Write-Progress cmdlet: the activity, the status, and the percentcomplete. This is seen here:

Write-Progress -Activity "Talleying size of Deleted items" -Status "Adding them up ..." -PercentComplete ($i/$items.count*100)

We now want to maintain a running tally of the size of the deleted items. To do this, we use the size property of the object and add it to the $size variable. To take the current value and add the new value to it, we use the += operator:

 $size += $_.size

We increment the $i variable because it is the one we are using to track our progress and to provide feedback to the user via the Write-Progress cmdlet:

 $i ++
}

Now want to format the number that is displayed. To format the number, we use the .NET Framework standard format strings which are documented on MSDN. In our example, N means we are formatting a number; the 2 means we will use two decimal places; and the 0 means to format it with the default pattern:

$size = "{0:N2}" -f ($size/1024)

The last thing we need to do is to report the total size of deleted items. We once again use the Write-Host cmdlet and this time we make the color of the text red. Because the $size variable is a simple variable, and not an object or a method or property that needs to be evaluated, we do not need to use a subexpression when printing out the value. This is seen here:

Write-Host -foregroundcolor Red "The deleted items are consuming $Size kilobytes of space"

The result from running the ReportSpaceUsedByDeletedItems.ps1 script is seen here:

Image of the result of running the script

 

Well, JH, that is it for the ReportSpaceUsedByDeletedItems.ps1 script. I hope you have enjoyed the article and that you will find the script useful. Stay tuned tomorrow, when we continue with Office Outlook Week. See you then.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys