I’m about at my wits end with the deployment API. I had a fairly simple requirement of adding a custom list item into the site directory so that the top tasks would have some additional items post upgrade. So I thought, no problem – I’ll just create the list item manually in my test environment, export it with gl-exportlistitem
and then use gl-importlistitem
to import during the upgrade. Only problem was that because the field IDs of the list have changed the deployment API decided to recreate all the columns thus duplicating all of them and putting the data into the new column rather than the old so I couldn’t even just delete the new columns without having to recreate the data. Also – I noticed that SP 1 just released yesterday – I only saw two fixes to the deployment stuff and neither addressed any of the dozen or so bugs that I’ve identified on this blog and reported to Microsoft – very frustrating. So my solution to this was to abandon the deployment API entirely and create a new command for importing list items: gl-addlistitem
.
I anticipate that I’ll be expanding this more and more over time as requirements present themselves (I may even create a new export command that can work with this but so far I haven’t needed to do so). The command itself is pretty simple – it offers two ways of adding data: the first is for simple stuff and allows you to just set field/value pairs via a single parameter; the second method allows you to specify an XML file thus allowing you to set more complex field values and add more than one item at a time as well as adding more than one attachment. Both methods support adding items to a document library or custom list. I’m currently not handling the case when the file already exists but I may allow for that in the future if necessary.
The code itself is pretty straightforward as well. The only thing that tripped me up was creating new folders. This turned out to be easy but it was a trick finding the right place to create the folder – I thought I could just use list.Folders.Add()
but that doesn’t work – you have to use the following:
1SPListItem newFolder = list.Items.Add("", SPFileSystemObjectType.Folder, folderPath.Trim('/'));
2newFolder.Update();
Trick is that you have can’t create “/SubFolder1/SubFolder2” if SubFolder1 doesn’t already exist so you have to split the path and systematically create each folder in turn:
1/// <summary>
2/// Adds the item.
3/// </summary>
4/// <param name="web">The web.</param>
5/// <param name="list">The list.</param>
6/// <param name="itemData">The item data.</param>
7/// <param name="publish">if set to <c>true</c> [publish].</param>
8internal static void AddItem(SPWeb web, SPList list, List<ItemInfo> itemData, bool publish)
9{
10 bool itemsAdded = false;
11 foreach (ItemInfo itemInfo in itemData)
12 {
13 SPListItem item;
14
15 SPFolder folder = GetFolder(web, list, itemInfo);
16 if (itemInfo.File == null)
17 {
18
19 if (list.BaseType == SPBaseType.DocumentLibrary)
20 {
21 string title = Guid.NewGuid().ToString();
22 if (!string.IsNullOrEmpty(itemInfo.LeafName))
23 title = itemInfo.LeafName;
24
25 SPFile file;
26 if (itemInfo.Author == null)
27 file = folder.Files.Add(title, new byte[] { });
28 else
29 file = folder.Files.Add(title, new byte[] {}, itemInfo.Author, itemInfo.Author, itemInfo.CreatedDate, DateTime.Now);
30
31 item = file.Item;
32 }
33 else
34 item = list.Items.Add(folder.ServerRelativeUrl, SPFileSystemObjectType.File);
35 }
36 else
37 {
38 // We have a file so we need to handle it.
39 // Make sure the leaf and folder properties are set.
40 if (string.IsNullOrEmpty(itemInfo.LeafName))
41 itemInfo.LeafName = itemInfo.File.Name;
42
43 if (list.BaseType != SPBaseType.DocumentLibrary)
44 {
45 // The list is not a document library so we'll add the file as an attachment.
46 item = list.Items.Add(folder.ServerRelativeUrl, SPFileSystemObjectType.File);
47
48 item.Attachments.Add(itemInfo.LeafName, File.ReadAllBytes(itemInfo.File.FullName));
49 }
50 else
51 {
52 // We've got the right folder so now add the file.
53 SPFile file;
54 if (itemInfo.Author == null)
55 file = folder.Files.Add(folder.ServerRelativeUrl + "/" + itemInfo.LeafName,
56 File.ReadAllBytes(itemInfo.File.FullName));
57 else
58 file = folder.Files.Add(folder.ServerRelativeUrl + "/" + itemInfo.LeafName, File.ReadAllBytes(itemInfo.File.FullName), itemInfo.Author, itemInfo.Author, itemInfo.CreatedDate, DateTime.Now);
59
60 // Get the SPListItem object from the added file.
61 item = file.Item;
62 }
63
64 }
65 // Set the field values
66 foreach (string fieldName in itemInfo.FieldData.Keys)
67 {
68 SPField field = item.Fields.GetFieldByInternalName(fieldName);
69
70 if (field.Type == SPFieldType.URL)
71 item[fieldName] = new SPFieldUrlValue(itemInfo.FieldData[fieldName]);
72 else
73 item[fieldName] = itemInfo.FieldData[fieldName];
74
75 }
76 if (itemInfo.Author != null && item.Fields.ContainsField("Created By"))
77 item["Created By"] = itemInfo.Author;
78
79 if (item.Fields.ContainsField("Created"))
80 item["Created"] = itemInfo.CreatedDate;
81
82 item.Update();
83
84 if (list.BaseType != SPBaseType.DocumentLibrary)
85 {
86 try
87 {
88 // Add any attachments
89 foreach (FileInfo att in itemInfo.Attachments)
90 {
91 item.Attachments.Add(att.Name, File.ReadAllBytes(att.FullName));
92 }
93 }
94 catch (ArgumentException)
95 {
96 throw new SPException("List does not support use of attachments. Item added but not published.");
97 }
98 }
99 else if (itemInfo.Attachments.Count > 0)
100 {
101 throw new SPException("List does not support use of attachments. Item added but not published.");
102 }
103 item.Update();
104
105
106 // Publish the changes
107 if (publish)
108 {
109 PublishItems.Settings settings = new PublishItems.Settings();
110 settings.Test = false;
111 settings.Quiet = true;
112 settings.LogFile = null;
113 PublishItems.PublishListItem(item, list, settings, "stsadm -o addlistitem");
114 }
115 itemsAdded = true;
116 }
117 if (itemsAdded)
118 list.Update();
119}
120
121/// <summary>
122/// Gets the folder.
123/// </summary>
124/// <param name="web">The web.</param>
125/// <param name="list">The list.</param>
126/// <param name="itemInfo">The item info.</param>
127/// <returns></returns>
128internal static SPFolder GetFolder(SPWeb web, SPList list, ItemInfo itemInfo)
129{
130 if (string.IsNullOrEmpty(itemInfo.FolderUrl))
131 return list.RootFolder;
132
133 SPFolder folder = web.GetFolder(list.RootFolder.Url + "/" + itemInfo.FolderUrl);
134
135 if (!folder.Exists)
136 {
137 if (!list.EnableFolderCreation)
138 {
139 list.EnableFolderCreation = true;
140 list.Update();
141 }
142
143 // We couldn't find the folder so create it
144 string[] folders = itemInfo.FolderUrl.Trim('/').Split('/');
145
146 string folderPath = string.Empty;
147 for (int i = 0; i < folders.Length; i++)
148 {
149 folderPath += "/" + folders[i];
150 folder = web.GetFolder(list.RootFolder.Url + folderPath);
151 if (!folder.Exists)
152 {
153 SPListItem newFolder = list.Items.Add("", SPFileSystemObjectType.Folder, folderPath.Trim('/'));
154 newFolder.Update();
155 folder = newFolder.Folder;
156 }
157 }
158 }
159 // Still no folder so error out
160 if (folder == null)
161 throw new SPException(string.Format("The folder '{0}' could not be found.", itemInfo.FolderUrl));
162 return folder;
163}
The syntax of the command can be seen below:
C:\>stsadm -help gl-addlistitem
stsadm -o gl-addlistitem
Adds a list item or items to an existing list.
Parameters:
-url <list view url to import into>
[-filename <file to add to the list (will add as attachment if not document library)>]
[-leafname <name to give the file if specified (be sure to include the file extension)>]
[-folderurl <url to place the specified file if not at the root (list relative)>]
{[-fielddata <semi-colon separated list of key value pairs: "Field1=Val1;Field2=Val2"> (use ';;' to escape semi-colons in data values)] |
[-datafile <path to a file with xml settings matching the following format: <Items><Item File="{optional file path}" FolderUrl="{optional folder url}" LeafName="{optional display name of file}" Author="{optional username}" CreatedDate="{optional create date}"><Fields><Field Name="Name1">Value1</Field><Field Name="Name2">Value2</Field></Fields><Attachments><Attachment>{path to file}</Attachment></Attachments></Item></Items> >]}
[-publish]
Here’s an example of how to do add a single item to a site directory:
stsadm -o gl-addlistitem -url "http://intranet/sitedirectory/siteslist/allitems.aspx" -fielddata "Title=Sales Site;URL=http://intranet/sales, Sales Home;Region=Local;Division=Sales;TopSite=true" -publish
To do the same thing but this time also add an attachment you would do the following:
stsadm -o gl-addlistitem -url "http://intranet/sitedirectory/siteslist/allitems.aspx" -filename "c:\sales\MissionStatement.txt" -fielddata "Title=Sales Site;URL=http://intranet/sales, Sales Home;Region=Local;Division=Sales;TopSite=true" -publish
To add a file to a document library you would do this:
stsadm -o gl-addlistitem -url "http://intranet/documents/forms/allitems.aspx" -filename "c:\file1.doc" -leafname "Test File 1.doc" -fielddata "Title=Title1;ImportDate=12/12/2007" -publish
To do the same thing using a file and at the same time add more than one item you’d do this:
stsadm -o gl-addlistitem -url "http://intranet/documents/forms/allitems.aspx" -datafile "c:\items.xml" -publish
Here’s the XML that was provided:
1<Items>
2 <Item File="c:\file1.doc" LeafName="Test File 1.doc" Author="domain\user" CreatedDate="12/18/2007 11:55 AM">
3 <Fields>
4 <Field Name="Title">Title1</Field>
5 <Field Name="ImportDate">12/12/2007</Field>
6 </Fields>
7 </Item>
8 <Item File="c:\file2.xml" FolderUrl="Documents/SubFolder1/SubFolder2">
9 <Fields>
10 <Field Name="Title">Title2</Field>
11 <Field Name="ImportDate">12/12/2007</Field>
12 </Fields>
13 </Item>
14</Items>
To add items to a custom list (not a document library) you could use the following:
stsadm -o gl-addlistitem -url "http://intranet/TestList/allitems.aspx" -datafile "c:\items.xml" -publish
Here’s the XML that was provided:
1<Items>
2 <Item>
3 <Fields>
4 <Field Name="Title">Title1</Field>
5 <Field Name="ImportDate">12/12/2007</Field>
6 </Fields>
7 <Attachments>
8 <Attachment>c:\file1.doc</Attachment>
9 <Attachment>c:\file2.doc</Attachment>
10 </Attachments>
11 </Item>
12 <Item FolderUrl="TestList/SubFolder1/SubFolder2">
13 <Fields>
14 <Field Name="Title">Title2</Field>
15 <Field Name="ImportDate">12/12/2007</Field>
16 </Fields>
17 </Item>
18 </Items>
Update 12/18/2007: I’ve updated this command so that it now also supports the setting of an Author and CreatedDate attribute when using XML as the data source. I’ve also fixed a bug with how folders were being created (previously they’d get created but were not visible via the browser). I’ve also fixed some other issues when dealing with different types of input data and target list types. Note also that this command is compatible with the exportlistitem2
command. All content above has been updated to reflect these changes.
Update 1/31/2008: I’ve modified this command so that it now also supports importing web part pages. You can use the exportlistitem2 command to export pages and then use the resultant exported manifest file in conjunction with the gl-addlistitem
command so that web part pages can be properly imported.