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>
8: private 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>
61: private 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:
130: private 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: }
144: private 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:
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-exportlistitem2 -url "http://intranet/documents/forms/allitems.aspx" -path "c:\documents"
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.stsadm -o gl-addlistitem -url "http://intranet/documents2/forms/allitems.aspx" -datafile "c:\documents\manifest.xml" -publish


