Skip to main content

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

Popular posts from this blog

Downloading a shared file from Onedrive for business using Powershell

I thought I'd quickly share this script I came up with to download a file that was shared using One Drive for Business (which is SharePoint under the covers) with Powershell. The following script takes a OneDrive for business URL which would look like https://mydom-my.sharepoint.com/personal/gscales_domain_com/Documents/Email%20attachments/filename.txt This script is pretty simple it uses the SharePoint CSOM (Client side object Model) which it loads in the first line. It uses the URI object to separate the host and relative URL which the CSOM requires and also the SharePointOnlineCredentials object to handle the Office365 SharePoint online authentication. The following script is a function that take the OneDrive URL, Credentials for Office365 and path you want to download the file to and downloads the file. eg to run the script you would use something like ./spdownload.ps1 ' https://mydom-my.sharepoint.com/personal/gscales_domain_com/Documents/Email%20attachments/filena

A walk-though using the Graph API Mailbox reports in Powershell

Quite recently the Reporting side of the Graph API has moved in GA from beta, there are quite a number of reports that can be run across various Office365 surfaces but in this post I'm going to focus on the Mailbox related ones. Accessing Office365 Reports using Powershell is nothing new and has been available in the previous reporting endpoint  https://msdn.microsoft.com/en-us/library/office/jj984326.aspx however from the end of January many of these cmdlets are now being depreciated in favour of the Graph API  https://msdn.microsoft.com/en-us/library/office/dn387059.aspx . Prerequisites  In comparison to using the Remote PowerShell cmdlets where only the correct Office365 Admin permissions where needed, to use the new Graph API reports endpoint you need to use OAuth for authentication so this requires an Application Registration  https://developer.microsoft.com/en-us/graph/docs/concepts/auth_overview  that is then given the correct oAuth Grants to use the Reports EndPoin

How to test SMTP using Opportunistic TLS with Powershell and grab the public certificate a SMTP server is using

Most email services these day employ Opportunistic TLS when trying to send Messages which means that wherever possible the Messages will be encrypted rather then the plain text legacy of SMTP.  This method was defined in RFC 3207 "SMTP Service Extension for Secure SMTP over Transport Layer Security" and  there's a quite a good explanation of Opportunistic TLS on Wikipedia  https://en.wikipedia.org/wiki/Opportunistic_TLS .  This is used for both Server to Server (eg MTA to MTA) and Client to server (Eg a Message client like Outlook which acts as a MSA) the later being generally Authenticated. Basically it allows you to have a normal plain text SMTP conversation that is then upgraded to TLS using the STARTTLS verb. Not all servers will support this verb so if its not supported then a message is just sent as Plain text. TLS relies on PKI certificates and the administrative issue s that come around certificate management like expired certificates which is why I wrote th
All sample scripts and source code is provided by for illustrative purposes only. All examples are untested in different environments and therefore, I cannot guarantee or imply reliability, serviceability, or function of these programs.

All code contained herein is provided to you "AS IS" without any warranties of any kind. The implied warranties of non-infringement, merchantability and fitness for a particular purpose are expressly disclaimed.