Using PowerShell to create rules in Outlook

I recently started using Microsoft PowerShell to carry out administrative tasks and it didn’t take long for me to fall in love with it. Being a script fanatic using DOS instead of Windows, VBScript and JScript, Console-apps in favor to Windows apps, regex, etc. it’s easy to see the tremendous power in PowerShell; you can say that it’s the command-prompt on steroids :-) I’m lovin’ it!

The task – Creating an Outlook rule using PowerShell

Ok, do this might seem to be a trivial task, but as always when you think something seems to be easy you run into problems, especially when working with COM-objects/Automation. So, in order to solve my problem I had to do a lot of looking around at the forums on the web.

The script below creates a new rule in Outlook (I’m currently running Outlook 2010) that checks the sender of the message and looks for certain words in the subject field. If the sender is e.g. foo@bar.com (FROMEMAIL) and the subject contain the words ‘Hello’ and ‘World’ (SUBJECTWORDARRAY), the message should be forwarded to fii@bar.com (FORWARDEMAIL) and moved to the MyCustomFolder (MOVETOFOLDER) folder which is a subfolder to the Inbox folder.

##########################################
# Based on PowerShell 2.0
##########################################
function add_outlook_rule
{
    param([string]$RuleName,
          [string]$FromEmail,
          [string]$ForwardEmail,
          [string]$RedirectFolder,
          [string[]]$SubjectWords)
              
    Add-Type $class -ReferencedAssemblies Microsoft.Office.Interop.Outlook
    
    Add-Type -AssemblyName microsoft.office.interop.outlook 
    $olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
    $olRuleType = "Microsoft.Office.Interop.Outlook.OlRuleType" -as [type]
    $outlook = New-Object -ComObject outlook.application
    $namespace  = $Outlook.GetNameSpace("MAPI")
    $inbox = $namespace.getDefaultFolder($olFolders::olFolderInbox)
    
    $rules = $outlook.session.DefaultStore.GetRules()
    $rule = $rules.Create($RuleName,$olRuleType::OlRuleReceive)

    $SubjectCondition = $rule.Conditions.Subject
    $SubjectCondition.Enabled = $true
    $SubjectCondition.Text = $SubjectWords
     
    $d = [System.__ComObject].InvokeMember(
        "EntryID",
        [System.Reflection.BindingFlags]::GetProperty,
        $null,
        $inbox.Folders.Item($RedirectFolder),
        $null)
    $MoveTarget = $namespace.getFolderFromID($d)
    
    # Uncomment the row below if you want to use the DeletedItems
    # folder as MoveToFolder.
    #$MoveTarget = $namespace.getDefaultFolder(
        $olFolders::olFolderDeletedItems)

    $MoveRuleAction = $rule.Actions.MoveToFolder
    [Microsoft.Office.Interop.Outlook._MoveOrCopyRuleAction].InvokeMember(
        "Folder",
        [System.Reflection.BindingFlags]::SetProperty,
        $null,
        $MoveRuleAction,
        $MoveTarget)
    $MoveRuleAction.Enabled = $true

    $FromCondition = $rule.Conditions.From
    $FromCondition.Enabled = $true
    $FromCondition.Recipients.Add($FromEmail)
    $fromCondition.Recipients.ResolveAll()

    $ForwardRuleAction = $rule.Actions.Forward
    $ForwardRuleAction.Recipients.Add($ForwardEmail)
    $ForwardRuleAction.Recipients.ResolveAll()
    $ForwardRuleAction.Enabled = $true

    $rules.Save()
}

##########################################
# SYNTAX: 
##########################################
# add_outlook_rule RULENAME 
#                  FROMEMAIL
#                  FORWARDEMAIL
#                  MOVETOFOLDER
#                  SUBJECTWORDARRAY
##########################################

add_outlook_rule "This is my custom rule" "fii.from@bar.com" "foo.forward@bar.com" "MyCustomFolder" @("foo","bar")

 

And now for the problems I ran into…

Problem number 1

The first problem I ran into was with the criterion that looked for certain words in the Subject field. I found out that the subject field expected an array of type string, but by some strange reason I didn’t get it to work, even tough I Reflected the PIA (Primary Interop Assembly) for Microsoft Outlook and checked exactly what types the class expected and tried to cast the variable in all possible ways available.  After some additional investigation I found out that the string array needed to contain more than one item which means that if you have an array with two or more items the script started to work.

$SubjectCondition = $rule.Conditions.Subject 
$SubjectCondition.Enabled = $true 

# Will not work...
# $a = $(“foo”)

# Works like a charm
$a = $(“foo”,”fii”)

$SubjectCondition.Text = $a

 

Problem number 2

When I tried to set the Folder property on the MoveToFolder object I got an error stating that I was trying to set a property that expected an object that implemented the interface MAPIFolder to a ComObject which apparently doesn’t do that.

