Tuesday, August 15, 2006

Reporting on Meeting Delegate Forward Rules in Outlook

Somebody sparked my interest to this last week, one of the functions of Outlook is the ability to delegate your calendar and forward the meeting invitations you receive to another user. What Outlook does in the background is creates a rule in the inbox to forward any Calendar request messages to this delegate user. A problem can occur if this delegate user is deleted or disabled and the original user doesn’t modify or delete their delegate rules. In this case anyone sending this person a meeting invitation may suddenly start receiving NDR’s because the account the delegate rule is forwarding to no longer exists or is no longer accepting messages something like what described by KB253557

To detect and report on forwarding rules what you can use is CDO 1.2 and the rule.dll component which you can download from here . The rule.dll com object is good for creating rules but it can also be used to report on rules that already exist(a good thing to keep in mind is that this can’t be used to modify rules that it didn’t create eg because the delegate rule was created in Outlook the rule.dll component can not be used to modify this rule in any fashion).

So putting it all together I’ve come up with a script that will check all the users on a server to firstly see if they have a delegate forwarding rule enabled and then check each of the accounts the rule references to see if there is a valid mailbox for this address. So basically the first part of the script takes one commandline parameter which is the name of the server you want to run this against. The next part is a ADSI query the retrieves the names of all the mailboxes on that server that aren’t hidden from the global address list.. To determine if a rule is a delegate rule the rule action property is checked if the rule action is 7 this equates to ACTION_DELEGATE . Basically its the same as reading the rules tables and equating to the OP_DELEGATE action type. Once its determined that a delegate rule exists the recipients that the rule forwards to are then checked to see if they are valid by first converting the recipient.id that is returned by the rule component into a address object which will then return the ExchangeDN of the user object which can then be searched for in active directory by using the LegacyExchangeDN property. The result of all these queries are then echoed to the commandline and also saved in a CSV on the c: drive called MeetingDelgatesForwards.csv.

This script is designed to by run from the commandline (cscript) with the servername you want to run it against as a commandline parameter. Eg cscript chkdelv4.vbs servername. Because this script logs on to each mailbox to check if a delegate rule exists its needs to be run with an account that has full right to everyones mailbox as per KB821897

Remediation: At first I was going to add a remediation function to this script so it would automatically delete any rules that it found where invalid but I decided to stop at just reporting because it’s possible that in some cases remediation of this problem is a lot more complicated that can be handled automatically in a script. Eg its possible to have meeting requests forwarded to multiple delegates where there would be one rule with multiple address’s is the rules action argument is this case you can’t modify the rule because it can only be modified by the agent that created it (eg Outlook) and deleting the rule would cause lose of data and functionality. In the end the hard work is mostly in identifying the accounts that this might be an issue with and then you can use whatever tools you need to remediate the action.

I’ve put a downloadable copy of the script here the script itself looks like

servername = wscript.arguments(0)
PR_HAS_RULES = &H663A000B
PR_URL_NAME = &H6707001E
PR_CREATOR = &H3FF8001E
Set fso = CreateObject("Scripting.FileSystemObject")
set wfile = fso.opentextfile("c:\MeetingDelgatesForwards.csv",2,true)
wfile.writeline("Mailbox,ForwadingAddress,Status")
set conn = createobject("ADODB.Connection")
set com = createobject("ADODB.Command")
Set iAdRootDSE = GetObject("LDAP://RootDSE")
strNameingContext = iAdRootDSE.Get("configurationNamingContext")
strDefaultNamingContext = iAdRootDSE.Get("defaultNamingContext")
Conn.Provider = "ADsDSOObject"
Conn.Open "ADs Provider"
svcQuery = ">LDAP://" & strNameingContext & "<(&(objectCategory=msExchExchangeServer)(cn=" & Servername & "));cn,name,legacyExchangeDN;subtree"
Com.ActiveConnection = Conn
Com.CommandText = svcQuery
Set Rs = Com.Execute
while not rs.eof
GALQueryFilter = "(&(&(&(& (mailnickname=*)(!msExchHideFromAddressLists=TRUE)(| (&(objectCategory=person)(objectClass=user)(msExchHomeServerName=" & rs.fields("legacyExchangeDN") & ")) )))))"
strQuery = ">LDAP://" & strDefaultNamingContext & "<" & GALQueryFilter & ";distinguishedName,mailnickname,mail;subtree"
com.Properties("Page Size") = 100
Com.CommandText = strQuery
Set Rs1 = Com.Execute
while not Rs1.eof
call procmailboxes(servername,rs1.fields("mail"))
wscript.echo rs1.fields("mail")
rs1.movenext
wend
rs.movenext
wend
rs.close
wfile.close
set fso = nothing
set conn = nothing
set com = nothing
wscript.echo "Done"




