Welcome to SharePoint User Group UK Sign in | Join | Help

A colleague of mine encountered this error when attempting to configure a new SharePoint install from scripts.

After some soul-searching and much swearing, we found the error to be caused by SharePoint not liking its accounts to contain Fully Qualified Domain Names. He had specified all of the accounts (farm creation acc, search accounts, app pools etc.) to be something like "Domain.Qualifier\AccountName".

The resolution was to specify the accounts like "Domain\AccountName".

As this was scripted, we decided to simply roll-back the environment to the previous snap-shot, then re-run the scripts with the accounts changed, however it is probably possible to delete the SSP, change the accounts using STSADM commands and then recreate it. These posts would help if that is the tack you decide to take:

- http://www.stationcomputing.com/scblogspace/Lists/Posts/Post.aspx?List=ec6ce151%2D566a%2D499e%2D94a5%2De153fafda404&ID=35

- http://support.microsoft.com/kb/934838

Note: you may need to ensure your NICs have NetBIOS support enabled for them to recognise the unqualified accounts.

I recently had a requirement to use a scripted approach to create a SharePoint farm that used Kerberos authentication.

A quick bit of research pointed me towards the excellent resources made available by Gary Lapointe (see his blog at http://stsadm.blogspot.com/).

Gary has created and made freely available a huge number of really useful custom STSADM commands and Powershell commandlets, as well as going through the code used to create them. It really is an excellent resource he has created, and well worth spending some time going over all the information that he has very generously compiled and offered there.

One of the items Gary has on the site is his Install scripts (http://stsadm.blogspot.com/2008/03/sample-install-script.html) which creates a Kerberos based MOSS farm, including creating and configuring the SSP and search, a portal web application, a mysites web application and a team sites web application.

This is all achieved with batch files that cal PSCONFIG and STSADM commands, with a few of his own custom STSADM commands being used – very helpfully, these commands are provided as a WSP which is installed as a part of the farm creation scripts. Very neat.

These scripts follow Microsoft's "Plan for Administrative and Service Accounts (Office SharePoint Server): Least-Privilege Admin Requirements when using Domain Accounts" (http://technet.microsoft.com/en-us/library/cc263445.aspx ).

I thought I would quickly go through the steps I followed to build my development vm using these scripts (note: these aren’t a comprehensive set of steps, for example I don’t indicate when I performed Windows updates and took vm snapshot’s etc., but rather cover the main items that must be completed in order to successfully create a Kerberos authenticated farm).

In order to enable Kerberos, many accounts used during the install process require Service Principal Name’s (SPN’s), and being entrusted to delegate authority. To make these changes, you require access to the ADSI Edit MMC snap-in. Instructions on how to install this if it is not available on the machine can be found at http://technet.microsoft.com/en-us/library/cc773354(WS.10).aspx


To Create an SPN

1. Log on to your Active Directory domain controller using the credentials of a user that has domain administrative permissions.
2. In the Run dialog box, type ADSIEDIT.MSC.
3. In the management console dialog box, expand the domain container folder.
4. Expand the container folder containing user accounts, for example CN=Users.
5. Locate the container for the SQL Server Service account, for example CN=mosssqlsvc.
6. Right-click this account, and then click Properties.
7. Scroll down the list of properties in the SQL Server Service account dialog box until you find servicePrincipalName.
8. Select the servicePrincipalName property and click Edit.
9. In the Value to Add field, in the Multi-Valued String Editor dialog box,enter the Service Principal Name string – which takes the form of: {Protocol}/{host and FQDN}:{Port}, e.g. “MSSQLSvc/mosssql:1433” or “HTTP/intranet.development.com” * – and click Add.
10. Click OK on the Multi-Valued String Editor dialog box, and then click OK on the properties dialog box for the SQL Server service account.

*One little “gotcha” that you may need to be aware of when creating a Kerberos authenticated farm is that SharePoint 2007 only supports Kerberos web applications running of the default ports. This is why I have not had to supply port numbers in the SPNs used by the application pool accounts.


Setting an Account as Entrusted to Delegate Authority

1. Start the “Active Directory Users and Computers” MMC snap-in.
2. In the left pane, click Users.
3. In the right pane, right-click the name of the user account, and then click Properties.
4. Click the Account tab, under Account Options, click to select the Account is trusted for delegation check box, and then click OK.

Ok, so to create my environment, the steps I followed were (note: these steps assume that the current account being used to perform the installation is a member of the local administrators group):

1. Create the vm, install the operating system and activate the Application Server, Domain Controller and DNS roles (I cover these steps in a bit more detail in this post: http://suguk.org/blogs/the_moss-pit/archive/2008/12/22/16362.aspx).
2. Create the SQL Server service account (svc-sqlsvr) as a domain account, give it an SPN of MSSQLSvc/{host and FQDN}:1433, and enable it for delegation.
3. Install SQL Server and all related SP’s.
4. Grant the current account performing the install sys_admin privileges on the database.
5. Create the following domain accounts, setting the one’s with SPN’s to be trusted for delegation (format: Account Name -- SPN -- Comments):

spadmin -- [none] -- Used to generate emails from the farm to users. Requires an email address to be specified like no-reply@{email}.{server}

spfarm -- HTTP/{host & FQDN}:{Admin port} -- [no comment]

sspapppool -- HTTP/sspadmin.{FQDN} -- Should not be a member of the Administrators group on any computer in the server farm.

sspsvc -- [none] -- This account should not be a member of the Administrators group on any computer in the server farm.

sspsearch -- [none] -- Should not be a member of the Farm Administrators group.

sspcontent -- [none] -- Should not be a member of the Farm Administrators group.

sspuserprofilesvc -- [none] -- Requires read access to the directory service, and Manage User Profiles personalization services permission.

sspexcelsvc -- [none] -- used for running excel services on the ssp

sphelpsearch -- [none] -- Should not be a member of the Farm Administrators group.

spcontentsearch -- [none] -- Should not be a member of the Farm Administrators group.

spportalapppool -- HTTP/portal.{FQDN} -- Should not be a member of the Administrators group on any computer in the server farm.

spmysitesapppool -- HTTP/mysites.{FQDN} -- Should not be a member of the Administrators group on any computer in the server farm.

spteamsiteapppool -- HTTP/teams.{FQDN} -- Should not be a member of the Administrators group on any computer in the server farm.

siteowner1 -- [none] -- Should be modified here and in the scripts to be a sensible account for your env.


6. Install MOSS, but don’t run the config wizard.
7. Install the MOSS infrastructure update (this is required to enable Kerberos), and the service packs.
8. Create your DNS entries to support the four web sites (sspadmin, portal, mysites, teams)
9. Run the Gary Lapointe’s farm creation scripts.

I then found a few more things I had to do to complete the farm install:

- configure the index server in central admin (Operations->Services on server->Search Indexing->Office SharePoint Server Search

- configure the default access account (SSP Admin->Search Administration->Default Content Access account)

Fix up the license type reset that happens when you install SP2 (CentralAdmin->Operations->Convert License Type)

- Go over the rest of the settings in the SSP and Central Admin to make sure they suit your needs (eg. crawl schedules and rules, profile import sched's, usage analytics etc.)

Following this process established the SharePoint farm which included the creating Central Admin, the SSP and three web applications.

A (rather major) memory leak has been found in SharePoint, caused by the failure of SPHttpApplication objects to be properly cleaned up - read all about it in Todd Carter's great post The Sharepoint Sasquatch Memory Leak

I have quickly whipped up a WSP that implements Todd's fix and thought I would attach it here for anyone else who might find it useful.  It simply deploys a DLL to the GAC which implements the classs he describes in his post.  I have done it as a WSP so that the DLL will be auto deployed to all web front-ends used by the web application it is deployed to.

The install process is as follows:

1) Install the wsp to the central admin solution store (using "stsadm -o addsolution -filename [filename]

2) From Central Admin, deploy the solution to your targeted web application.  This will deploy the SPHttpApplicationLeakFix DLL to the GAC on all the web front-ends for that web application.

3) Go onto all the web front-ends and change the global.asax file (found at: '\InetPub\wwwroot\wss\VirtualDirectories\[Web App Dir Name]'). The two lines that need to be modifed are:


they should be changed to:


That's it - at this stage your sharepoint web application should still load and behave as normal, though hopefully its w3 worker process won't be consuming the huge levels of memory it was previously!

A zip containing the WSP and a small readme is attached to this article.

 

NOTE: Now the update contains the wsp - apologies for the complete balls-up ;)
Another really short bog entry - another pointer to someone elses blog! This is a great little walkthrough re: creating a development vm with DNS and AD and Mail - http://blogs.msdn.com/johnwpowell/archive/2008/07/07/walkthrough-build-a-windows-sharepoint-services-3-0-development-virtual-pc-with-sql-server-active-directory-and-e-mail.aspx
Havent blogged for ages - must get back into it soon. Anyways, here is a nice blog entry on setting up FBA for SharePoint 2007 using IIS7: http://blog.summitcloud.com/2009/10/enable-forms-based-authentication-for-sharepoint/
I'd seen references to SPUtility before, and probably even used the object when basing code of blog posts I'd found on the net, but inspiration struct me the other day, and I decided to look it up in the SDK.
 
It is a static object that contains a number of useful functions - it also contains a number of obsolete functions, and some downright weird ones (like SPUtility.HideTaiwan and SPUtility.IsEastAsia strike me as ones that won't be widely used).
 
To access the object, you need to add the following using statement:
 
using Microsoft.SharePoint.Utilities;
 
 
I had a quick go through the SDK, an the following are what strikes me as some of the more useful functions this object provides (note, I havent used all of these yet, just went through the SDK then googled the functions I thought sounded interesting):
  • SPUtility.FormatDate

    Allows you to format a given date to any of the SPDateFormat types


    DateTime curDate = DateTime.Now();
    DateTime regionDate = web.RegionalSettings.TimeZone.UTCToLocalTime(web.ParentWeb.RegionalSettings.TimeZone.LocalTimeToUTC(curDate));
    return Convert.ToDateTime(SPUtility.FormatDate(web, regionDate, SPDateFormat.ISO8601));



  • Get the 12-Hive filesystem path

    Returns the filesystem path for the 12-Hive, or any of the folders beneath it.  This is typically (though not always) going to be C:\Program Files\Common Files\Microsoft Shared\web server extensions\12

    //Get path to features directory
    //Would typically return "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES"
    string featurePath = SPUtility.GetGenericSetupPath("template\\features");


  • Get Full (absolute) URL

    Converts a relative Url into an absolute Url


    //Get url to list item
    SPListItem item = spList.Items[1];
    string itemUrl = SPUtility.GetFullUrl(spSite, item.Url);


  • Redirect to page

    Send a HTTP redirect to the client's browser


    //Redirect to specified page, adding querystring
    string url = "http://portal/TestResults/Pages/results.aspx";

    string queryString = "successflag=passed";

    SPUtility.Redirect(url, SPRedirectFlags.Default, Context, queryString);


  • Send email

    Lets you send an email from the context of the given SPWeb

    //Send email from current SPWeb
    SPWeb web = SPContext.Current.Site.OpenWeb();
    string subject = "Email from the " + web.Title + " web";
    string body = "The body of the email";

    SPUtility.SendEmail(web, false, false, "someone@somewhere.com", subject, body);


  • Transfer to SharePoint success/error page

    Allows you to transfer the browser to the ootb error and success pages

    //Transfer to Error Page
    SPUtility.TransferToErrorPage(ex.Message);

    //Transfer to success page, and specify url to move onto after "Ok" clicked
    SPUtility.TransferToSuccessPage("Operation was completed", @"/Docs/default.aspx", "", "");



There are a bunch of others on there, and although they are not really very well documented, they are certainly worth a look through.


In a Page Visitor Tracking web part I have been writing, I use the SPSecurity.RunWithElevatedPrivileges function to add or update a line in a Visitor Tracking list on the current site.  The idea being that not all visitors to the page will have the rights to write to this list, so some level of impersonation is needed.
 
Frustratingly, even though I had used this function to elevate the privileges of the current user, I was still getting an "Access Denied" message when running the functionality under my test user.
 
After a bit of searching on the net (great entry by Daniel Larson - http://daniellarson.spaces.live.com/blog/cns!D3543C5837291E93!927.entry?sa=486500840), I determined that the problem was that I was getting my reference to the list whilst the code was still running under the current users context, and not the elevated user.
 
e.g. (Note, this code sample has been cut down a great deal to illustrate the point - no "try...catch" blocks, for example.  It should be used only as a guide, and not copied to be used as is)
 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

protected override void CreateChildControls()
{
    //Get the current web
   
SPWeb web = SPControl.GetContextWeb(Context);

    //Get the list to record visits
   
visList = web.Lists[_visitorList];

    //See if this user already exists in list
   
_existItemId = GetVisitorItemId(visList);

    //If not in list, add them
   
if (_existItemId == 0)
    {
       
//Run the AddCurrentUserToList function with elevated priv's
      
SPSecurity.CodeToRunElevated elevatedAddCurrentUserToList = new SPSecurity.CodeToRunElevated(AddCurrentUserToList);

        SPSecurity.RunWithElevatedPrivileges(elevatedAddCurrentUserToList);
    }
   else
   
{
       
//Run the AddCurrentUserToList function with elevated priv's
       
SPSecurity.CodeToRunElevated elevatedIncrementNumberOfVisits = new SPSecurity.CodeToRunElevated(IncrementNumberOfVisits);

        SPSecurity.RunWithElevatedPrivileges(elevatedIncrementNumberOfVisits);
    }
}


private
void AddCurrentUserToList()

{
   
//Create new ListItem
   
SPListItemCollection items = visList.Items;
    SPListItem newItem = items.Add();

    //Set field values for new ListItem

    //Update Item
    newItem.Update();
}


private
void IncrementNumberOfVisits()

{
    //Get the ListItem to update

    //Increment the number of visits

    //Update the item
   
updateItem.Update();
}

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


Because of this, I changed my code so that I made a new reference to the SPWeb in the elevated code block, and obtained a new reference to the list:


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

protected override void CreateChildControls()
{
   
//Get the current web
   
SPWeb web = SPControl.GetContextWeb(Context);
    _curWeb = web.Url;

    SPList visList = null;

    //Get the list to record visits
   
visList = web.Lists[_visitorList];

    //See if this user already exists in list
   
_existItemId = GetVisitorItemId(visList);

    //If not in list, add them
   
if (_existItemId == 0)
    {
       
//Run the AddCurrentUserToList function with elevated priv's
       
SPSecurity.CodeToRunElevated elevatedAddCurrentUserToList = new SPSecurity.CodeToRunElevated(AddCurrentUserToList);

        SPSecurity.RunWithElevatedPrivileges(elevatedAddCurrentUserToList);
    }
   
else
   
{
       
//Run the AddCurrentUserToList function with elevated priv's
       
SPSecurity.CodeToRunElevated elevatedIncrementNumberOfVisits = new SPSecurity.CodeToRunElevated(IncrementNumberOfVisits);

        SPSecurity.RunWithElevatedPrivileges(elevatedIncrementNumberOfVisits);
    }
}


private
void AddCurrentUserToList()

{
   
//Get list
   
SPSite site = new SPSite(_curWeb);
    SPWeb web = site.OpenWeb();
    SPList visList = web.Lists[_visitorList];

    web.AllowUnsafeUpdates = true;

     //Create new ListItem
    
SPListItemCollection items = visList.Items;
    SPListItem newItem = items.Add();

    //Set field values for new ListItem

    //Update Item
    newItem.Update();

    web.AllowUnsafeUpdates = false;

    //Cleanup
   
web.Dispose();
    site.Dispose();
}

 

private void IncrementNumberOfVisits()
{
   
//Get list
   
SPSite site = new SPSite(_curWeb);
    SPWeb web = site.OpenWeb();
    SPList visList = web.Lists[_visitorList];

    web.AllowUnsafeUpdates = true;

    //Get the ListItem to update

    //Increment the number of visits

    //Update the item
   
updateItem.Update();

    web.AllowUnsafeUpdates = false;

    //Cleanup
   
web.Dispose();
    site.Dispose();
}

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
SPList's, SPWeb's etc get the identity of the user context they are created under associated with them for their entire lifetime.  Like most things, seems obvious once you understand it.
 
To get around this, I no longer use the SPList globally, but instead keep the URL of the web globally, and use this in the elevated code blocks to derive an SPSite, SPWeb and finally the required SPList.
 
Important to note that this as the SPWeb that I create in the elevated code block is a different SPWeb to what I am using outside this block (it has the SHAREPOINT\Security identity associated with it, not the current users) it can (and should) safely disposed of from within this block.

-----------------------------------------------------------

UPDATE:  As can be seen in the comments below,
Keith Dahlby has posted a much more elegant way of achieving the elevation of privileges by using the site's token.

I have changed my code to follow this (and incorporated the functionality in my SharePoint.Common library) and recommend reading his article - see http://solutionizing.net/2009/01/06/elegant-spsite-elevation/

Keep up the good work Keith!




The thought occured to me that my previous article on this subject detailed how I go about building a nice clean development environment, but not what the next steps were, i.e. getting a working copy of the production web application on that environment.  Hopefully, this article will show you how.
 
So, assuming you have got a vm which has SharePoint installed on it, but not really configured, and with no web apps (as it would be after following my previous article), the next steps I follow are:

 
DNS Enteries
 
Create some DNS host enteries for the web applications you are going to be creating.  In the domain's forward looking zone, I create the following 3 host enteries pointing to my vm's ip address:
  • ssp
  • mysite
  • moss
 

Configure Services on Server

Next, I complete the MOSS configuration by opening Central Admin (Start->All Programs->Microsoft Office Server->SharePoint 3.0 central Administration). Click on the "Operations" tab, and "Services on Server" link.
 
As the development vm is a single machine environment, I would activate all of the services, and in this order (NB: you can see all available services by choosing the "Custom" server role):
  • Document Load Balancing
  • Document Conversions Launcher Service  (select your vm name in the "Load-balancer" dropdown)
  • Excel Calculation Services
  • Office SharePoint Server Search  (using the svc-spssearch account)
  • Windows SharePoint Services Search  (using the svc-spssearch account for both service and content access accounts)

Create SSP
 
Next step is to create a Shared Services Provider. To do this, click the "Shared Services Administration" in your Quicklaunch, then click the "New SSP" link. The resultant page asks you to specify an SSP web application and MySites web applciation, but also provides you with links to create these web apps.
 
Click the create a new web app link for your ssp, and create it on port 80 using "ssp" as your host header.  For the application pool identity, use the svc-spsapool account. Change the Database name to be something meaningful (like WSS_Content_SSP), and select your vm as the search server in the drop down.  Repeat this process to create your Mysite web app.
 
Back on the create SSP page, use the svc-spsadmin as you ssp service credentials (you could really create a unique account for this if you wanted to), and then click ok to generate your SSP.
 
 

Create Web App
 
Time to create the web application that will eventually hold a copy of your prod web app.  Click the "Back to Central Admin" link in the QuickLaunch, then click the "Application Management" tab.  On this screen, click "Create or Extend a Web Application".
 
As before, create it on port 80 using "moss" as your host header.  For the application pool identity, use the svc-spsapool account. Change the Database name to be something meaningful (like WSS_Content_Moss), and select your vm as the search server in the drop down.
 
 

Configue SSP
 
Click on the "SharedServices1" link in the quicklaunch to get to the setup screen for this SSP.  Most of the defaults will be acceptable here, with the only things I think you need to consider being:
  • the time zone
  • upload file size limit (may need to be increased)
  • recycle bin status (ensure default values are appropriate for you)
Once you "ok" this screen, you are taken into the SSP web app itself, where you can configure things like profile importing and search.  The things I would look at for my dev vm are:
  • User Profiles - configure the profile importing so that the full import (weekly) and incremental import (daily) take place at an appropriate time (when you vm will actually be on).

  • Search settings - click on "Content source and crawl schedules", and then on the auto generated "Local Office SharePoint Server Sites" content source.  This should show 4 start addresses - http://moss, http://mysite, http://ssp and sps3://mysite. 

    Click the "Create a schedule" link under the "Full Crawl Schedule" dropdown, and specify a weekly time for a full crawl of the content to be done.  This can be resource intensive, so best if you plan it for a time when you know your vm will be active, but you probably wont be using it (weekly team meetings time is ideal).

    Repeat this process for the incremental crawl schedule, but make it a daily crawl - I tend to make it repeat every 20mins.

 
Backup your "Baseline" Dev SharePoint Farm
 
At this point you have a SharePoint farm on you vm that is a good clean baseline farm. I would recommend backing the vm up at this point, and using a fresh copy of it for each "production" farm copy you are going to make to develop against.
 
 
 

Backup Production Environment
 
If it is small enough, I like to take a backup of the production environment, and install it onto my dev environment.  Often the production web application is too large to make a straight backup and restore viable, but you can usually get around this by exporting the root and a few sub sites of the production environment and importing them onto your dev environment.  The steps that follow here assume that the prod envirnment is small enough to do a backup and restore:
 
  • Backup up the web app: use STSADM to backup the web app to a file with the command
     
            stsadm.exe -o backup -url -filename
           
        You can add a "-overwrite" flag as well, which indicates that if the file already exists it should be overwritten - probably not necessary for most ad hoc backups.

  • Web.Config: grab a copy of the web.config to get an custom settings and safecontrols

  • Hive-12: next, you need to grab a copy of the production's have 12 directory so that you pick-up any additions/customizations here.  The directory is (generally) found at C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE

  • Solutions/DLL's: last step is to add any of the customized solutions used on the production farm to your dev environment. 

    If you have the solution packages to hand then great, put them on your vm and install them using "stsadm -o addsolution" - this way you get them added to the dev farm's solution store correctly, and the web.config gets updated appropriately.

    If you don't have them, a dirtier way of getting the functionality across is to just copy the custom DLL's from the GAC and/or bin directory onto your vm, and update the web.config yourself.

Restore to Dev Environment
 
It is best to restore you prod backup over an existing site collection, so the first thing to do is to create a site collection in your "Moss" web application of the same template type used on prod (eg. publishing). Do this by opening Central Admin, clicking on the "Application Management" tab, and clicking "Create Site Collection"
 
Take a backup of the existing 12-Hive Template directory (I generally create a zip "TEMPLATE-orig.zip" of it in the '12' directory), and then restore your prod copy of this directory over the top of the existing one.
 
As mentioned previously, if you have all the customizations for the production environment you can install and deploy them, otherwise copy the DLL's to the appropriate directory (generally the GAC) and manually update the web.config.
 
Lastly, bring your vm's web.config in to line with web.config from the prod environment.  I would suggest doing this manually (rather then trying to copy one over the top of the other) as some differences may be acceptable (or even necessary).  Areas to pay close attention to include:
  • Safe Controls
  • Any additional custom ConfigSections
  • Custom Errors section

 

And thats it - hopefully now you should have a replica of your prod environment on your vm, ready for you to start developing against.

 

Thought I would detail the steps I follow when creating a stand-alone development vm for SharePoint, and some of the software I like to have on one.
 
I tend to use VMWare Workstation for my vm's as I like the interface, but Microsoft's Virtual PC works just as well, with the (huge) added advantage of being free. 
 
I've not tried MS Windows Server 2008 as yet as it is not used at any of the business locations I have worked at, so this guide covers using Server 2003 - though I expect the process would be almost the same on 2008.
 

- Build the VM
 
First step is to build a VM in whichever VM hosting software you are using.  Assign it as much RAM as you can afford on your system (I recommend at least 1gig), and enough disk space to cover your future needs - it is not possible to extend your virtual machine drive size on most VM Hosting software (although you can add additional virtual drives to you VM and gain more space that way). I tend to go for around 40Gig which, after all the software I require is installed, gives me around 15gig space remaining.  I also generally call my vm something like "DevBox32" or "DevBox64" depending on what version of the OS I am using.


Update:
As shown in the comments below, it is possible to increase the size of a virtual drive, but you must detatch it from the vm first - painful
, but nice to know.  Many thanks to Cimares (Paul Hunt) for this little nugget.
 

Install your OS on the virtual machine, using the latest Release if possible (MS Windows Server 2003 R2 at time of writing). 
 
For development vm's, I strongly recommend the KISS practices for usernames and passwords, and so I use "Administrator" and "password" for the OS admin account. For any other OS admin-type passwords (eg the recovery password in DNS) I also use "password".
 
Once the install is complete, I run Windows Activate and Windows Update to get all the latest patches and hotfixes.  At this point, it is a really good idea to backup your virtual machine (ie. copy the folder on the host's filesystem that contains all your virtual machine's files) so that you have a nice "baseline" virtual machine that you can use in the future if you need to create other Server 2003 vm's that don't need all the rest of the stuff we are going to put on this one.

 
- Turn off System Beep
 
A lot of the installers used cause a system beep, which can be annoying for people sat around you, so I tend to turn it off.  Do this through using the Regedit utility, changing the HKEY_CURRENT_USER/Control Panel/Sound/Beep to "No".  Reboot the vm so the new setting is applied.

--- Update: actually, have found the most effective way of switching off the system beep is to run this command fron the windows run box:
sc config beep start= disabled
 

- AD and DNS
 
Now, I make the vm a Domain Controller in a new AD Forest using the "Manage Your Server" mmc snap-in (can be found in "Administrative Tools", and is shown in Server 2003 every time you log in until "Dont display this page at logon" is checked). 
 
I tend to call the domain "Development" and ignore the MS warning about using a multipart domain name. I allow the Domain Controller wizard to install DNS for me by selecting the appropriate option when it comes up, and reboot the machine at the end as the wizard directs.
 

- Create Accounts
 
I then relax the Domain Security password policies, by using the "Domain Security Settings" snap-in in Administrative Tools.  Keep all the settings defined, but set them to be 0 - eg. Enforce Password History to be 0, Max Password Age to be 0, etc.
 
This allows me to use the username as the password also for the service and testuser accounts required, keeping it all nice and simple.  I then restart the vm so that it boots with the new password policies active.
 
Once the machine is back up, I make the following accounts by going into "Active Directory User and Computers" snap-in, using the username as the password for each one, and unchecking the "User must change password at next logon" and checking the "Password never expires" options:
  • svc-sqlsver (SQL server service account)
  • svc-spsadmin (SharePoint Server service account)
  • svc-spsapool (SharePoint Server application pool account)
  • svc-spssearch (Sharepoint Server Search account)
  • testuser (test user account)


I make each of these accounts members of the "Administration" group except for the testuser account.  Make it a member of the "Remote Desktop Users" group.


- Application Server
 
Next step is to install the dotNet frameworks - I install versions 2.0 and 3.0. After they are installed, run Windows update to get the latest service packs and fixes for these on.
 
When this is complete, open the "Manage Your Server" snap-in again, and add the "Application Server" role.  When installing this, ensure "Frontpage extensions" remains unchecked, and check "Enable asp.net".
 
At this point we have finished with the "Manage my Server" snap-in, so I check "Don't display this page at logon" and close it.
 

- Turn off IE Enhanced Security
 
Again, to keep the whole environment simple, I turn off IE's enhanced security.  Do this by opening "Add or Remove Programs" in the Administrative Tools, and selecting "Add/Remove Windows Components". "Internet Explorer Enhance Security Configuration is an option in this list.
 

- Install SQL Server
 
Next step is to get the database on there.  I tend to use SQL Server 2005, and first install the server component, ensuring "SQL Server Databse Services", "Analysis Services", "Report Services" and "Integration Services" are installed.
 
Use the "svc-sqlsvr" account we created earlier for the service account, and use Windows Auth mode. Collation can be a prickly issue with some DBAs, but I just use the default on my vm.
 
I then install the SQL server client components on the vm, ensuring that the "Connectivity components", "Management Tools" and "Legacy Components" are installed. 
 
On completion of the client tools installing, I reboot the virtual machine (to remove some file locks these installs leave about).
 
When the vm has rebooted, I install SP3 for SQL Server, and then run windows update (it is cumulative, so includes SP1 and SP2).
 

- SharePoint
 
I generally install MOSS Enterprise on my vm's, as this is what most of the companies I have worked for are using.
 
Install it as a full install (not single machine) so that MSDE is not installed, and use the svc-spsadmin account when prompted.
 
Once it has completed installing, I then put on the WSS SP1, MOSS SP1 and then any infrastructure updates (in that order). MSDN license holders will note that there is a version of MOSS available that has SP1 integrated with it.  I have found that this contains DLLs of a different version to a base MOSS install that has had SP1 applied against it, meaning backup/restores of site collections from one to the other fails to work because of version incompatabilities.  Because of this, I tend to just build it from the base up.
 

- Office and SharePoint Designer
 
Office 2007 are the next things to go onto the vm. You need Office 2007 on there to replicate your general users environment and perform any sort of testing. Although I am not a big fan of SharePoint Designer, it can be a handy piece of kit to have around. 
 
I install both these pieces, then run windows update to get the latest patches, and then activate them.
 

- Visual Studio and Source Safe
 
Time to put the development IDE on - I use Visual Studio 2008.  I also stick on Visual Source Safe so that I have some form of source control (even one that can't do proper branching/merging ;)
 

- SDK's and more
 
Spend any time developing with SharePoint object model and you will realize having the SDK's available is invaluable.  I stick on both the WSS SDK and the Office Server (MOSS) SDK.  I also put on the WSS Visual Studio Extensions so I can get the SharePoint Solution Generator.
 

- Desktop Shortcuts
 
Make some desktop shortcuts now for the Hive-12, your wwwroot/vss directory, wherever you are storing your studio project files, and a command prompt for accessing STSADM.
 

- Niceties
 
At this point, you have a nice development vm ready for use, but there are some other "niceties" that you should consider installing to make your life easier:
 
  • Powershell and the PowerGUI - to enable some power scripting
  • Reflector - brilliant bit of reverse engineering kit - install it into your 'ProgramFiles' directory, and make a shortcut in your Start menu
  • DebugView - the best way for outputting debug statements - install it into your 'ProgramFiles' directory, and make a shortcut in your Start menu
  • Fiddler - useful proxy-ing debug tool
  • Adobe Acrobat - you are going to need it on there sooner or later
  • MS IE Dev toolbar - In-browser set of dev tools
  • MS Sandcastle, HTML Help and Sandcastle Helpfile Builder - the replacement for ndoc that I use for producing API doc
  • SharePoint Explorer Community Edition - brilliant free in-browser toolbar for administering a sharepoint site - will save you hours!
  • Textpad - everyone has a favourite notepad replacement, mine is textpad
  • 7-Zip - great free compression utility - supports most formats
  • BGInfo - updates your desktop background with system information - useful for easily seeing whats what on your vm
  • Process Explorer - powerful (though resource hungry) replacement for the windows Task Manager
  • Infopath Forms Server - if it is used in your prod setup

Lastly, but certainly not leastly, create a README.TXT to be stored with your vm files that explains what OS and SP is on it, what else is installed etc. - much easier to open a text file then have to boot the vm and poke around.

Happy dev-ing!!
 
 
I think everyone agrees that proper error trapping and logging is a good idea - how else are you supposed to be able to work out what has gone wrong on a production system where you can't simply connect Visual Studio up and start debugging?
 
But too many times I have seen logging calls where the developer simply captures an exception and logs its .Message - this is lazy, and in an environment which has multiple web-front ends and hundreds (maybe thousands) of users, next to useless for the poor support staff who's job it is to check the logs the next day.  What is a log message that only contains "Object not set to an instance of an object" going to tell them then??
 
Every log message should have some contextual information which qualifies the log message!!
 
It is taken for granted that the Date/Time of the error should be included in the log (kind of automatic if you are logging to an Event Log), but a whole lot more information should also be included - some examples might be:
- the user who was in context when the error occured;
- the SPWeb that the web part in error was active on;
- the name of the SPList that was trying to be accessed; etc. etc.
 
Providing this kind of contextual information may mean that instead of one huge try...catch loop, you need to use a few - so be it.  The value gained by having meaningful logging messages is more then worth it.
 
The aim for every log message should be to provide enough information so that someone who comes along later and reads it can easily determine what has gone wrong where, and guide them to the solution to rectify the problem.
 
 
 One of the things that SharePoint is lacking out-of-the-box is a tree control field type.  In a recent project, we required one, so that list items and document library items could be tagged appropriately using hierarchical in-house taxonomies.
 
Additional requirements for this tree control included that multiple nodes in the tree were able to be selected, and that the data displayed in the tree could be maintained by non-IT Admin staff.
 
Luckily, SharePoint allows you to define and create your own custom field types.  We decided that a tree control that stored the tree node path for selected nodes as a string would suffice.  So, for example, the following:


 
would be stored as: "Americas/North America/USA; Europe/United Kingdom"
 
To meet the requirement that it could be maintained by non-IT staff, it was decided to source the tree from a SharePoint list.  The list structure was defined to have the following columns:
  • Title - unused, but always sensible to leave this field in your custom lists, as some SharePoint functionality expects it to be there.
  • Level1
  • Level2
  • Levelx - The level columns hold the node names for that level of the tree control.  The root node of the tree is the name of the list
  • HoverText - Tooltip text to be displayed when user hovers over that node
  • ShowCheckbox - if that node should display a checkbox or not

 
(click to see a version that isnt squashed down)

 

How to create the Custom Field

When creating a custom field, there are 3 aspects that must be addressed:

1) The Field Type Definition
2) The UI and logic for adding your field to a list
3) The UI and logic for your field when an item in that list is being edited

(Note: you could have a fourth section here if you wanted to customise the way your field was displayed when the list item properties were viewed, but I was happy for the tree node path to be displayed as text, so no customization was required).

The WSP package associated with this post installs the following files:
- …\TEMPLATE\CONTROLTEMPLATES\TreeListControl.ascx
- …\TEMPLATE\CONTROLTEMPLATES\TreeListEditControl.ascx
- …\TEMPLATE\XML\FLDTYPE_TreeList.XML
- TreeListControl.dll (to the GAC)

 


1 - The Field Type definition

There are two parts to defining your field - the code implements your field type, and the FLDTYPES_name.XML file that contains all the information SharePoint requires to implement your custom field.

1) The code that implements the field type

The class for your custom field is going to override one of the existing SPField types.  My class here, “TreeListControl.TreeListField” (contained in FieldDef.cs),  inherits from SPFieldsMultiLineText.  The major change to this class that mine is making is the addition of a “Dictionary” collection to hold the property values for the field:

 

//Dictionary of property values
private static Dictionaryint, string> dictUpdatedPropertyValue = new Dictionaryint, string>();

//Properties in a delimited string
private string _delimitedPropertyValues;

 

When users choose to add this field (say as a custom column in a list) they are asked to set some properties about (the source list for the tree, number of levels, etc.).  These values need to be stored by your custom field so they can be used later on when implementing the field.  The dictionary collection is used to store these property values.  Until the values are put in the dictionary, they are held in a the _delimitedProperty values string.

 

I also added the following properties to allow me to access the dictionary:

 

///
/// Access the delimited properties for the control
///

public string DelimitedPropertyValues
{
    get { return dictUpdatedPropertyValue.ContainsKey(ContextId) ? dictUpdatedPropertyValue[ContextId] : _delimitedPropertyValues; }
   
set { this._delimitedPropertyValues = value; }
}

 

 

And very importantly, I override the base field rendering control, so that my TreeList control will be rendered:

///
/// Override the field rendering control to use the custom TreeList control
///

public override Microsoft.SharePoint.WebControls.BaseFieldControl FieldRenderingControl
{
   get
   
{
      Microsoft.SharePoint.WebControls.BaseFieldControl TreeList =
new TreeList();
      TreeList.FieldName = InternalName;
      
return TreeList;
   
}
}

 

 

The rest of the functions in this class are quite straight forward, and simply used for maintaining the values stored in the dictionary.


2) The FLD_type.XML

 

 This file defines the field for SharePoint, and gets deployed to …\12\TEMPLATE\XML. most of the fields in here are pretty self-explanatory, though I would like to highlight a few of them:


 

