I’ve seen numerous examples of people needing to save all the files from a document library or custom list (containing attachments) to disk. I didn’t necessarily need the ability myself for the upgrade we are doing but I did need a quick way to generate lots of different samples to make sure that my gl-addlistitem command was working correctly. So I decided to create a new command which would make my testing easier as well as help the many out there that have the need of saving lots of files out to disk. The command I created is gl-exportlistitem2. I already had an gl-exportlistitem command which used the deployment API and I just wasn’t feeling very creative with the name so I just added “2” (maybe “savelistdata” is better???). The command does two key things – saves all the files to a specified path and creates a Manifest.xml file that contains information about the files and any list items that were in the list. This information can then be used by the gl-addlistitem command to actually import the data into another list. For this initial version I’ve kept things fairly simple – there’s no compression, no security information, and no version history. I’m only storing the file(s) (if present) and any field data (perhaps I’ll look to handle more data in the future but for now this met my needs). The nice thing is that if you don’t need any of the other information then what I created actually works better than using the deployment API as mine actually takes folder location into account whereas the deployment API is extremely buggy when it comes to folders. I also included a simplified version of the code which just simply dumps all the files to disk without the manifest information (the command does not use this but I kept it in the source in case anyone needed it). The code to do all of this is really straightforward – I decided to break it up into two chunks – the first gathers all the necessary data from the list and stores it in some custom data classes and the second takes those classes and saves to disk and creates the actual manifest file:

  1/// <summary>
  2/// Gets the item data.
  3/// </summary>
  4/// <param name="web">The web.</param>
  5/// <param name="list">The list.</param>
  6/// <param name="ids">The ids.</param>
  7/// <returns></returns>
  8private static List<ItemInfo> GetItemData(SPWeb web, SPList list, List<int> ids)
  9{
 10    List<ItemInfo> itemData = new List<ItemInfo>();
 11
 12    foreach (SPListItem item in list.Items)
 13    {
 14        if (!(ids.Count == 0 || ids.Contains(item.ID)))
 15            continue;
 16
 17        ItemInfo info = new ItemInfo();
 18        itemData.Add(info);
 19        info.ID = item.ID;
 20
 21        if (item.File != null)
 22        {
 23            info.File = new FileDetails(item.File.OpenBinary(), item.File.Name, item.File.Author, item.File.TimeCreated);
 24            info.Title = item.File.Name;
 25        }
 26        else
 27            info.Title = item.Title;
 28
 29        info.FolderUrl = item.Url.Substring(list.RootFolder.Url.ToString().Length, item.Url.LastIndexOf("/") - list.RootFolder.Url.ToString().Length);
 30
 31        try
 32        {
 33            foreach (string fileName in item.Attachments)
 34            {
 35                SPFile file = web.GetFile(item.Attachments.UrlPrefix + fileName);
 36                info.Attachments.Add(new FileDetails(file.OpenBinary(), file.Name, file.Author, file.TimeCreated));
 37            }
 38        }
 39        catch (ArgumentException)
 40        {}
 41
 42        foreach (SPField field in list.Fields)
 43        {
 44            if (!field.ReadOnlyField && 
 45                field.InternalName != "Attachments" && 
 46                field.InternalName != "FileLeafRef" &&
 47                item[field.InternalName] != null)
 48            {
 49                info.FieldData.Add(field.InternalName, item[field.InternalName].ToString());
 50            }
 51        }
 52    }
 53    return itemData;
 54}
 55 
 56/// <summary>
 57/// Gets the item data from XML.
 58/// </summary>
 59/// <param name="itemData">The item data.</param>
 60/// <param name="manifestPath">The manifest path.</param>
 61private static void SaveItemData(List<ItemInfo> itemData, string manifestPath)
 62{
 63    if (string.IsNullOrEmpty(manifestPath))
 64        throw new ArgumentNullException("manifest", "No directory was specified for the manifest.");
 65
 66    if (!Directory.Exists(manifestPath))
 67        Directory.CreateDirectory(manifestPath);
 68
 69    string dataPath = Path.Combine(manifestPath, "Data");
 70
 71    StringBuilder sb = new StringBuilder();
 72
 73    XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));
 74    xmlWriter.Formatting = Formatting.Indented;
 75
 76    xmlWriter.WriteStartElement("Items");
 77
 78    foreach (ItemInfo info in itemData)
 79    {
 80        xmlWriter.WriteStartElement("Item");
 81
 82        if (info.File != null)
 83        {
 84            string folder = Path.Combine(dataPath, info.FolderUrl.Trim('\\', '/')).Replace("/", "\\");
 85            if (!Directory.Exists(folder))
 86                Directory.CreateDirectory(folder);
 87
 88            xmlWriter.WriteAttributeString("File", Path.Combine(folder, info.File.Name));
 89            xmlWriter.WriteAttributeString("Author", info.File.Author.LoginName);
 90            xmlWriter.WriteAttributeString("CreatedDate", info.File.CreatedDate.ToString());
 91            File.WriteAllBytes(Path.Combine(folder, info.File.Name), info.File.File);
 92        }
 93        xmlWriter.WriteAttributeString("LeafName", info.Title);
 94        xmlWriter.WriteAttributeString("FolderUrl", info.FolderUrl);
 95
 96        xmlWriter.WriteStartElement("Fields");
 97        foreach (string key in info.FieldData.Keys)
 98        {
 99            xmlWriter.WriteStartElement("Field");
100            xmlWriter.WriteAttributeString("Name", key);
101            xmlWriter.WriteString(info.FieldData[key]);
102            xmlWriter.WriteEndElement(); // Field
103        }
104        xmlWriter.WriteEndElement(); // Fields
105
106        xmlWriter.WriteStartElement("Attachments");
107        foreach (FileDetails file in info.Attachments)
108        {
109            string folder = Path.Combine(Path.Combine(dataPath, info.FolderUrl.Trim('\\', '/')).Replace("/", "\\"), "item_" + info.ID);
110            if (!Directory.Exists(folder))
111                Directory.CreateDirectory(folder);
112
113            xmlWriter.WriteElementString("Attachment", Path.Combine(folder, file.Name));
114
115            File.WriteAllBytes(Path.Combine(folder, file.Name), file.File);
116        }
117        xmlWriter.WriteEndElement(); // Attachments
118
119        xmlWriter.WriteEndElement(); // Item
120    }
121
122    xmlWriter.WriteEndElement();
123    xmlWriter.Flush();
124
125    File.WriteAllText(Path.Combine(manifestPath, "Manifest.xml"), sb.ToString());
126}
127 
128#region Private Classes
129 
130private class FileDetails
131{
132    public byte[] File = null;
133    public string Name = null;
134    public SPUser Author = null;
135    public DateTime CreatedDate = DateTime.Now;
136    public FileDetails(byte[] file, string name, SPUser author, DateTime createdDate)
137    {
138        File = file;
139        Name = name;
140        Author = author;
141        CreatedDate = createdDate;
142    }
143}
144private class ItemInfo
145{
146    public FileDetails File = null;
147    public string FolderUrl = null;
148    public List<FileDetails> Attachments = new List<FileDetails>();
149    public Dictionary<string, string> FieldData = new Dictionary<string, string>();
150    public int ID = -1;
151    public string Title = null;
152}
153#endregion

The syntax of the command can be seen below:

C:\>stsadm -help gl-exportlistitem2

stsadm -o gl-exportlistitem2

Exports list items to disk (exported results can be used with addlistitem).

Parameters:
        -url <list view url to export from>
        -path <export path>
        [-id <list item ID (separate multiple items with a comma)>]

Here’s an example of how to do export list items:

stsadm -o gl-exportlistitem2 -url "http://intranet/documents/forms/allitems.aspx" -path "c:\documents" 

Note that a “Data” folder will be created under the path specified – all files will be put in this folder and the folder structure will mirror that of the list. The Manifest.xml file will be in the root of the folder specified. Attachments will be stored in sub-folders using the name “item_{ID}” where {ID} is the item ID. Once exported you could then use the gl-addlistitem command to import these items to another list:

stsadm -o gl-addlistitem -url "http://intranet/documents2/forms/allitems.aspx" -datafile "c:\documents\manifest.xml" -publish 

Update 1/31/2008: I’ve modified this command so that it now also supports exporting web part pages. The resultant exported manifest file can be used in conjunction with the gl-addlistitem command so that web part pages can be properly imported using that command.