sub procmailboxes(servername,MailboxAlias)

Set msMapiSession = CreateObject("MAPI.Session")
on error Resume next
msMapiSession.Logon "","",False,True,True,True,Servername & vbLF & MailboxAlias
if err.number = 0 then
on error goto 0
Set mrMailboxRules = CreateObject("MSExchange.Rules")
mrMailboxRules.Folder = msMapiSession.Inbox
Wscript.echo "Checking For any Delegate forwarding Rules"
nfNonefound = 0
for Each roRule in mrMailboxRules
for each aoAction in roRule.actions
if aoAction.ActionType = 7 then
nfNonefound = 1
Wscript.echo "Delegate Rule found Forwards to"
for each aoAdressObject in aoAction.arg
Set objAddrEntry = msMapiSession.GetAddressEntry(aoAdressobject)
wscript.echo "Address = " & objAddrEntry.Address
if verifyaddress(objAddrEntry.Address) = 1 then
wfile.writeline(mailboxAlias & "," & objAddrEntry.Address & ",Account Valid")
wscript.echo "Account okay"
else
wscript.echo "Account not valid"
wfile.writeline(mailboxAlias & "," & objAddrEntry.Address & ",Account Invalid")
end if
next
end if
next
next
if nfNonefound = 0 then
wscript.echo "No Delegate forwarding rules found"
wfile.writeline(mailboxAlias & "," & "No Delegate forwarding rules found")
end if
else
Wscript.echo "Error Opening Mailbox"
wfile.writeline(mailboxAlias & "," & "Error Opening Mailbox")
end if
Set msMapiSession = Nothing
Set mrMailboxRules = Nothing

End Sub

function verifyaddress(exlegancydn)

vfQuery = ">LDAP://" & strDefaultNamingContext & "<(legacyExchangeDN=" & exlegancydn & ");name,distinguishedName;subtree"
Com.CommandText = vfQuery
Set Rschk = Com.Execute
aoAccountokay = 0
While Not Rschk.EOF
set objUser = getobject("LDAP://" & replace(rschk.fields("distinguishedName"),"/","\/"))
if objUser.AccountDisabled then
aoAccountokay = 0
else
aoAccountokay = 1
end if
rschk.movenext
wend
rschk.close
set rschk = nothing
set connchk = nothing
set comchk = nothing
verifyaddress = aoAccountokay

end function

26 comments:

Todd said...

Great script, as always. Thank you.

Can this be done without rule.dll?

I'm interested in disabling OOF and all rules using extended MAPI, if possible. We would do this in the event of an employee separation.

Thank you.

Glen said...

Its better to use the Rule.dll because this allows you to read what type of rule it is eg if its a delegate rule etc. You can do it without the rule.dll using a technique such as http://gsexdev.blogspot.com/2005/10/reporting-on-forwarding-rules-in.html . This script just shows any forarding rules. You can modify the OOF status using CDO pretty easily eg

set objSession = CreateObject("MAPI.Session")
strProfile = "YourServer" & vbLf & "YourAlias"
objSession.Logon "Profile Name",,, False,, True, strProfile
objSession.outofoffice = false

To remove the forwarding rules this is a little tricker you could just go though all the assocated rule messages in the inbox and delete them which should have the affect of disabling these rules.

Cheers
Glen

Todd said...

Thank you again, Glen. I took some of your code from that quoted example and did just that--deleted the messages.

Sub Disable_Rules (oUser)
Dim oMAPI, bRules, oInfoStores, oInfoStore, oInbox
Dim oMessages, oMessage, i, sRuleName

WScript.Echo " Checking Rules against " & oUser.MsExchHomeServerName & " for user " & _
oUser.legacyExchangeDN & "..."

Set oMAPI = CreateObject("MAPI.Session")

On Error Resume Next
oMAPI.Logon "","",False,True,True,True, _
Right(oUser.MsExchHomeServerName, _
Len(oUser.MsExchHomeServerName) - InStrRev(oUser.MsExchHomeServerName, "=")) & _
vbLF & oUser.legacyExchangeDN

