Wednesday, March 29, 2006

Displaying the Junk-Email setting of all users on a Server (Exchange 2003)

I got a good question this week about one of my previous posts about displaying and setting junk Email setting on Exchange 2003. What this person wanted to do was display the junkemail setting of all users on a server so they could see who had enabled junk email filtering in OWA. This was a good idea which I thought could be expended a little further an also include reporting on the junk email settings from Outlook 2003 as well. So what I’ve done is put together a script that creates a CSV file that shows you firstly weather the Extended Junk Email rule has been created in a mailbox, If junk email filtering is enabled in OWA, what level junk email filter has been set to in Outlook 2003 and what the delete setting is set to in Outlook 2003. What you end up with is a pretty useful little report on how people are using the Junk Email filtering setting in Outlook and OWA on your server.

I’ve created two versions of the script the first is a WebDAV version which can be run locally or remotely and I’ve also done a Exoledb version that must be run locally on the server. Both scripts work the same way they do a search of the inbox folder for the extended junk email rule by searching for IPM.ExtendedRule.Message items that are named JunkEmailRule. The existence of this rules mean someone has either turned on junk email filtering in OWA or connected to the mailbox with Outlook 2003. To see what the current junk-email filtering setting are you need to check properties on the rule object itself. Checking the PR_Rulemsgstate http://schemas.microsoft.com/mapi/proptag/x65E90003 property will tell you weather someone has disabled this rule via OWA if the value is 48 then the check box will be un-ticked in OWA. The other two setting I’ve mentioned before.

http://schemas.microsoft.com/mapi/proptag/0x61010003 which stores a long value that sets that level of junk email protection you want the long values for each of the setting are

No Automatic filtering = -1
Low = 6
High = 3
Safe Lists only = -2147483648

The “Permanently delete suspected junk e-mail instead of moving it to the Junk E-mail folder” is stored in the http://schemas.microsoft.com/mapi/proptag/0x61020003 as

Disabled = 0
Enabled = 1

The script reports the result to a csv file on the c drive called junkemailsettings.csv

To run the scripts for the WebDAV version it uses the Exadmin virtual directory so you need to include the servername you want to run it against and also your default domain name (you can find this by checking the properties of the exadmin virtual directory in IIS admin). Eg cscript showjmailopsdav1.vbs servername domain.com

To run the exoledb version you just need the servername as command line parameters. Both scripts require that you run them with an account that has rights to every users mailbox

I’ve put a downloadable copy of the script here the webdav version look like