Field Name="ParentType">NoteField>

Setting the parent type of the field to “Note” allows this field allows us to avoid the 255character limit that most of the other parent fields type’s have - a Note field has no character length restriction.

Field Name="FieldTypeClass">TreeListControl.TreeListField, TreeListControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f525b76b9e4fb66Field>

This defines the assembly and class therein that implements this field.

 

Property Schema with single field to hold the custom properties for this field -->
PropertySchema
>
   Fields
>
      Field ID="DelimitedPropertyValues" Hidden="TRUE" Name="DelimitedPropertyValues" DisplayName="(Hidden) DelimitedPropertyValues" Type="Text"
/> 
   Fields
>
PropertySchema>

As the comment indicates, the property schema section is where we define our field type has an additional hidden field used for holding our properties.  This “PropertySchema” was meant to allow you to define multiple property fields (one per property), but there is a bug that prevents this from working.  Hence, we store one property, which in actuality is a delimited string of all our properties.

 

 

 

 

 

 

 

2 - UI and Logic for adding your Field to a List

 

This basically refers to presenting a screen to allow a user to set or change the properties that your custom field uses, and storing these property settings.  So it deals with our “DelimitedPropertyValues” property above.

My Tree control is going to have 10 properties:

1. Site (string) - the name of the site holding the list of data that defines this tree.

