MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Wednesday, December 12, 2007

Add List Item

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: SPListItem newFolder = list.Items.Add("", SPFileSystemObjectType.Folder, folderPath.Trim('/')); newFolder.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>
   8: internal 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>
 128: internal 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:
<Items>
    <Item File="c:\file1.doc" LeafName="Test File 1.doc" Author="domain\user" CreatedDate="12/18/2007 11:55 AM">
        <Fields>
            <Field Name="Title">Title1</Field>
            <Field Name="ImportDate">12/12/2007</Field>
        </Fields>
    </Item>
    <Item File="c:\file2.xml" FolderUrl="Documents/SubFolder1/SubFolder2">
        <Fields>
            <Field Name="Title">Title2</Field>
            <Field Name="ImportDate">12/12/2007</Field>
        </Fields>
    </Item>
</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:

<Items>
    <Item>
        <Fields>
            <Field Name="Title">Title1</Field>
            <Field Name="ImportDate">12/12/2007</Field>
        </Fields>
        <Attachments>   
            <Attachment>c:\file1.doc</Attachment>
            <Attachment>c:\file2.doc</Attachment>
        </Attachments>
    </Item>
    <Item FolderUrl="TestList/SubFolder1/SubFolder2">
        <Fields>
            <Field Name="Title">Title2</Field>
            <Field Name="ImportDate">12/12/2007</Field>
        </Fields>
    </Item>
 </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.

30 comments:

denap said...

Gary, first of all thanks for the great contribution to the community, much appreciated.

Question:

Have you ever, or could we use the stsadm extenstions to load content (files) and their meta-data en masse to a doc library?

I'm thinking specifically of a multi-step business user tool, likely hosted in excel that would:
1. take a source folder to be loaded and the destination target url and recursively list the files found in the source
2. present the user with the content-types (CT) found in the url and allow a selection of CT on a per file basis
3. process items on a CT basis and allow the filling of meta-data
4. use addlistitem to post the data to the library

I think this is possible with most of the commands you've already created, no?

Gary Lapointe said...

it's definitely possible - you can use your code and GUI to build out an XML file that would then be passed into the addlistitem command.

Nick said...

Gary

I'm trying to use this to post a link to the site directory like you show in the example... I am not getting the information populated to the Owner, Region, and Division columns. Any suggestions?

Here is my command (i substituted out my own domain and username)

stsadm -o gl-addlistitem -url "http://sharepoint.grey.com/sitedirectory/siteslist/allitems.aspx" -fielddata "Title=Test Title;URL=http://sharepoint.grey.com/sites/test;Owner=DOMAIN\USERNAME;Region=New York;Division=Sales;TopSite=true" -publish

Here is the output.

Progress: Processing item: -1
Progress: Publishing item: Test Title
Operation completed successfully.

Gary Lapointe said...

Nick - Make sure you are using the internal field name - for the site directory the internal name is RegionMulti and DivisionMulti (don't remember what the Owner name is) - the names I use in my example are different because I did an upgrade and the names that came over from the upgrade were different - sorry for the confussion.

Tanzim Akhtar said...

Hi,
What will be the steps for the XML to be used as a datafile for "LINKS" list?

Thank you

Gary Lapointe said...

Easiest way to find out would be to use the gl-exportlistitem2 command to export the items from an existing links list and then modify the manifest.xml file that is produced (or use it as a model to create a new one).

Tanzim Akhtar said...

Great thank you Sir. :)

Mario Desjardins said...

Hello Gary, I try to use command gl-addlistitem with XML data file and I get this error.

XML DATA (I replace <> with {} for this exemple post. HTML tag not accepted)
========
{Items}
{Item File="c:\file1.txt" LeafName="Test File 1.txt" Author="contoso\administrator" CreatedDate="12/18/2007 11:55 AM"}
{Fields}
{Field Name="Title"}Title1{/Field}
{Field Name="ImportDate"}12/12/2007{/Field}
{/Fields}
{/Item}
{/Items}

ERROR
=====
C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN>STSA
DM -o gl-addlistitem -url "http://portail.contoso.local/Importation/Forms/AllIte
ms.aspx" -datafile "C:\Import1.xml" -publish

