Thursday, July 05, 2007

Synconize items from a public folder to a mailbox folder with WebDAV replication

A while ago I wrote this script to synchronize contacts from a mailbox folder to a central contacts folder using Webdav replication and CDO 1.2 as the copy mechanism. A few people have asked about going the other way eg from a central contacts folder or calendar folder to a local mailbox folder so I've managed to find a bit of time this week to create a port that would do this. The underlying functionality of how this work is written up in the other post so i won't repeat this the main points with the new script are

  • There are four scripts in the download like the previous post there is both FBA and NTLM/Basic version to cater for different authentication. Theres also a separate version for contacts/mail objects and calendar objects.
  • The main differences with the calendar version is that as well as copying the calendar item to the target folder it changes the colour of the appointment to blue and also sets the freebusy status of this appointment to FREE. The following lines are responsible for this
objCopyappt.Fields.Add "0x8214", vbLong, 2, "0220060000000000C000000000000046"
objCopyappt.Fields.Add "0x8205", vbLong, 0, "0220060000000000C000000000000046"

  • WebDAV replication by default only replicates in chunks of 512 items within one request. This script caters for folders where there are more then 512 items by looking at the number of items returned in the request and then making furthers request until all items in the folder are replicated.
Depending on the version your using you will need to configure all the following variable for the script to work correctly

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

I've put a download copy of the code here the code itself looks like

snServername = "servername"
mnMailboxname = "username"
domain = "domain"
strpassword = "password"
strusername = domain & "\" & mnMailboxname
DestinURL = "https://" & snServername & "/exchange/" & mnMailboxname & "/mailboxfolder/"
SourceURL = "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 req = createobject("microsoft.xmlhttp")
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")
dfFolderID = getfid()
wscript.echo dfFolderID
colbblob = Collabblobget()
QueryPF(colbblob)


sub QueryPF(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 Copyappt(Octenttohex(oNode1.nodeTypedValue),oNode2.text)
case "delete" wscript.echo oNode3.text
wscript.echo oNode2.text
Deleteappt(oNode2.text)
case "change" Wscript.echo "Change"
call Deleteappt(oNode2.text)
call Copyappt(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
wscript.echo idNodeList.length
if idNodeList.length = 512 then QueryPF(colblob)

End Sub

Sub Copyappt(messageEntryID,ReplID)

set objappt = objSession.getmessage(messageEntryID,objpubstore.ID)
set objCopyappt = objappt.copyto(dfFolderID)
objCopyappt.Unread = false
objCopyappt.Fields.Add "0x8542", vbString,
ReplID,"0820060000000000C000000000000046"
objCopyappt.Fields.Add "0x8214", vbLong, 2, "0220060000000000C000000000000046"
objCopyappt.Fields.Add "0x8205", vbLong, 0, "0220060000000000C000000000000046"
objCopyappt.Update
Set objCopyappt = Nothing
wscript.echo objappt.subject

end Sub

Sub Deleteappt(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
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.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

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



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



function getfid()

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 "cookie", reqsessionID
req.SetRequestHeader "cookie", reqCadata
req.setRequestHeader "Depth", "0"
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
getfid = 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

2 comments:

Torti said...

Hi,

hope its not to late to ask some question to this skript.

First of all: THX ;-)

Second: I try to use the script to sync a Public Folder to a user Folder. If I run the skript (NTML) under the Useraccount it works perfectly.

But I want to syns another Userfolder.

Example: I want to sync PubFold "a" under an Administrator-Account to UserFolder "Contact". If the Adminstrator has no Accessrights to this folder the script returns "Access denied".
If Accessrights are set, the Script returns in Collabblobget "404 Resource not found"

Hope someone could give me a hint what I´m doing wrong.

Glen said...

Administrator accounts are denied access to any other mailbox other then their own. To use this script how you want you need to create an account for the purpose and then assign that account super user rights to the store (or just to the other mailboxes you want to sync). There a walk though of assigning rights to a super user on http://www.petri.co.il/grant_full_mailbox_rights_on_exchange_2000_2003.htm

Cheers
Glen