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.