HOWTO: WebDAV: EWS: Programmatically recover soft deleted items from dumpster
As you may already know, EWS has support for Soft-Deleting items but the irony is it cannot access the soft-deleted items. This is a design challenge and product group is aware of this limitation. Hopefully we should see this support natively in EWS sooner or later. If you really need a solution or as we term it as "workaround" to the problem, you can still use WebDAV or MAPI to recover them. MAPI is very powerful but cant work from managed code.
I had faced little tough time to decode the way it works, ofcourse Glen's blog was very helpful, but unfortunately it did not work as expected and I had to create my own code.
Disclaimer: This is just a sample code to serve as proof of concept, I would recommend to use MAPI
See the below KB for MAPI sample code to do the job
https://support.microsoft.com/kb/232265
public static void WebDAV_RecoverDeletedFolder(string hostname, string user,string pass,string sourceFolder, string targetFolder)
{
Console.WriteLine("Info: Searching for \"Soft Deleted\" items in \"" + sourceFolder + "\", will move them to \"" + targetFolder + "\"");
sourceFolder = sourceFolder.Replace(" ", "%20").ToLower();
targetFolder = targetFolder.Replace(" ", "%20").ToLower();
int itemCount = 0;
string sourceForlderURL = "https://" + hostname + "/exchange/" + user + "/" + sourceFolder;
#region Search all messages and generate the XML to copy them to new folder
HttpWebRequest xmlReq = (HttpWebRequest)System.Net.WebRequest.Create(sourceForlderURL);
StringBuilder strXml = new StringBuilder();
StringBuilder strXmlForDeletion = new StringBuilder();
strXml.Append("<?xml version='1.0'?>");
strXml.Append("<D:searchrequest xmlns:D='DAV:'>");
strXml.Append("<D:sql>SELECT ");
strXml.Append("\"DAV:href\"");
strXml.Append("FROM SCOPE ('SOFTDELETED TRAVERSAL OF \"" + sourceForlderURL + "\"')");
strXml.Append("</D:sql></D:searchrequest>");
xmlReq.Credentials = new System.Net.NetworkCredential(user, pass);
xmlReq.AllowAutoRedirect = true;
xmlReq.Method = "SEARCH";
xmlReq.Headers.Add("Translate", "f");
xmlReq.ContentType = "text/xml";
xmlReq.ContentLength = strXml.Length;
StreamWriter sw = new StreamWriter(xmlReq.GetRequestStream());
sw.Write(strXml.ToString());
sw.Flush();
sw.Close();
strXml.Remove(0, strXml.Length);
string strOut = string.Empty;
HttpWebResponse httpResp = (HttpWebResponse)xmlReq.GetResponse();
if ((int)httpResp.StatusCode > 200 && (int)httpResp.StatusCode < 300)
strOut = new StreamReader(httpResp.GetResponseStream()).ReadToEnd();
else
Console.WriteLine("Error: " + httpResp.StatusDescription + " (" + httpResp.StatusCode + "), while searching for deleted items");
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(strOut);
XmlNodeList xNodeList = xDoc.GetElementsByTagName("a:href");
strXml.Append("<?xml version=\"1.0\" ?>");
strXml.Append("<D:copy xmlns:D=\"DAV:\">");
itemCount = xNodeList.Count / 2;
Console.WriteLine("Info: " + itemCount + " deleted items found to recover");
if (itemCount <= 0)
return;
for (int nCtr = 0; nCtr < xNodeList.Count - 1; nCtr += 2)
{
strXml.Append("<D:target>");
strXml.Append("<D:href>" + xNodeList[nCtr].InnerText + "</D:href>");
strXml.Append("<D:dest>" + xNodeList[nCtr + 1].InnerText.ToLower().Replace(sourceFolder, targetFolder) + "</D:dest>");
strXml.Append("</D:target>");
//We will use this XML later for deletion of these items
strXmlForDeletion.Append("<D:href>" + xNodeList[nCtr].InnerText + "</D:href>");
}
strXml.Append("</D:copy>");
#endregion
#region Start Copying all the messages to target folder
xmlReq = (HttpWebRequest)System.Net.WebRequest.Create(sourceForlderURL);
xmlReq.Credentials = new System.Net.NetworkCredential(user, pass);
xmlReq.AllowAutoRedirect = true;
xmlReq.Method = "BCOPY";
xmlReq.Headers.Add("Translate", "f");
xmlReq.ContentType = "text/xml";
xmlReq.ContentLength = strXml.Length;
sw = new StreamWriter(xmlReq.GetRequestStream());
sw.Write(strXml.ToString());
sw.Flush();
sw.Close();
strOut = string.Empty;
httpResp = (HttpWebResponse)xmlReq.GetResponse();
if ((int)httpResp.StatusCode > 200 && (int)httpResp.StatusCode < 300)
{
strOut = new StreamReader(httpResp.GetResponseStream()).ReadToEnd();
Console.WriteLine("Success: " + itemCount + " Items copied to target folder");
}
else
Console.WriteLine("Error: " + httpResp.StatusDescription + " (" + httpResp.StatusCode + "), while copying deleted items to target folder");
#endregion
#region Delete all the messages from dumpster, to avoid duplication on next run
strXml.Remove(0, strXml.Length);
Console.WriteLine("Info: Deleting " + itemCount + " items from dumpster");
strXml.Append("<?xml version=\"1.0\" ?>");
strXml.Append("<D:delete xmlns:D=\"DAV:\">");
strXml.Append("<D:target>");
strXml.Append(strXmlForDeletion.ToString());
strXml.Append("</D:target>");
strXml.Append("</D:delete>");
xmlReq = (HttpWebRequest)System.Net.WebRequest.Create(sourceForlderURL);
xmlReq.Credentials = new System.Net.NetworkCredential(user, pass);
xmlReq.AllowAutoRedirect = true;
xmlReq.Method = "BDELETE";
xmlReq.Headers.Add("Translate", "f");
xmlReq.ContentType = "text/xml";
xmlReq.ContentLength = strXml.Length;
sw = new StreamWriter(xmlReq.GetRequestStream());
sw.Write(strXml.ToString());
sw.Flush();
sw.Close();
strXml.Remove(0, strXml.Length);
strOut = string.Empty;
httpResp = (HttpWebResponse)xmlReq.GetResponse();
if ((int)httpResp.StatusCode > 200 && (int)httpResp.StatusCode < 300)
{
strOut = new StreamReader(httpResp.GetResponseStream()).ReadToEnd();
Console.WriteLine("Success: " + itemCount + " Items deleted from dumpster");
}
else
Console.WriteLine("Error: " + httpResp.StatusDescription + " (" + httpResp.StatusCode + "), while deleting items from dumpster");
#endregion
}
Comments
- Anonymous
April 28, 2008
PingBack from http://microsoftnews.askpcdoc.com/exchange-2007/howto-webdav-ews-programmatically-recover-soft-deleted-items-from-dumpster