2. ListName (string) - the name of this list holding the tree data.

3. Levels (int) - the number of levels this tree has.  user will select list via a dropdown.

4. SortByAlpha (bool) - whether the nodes in this list will be sorted alphabetically, or be displayed the way they occur in the list.

5. ShowSetNull (bool) - if a “Set this value to be null” link should be displayed beneath the tree.

6. ShowCheckboxes (bool) - if this tree should render checkboxes on nodes as defined in the list holding the tree data.

7. CheckBoxDisplay (string) - a string indicating if the checkboxes should be displayed on “All Nodes”, “Leaf nodes” or “List Defined” nodes.  User will select which one via a dropdown.

8. AutoCheckSubnodes (bool) - if a node containing subnodes is checked, should they all be automatically checked also.

9. ExpandCheckedNodes (bool) - if the tree should load in an expanded state, showing all nodes that have been checked.

10. ShowHoverText (bool) - indicates if the tree control should show the hover text as defined in the list holding the Tree data.


 

So, the first thing I need to do is to create a control that allows the user to set all of these properties when they use my custom field. 

 

In my fileset, the control is the TreeListEditField.ASCX file, and it gets deployed to …\12\TEMPLATE\CONTROLTEMPLATES.

 

The majority of this control is fairly straight forward, simple ASP.Net controls like Labels and TextBoxes to gather my property values, but you may not be familiar with the controls that enclose these, ie:

wssuc:InputFormSection runat="server" id="MySections" Title="Tree List Details">

       Template_InputFormControls>

             wssuc:InputFormControl runat="server">

                    Template_Control>

 

 

The “InputFormSection” control & “InputFormControl” are SharePoint controls that creates the standard look and feel that SharePoint forms have.  So, with those encasing my control (made up of labels, textboxes, dropdowns etc), I end up with something like this:

 

(click to see an image that is not squashed down)

 

 

Great, so now I need to put together the code that is going to be associated with this control.  All this code really needs to do is to create the correct DelimitedPropertyString for the values set by the user in the control.  So (for example) if they have checked a checkbox, put a “True;” in the delimited string.

 

I have a little logic around the “Load Lists” functionality - I want the user to enter a url to a SharePoint site containing the list holding the tree data, to then click a button to load the lists on this site into a dropdown, and finally to select which specific list holds the tree data.  I ensure that the path to the site is a relative url by removing the root url for the current web application if it exists in the given site url.

 

The OnSaveChange method is used to create a delimited string for all the properties, and then set this string against the controls “DelimitedPropertyValues” property.

 

So that we set these controls to have the correct values when someone is editing an existing TreeList column, the InitializeWithField method must parse the “DelimitedPropertyValues” string and set the state of the controls to be correct as per the values contained in that string.

 

 

 

 

 

 