Progress: Loading data from XML manifest file.
An error occured executing the command: Name cannot begin with the '/' character
, hexadecimal value 0x2F. Line 1, position 35.
at System.Xml.XmlTextReaderImpl.Throw(Exception e)
at System.Xml.XmlTextReaderImpl.Throw(String res, String[] args)
at System.Xml.XmlTextReaderImpl.Throw(Int32 pos, String res, String[] args)
at System.Xml.XmlTextReaderImpl.ParseQName(Boolean isQName, Int32 startOffset
, Int32& colonPos)
at System.Xml.XmlTextReaderImpl.ParseElement()
at System.Xml.XmlTextReaderImpl.ParseDocumentContent()
at System.Xml.XmlTextReaderImpl.Read()
at System.Xml.XmlLoader.ParsePartialContent(XmlNode parentNode, String innerx
mltext, XmlNodeType nt)
at System.Xml.XmlElement.set_InnerXml(String value)
at Lapointe.SharePoint.STSADM.Commands.Lists.AddListItem.GetItemDataFromXml(S
PSite site, SPList list, String dataFile)
at Lapointe.SharePoint.STSADM.Commands.Lists.AddListItem.Execute(String comma
nd, StringDictionary keyValues, String& output)


THANK YOU

Gary Lapointe said...

Ooops - Add a SiteUrl attribute in the root Items element (just give it the url of the site collection - http://portail.contoso.local/). I'll try to put a fix in for my next build.

Gerard said...

Gary,

thanks for your great work.

Question:
Is it possible (with GL-Addlistitem or otherwise) to add MULTIPLE list items where one of the fields has the Audience Targeting field type?

I tried it with gl-addlistitem, and got this error:
" Could not set field Target Audiences for item 0." (the field was not populated). Note: the Audience Targeting field is read-only - when I open the list with MS Access and try to enter values in that field, it says the field is read-only. Is there a way to get around this?

What I would like to be able to do is populate the Audience Targeting field in hundreds of list items at once, rather than having to enter the values one by one for each item.

Many Thanks,

Gerard

Trent said...

Gary, your STSADM custom extensions are really useful. Is there a custom extension available for adding link items to a list? gl-addlistitem does not seem to do it. Thanks!

Gary Lapointe said...

You can use gl-addlistitem:

stsadm -o gl-addlistitem -url http://moss/lists/links/allitems.aspx -fielddata "URL=http://domain/page.aspx, My Link"

Note the space after the comma before the description.

drewortiz said...

Hi Gary,
We have used your extensions and are very happy with the results so far. I do have one request that you could consider and it's around moving items into a folder within a list/library. Is there an existing option to do this? If not, would you be adding it in the future.

drewortiz said...

Hi Gary,
I'm using the gl-exportlistitem2 in combination with gl-addlistitem to copy files from one collection to another. This approach allows me to merge files from different sites/lists into one target lists. The need we have is to be able to specify folders in the target list to add the file. Is there a way to do this with the existing extension? I know I can edit the manifest file for each list and specify a folder URL but I'm trying to automate this as much as possible and would prefer to specify the target folderURL from the command line. Any suggestions?

Gary Lapointe said...

Unfortunately the only way to do this would be to update the manifest.xml file - if you want to do it in batch I'd recommend using PowerShell or some other custom code that simply wraps my command.

Anonymous said...

Gary-
First, thank you for the STSADM commands; they are a great help.

I am currently running a WSS 3.0 environment and trying to migrate documents from the root of a library to a folder within the same library.

I have been fiddling with gl-exportlistitem and gl-addlistitem and have found myself stuck in three places.

1) I am trying to grab only those files in the root, not other existing subfolders. Is there a way to export only these files?

2) Is there any way to further filter those files exported, say by a particular metadata value (ex. Category=Value)?

3) Upon importing, I was having trouble with the "-folderurl" parameter. For some reason I cannot figure out the proper syntax your function is looking for. Can you supply an example of how to import files from a Manifest.xml file into a library subfolder? Or instruction on how to attain the value your function requires?

It would be an immense help, even if it requires a little development or coding on my own.

I'll check back here, or you are welcome to email me (I do not have a blogspot account):

