Krichie - That SharePoint Guy

July 6, 2008

Exporting site content from a SharePoint Content Database for recovery purposes.

Filed under: SharePoint — Keith Richie @ 11:03 pm

My good friend Todd Klindt pointed me to this posting written by Mark Jen which showed some sample code to export site content from a SharePoint Content Database directly.

!!! DISCLAIMER !!!!

Everyone who knows me, knows that I am TOTALLY AGAINST doing direct database calls to the SharePoint databases UNLESS it is for complete disaster recovery, or complete offline reporting.   This is one of the reasons DeliverPoint will NEVER have any direct SharePoint database calls in it.

For additional information, see the following Microsoft articles:

“Support for changes to the databases that are used by Office Server products and by Windows SharePoint Services”

http://support.microsoft.com/kb/841057/en-us

“SharePoint Database Access”

http://msdn.microsoft.com/en-us/library/bb861829.aspx

Do NOT run this code against Live SharePoint databases.

Ok, now with that out the way…The problem with Mark’s original code, is that it was written for V2 (WSS 2.0/SPS2003), not V3 (WSS 3.0/MOSS 2007).  Also, his query is targeted at specific files (such as .doc/.ppt/.etc).

With a couple of slight changes, I updated the query and code to work with a V3 content database, and also just have it export everything.

Since his code was also just a snippit, I’m placing an updated version  with complete source here.

// BEGIN SPDBEX.CS

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.IO;

namespace spdbex
{
    class Program
    {
        static void Main(string[] args)
        {
            // replace this string with your 

            // Sharepoint content DB connection string
            string DBConnString =
             "Server=DATABASESERVER;" +
             "Database=CONTENTDBNAME;Trusted_Connection=True;";

            // create a DB connection
            SqlConnection con = new SqlConnection(DBConnString);
            con.Open();

            // the query to grab all the files.
            SqlCommand com = con.CreateCommand();
            com.CommandText = "SELECT ad.SiteId, ad.Id, ad.DirName," +
                " ad.LeafName, ads.Content" +
                " FROM AllDocs ad, AllDocStreams ads" +
                " WHERE ad.SiteId = ads.SiteId" +
                " AND ad.Id = ads.Id" +
                " AND ads.Content IS NOT NULL" +
                " Order by DirName";

            // execute query
            SqlDataReader reader = com.ExecuteReader();

            while (reader.Read())
            {
                // grab the file’s directory and name
                string DirName = (string)reader["DirName"];
                string LeafName = (string)reader["LeafName"];

                // create directory for the file if it doesn’t yet exist
                if (!Directory.Exists(DirName))
                {
                    Directory.CreateDirectory(DirName);
                    Console.WriteLine("Creating directory: " + DirName);
                }

                // create a filestream to spit out the file
                FileStream fs = new FileStream(DirName + "/" + LeafName,
                    FileMode.Create, FileAccess.Write);
                BinaryWriter writer = new BinaryWriter(fs);

                // depending on the speed of your network,
                // you may want to change the buffer size (it’s in bytes)
                int bufferSize = 1000000;
                long startIndex = 0;
                long retval = 0;
                byte[] outByte = new byte[bufferSize];

                // grab the file out of the db one chunk
                // (of size bufferSize) at a time
                do
                {
                    retval = reader.GetBytes(4, startIndex, outByte, 0,
                        bufferSize);
                    startIndex += bufferSize;

                    writer.Write(outByte, 0, (int)retval);
                    writer.Flush();
                } while (retval == bufferSize);

                // finish writing the file
                writer.Close();
                fs.Close();

                Console.WriteLine("Finished writing file: " + LeafName);
            }

            // close the DB connection and whatnots
            reader.Close();
            con.Close();
        }
    }
}

// END SPDBEX.CS

Be sure to change the DATABASESERVER and CONTENTDBNAME for the connection string, then simply execute the C# compiler on your server as follows:

%WINDIR%\Microsoft.NET\Framework\v2.0.50727\csc /target:exe /out:spdbex.exe spdbex.cs

The code in Marks’ original form, does not allow you to specify any target directory etc, and just begins exporting in the CURRENT DIRECTORY you’re in for any root site, so be sure to run this from a directory/folder you create to hold the data from the root.

I’ll ping Mark and see if he’ll allow me to take that code and polish it up with other options (Such as site selection, specific folder targeting etc) if he doesn’t want to update it.

HTH

- Keith

12 Comments »

  1. Thanks Keith. This works great.

    tk

    Comment by Todd Klindt — July 6, 2008 @ 11:25 pm

  2. [...] Exporting site content from a SharePoint Content Database for recovery purposes [...]

    Pingback by Links (7/6/2008) « Steve Pietrek - Everything SharePoint — July 7, 2008 @ 7:44 am

  3. [...] Exporting Site Content From a SharePoint Content Database for Recovery Purposes (Keith Richie) [...]

    Pingback by Dew Drop - July 7, 2008 | Alvin Ashcraft's Morning Dew — July 7, 2008 @ 6:46 pm

  4. Keith

    Loving it buddy !

    Comment by Neil Hodgkinson — July 12, 2008 @ 5:49 am

  5. Thanks for the excellent code - it’s a real life saver!

    Comment by Mark Fertig — October 15, 2008 @ 12:58 am

  6. Hi,

    Thanks for this code, it’s been really useful. The only problem I have at the moment is we have quite a few folder paths that are longer than 248 characters. Do you know if there is a workaround to this?

    Thanks

    Comment by Al — November 11, 2008 @ 11:30 pm

  7. The code would need to be adjusted to create a shorter folder path on the file system for each folder found, then create an appropriate reference map that would then be exported (That is if you were still exporting everything in the content database)
    Optionally, you could modifiy the Query to and an AND clause to point to a specific Site GUID for the site in question.

    Just thoughts at this moment. I’d like to update the code to handle those scenarios, but it’s not something I have time for right at this moment. I’ll make a note of it though.

    Comment by Keith Richie — November 11, 2008 @ 11:40 pm

  8. Hi,

    Thanks for that. I have one other question. Currently, my SQL instance for the Sharepoint databases is only accessible via Named Pipes (I just can’t get it to work using TCP/IP).

    How would I adapt your code to allow me to either used named pipes or a DSN on the Sharepoint server?

    (Unfortunately I’m a complete C# novice)

    Comment by Al — November 12, 2008 @ 8:46 pm

  9. Hi,

    I managed to find some code on the web to allow me to use a DSN. Thanks once again.

    Comment by Al — November 12, 2008 @ 9:11 pm

  10. My god… you have just saved my life. This is the only way I was able to get the content after upgrading from WSS3 to MOSS 2007 and experiencing complete meltdown. You are a god among men- a gentleman, a saint!

    Comment by John — November 17, 2008 @ 4:48 pm

  11. I deleted a site from the site settings…but then hit cancel. From what I can tell the database is intact…can I rebuild my site with the code above

    Comment by Virginia — December 14, 2008 @ 9:57 am

  12. mzxivrpxxcystcgnwell, hi admin adn people nice forum indeed. how’s life? hope it’s introduce branch ;)

    Comment by NaitepagKip — December 27, 2008 @ 5:11 am

RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.