3 - UI and Logic for your Field when an item in that list is being edited


This is very similar to the previous section, except we are now defining how the column will look and behave when that list item is being edited - that is, that it will display the tree as defined by our list.

 

As with the TreeListEditField control, this is an ascx file that gets stored on the filesystem at …\12\TEMPLATE\CONTROLTEMPLATES.  In the attached solution, this control is called TreeListFieldControl.ascx. 

 

It is made up of three parts - the tree control, a link that can be used to set the string value set by tree control to null, and a label that can be used to show the current string value.  These controls are contained by a “RenderingTemplate” control, which allows us to create our control from these three parts.

SharePoint:RenderingTemplate ID="TreeList" runat="server">

    Template>

        asp:TreeView runat="server" ID="treeList" />

        br />br />

        asp:LinkButton ID="setNullLink" runat="server" Text="Set this value to be null">asp:LinkButton>

        br />br />

        asp:Label  ID="valueLabel" runat="server">asp:Label>

    Template>

SharePoint:RenderingTemplate>

 

 

Ok, so like before, we now need to implement the code that is going to be associated with this control.  This code has quite a bit to do - it must get the properties that have been set against the control, then build the tree control from the designated list, and configure it based on the properties. 

 

I am not going to describe how the code does this in depth, as it is well commented in the cs file, and hopefully is easy to understand, however I want to point out that I use a viewstate variable (“drawnTree”) to determine whether to run the tree creation code, rather then the usual “if !Page.IsPostBack”.

 