This is the error message I received:

Exception setting "Folder": "Cannot convert the "System.__ComObject" value of type "System.__ComObject#{00063006-0000-0000-c000-000000000046}" to type "Microsoft.Office.Interop.Outlook.MAPIFolder"."

No matter how I tried to cast the ComObject to a MAPIFolder the script constantly failed. I tried to get a handle on the desired folder in many different and this part was actually quite easy, but the problem still remained: It was not possible to cast the ComObject to a MAPIFolder. So what now? Give up? Never! :-)

I came up with the idea that maybe the solution would be not to cast the ComObject but instead try to interact with it somehow, and for this we have our .NET friend – Reflection! By using reflection on objects in .NET it is possible to get and set properties on ComObjects and this turned out to be the solution to my problem.

In the first code block below I use the InvokeMember method that is part of reflection for types in .NET to first get a property from a ComObject. There are other ways to get a handle on a folder in Exchange/Outlook, but I wanted to show an alternate way of doing this in this sample. The second code block I set the Folder property on the MoveToFolder action object. This solved my second problem.

$d = [System.__ComObject].InvokeMember(
	"EntryID",
	[System.Reflection.BindingFlags]::GetProperty,
	$null,
	$inbox.Folders.Item($RedirectFolder),
	$null)
$MoveTarget = $namespace.getFolderFromID($d)
	
$MoveRuleAction = $rule.Actions.MoveToFolder
[Microsoft.Office.Interop.Outlook._MoveOrCopyRuleAction].InvokeMember(
	"Folder",
	[System.Reflection.BindingFlags]::SetProperty,
	$null,
	$MoveRuleAction,
	$MoveTarget)
$MoveRuleAction.Enabled = $true

 

Problem number 3

When everything started to work (scripts are like that – it’s all or nothing) everything seemed fine until I used the script in a batch where I created many rules on a specific mailbox store. The problem here turned out to be a limit in the definition storage on the Exchange server that only allowed the size of the rule definitions for an account to be 32 kb. In my case I exceeded the limitation and got an error when executing my scripts.

More information

Advertisements

Architecture mismatch when using ODBC

Today I ran into a problem when I was trying to connect to a data source using ODBC.  Currently I’m working on a web application that uses ASP.NET running on .NET Framework 2.0 and that has one connection to a SQL Server database and a number of other connections to databases based on Pervasive SQL. The web application uses ODBC to connect to the Pervasive SQL databases.

Ok, and now for the problem…

When I was trying to start the web application I got an error message saying “ERROR [IM014] [Microsoft][ODBC Driver Manager] The specified DSN contains an architecture mismatch between the Driver and Application”.

After a little investigation I found out that my Visual Studio project was compiled targeted towards the x64 architecture (64 bit) using ODBC drivers created for the x86 architecture (32 bit) which led to the error message. Apparently you need to have ODBC-drivers that is compiled for the same architecture as the application that uses them.

This is how I solved the problem and got my application up and running…

The first thing I did was to make sure that I had a valid ODBC connection, and since I’m running Windows 7 x64 (64 bit) I needed to open the old ODBC Data Source Administrator using the following path:

%WINDIR%\SYSWOW64\ODBCAD32.exe

image

The WOW part in SYSWOW64 basically means Windows 32 bit on Windows 64 bit and is sort of a copy of the system32 folder created on the x86/32 bit architecture

I checked my ODBC connection and made sure that it was properly created.

Next thing – Visual Studio …

Ok, the ODBC connection seems to work and the next thing I had to do was to change the platform for the projects in my Visual Studio solution. First a little background: the solution contains five C# projects that are class libraries and two projects that are ASP.NET web site projects. The web sites has references to the class libraries and everything is created with default settings in Visual Studio, i.e. the platform is set to “Any CPU”. The web sites are configured to run using IIS and not Cassini, the ASP.NET development server used by Visual Studio.

Changing the target platform for the class libraries was easy and I simply just opened the properties for each project and changed the Platform target found under the Build tab in the Properties dialog.

image

When I tried to do the same thing with the The ASP.NET web site projects I discovered that that type of Visual Studio projects doesn’t have any setting for this. In order to make the web sites run on the x86 architecture I found out that this has to be done in Internet Information Services Manager (IIS).

Next and final thing – IIS

So, almost there! I started the IIS Manager (INETMGR) and expanded the node for Application Pools. This is where we get the possibility to tell the application what platform to run on. I selected the application pool used by my web site and clicked Advanced Settings in the right panel. The next thing I did was to alter the Enable 32-Bit Applications setting to true which basically means that my web site application should run as an application using the x86 (64 bit) architecture.

image

 

Problem solved!

I solved the problem following the steps above and my web site application is up and running.