Thursday, December 6, 2007

Move or Copy SharePoint Document Library Files Programmatically

In this post I published some code that took a look at using FrontPage Server Extentions to upload a file to a document library where the requirement was to be able to save metadata along with the document and not use the API. Here are a few more static methods that can be inserted into that code to provide simple file moving or copying functionality from a remote client. Documentation about the move RPC method can be found here. To copy rather than move the file, change the doCopy flag to true. An alternative method for remotely moving a file from one document library to another location is to use the copy web service which is documented on MSDN here. For local SharePoint calls, the appropriate API method is SPListItem.CopyTo or SPFile.MoveTo.

Update: You can download a comprehensive c# class library to automate RPC calls - including moving files (even between web sites or servers). See this blog post for more information.

        public static bool Move(string oldUrl, string newUrl)
        {
            string result = "";
            string webUrl = GetWebURL(oldUrl);
            oldUrl = oldUrl.Substring(webUrl.Length + 1);
            newUrl = newUrl.Substring(webUrl.Length + 1);
            return Move(webUrl, oldUrl, newUrl, out result);
        }
 
        public static bool Move(string webUrl, string oldUrl, string newUrl, out string result)
        {
            EnsureFolders(webUrl, newUrl);
            string renameOption = "findbacklinks";
            string putOption = "overwrite,createdir,migrationsemantics";
            bool doCopy = false;
            string method = "method=move+document%3a12.0.4518.1016&service_name=%2f&oldUrl={0}&newUrl={1}&url_list=[]&rename_option={2}&put_option={3}&docopy={4}";
            method = String.Format(method, oldUrl, newUrl, renameOption, putOption, doCopy.ToString().ToLower());
            try
            {
                using (WebClient webClient = new WebClient())
                {
                    webClient.Credentials = CredentialCache.DefaultCredentials;
                    webClient.Headers.Add("Content-Type", "application/x-vermeer-urlencoded");
                    webClient.Headers.Add("X-Vermeer-Content-Type", "application/x-vermeer-urlencoded");
                    result = Encoding.UTF8.GetString(webClient.UploadData(webUrl + "/_vti_bin/_vti_aut/author.dll", "POST", Encoding.UTF8.GetBytes(method)));
                    if (result.IndexOf("\n<p>message=successfully") < 0)
                        throw new Exception(result);
                }
            }
            catch (Exception ex)
            {
                result = ex.Message;
                return false;
            }
            return true;
        }
 
        public static void EnsureFolders(string rootUrl, string folderUrl)
        {
            StringBuilder sb = new StringBuilder(rootUrl.TrimEnd('/'));
            string[] segments = folderUrl.Split('/');
            for (int i = 0; i < segments.Length - 1; i++)
            {
                sb.Append("/");
                sb.Append(segments[i]);
                CreateFolder(sb.ToString());
            }
        }
 
        public static bool CreateFolder(string folderURL)
        {
            try
            {
                WebRequest request = WebRequest.Create(folderURL);
                request.Credentials = CredentialCache.DefaultCredentials;
                request.Method = "MKCOL";
                WebResponse response = request.GetResponse();
                response.Close();
                return true;
            }
            catch (WebException)
            {
                return false;
            }
        }

21 comments:

Rodney said...

Does this also ensure the MetaData is also copied?

txs8311 said...

Yes, the RPC move method will copy metadata along with the file.

sagadudu said...

Do you know if a large document with upwards of 5,000 documents can be moved with this implementation.

txs8311 said...

On a test of 5000 (small) files moved from one document library to another and with a couple of quick optimizations to the code above (caching the WebClient, not ensuring folders) – the total time elapsed was about 17 minutes.

Using the SharePoint API the same move took 3.5 minutes.

For regular moves of large numbers of files where this kind of performance lag is an issue, you’d probably want to consider other options over RPC (like building a custom web service).

Anonymous said...