This is because we use this control on created web pages (in the “PublishingControls:EditModePanel”), which means a page could be considered a postback (say, after web parts have been added) and the tree control not drawn.

 

And that’s it.  Install the solution to your farm (you can use the ~InstallSolution.bat included to help with this), deploy it to your web application (best to do this through central admin), and then it should be available for use in your lists and site columns.

 

 

 

 

Some things to note:

• The code currently assumes all users will have read access to the list containing the tree control data, if this is not true, you will need to implement some impersonation in the code around where it parses this list and builds the tree

• Because the checked tree nodes are stored as a string showing the TreeNode path, the ‘/’ character cannot be used in any of the tree node names

• I have taken out all of my logging and tracing calls, as these rely on other in-house code libraries. If you want to use this code in a prod system, I suggest you add your own in there

• I have taken out the custom styling classes (css) from the ascx’s, you may want to consider adding in your own

• Although this control works as is, this is really meant as a starting point for you to implement the control you need - an XML file might be a better source for your tree data, you might not like the strict design for the tree data list this control requires, you might want additional tree functionality, etc.

 

The Visual Studio 2008 solution is in the "TreeListControl.zip" attached to this post - contains compiled WSP file, so can be installed and used directly on SharePoint install.

 
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-


After recieving a number of comments on this tree control, I feel I should point out that, as only the text of the tree node is stored against a list item, changing the value of the tree node by updating list item value that sources the tree nodes will not cause the value to be automatically updated on all the list items it is applied against.