servername = wscript.arguments(0)
domainname = wscript.arguments(1)
Set fso = CreateObject("Scripting.FileSystemObject")
set wfile = fso.opentextfile("c:\Junkemailsettings.csv",2,true)
wfile.writeline("Mailbox,OWAJunkEmailState,Outlook Filter Setting,Outlook Delete
Junk Email Setting")
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,mail;subtree"
com.Properties("Page Size") = 100
Com.CommandText = strQuery
Set Rs1 = Com.Execute
while not Rs1.eof
call ProcMailbox(servername,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 ProcMailbox(snServername,mnMailboxname)
wscript.echo "Processing : " & mnMailboxname
SourceURL = "http://" & snServername & "/exadmin/" & domainname & "/mbx/" &
mnMailboxname & "/inbox/"
strQuery = "<?xml version=""1.0""?><D:searchrequest xmlns:D = ""DAV:"" >"
strQuery = strQuery & "<D:sql>SELECT ""DAV:displayname"", ""http://schemas.microsoft.com/mapi/proptag/x65E90003"",
"
strQuery = strQuery & """http://schemas.microsoft.com/mapi/proptag/x61010003"",
""http://schemas.microsoft.com/mapi/proptag/x61020003"""
strQuery = strQuery & " FROM scope('shallow traversal of """
strQuery = strQuery & SourceURL & """') Where ""DAV:ishidden"" = True AND ""DAV:isfolder""
= False AND "
strQuery = strQuery & """http://schemas.microsoft.com/exchange/outlookmessageclass""
= 'IPM.ExtendedRule.Message' "
strQuery = strQuery & "AND ""http://schemas.microsoft.com/mapi/proptag/0x65EB001E""
= 'JunkEmailRule' </D:sql></D:searchrequest>"
set req = createobject("microsoft.xmlhttp")
req.open "SEARCH", SourceURL, false,"", ""
req.setrequestheader "Content-Type", "text/xml"
req.setRequestHeader "Translate","f"
req.send strQuery
wscript.echo "Status: " & req.status
wscript.echo "Status text: " & req.statustext
If req.status >= 500 Then
ElseIf req.status = 207 Then
set oResponseDoc = req.responseXML
set oNodeList = oResponseDoc.getElementsByTagName("a:href")
set ostateList = oResponseDoc.getElementsByTagName("d:x65E90003")
set ofilterList = oResponseDoc.getElementsByTagName("d:x61010003")
set odeleteList = oResponseDoc.getElementsByTagName("d:x61020003")
For i = 0 To (oNodeList.length -1)
set oNode = oNodeList.nextNode
Filterlist = ofilterList(i).nodetypedvalue
if filterlist = "" then
flist = "No Automatic filtering"
else
select case Filterlist
case -1 flist = "No Automatic filtering"
case 6 flist = "Low"
case 3 flist = "High"
case -2147483648 flist = "Safe Lists only"
end select
end if
delist = odeleteList(i).nodetypedvalue
if delist = "" then
delset = "Disabled"
else
select case delist
case 1 delset = "Enabled"
case 0 delset = "Disabled"
end select
end if
Rule_State = ostateList(i).nodetypedvalue
select case Rule_State
case 48 wfile.writeline(mnMailboxname & "," & "OWA Junk Email Setting Off," &
flist & "," & delset)
case 49 wfile.writeline(mnMailboxname & "," & "OWA Junk Email Setting Enabled,"
& flist & "," & delset)
end Select
Next
if oNodeList.length = 0 then
wfile.writeline(mnMailboxname & "," & "No Junk Email rules Exists")
End if
Else
End If

end sub

Tuesday, March 21, 2006

Replicating an Exchange Folder using WebDAV Replication and CDO 1.2

One of the lesser know and cooler (well I think) features of WebDAV with Exchange is the ability to perform replication of a collection (or a folder). The Exchange SDK has some documentation on how this works and the general plumbing of how you structure requests and responses. Previously I had a script that would copy contacts between a public folder and mailbox this was a pretty simple script with no intelligence so it would produce duplicates if it was run twice etc. I wanted something that would go a little further then this script and allow full one way sync of a private contacts folder with a public contacts folder so basically a script that could be run at intervals and any updates to the private contacts folder such as changes of email addresses, phone number, addition of contacts or deletion of contacts would be reflected in the public contacts folder. For this combining the original CDO 1.2 script to copy contacts (the reason that CDO is used to do the copy instead of WebDAV is the ability to retain the MAPI properties on the item being copied) with WebDAV replication gave the desired functionality.

How it works.

This script is split into many functions and subroutines all which perform different functions here’s a basic run though of how the script works.

The front end of the script sets up variables for the folders that you wish to copy I used this script to copy contacts but it will work fine to replicate any other folder (I haven’t tested calendars). I’ve created two copies of the script the first is a non FBA version and the second is a FBA version if your running forms based authentication. In the FBA version some code is included to perform a synthetic form logon using the username and password and then retrieve the cookies for further use in the script for authentication.

The next part of the script performs a Mapi logon to the source mailbox the session is require to perform the copying of the contacts.

The getpfid function returns the MAPI EntryID for the destination folder by using WebDAV to return the entryID from the destination folder URL. This EntryID is required to perform the copy via CDO 1.2.

To use WebDAV replication you need to use a collblob which “is the opaque binary stream generated by the server that represents the state of the contents of a collection. The collblob contains information about changes in the contents of the collection on the server and the query specified in the request for the Manifest of a Collection. The collblob tracks only resources that match the search criteria specified in the SEARCH Method query in the manifest request. “. ref In WebDAV this collblob property itself is returned as a BASE 64 encoded string. You need to store this property and include it in any queries that you make regarding the folder you are replicating. With this script what I’ve done is when a query is made to ascertain the replication status of the folder the result for the collblob is stored in a custom property on the destination folder. So on the destination folder there is a property that is based on the name of the source folder that holds the collblob from the replication query of the source folder. This is what the next function does Collabblobget retrieves the previous collblob from the destination folder to be used in the query of the source folder.

The QueryMailbox sub executes the WebDAV query on the source folder using the previous collblob if one exists. The result of this query are then parsed the collblob that is returned is first stored in a custom property on the source folder as previously explained. The result of this query will contain all the resources within the collection that have been added, changed or deleted since the last query with that collblob. A case statement is used to create a logic tree on the changetype property depending on what type of change it is different functions and sub’s are called. If this is a new resource then the CopyContact sub is called and the EntryID and repl-UID is passed in. The CopyContact sub uses CDO 1.2 to copy the item between the mailbox and public folder and also it creates a custom property on the item to store the repl-UID of the original items this is important when it comes to making changes or deletion in the future. If the changetype is modify then instead of finding out what property has been changed I decided to simplify things by just deleting the old item and then re-copying the source item. If the changetype is delete then the DeleteContact sub is called which finds the contact in the public folder by searching on the Repl-UID stored in the custom property in the destination folder and then do a WebDAV delete on the resource.

Running the script

Before you run the script you need to customise different variables with the script for the
snServername = "servername"
mnMailboxname = "mailbox"
SourceURL = "http://" & snServername & "/exchange/" & mnMailboxname & "/contacts/"
DestinURL = "http://" & snServername & "/public/foldername/"

For the FBA script you also need the password and domain of the account you are going to use in the synthetic logon.

To run the script itself just use cscript replfld.vbs

This is script is not really designed to sync multiple folders at a time eg if you wanted to sync two users contacts folder with on public folder this type of script will still produce duplicates at the destination folder. I’m working on another script to do this.

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

snServername = "servername"
mnMailboxname = "mailbox"
domain = "domain"
strpassword = "password"
strusername = domain & "\" & mnMailboxname
SourceURL = "https://" & snServername & "/exchange/" & mnMailboxname &
"/contacts/"
DestinURL = "https://" & snServername & "/public/foldername/"

szXml = "destination=https://" & snServername & "/exchange/&flags=0&username=" &
strusername
szXml = szXml & "&password=" & strpassword & "&SubmitCreds=Log On&forcedownlevel=0&trusted=0"
set req = createobject("microsoft.xmlhttp")
req.Open "post", "https://" & snServername & "/exchweb/bin/auth/owaauth.dll",
False
req.send szXml
reqhedrarry = split(req.GetAllResponseHeaders(), vbCrLf,-1,1)
for c = lbound(reqhedrarry) to ubound(reqhedrarry)
if instr(lcase(reqhedrarry(c)),"set-cookie: sessionid=") then reqsessionID =
right(reqhedrarry(c),len(reqhedrarry(c))-12)
if instr(lcase(reqhedrarry(c)),"set-cookie: cadata=") then reqcadata=
right(reqhedrarry(c),len(reqhedrarry(c))-12)
next
set objSession = CreateObject("MAPI.Session")
strProfile = snServername & vbLf & mnMailboxname
objSession.Logon "",,, False,, True, strProfile
Set objInfoStore = objSession.GetInfoStore(objSession.Inbox.StoreID)
Set objpubstore = objSession.InfoStores("Public Folders")
pfPublicFolderID = getpfid()
colbblob = Collabblobget()
QueryMailbox(colbblob)

wscript.echo "Done"

sub QueryMailbox(colbblob)

strQuery = "<?xml version=""1.0""?><D:searchrequest xmlns:D = ""DAV:"" xmlns:R=""http://schemas.microsoft.com/repl/""><R:repl><R:collblob>"
& colbblob & "</R:collblob></R:repl>"
strQuery = strQuery & "<D:sql>SELECT ""DAV:href"", ""urn:schemas:httpmail:subject"",
""http://schemas.microsoft.com/mapi/proptag/x0fff0102"",""
http://schemas.microsoft.com/repl/repl-uid""
"
strQuery = strQuery & " FROM scope('shallow traversal of """
strQuery = strQuery & SourceURL & """') Where ""DAV:ishidden"" = False AND ""DAV:isfolder""
= False "
strQuery = strQuery & "</D:sql></D:searchrequest>"
req.open "SEARCH", SourceURL, false
req.setrequestheader "Content-Type", "text/xml"
req.SetRequestHeader "cookie", reqsessionID
req.SetRequestHeader "cookie", reqCadata
req.setRequestHeader "Translate","f"
req.send strQuery
If req.status >= 500 Then
wscript.echo "Status: " & req.status
wscript.echo "Status text: An error occurred on the server."
ElseIf req.status = 207 Then
wscript.echo "Status: " & req.status
wscript.echo "Status text: " & req.statustext
set oResponseDoc = req.responseXML
set oNodeList = oResponseDoc.getElementsByTagName("d:collblob")
For i = 0 To (oNodeList.length -1)
set oNode = oNodeList.nextNode
colblob = oNode.Text
Collabblobset(colblob)
Next
set idNodeList = oResponseDoc.getElementsByTagName("f:x0fff0102")
set replidNodeList = oResponseDoc.getElementsByTagName("d:repl-uid")
set replchangeType = oResponseDoc.getElementsByTagName("d:changetype")
for id = 0 To (idNodeList.length -1)
set oNode1 = idNodeList.nextNode
set oNode2 = replidNodeList.nextNode
set oNode3 = replchangeType.nextNode
select case oNode3.text
case "new" call CopyContact(Octenttohex(oNode1.nodeTypedValue),oNode2.text)
case "delete" wscript.echo oNode3.text
wscript.echo oNode2.text
DeleteContact(oNode2.text)
case "change" Wscript.echo "Change"
call DeleteContact(oNode2.text)
call CopyContact(Octenttohex(oNode1.nodeTypedValue),oNode2.text)
end select
next
Else
wscript.echo "Status: " & req.status
wscript.echo "Status text: " & req.statustext
wscript.echo "Response text: " & req.responsetext
End If

End Sub

function Collabblobget()

xmlreqtxt = "<?xml version='1.0'?><a:propfind xmlns:a='DAV:' xmlns:cp='" &
SourceURL & "'><a:prop><cp:collblob/></a:prop></a:propfind>"
req.open "PROPFIND", DestinURL, false, "", ""
req.setRequestHeader "Content-Type", "text/xml; charset=""UTF-8"""
req.setRequestHeader "Depth", "0"
req.SetRequestHeader "cookie", reqsessionID
req.SetRequestHeader "cookie", reqCadata
req.setRequestHeader "Translate", "f"
req.send xmlreqtxt
set oResponseDoc = req.responseXML
set oCobNode = oResponseDoc.getElementsByTagName("d:collblob")
For i1 = 0 To (oCobNode.length -1)
set oNode = oCobNode.nextNode
Collabblobget = oNode.Text
Next

End function

Sub Collabblobset(colblob)
xmlstr = "<?xml version=""1.0""?>" _
& "<g:propertyupdate " _
& " xmlns:g=""DAV:"" xmlns:e=""http://schemas.microsoft.com/exchange/""" _
& " xmlns:dt=""urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/"" " _
& " xmlns:cp=""" & SourceURL & """ " _
& " xmlns:header=""urn:schemas:mailheader:"" " _
& " xmlns:mail=""urn:schemas:httpmail:""> " _
& " <g:set> " _
& " <g:prop> " _
& " <cp:collblob>" & colblob & "</cp:collblob> " _
& " </g:prop> " _
& " </g:set> " _
& "</g:propertyupdate>"

req.open "PROPPATCH", DestinURL, False
req.setRequestHeader "Content-Type", "text/xml;"
req.SetRequestHeader "cookie", reqsessionID
req.SetRequestHeader "cookie", reqCadata
req.setRequestHeader "Translate", "f"
req.setRequestHeader "Content-Length:", Len(xmlstr)
req.send(xmlstr)
wscript.echo req.responsetext

end sub

Sub CopyContact(messageEntryID,ReplID)
set objcontact = objSession.getmessage(messageEntryID)
set objCopyContact = objcontact.copyto(pfPublicFolderID,objpubstore.ID)
objCopyContact.Unread = false
objCopyContact.Fields.Add "0x8542", vbString, ReplID,"0820060000000000C000000000000046"
objCopyContact.Update
Set objCopyContact = Nothing
wscript.echo objcontact.subject

end Sub

Sub DeleteContact(replUID)

strQuery = "<?xml version=""1.0""?><D:searchrequest xmlns:D = ""DAV:"">"
strQuery = strQuery & "<D:sql>SELECT ""DAV:Displayname"""
strQuery = strQuery & " FROM scope('shallow traversal of """
strQuery = strQuery & DestinURL & """') Where ""http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-C000-000000000046}/0x8542""
= '" & replUID & "' AND ""DAV:isfolder"" = False "
strQuery = strQuery & "</D:sql></D:searchrequest>"
req.open "SEARCH", DestinURL, false
req.setrequestheader "Content-Type", "text/xml"
req.SetRequestHeader "cookie", reqsessionID
req.SetRequestHeader "cookie", reqCadata
req.setRequestHeader "Translate","f"
req.send strQuery
wscript.echo req.responsetext
If req.status >= 500 Then
wscript.echo "Status: " & req.status
wscript.echo "Status text: An error occurred on the server."
ElseIf req.status = 207 Then
wscript.echo "Status: " & req.status
wscript.echo "Status text: " & req.statustext
set oResponseDoc = req.responseXML
set oNodeList = oResponseDoc.getElementsByTagName("a:href")
For i = 0 To (oNodeList.length -1)
set oNode = oNodeList.nextNode
wscript.echo oNode.text
req.open "DELETE", oNode.text, false
req.SetRequestHeader "cookie", reqsessionID
req.SetRequestHeader "cookie", reqCadata
req.send
wscript.echo "Status: " & req.status
Next
Else
wscript.echo "Status: " & req.status
wscript.echo "Status text: " & req.statustext
wscript.echo "Response text: " & req.responsetext
End If

end Sub

function getpfid()

xmlreqtxt = "<?xml version='1.0'?><a:propfind xmlns:a='DAV:' xmlns:e='http://schemas.microsoft.com/mapi/proptag/'><a:prop><e:x0FFF0102

/></a:prop></a:propfind>"
req.open "PROPFIND", DestinURL, false, "", ""
req.setRequestHeader "Content-Type", "text/xml; charset=""UTF-8"""
req.setRequestHeader "Depth", "0"
req.SetRequestHeader "cookie", reqsessionID
req.SetRequestHeader "cookie", reqCadata
req.setRequestHeader "Translate", "f"
req.send xmlreqtxt
set oResponseDoc = req.responseXML
set oNodeList = oResponseDoc.getElementsByTagName("d:x0FFF0102")
For i = 0 To (oNodeList.length -1)
set oNode = oNodeList.nextNode
getpfid = Octenttohex(oNode.nodeTypedValue)
Next

end function

Function Octenttohex(OctenArry)
ReDim aOut(UBound(OctenArry))
For i = 1 to UBound(OctenArry) + 1
if len(hex(ascb(midb(OctenArry,i,1)))) = 1 then
aOut(i-1) = "0" & hex(ascb(midb(OctenArry,i,1)))
else
aOut(i-1) = hex(ascb(midb(OctenArry,i,1)))
end if
Next
Octenttohex = join(aOUt,"")
End Function

Thursday, March 09, 2006

Using Monad to query an Exchange Mailbox via WebDAV to display Unread Emails

I’ve had a chance this week to play around a bit more with MSH and the .NET 2.0 framework and thought I’d share this sample that uses the .net framework to query an Exchange mailbox via WebDAV to display the number of unread email in a mailbox and the subject of the unread email. One of the good things with monad is that you can use both Com and .NET objects pretty easy so all the Microsoft.http browser object stuff will still work but its better to use the new stuff whenever you can. Vivek and Lee Holmes have come up with a good sample of doing a http get using Monad. This helped along with the basic .NET sample from the Exchange SDK in coming up with a simple script that queries an Exchange mailbox using the currently logged on users credentials for any email that is unread. The query itself is a simple WebDAV search that looks for any mail in the inbox folder where the urn:schemas:httpmail:read property is false.

The script itself takes two command-line parameters which is the servername and the mailbox alias (in Exchange 2003 you could use the email address). Eg to run the script you can use displayunread.msh servername mailboxalias (or displayunread.msh servername address@domain.com in Exchange 2003 would work)

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


param([String] $servername = $(throw "Please specify the Servername"),
[String] $mailbox = $(throw "Please specify a Mailbox"))
$strRootURI = "http://" + $servername + "/exchange/" + $mailbox + "/inbox"
$strQuery = "<?xml version=`"1.0`"?><D:searchrequest xmlns:D = `"DAV:`" ><D:sql>
SELECT `"DAV:displayname`",`"urn:schemas:httpmail:subject`" FROM scope('shallow
traversal of `"" + $strRootURI + "`"')
Where `"DAV:ishidden`" = False AND `"DAV:isfolder`" = False AND
`"urn:schemas:httpmail:read`" = false</D:sql></D:searchrequest>"
$WDRequest = [System.Net.WebRequest]::Create($strRootURI)
$WDRequest.ContentType = "text/xml"
$WDRequest.Headers.Add("Translate", "F")
$WDRequest.Method = "SEARCH"
$WDRequest.UseDefaultCredentials = $True
$bytes = [System.Text.Encoding]::UTF8.GetBytes($strQuery)
$WDRequest.ContentLength = $bytes.Length
$RequestStream = $WDRequest.GetRequestStream()
$RequestStream.Write($bytes, 0, $bytes.Length)
$RequestStream.Close()
$WDResponse = $WDRequest.GetResponse()
$ResponseStream = $WDResponse.GetResponseStream()
$ResponseXmlDoc = new-object System.Xml.XmlDocument
$ResponseXmlDoc.Load($ResponseStream)
$DisplayNameNodes = $ResponseXmlDoc.GetElementsByTagName("d:subject")
""
"Number of Unread Email : " + $DisplayNameNodes.Count
$DisplayNameNodes

Thursday, March 02, 2006

Updating appointments that are 1 hour out because of the Commonwealth Games Timezone Changes

Since i posted this last month I had a few questions around updating appointments that appear 1 hour out after you have updated the Timezone patch for the Commonwealth games. Firstly the only supported method of updating affected appointment is in http://www.microsoft.com/australia/timezone/2005.aspx which is to export the appointments in the overlap period to a text file, delete the appointments and then re-import those appointments.

One completely unsupported method to update appointments is to use CDOEX and Exoledb to move the appointment times one hour into the past for appointments that exists in the overlap period. One issue with using this method is that if you have any reoccurring appointments that have been expanded within the overlap period then changing the date and time on these instances of the recurring appointments will break the recursion on that instance of the expanded appointment so although outlook will show the appointment now occurs at the right time is will show the circle with the cross through it to indicate this appointment is an exception to the normal recurrence of the appointment. The other issue is that apart from myself this method hasn’t been really tested so there may be some other potential snags. But if you’re looking for something to test in a development environment to see if you can fix this problem this script could come in handy. The other problem is there has now been a patch released for home users that modifies the current timezone instead of adding a new time-zone. Its recommended that you don’t use this patch if you use Exchange or OWA and its quite possible that machines will have this patch applied and have created appointments that appear correct with the old timezone data. In this case running this script would actually make those appointments appear wrongly. You really need to be aware of how your machines have been patched before contemplating a fix.

The Script

Before you run the fix script I recommend you run a scan using another script I posted here. This will give you a list of all the appointments that are in the affected overlap period and which appointments will be modified by the script. The fix script queries for appointments within the overlap period that appear to be created from machines that haven’t had the patch applied or from OWA where the Timezone has been updated in the options. By looking at the urn:schemas:calendar:timezoneid property or the http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-C000-000000000046}/0x8234 mapi property. The script then shifts the time of the appointment back one hour by adjusting the start and end times and updates the timezone information so that if the script is run again it wont modify the appointments for a second time.

Before using the script you need to update the domainname variable within the script to reflect the default SMTP domain name you are using eg

domainname = "domain.com"


The script uses Exoldb so it needs to be run from the server where the mailbox is located. The script is designed to be run on one mailbox at a time so to run the script you need to enter the mailbox you what to run it against as a commandline parameter eg cscript gapchangeex.vbs username. As stated before don’t run this script in production unless you have thoroughly tested it.

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

user = wscript.arguments(0)
Set fso = CreateObject("Scripting.FileSystemObject")
fname = "c:\" & wscript.arguments(0) & ".csv"
set wfile = fso.opentextfile(fname,2,true)
wfile.writeline("User,Subject,UTC timestart,UTC timeend, InstanceType,
OutlookTimezone,CDOTimezoneEnumID")
public datefrom
public dateto
datefrom = "2006-03-26T10:00:00Z"
dateto = "2006-04-02T10:00:00Z"
domainname = "domain.com"
sConnString = "file://./backofficestorage/" & domainname
sConnString = sConnString & "/mbx/" & user & "/calendar"
call QueryCalendarFolder(sConnString,user)

wscript.echo "Done"


Public Sub QueryCalendarFolder(sConnString,user)
SSql = "SELECT ""DAV:href"", ""DAV:parentname"", ""urn:schemas:calendar:timezoneid"",
""urn:schemas:httpmail:subject"", "
SSql = SSql & """http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-C000-000000000046}/0x8234"",
""urn:schemas:calendar:timezone"", "
SSql = SSql & """urn:schemas:calendar:instancetype"", ""urn:schemas:calendar:dtstart""
, ""urn:schemas:calendar:dtend"" "
SSql = SSql & "FROM scope('shallow traversal of """ & sConnString & """') "
SSql = SSql & " Where ""DAV:isfolder"" = false AND ""DAV:ishidden"" = false "

SSql = SSql & "AND ""urn:schemas:calendar:dtend"" > CAST(""" & datefrom & """ as
'dateTime') " _
& "AND ""urn:schemas:calendar:dtstart"" < CAST(""" & dateto & """ as 'dateTime')"

Set oConn = CreateObject("ADODB.Connection")
oConn.Provider = "Exoledb.DataSource"
oConn.Open sConnString
Set oRecSet = CreateObject("ADODB.Recordset")
oRecSet.CursorLocation = 3
oRecSet.Open sSQL, oConn.ConnectionString
if err.number <> 0 then wfile.writeline(user & "," & "Error Connection to
Mailbox")
While oRecSet.EOF <> True
select case oRecSet.fields("http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-C000-000000000046}/0x8234").value
case "(GMT+10:00) Canberra, Melbourne, Sydney" susapt = 1
upto = "(GMT+10:00) Canberra, Melbourne, Sydney (Commonwealth Games)"
case "(GMT+09:30) Adelaide" susapt = 1
upto = "(GMT+09:30) Adelaide (Commonwealth Games)"
case "(GMT+10:00) Hobart" susapt = 1
upto = "(GMT+10:00) Hobart (Commonwealth Games)"
case "(GMT+10:00) Canberra, Melbourne, Sydney (Commonwealth Games)" susapt = 2
case "(GMT+09:30) Adelaide (Commonwealth Games)" susapt = 2
case "(GMT+10:00) Hobart (Commonwealth Games)" susapt = 2
end select
select case oRecSet.fields("urn:schemas:calendar:timezoneid").value
case "78" susapt = 2
case "79" susapt = 2
case "80" susapt = 2
case "57" susapt = 1
uptoid = "78"
case "19" susapt = 1
uptoid = "79"
case "42" susapt = 1
uptoid = "80"
end select
if susapt = 0 then
if instr(oRecSet.fields("urn:schemas:calendar:timezone").value,"TZID:GMT +0930")
then
susapt = 1
upto = "(GMT+09:30) Adelaide (Commonwealth Games)"
end if
if instr(oRecSet.fields("urn:schemas:calendar:timezone").value,"TZID:GMT +1000")
then
susapt = 1
upto = "(GMT+10:00) Canberra, Melbourne, Sydney (Commonwealth Games)"
end if

end if
if susapt = 1 then
Wscript.echo User
wscript.echo oRecSet.fields("DAV:Href").value
wscript.echo oRecSet.fields("urn:schemas:httpmail:subject").value
wscript.echo oRecSet.fields("http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-C000-000000000046}/0x8234").value
wscript.echo oRecSet.fields("urn:schemas:calendar:timezoneid").value
wscript.echo oRecSet.fields("urn:schemas:calendar:instancetype").value
wscript.echo
wfile.writeline(user & "," &
oRecSet.fields("urn:schemas:httpmail:subject").value & ","_
& oRecSet.fields("urn:schemas:calendar:dtstart").value & "," &
oRecSet.fields("urn:schemas:calendar:dtend").value _
& "," & oRecSet.fields("urn:schemas:calendar:instancetype").value & "," _
& replace(replace(oRecSet.fields("http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-C000-000000000046}/0x8234").value,vbcrlf,""),",","")
_
& "," & oRecSet.fields("urn:schemas:calendar:timezoneid").value)
set apptobj = createobject("ADODB.Record")
apptobj.open cstr(oRecSet.fields("DAV:HREF").value),,3
wscript.echo dateadd("h",-1,oRecSet.Fields("urn:schemas:calendar:dtstart").value)
apptobj.Fields("urn:schemas:calendar:dtstart").value = dateadd("h",-1,oRecSet.Fields("urn:schemas:calendar:dtstart").value)
apptobj.Fields("urn:schemas:calendar:dtend").value = dateadd("h",-1,oRecSet.Fields("urn:schemas:calendar:dtend").value)
if uptoid <> "" then apptobj.Fields("urn:schemas:calendar:timezoneid").value =
uptoid
if upto <> "" then apptobj.Fields("http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-C000-000000000046}/0x8234").value
= upto
apptobj.Fields.update
apptobj.close
uptoid = ""
upto = ""
end if
susapt = 0
oRecSet.MoveNext
wend
oRecSet.Close
oConn.Close
Set oRecSet = Nothing
Set oConn = Nothing
End Sub