twhite -AT- fire.ca.gov

Thank you,
-Trey White

Gary Lapointe said...

Trey - thanks for the feedback. Hope this helps:
1) using gl-exportlistitem2 there is no way to export items in a specific folder only - what you should look into is gl-exportlistfolder and its compliment, gl-importlistfolder.
2) I've not built in any way to add a filter but you could easily modify the code to do what you want. That being said it would probaby be easier to use PowerShell in combination with my stsadm commands - so you could use the Get-SPList-gl cmdlet and then loop through the items collection and if the desired field value matches your criteria then use the gl-exportlistitem and gl-importlistitem commands passing in the appropriate ID.
3) The -folderurl property will only be useful if you are not using the -datafile parameter (I might be missing a validation check on that - can't remember).

Couple points - using PowerShell you should be able to use the SPFile.MoveTo() method to simply move the item where you want (http://msdn.microsoft.com/en-us/library/ms468280.aspx) - so again, use my Get-SPList-gl cmdlet, loop through the items, call item.File.MoveTo(). Another option is to just use the manage content and structure feature (http://office.microsoft.com/en-us/sharepointserver/HA101317231033.aspx) - you can move items using this tool.

Anonymous said...

Gary-
Thanks for the swift reply.

I'll definitely check out gl-import/exportlistfolder.

I must say I am somewhat amateur when it comes to coding. I am familiar with C# and comfortable with it, but have only used Powershell a little bit yet (added a calculated column to make a "Title" column for doc libraries have the full drop down menu the Name field has).

1) How do I call stsadm cmdlets from a Powershell script?

2) Where can I find the object structure I'll be working with so I can be confident I am changing values on the right objects? using the right methods?

3) the -folderurl parameter, it only works then when importing single files?

4) let's say i would like to add the folder functionality to both your cmdlets, how does one go about compiling C# into a STSADM extension??

Once again, thank you for your time and expertise, Gary. It's a huge help.

-Trey

Gary Lapointe said...

Trey - sorry for the delay in responding - been slammed. Here's some answers to your questions:
1. You call stsadm commands via powershell just like you would via a normal command window
2. You can get some details about the objects from the SDK. Primarily you'll be working with SPSite, SPWeb, SPList, SPListItem, and SPField objects - they're fairly straightforward and if you use my powershell cmdlets then you can get to them pretty easily.
3. yes - it does not work when using the datafile parameter
4. You could download my code and modify it as you see fit - just do a build of the solution and it will generate a new WSP for you.

RR said...

Hi Gary
Thanks for your great contribution.

Here is my question. Can I add a folder to a document library by means of the gl-addlistitem command?

Many thanks

Gary Lapointe said...

I haven't tried but I imagine it should work - a folder is just an item with a Folder content type.

Gary Lapointe said...

Well, sort of :)

Arun said...

Gary, may I know how to set the value for a lookup field? When I try to execute adding list items from a datafile with a lookup field, I'm encountered with the following error;
An error occured executing the command: Invalid data has been used to update the list item. The field you are trying to update may be read only.

Gary Lapointe said...

The value needs be in the format "ID#;Title".

JP Medlock said...

Awesome tools!! One quick question. I am using Add List Item to write to a custom list. One of the columns is a "User or Group" type. I am trying to write DOMAIN\USERNAME to this column. All other columns write fine except this one. It comes up blank. Any trick or will it just not write this column type? I can work around but it would really be nice if that would work. Thanks.

Gary Lapointe said...

You need to add it in the form "ID;#Name" - so something like this: "1;#SharePoint Administrator"

Anonymous said...

Gary, I am trying to update a list item by its ID#. How would you suggest i go about getting this accomplished? Thank you for your help.

-Patrick

Gary Lapointe said...

I'd use powershell to get the SPListItem object and then update it as needed.

Anonymous said...

Gary:

when i add an item using the addlistitem command, the new data does not trigger the workflow associated with the list. if i create an item manually, the workflow triggers. any suggestions on how to get the workflow to trigger after using the addlistitem command? Thanks for your help.

-Patrick

jp medlock said...

What format might I use to add a date and time for a date-time field? I have a calendar list I'd like to import records into from a text file. Thanks for all your great work!