It would be a simple change to make the Tree control store a list item guid rather then the node path string, which would then make this possible.

The company I wrote this for uses an external search provider (not microsoft), and it was easier for them to process a bunch of node paths to provide their search filters, rather then to process a bunch of id's, and perform a lookup back to a sharepoint list, hence this design was used.

To resolve this problem for the company mentioned, we created a web part which allows the user to update the values assigned to list items.  So when they want to change a tree node item, they delete the item in the list, create a new one, then use this web part to change the original items node path to the new items node path - not really an ideal solution, but seems to work well enough.

 

SharePoint lets you customize just about anything, and one of the things I have used in various features a few times is a custom action in the dropdown menu for list items.  Here I will briefly go through how to define a custom action in a feature.
 

 
This dropdown menu is known as the Edit Control Block (ECB), and it is one of the many places SharePoint lets you define a custom action
.
 
In this example, my feature will create a document library, and associate a custom action with it.  Tthe action will allow a user to send the document off to an email recipient to be reviewed.  It does this by directing the browser to a custom layout page, and passing the library and list item guid's in the querystring.  This page has controls on it enabling the user to create and send an email, and it attaches a copy of the document to it (Note: I haven't included the code for this custom layout page in this posting - holler if you want it).
 
The list and the custom action are defined in my feature's "Elements.xml":
 
 
List instance deefinition -->
ListTemplate
Name="ListNameGoesHere"
Type="101"
BaseType="1"
OnQuickLaunch="False"
FolderCreation="False"
SecurityBits="11"
DisplayName="List Display Name goes here"
Description="List description goes here."
NoCrawl="True"
AllowDeletion="True"
DisallowContentTypes="False"
DisableAttachments="False"
DontSaveInTemplate="True"
EnableModeration="False"
MultipleTypes="False"
Image="/_layouts/images/itdl.gif">
ListTemplate>
 

Send Item for Review action -->
CustomAction
Id="{9438f6c5-8074-4524-a73d-b605dda61243}"
RegistrationType="List"
RegistrationId="101"
Location="EditControlBlock"
Sequence="111"
Title="Send Item for Review">
UrlAction
Url="{SiteUrl}/_layouts/ReviewItem.aspx?List={ListId}&ID={ItemId}"/>
CustomAction>