If Err.number <> 0 Then
WScript.Echo " MAPI logon failed."
WScript.Echo " " & Err.Description
Else
On Error GoTo 0
Set oInfoStores = oMAPI.InfoStores
Set oInfoStore = oMAPI.GetInfoStore
Set oInbox = oMAPI.Inbox

bRules = oInbox.Fields.Item(PR_HAS_RULES)
If bRules Then
WScript.Echo " Mailbox has rules:"
Set oMessages = oInbox.HiddenMessages
i = 0
For Each oMessage in oMessages
If oMessage.Type = "IPM.Rule.Message" Then
On Error Resume Next
sRuleName = ""
sRuleName = oMessage.Fields(PR_RULE_MSG_NAME)
On Error GoTo 0
If sRuleName <> "" And sRuleName <> "#NET FOLDERS#" Then
i = i + 1
WScript.Echo " " & i & ": " & sRuleName
If Not bTestMode Then
oMessage.Delete
WScript.Echo " Deleted!"
End If
End If
End If
Next
WScript.Echo " " & i & " rule(s) found."
Else
WScript.Echo " Mailbox has no rules"
End If

oMAPI.Logoff
Set oInbox = Nothing
Set oInfoStore = Nothing
Set oInfoStores = Nothing
End If

Set oMAPI = Nothing
End Sub

Brandon said...

Hi Glen

From the outset this appears to gather the information that I need, so thank you.

I have a problem though when running the script:
I have copied the vbs and dll file to the same directory and opened a cmd pointing to that directory with an account that has access to the mail boxes.
If I try run the script with the just the server name it gives me an error
can't create object: 'MSExchange.Rules'
If I use the FQDN for the Exchange server the script runs and creates the csv file but it does not add any data.

Any suggestions please?

Glen said...

If your getting the error "can't create object: 'MSExchange.Rules'"

This sounds like you do not have the Rule.dll on the machine your running this script on. Bascially you need to download the rule.dll from somewhere like http://www.cdolive.net/download/ruleasp.zip

and then use regsvr32 to register the dll.

Anonymous said...

Glen,

