One of the things I’ve been working on recently was trying to get the site directory into a useable state post upgrade. The main issue I needed to address was that the upgrade resulted in duplicate columns for Division and Region. The duplication was because the default Site Directory template creates columns with a display name of Division (and Region) but the internal name is DivisionMulti (and RegionMulti). So when the upgrade moved the source list over it saw the names as being differing so instead of replace or merging the columns it just added new ones (this time with the internal name matching the display name).

So, I needed a simple command to allow me to delete the DivisionMulti and RegionMulti columns. The problem that I encountered was that the indexer for the Fields collection of the list (SPFieldCollection.Item[string]) wants the display name – this threw me off at first and I can see where it could potentially mess someone up really bad if they’re not paying close attention to how they are using it. By passing in only the display name you will be returned back the first item that it comes across with no indication whatsoever that there may actually be another item in the list with the same name.

So my challenge here was to make the command as “safe” as I could. I accomplished this by allowing the user to pass in either the display name or the internal name (with the internal name being the safest approach). If the display name was passed in then I do a check to make sure that there’s only one field with that display name – if I find more than one then I return back the matches along with the internal name so that the command can be attempted again. I thought about only allowing the internal name but figured that 90% of the time the display name will work just fine and it’s easier to discover.

The other thing I needed to do was to provide some details as to why a field may not be able to be deleted. To do this I had to look into the CanBeDeleted property of the SPField object. From this I was able to see that if the field is derived from a base type or if it’s sealed or if it’s marked as not allowing deletion via the AllowDeletion property then it cannot be deleted. The AllowDeletion property is an easy enough one to get around so I provided a -force switch to allow the user to override this setting and delete the field regardless. I chose not to do anything (not allow the deletion) if it’s derived from a base type or if it’s sealed only because of numerous ramifications that result from attempting to delete these fields.

There’s only two core parts to this code – the first involves a utility method that I created to retrieve the SPField object and the other is a small amount of code to determine whether or not the field can be deleted or not:

  1internal static SPField GetField(string listViewUrl, string fieldName, string fieldTitle, bool useFieldName, bool useFieldTitle)
  2{
  3    SPList list = Utilities.GetListFromViewUrl(listViewUrl);
  4    
  5    if (list == null)
  6    {
  7        throw new Exception("List not found.");
  8    }
  9    
 10    SPField field = null;
 11    try
 12    {
 13        // It's possible to have more than one field in the fields collection with the same display name.  The Fields collection
 14        // will merely return back the first item it finds with a name matching the one specified so it's really a rather useless
 15        // way of retrieving a field as it's extremely misleading and could lead to someone inadvertantly messing things up.
 16        // I provide the ability to use the display name for convienence but don't rely on it for anything.
 17        if (useFieldTitle)
 18            field = list.Fields[fieldTitle];
 19    }
 20    catch (ArgumentException)
 21    {
 22    }
 23    
 24    if (field != null || useFieldName)
 25    {
 26        int count = 0;
 27        string foundFields = string.Empty;
 28        // If the user specified the display name we need to make sure that only one field exists matching that display name.
 29        // If they specified the internal name then we need to loop until we find a match.
 30        foreach (SPField temp in list.Fields)
 31        {
 32            if (useFieldName && temp.InternalName.ToLower() == fieldName.ToLower())
 33            {
 34                field = temp;
 35                break;
 36            }
 37            else if (useFieldTitle && temp.Title == fieldTitle)
 38            {
 39                count++;
 40                foundFields += "\t" + temp.Title + " = " + temp.InternalName + "\r\n";
 41            }
 42        }
 43        if (useFieldTitle && count > 1)
 44        {
 45            throw new Exception("More than one field was found matching the display name specified:\r\n\r\n\tDisplay Name = Internal Name\r\n\t----------------------------\r\n" +
 46                                foundFields +
 47                                "\r\nUse \"-fieldinternalname\" to delete based on the internal name of the field.");
 48        }
 49    }
 50    
 51    if (field == null)
 52        throw new Exception("Field not found.");
 53    return field;
 54}
 55 
 56public override int Run(string command, StringDictionary keyValues, out string output)
 57{
 58    output = string.Empty;
 59 
 60    InitParameters(keyValues);
 61    SPBinaryParameterValidator.Validate("fielddisplayname", Params["fielddisplayname"].Value, "fieldinternalname", Params["fieldinternalname"].Value);
 62 
 63    string url = Params["url"].Value;
 64    string fieldTitle = Params["fielddisplayname"].Value;
 65    string fieldName = Params["fieldinternalname"].Value;
 66    bool useTitle = Params["fielddisplayname"].UserTypedIn;
 67    bool useName = Params["fieldinternalname"].UserTypedIn;
 68    bool force = Params["force"].UserTypedIn;
 69 
 70    SPField field = Utilities.GetField(url, fieldName, fieldTitle, useName, useTitle);
 71 
 72    if (!field.CanBeDeleted)
 73    {
 74        if (field.FromBaseType)
 75        {
 76            throw new Exception(
 77                "The field is derived from a base type and cannot be deleted.  You must delete the field from the base type.");
 78        }
 79        else if (field.Sealed)
 80        {
 81            throw new Exception("This field is sealed and cannot be deleted.");
 82        }
 83        else if (field.AllowDeletion.HasValue && !field.AllowDeletion.Value && !force)
 84        {
 85            throw new Exception(
 86                "Field is marked as not allowing deletion - specify \"-force\" to ignore this setting and attempt deletion regardless.");
 87        }
 88        else if (field.AllowDeletion.HasValue && !field.AllowDeletion.Value && force)
 89        {
 90            field.AllowDeletion = true;
 91            field.Update();
 92        }
 93        else
 94        {
 95            throw new Exception("Field cannot be deleted.");
 96        }
 97    }
 98 
 99    field.Delete();
100 
101    return 1;
102}

The syntax of the command I created, gl-deletelistfield, can be seen below:

C:\>stsadm -help gl-deletelistfield

stsadm -o gl-deletelistfield

Deletes a field (column) from a list.

Parameters:
        -url <list view URL>
        [-fielddisplayname <field display name>] / [-fieldinternalname <field internal name>]
        [-force (used to force deletion if AllowDeletion property is false)]

Here’s an example of how to delete the DivisionMulti column from the site directory:

stsadm –o gl-deletelistfield -url "http://intranet/sitedirectory/lists/sites/allitems.aspx" -fieldinternalname DivisionMulti