The element creates the document library to which the action will be associated.  I am using the out-of -the-box document library template here (101), but you could of course do the same thing with a list instance based of your own list template.
 
The settings I have specified in the element are as follows:
  • Id - a Guid for this action
  • RegistrationType - Specifies which type of object this action will be associated to, in this case a list, though it could be a file type, or content type, etc.
  • RegistrationId - The identifier of the object specified above to link this action to.  In this case, the TemplateId of the list.
  • Location - Where this custom action will be added - in this case the ECB - though there are quite a few other places where you can add a custom action.
  • Sequence - The order in which the custom action(s) will be displayed.  Presumably you could use this to put your action lower in the list the the out-of-the-box ones, though I have only used it to order my actions (if I have multiple ones) at the top of this list.
  • Title - the text to be displayed on your action
  • UrlAction - this is a sub-element of the CustomAction element, and specifies the page where the browser will be directed to when this action is triggered.  the URL I am redirecting to uses a few SharePoint “tokens”. Windows SharePoint Services supports the following tokens with which to start a relative URL:

        - ~site - Web site (SPWeb) relative link.
        - ~sitecollection - site collection (SPSite) relative link.

    In addition, you can use the following tokens within a URL:

        - {ItemId} - Integer ID that represents the item within a list.
        - {ItemUrl} - URL of the item being acted upon.
        - {ListId} - GUID that represents the list.
        - {SiteUrl} - URL of the Web site (SPWeb).
        - {RecurrenceId} - Recurrence index. This token is not supported for use in the context menus of list items
So, when the user selects my action “Send Item For Review”, it will direct the browser to …/_layouts/ReviewItem.aspx, passing the list ID and list item ID in the querystring. This page has controls on it that allow the user to enter the email details, and send it off (with the item attached) for review.
 

You can use custom actions all over the place - the ECB, the “Site Actions” menu, etc.  Further reading on custom actions:
   -    -    -    -    -    -    -    -    -

Follo
wing a request in the comments for an example of the page my custom action was passing to - I have *very quickly* cleaned this page of any customer specific information, and added it in the zip attached to this post.  It is simply an ASPX page and the code-behind (CS) file - the ASPX is deployed to a directory under TEMPLATE/LAYOUTS, and is referenced (as shown above) by the site action.  PLEASE NOTE: I have not checked if these would work following my quick cleaning, but think they are an adequate example of how you parse the URL to create objects for the given SPWeb / SPList etc.  Enjoy!

 Sometimes it may not be possible to actually receive an alert email on your system - maybe its a dev environment that doesn't support email, or exchange might be throwing a hissy fit, or maybe you just don't want to wait for the email to filter through to you.
 
A friend from Microsoft, Brendan Griffin, gave me the following SQL that shows the alerts that web app currently has queued to go.  This SQL needs to be run against the WSS_Content database for the web app which is expected to be producing the alerts:
 

SELECT

   EventTime,
   ItemName,
   ItemFullUrl
FROM
  
EventCache
WHERE
   ID > (SELECT Id FROM EventBatches)
 

I tend to use this now when developing to determine if the alerts I expect are being created.


This is something that can really have you pulling your hair out, but here is a great little trick I found on Tyler Holmes's blog (http://blog.tylerholmes.com/2008/02/deleting-content-types-in-sharepoint.html).
 
I always seem to have trouble finding his posting again, so thought I would stick it in here.  Point being, all credit for this goes to Tyler.
 
In order to delete a content type, it cannot be in use by any lists in the site collection.  If it is, attempting to delete it will show the incredibly helpful error message:
The content type is in use.
The following is Tyler's process for determining which lists in a site collection are using a particular content type:


Finding Lists/Libraries using the Content Type

The first thing we'll need is access to the content database with a SQL Client like SQL Management Studio. There's a tutorial on how to connect here.

We'll also need a tool like the SharePoint Explorer for WSS 3.0 (needs to be run on the WSS server itself) to figure out what the SiteCollection ID is.

Ok, lets get to it.

Determining the Site Collection ID of your Site Collection

  1. Run the SharePoint Explorer (SPE) on the server that's hosting the WSS site.

  2. Find your Web Application with SPE and click on the Content Databases tab to figure out what the name of the SQL Server instance hosting the content and what the name of the content database is. 

  3. Click on the Site Collection itself and grab the ID property of the site, we'll need this when we run the stored procedure. 

  4. The last thing we'll need is the ContentTypeID of the content type we want to delete, the easiest way to get this is right out of the URL when you go to delete (or edit) the content type (Site Settings->Site Content Types->Click on your content type). Here's a sample URL:

    http://w2k3-tyler-virt/_layouts/ManageContentType.aspx?ctype=0x010700037B79D2DD41C24A8F55D82FC6B71FAC&Source=http%3A%2F%2Fw2k3%2Dtyler%2Dvirt%2F%5Flayouts%2Fmngctype%2Easpx

    In
    this example the Content Type ID is:

    0x010700037B79D2DD41C24A8F55D82FC6B71FAC.

  5. Now we open up the SQL Server Management Studio, connect to the [ServerName]\OfficeServers instance and run the following stored procedure against our sites content database (we figured this out in #2).

    [proc_ListContentTypeInUse] [SiteCollectionId], [ContentTypeId]

    Or in out case:

    [proc_ListContentTypeInUse] 'D2F8C831-4CA7-41C0-8497-82F897B61B2B', 0x010700037B79D2DD41C24A8F55D82FC6B71FAC

What you get back is a result set showing all the lists that the given ContentType is being used in.  If you go and remove the content type from all those lists you should be good to go and that pesky error message will go away.



This process has helped me a number of times!  Many thanks Tyler, and I hope you don't mind me plagarising your work here ;)


  I was quite suprised that creating an SPUser object from the information contained in a "person" type list field is not straight-forward.  After trying a few ways myself, I decided it was time to hit the search engine.
 
I found the solution on this Microsoft Forum discussion: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3118673&SiteID=1
 
The trick is, you must use the SPFieldUser classes to return the object.  The code below shows how this is achieved (taken directly from the discussion):
 
private SPUser GetSPUser(SPListItem item, SPField field)
{
    string currentValue = item[field.Title].ToString();
    SPFieldUser userField = (SPFieldUser)field;
    SPFieldUserValue fieldValue = (SPFieldUserValue)userField.GetFieldValue(currentValue);
    return fieldValue.user;
}
 

It all seems a little bit over-complicated to me, but this is a function I have added to my Common.SharePoint library - no need to have to re-solve the same problem at some time in the future....