I tried your code with a Sharepoint2003 site. It works well, as it does with with 2007. Now my question is if you have tried or have an idea of how implement this to move files from one Sharepoint2003 site to a Sharepoint2007 one while keeping metadata?

txs8311 said...

Unfortunately the 'move documents' FrontPage RPC method won't copy between servers/sites/webs. To do so you'll need to download the file locally and then upload it to the new location.

We've developed a free (beta) library which automates this - check out the HubKey.Net.FrontPageRPC.MoveDocument method. To download a copy of the source code, select 'FrontPageRPC' as your interest on the contact page on HubKey's website.

Anonymous said...

Thanks for your post.
It worked well although I encountered one problem. It did preserve all versions, but did modify the information about "Modified by" for all version. It changes the "Modified By" value to the person who is executing the code. Is there a way to preserver "Modifed By" for all versions?

Anonymous said...

I think that your classes will perform what I need, but not sure how to implement. Tried finding out how to implement but no luck. Thanks.

txs8311 said...

If you've downloaded the library from our web site, take a look at the readme.txt file - it gives a brief example of how to move files between servers.

mcgraw said...

I'm a sharepoint admin with very little development skills. I'm working on creating some infopath forms publishing them to a sharepoint forms library. Once a form is submitted I need to have it moved to anothe forms library that has a different set of permissions. Can anyone give me an idea how this can be accomplished with code and how i would implement it.

txs8311 said...

mcgraw, this sort of scenario is typically addressed on the server side by either Event Handling code or by creating a workflow. Simple workflows can be created without code by using Microsoft Office SharePoint Designer 2007. A very useful set of extended workflow activities can be downloaded here which includes a Copy List Item Extended Activity which allows cross site list item copying. Only if for whatever reason you're not able to implement a server side solution would you then have to use code like the above. HubKey (the company I work for) has very good experience in implementing all of these technologies.

mcgraw said...

Thanks for your response. I've tried moving the forms to another library using a workflow, but it will only move the form to another library that the user has contribute access to and that's what I'm trying to avoid. For a user to submit a form they must have contribute permissions to the document library. Once a form has been submitted I no longer want the users to be able access the submitted forms. This is why I would like to move them to another library that has a different permission set. From what I've found I can't accomplish this with a workflow. Do you think this is a possibility with the extended workflow activities or do you think it would require custom event handler code?

txs8311 said...

It should be possible - the Copy List Item Extended Activity runs under elevated privileges for example.

The Grant Permission on Item Activity might be useful as well - it may circumvent your need for another document library altogether.

Doug said...

txs8311, Thanks a lot. I've give it a try and let you know how it turns out.

mcgraw said...

txs8311, thanks so much for the tip. the Copy List Item Extended workflow works perfect!

mchavez said...

I have to create 200 mysites in sharepoint for 200 employees. How can I copy a profile 200 times instead of manually having to create each site 200 times

txs8311 said...

mchavez - sorry, not sure how this relates to copying files. It sounds like you're after something like this:

http://www.google.com/search?hl=en&q=sharepoint+create+mysite+programmatically

Anonymous said...

where is the definition of GetWebURL??

Thank you

txs8311 said...

GetWebURL is listed in the previous post.

Thanks

Keith said...

Hi, I'm trying to use this code to move a folder within a site and I'm getting a file or folder not exists error (oldUrl) (status=13109).

An example of my old and new url:

oldUrl="http://sharepoint:5488/sites/mysite/place/students/Current/A/AStudent"; <- Browses to folder if pasted into browser

newUrl="http://sharepoint:5488/sites/mysite/place/students/Archive/A/AStudent"; <- folder that does not yet exist

Anonymous said...

Can you give a sample as well please?

I am getting error converting to WebUrl. Not sure what format I need to pass in.

I need to save test.xls on sharepoint and I am passing like this.

Move(@"C:\test\FromSharepoint\test.xls","http://sharepoint.abc.com/dept/accounting/ToSharepoint/test.xls");

Please correct what am I doing wrong here.