Great script, and very close to what I need to do...but ever so slightly different. I simply need to take a list of users and find out to which mailboxes they have been assigned as delegates. Basically, we are correcting the legacyExchangeDN for anyone who has been married and their username is now different from their legExDN. (Not my idea...my manager's.) I have worked my way around every problem that will stem from the change except pinpointing the delegates issue. Anyway to slightly modify this script to tell me who the delegates are on the mailbox in Exchange 2003?

Thanks,

Jon

Glen said...

Hi Jon

Have a look at http://gsexdev.blogspot.com/2005/06/reverse-permissions-audit-scripts-part.html

Sound like its what you want

Cheers
Glen

Anonymous said...

I run the script using the fully qualified name of the mail server and I receive a pop-up that states 'Done'. The 'MeetingDelegatesForwards.csv' file is created but only contains the header row.

Help....

Glen said...

Sorry using the FQDN of the mail server would cause the script to fail because it searches based on the Netbios name of the server. Try the netbios name and see how it goes

Anonymous said...

Glen,
I tried the script. It worked initially but in the middle of processing it generates "error opening mailbox" then till the end. I tried two servers, one happend at 66th mailbox and other one happened at 70th mailbox. I have 100-300 users each server. If it was the account I'm using to run it shouldn't be able to any mailbox. But I do get some results with valid or invalid accounts. What did I do wrong?

Erik said...

I've the same problem. Account has proper access, but gets errors starting with 66th mailbox.

Glen said...

in the past what i have found is that the rule.dll holds the connection open to the Exchange server so after you have gone through and number of mailbox you
run into the Mapi session limit
http://support.microsoft.com/kb/842022. I wouldn't recommend adjusting the registry to try and fix this
what has been successfully used by other people is using two script because once the script has finished running it does destroy all the object handles. Trying
implict destruction is VB doesn't seem to help. A lot of this has to do with the way CDO 1.2 threads and the fact that rule.dll was only every written as a sample
to start with.

I've created a version that uses RDO http://msgdev.mvps.org/exdevblog/rdochkdel4.zip. RDO gets around the issue with security prompts ect it also doesn't require separate library's to access the rules and acl collections and because its not using sample code it shouldn't have the issue of leaving the
connection open. http://www.dimastr.com/redemption/.

I've modified the verify address statement so it will include the OU of each account it can find in Active Directory.

Let me know how it goes this was an email i sent a while ago to a person that was having a similar problem but i never got any feedback so not sure if it worked or not

Cheers
Glen

Anonymous said...

Regarding the sessions limit and whatnot. Maybe this will work instead. Perhaps just pass the msMapiSession object into the subroutine and do a msMapiSession.Logoff call before looping to the next mailbox?


servername = wscript.arguments(0)
PR_HAS_RULES = &H663A000B
PR_URL_NAME = &H6707001E
PR_CREATOR = &H3FF8001E
Set fso = CreateObject("Scripting.FileSystemObject")
set wfile = fso.opentextfile("c:\MeetingDelgatesForwards.csv",2,true)
wfile.writeline("Mailbox,ForwadingAddress,Status")
set conn = createobject("ADODB.Connection")
set com = createobject("ADODB.Command")
Set iAdRootDSE = GetObject("LDAP://RootDSE")
Set msMapiSession = CreateObject("MAPI.Session")

strNameingContext = iAdRootDSE.Get("configurationNamingContext")
strDefaultNamingContext = iAdRootDSE.Get("defaultNamingContext")
Conn.Provider = "ADsDSOObject"
Conn.Open "ADs Provider"
svcQuery = "<LDAP://" & strNameingContext & ">;(&(objectCategory=msExchExchangeServer)(cn=" & Servername & "));cn,name,legacyExchangeDN;subtree"
Com.ActiveConnection = Conn
Com.CommandText = svcQuery
Set Rs = Com.Execute
while not rs.eof
GALQueryFilter = "(&(&(&(& (mailnickname=*)(!msExchHideFromAddressLists=TRUE)(| (&(objectCategory=person)(objectClass=user)(msExchHomeServerName=" & rs.fields("legacyExchangeDN") & ")) )))))"
strQuery = "<LDAP://" & strDefaultNamingContext & ">;" & GALQueryFilter & ";distinguishedName,mailnickname,mail;subtree"
com.Properties("Page Size") = 100
Com.CommandText = strQuery
Set Rs1 = Com.Execute
while not Rs1.eof
call procmailboxes(servername,rs1.fields("mail"),msMapiSession)
wscript.echo rs1.fields("mail")
rs1.movenext
wend
rs.movenext
wend
rs.close
wfile.close
set fso = nothing
set conn = nothing
set com = nothing
wscript.echo "Done"




sub procmailboxes(servername,MailboxAlias,msMapiSession)

on error Resume next
msMapiSession.Logon "","",False,True,True,True,Servername & vbLF & MailboxAlias
if err.number = 0 then
on error goto 0
Set mrMailboxRules = CreateObject("MSExchange.Rules")
mrMailboxRules.Folder = msMapiSession.Inbox
Wscript.echo "Checking For any Delegate forwarding Rules"
nfNonefound = 0
for Each roRule in mrMailboxRules
for each aoAction in roRule.actions
if aoAction.ActionType = 7 then
nfNonefound = 1
Wscript.echo "Delegate Rule found Forwards to"
for each aoAdressObject in aoAction.arg
Set objAddrEntry = msMapiSession.GetAddressEntry(aoAdressobject)
wscript.echo "Address = " & objAddrEntry.Address
if verifyaddress(objAddrEntry.Address) = 1 then
wfile.writeline(mailboxAlias & "," & objAddrEntry.Address & ",Account Valid")
wscript.echo "Account okay"
else
wscript.echo "Account not valid"
wfile.writeline(mailboxAlias & "," & objAddrEntry.Address & ",Account Invalid")
end if
next
end if
next
next
if nfNonefound = 0 then
wscript.echo "No Delegate forwarding rules found"
wfile.writeline(mailboxAlias & "," & "No Delegate forwarding rules found")
end if
else
Wscript.echo "Error Opening Mailbox"
wfile.writeline(mailboxAlias & "," & "Error Opening Mailbox")
end if
Set mrMailboxRules = Nothing
msMapiSession.Logoff

End Sub

function verifyaddress(exlegancydn)

vfQuery = "<LDAP://" & strDefaultNamingContext & ">;(legacyExchangeDN=" & exlegancydn & ");name,distinguishedName;subtree"
Com.CommandText = vfQuery
Set Rschk = Com.Execute
aoAccountokay = 0
While Not Rschk.EOF
set objUser = getobject("LDAP://" & replace(rschk.fields("distinguishedName"),"/","\/"))
if objUser.AccountDisabled then
aoAccountokay = 0
else
aoAccountokay = 1
end if
rschk.movenext
wend
rschk.close
set rschk = nothing
set connchk = nothing
set comchk = nothing
verifyaddress = aoAccountokay

end function

Anonymous said...

I have a auser that had a delegate that was removed from AD where do I find this rule so I can delete it. Everyone that send him a meeting request gets a NDR.

Thanks

Glen said...

Removing the delegate from AD will really do nothing you need to login to the mailbox with Outlook and remove the delegate via Outlook. If you can see this delegate in Outlook anymore then you may need to use a Mapi editor like MFCmapi or Outlook spy to delete the rule from rules table.

Cheers
Glen

david said...

I am having an issue with the script. When I run it against one of my 3 mail servers it fails after 50 accounts with:

Object doesn't support this property or method: 'objUser.AccountDisabled'

Any ideas? It works for the other 2 servers.

Glen said...

I've been talking about this script with someone else over the past weeks and i've got an updated version because there was some problems with that original version in that it just read the hidden rule object in folder associated contents and not the Mapi rules table itself. It was also a very crude parser then seemed to error on different objects. To fix this i came up with a version that used redemption instead you can get a copy from http://www.dimastr.com/redemption/download.htm the advantage of this is this actually can read the Rules table in a mailbox so there's no need for the unreliable parser. Unfortunately for this particular guy and i've had a few other people report this sometime is that this would work but after about 32 mailboxes the script would stop because of the Open Mapi Session limit. I couldn't reproduce this and the code looked good in that it should have released the Mapi session when logoff method was called. To work around this problem i created a two script method the premise being that all the mapi code was in one script and the mapi session would get destroyed when the script finished running. I got a mail this morning from this guy and he said it worked okay. I've posted this two script example on http://msgdev.mvps.org/rdombxrules.zip if your interested in the one script its still their as well http://msgdev.mvps.org/rdombxrulesold.zip.

Cheers
Glen

karunakar said...

Hello Glen,

Can you please give me more information on how to use this script.

I don't know much about scripting i am having exactly same problem as you said on article.

Example:

I am sending a Meeting request to Person A and getting bounce back from Person B. Person B has been deleted long ago.

Can you please help me to figure out the rule i checked rules in person A mailbox nothing there.

Do need to change anything in the script to get the details?

Thanks.
Karunakar

Glen said...

This script will generate a report of any mailboxes with these type of rules. As long as the account you are using has full rights to the mailboxes you trying to report on it should work okay. If your getting error let me know.

To fix the problem you need to use something like MFCmapi have a look at the procedure outlined in http://groups.google.com.au/group/It_discussions/browse_thread/thread/cfc0a021715db0af

Cheers
Glen

Karunakar said...

Thank you very much Glen, I have deleted that entry but still i can see delegates in the list.

If you don't mind can you give any other method to figure out this E-mail NDR reports.

Actually we have deleted that user but still going there and getting bounce back.

Thanks.

karunakar said...

Thank you again and again Glen...

I have fixed this issue by following your google link.

Thank you mate, you are great.

karunakar

Anonymous said...

Glen...Thanks you.
Discovered delegates and no more NDR's now

Sarvesh said...

Hi Glen,

This is a nice script.

But my story is little different.
We have some users where we have some bad users added in delegated list of some users. so we were getting NDR's.. oh.. sorry some phantom NDS as per my manager.. So we started researching.. found the MFCMAPI.exe way.. we straight deleted all the rules from Rule table. - “Schedule+ EMS Interface”
Now the situation is i can see old users which were delegated but delegation is not working.
What I tried so far:
Tried to remove all the users from delegate list.
Added them back.. and checked if delegation works..-- found that Delegation is not working...

Questions for you :

Is there any way to recover the deleted Rule Table Entry ?

How can we re-create delegated Rule Table Entry ?

Where is this entire configuration stored which we can see for every user apart from MFCMAPI.exe or Mdbview.exe ?
a. Database
b. Mailbox Object which is created in database.
3. Some another location ??

Please help me.

Regards,
--
S a r v e s h

Anonymous said...

Good work, well done! Thank you!

Anonymous said...

Looks like just what I want, but I get this when I attempt to run it:

C:\chkdelv4.vbs(44, 1) Microsoft VBScript runtime error: ActiveX component can't
create object: 'MAPI.Session'

Any clues? Thanks for the help and the script!

Anonymous said...

Glen,
I tried the rdo version and got the prompt. What should I do here? The prompt is prompted with exchange server name and mailbox name.

Thanks.