Propagate Site Content Types to List Content Types


[Aka: Make Content Type Inheritance Work for XML Based Content types]

[Note:Updated Nov 6]

If you are working with XML based content types, you’ll sooner or later fall into a trap well hidden by Microsoft. Simply put Content Type inheritance don’t work for XML based content types. SharePoint does not check what fields you’ve added/deleted/changed since you last deployed your content type feature, so you don’t have the “luxury” (it bloody well should work!) of choosing whether or not to propagate changes down to inherited content types as in the web interface or object model.

I found MS mention it briefly here.

The Role of List Content Types

I’ll take a small detour here to explain the concept of list content types. Whenever you assign a content type to a list (e.g. the pages document library for publishing webs) a new list content type is created which inherits from the site content type that you are assigning to the list and with the same name.

Actually the inheritance copies the site content type definition to the list content type in its entirety and assigns an “inherited” content type id to the new list content type.

If you later modify the site content type, through the web or object model, you have the option of propagating those changes to inherited content types, which in particular includes all the list content types on the site.

Don’t Disconnect the Content Type

Another small detour before I’ll present what to do about this mess. It should be stressed that any modification of the site content type through the web or object model, will disconnect the site content type from the underlying xml metadata file. If it happens you my reestablish the link as described here. This is very bad news. It not only means that you should be careful about what you do to your content types, but it also means that there is no simple way to propagate the changes code wise – we can’t just update the site content type (re-add fields) and have the changes propagate through the object model. 😦

The Code to Propagate

Finally I’m ready to present the code I used to propagate changes to my content types. πŸ™‚

The procedure is:

  1. Locate the site content type in question (run it multiple times if needed)
  2. Start at the root web site
    1. Go through all lists on the web
    2. If the list is associated with the site content type (actually the inherited list content type)
      1. Compare every field on the site content type with the list content type
      2. Add, remove or change the field in question on the list content type
    3. Go recursively through every subweb and continue from step a

I created the following code as a new extension to stsadm, called like this:

stsadm -o cbPropagateContentType -url <site collection url> -contenttype <contenttype name> [-verbose] [-removefields] [-updatefields]

For instance:

stsadm -o cbPropagateContentType -url http://localhost -contenttype “My Article Page” –verbose

The “removefields” switch specifies whether or not fields found in the list content type that are not in the site content type should be removed or not. Default is not. New fields in the site content type will always be added to the list content types.

Note: I’ve not tried to create an “update” field. I’ve not had a use for that yet and it will also require considerably more testing to ensure that it works correctly. You really don’t want to break your list content types in case of errors…

Updated (Nov 6): An update option has now been added you should consider that option as beta.

Note 2: If the job stops prematurely no harm is done. Restart it and it will continue from where it stopped – it will examine and skip the webs/lists that it already has processed.

Finally something tangible:

Save the following as “stsadmcommands.CBPropagateContentType.xml” and save it into “\Config” in the root of the SharePoint install folder (remember to update the assembly reference to whatever you compile the code into):

<?xml version="1.0" encoding="utf-8" ?>
<commands>
  <command name="cbpropagatecontenttype"
          class="Carlsberg.SharePoint.Administration.STSAdm.CBPropagateContentType,
          Carlsberg.SharePoint.Administration, Version=1.0.0.0, Culture=neutral,  
          PublicKeyToken=55c69d084ac6678f"/>
</commands>

And finally the code you need to compile to an assembly that the xml file should specify:

Updated (Nov 6): Code has been updated a bit. Some mistakes with display name/internal name have been fixed and the update option has been added. I’m not yet satisfied with the testing of the update method so consider it to be beta.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.StsAdmin;

namespace Carlsberg.SharePoint.Administration.STSAdm
{
    /// 
    /// A custom STSAdm command for propagating site content types to lists
    /// content types.
    /// 
    /// The code is provided as is, I don't take any responsibilty for 
    /// any errors or data loss you might encounter.
    /// 
    /// Use freely with two conditions:
    /// 1. Keep my name in there
    /// 2. Report any bugs back to https://soerennielsen.wordpress.com
    /// 
    /// Enjoy
    /// SΓΈren L. Nielsen
    /// 
    /// 
    class CBPropagateContentType : ISPStsadmCommand
    {
        #region Input parameters
        private string providedUrl;
        private string contentTypeName;
        private bool removeFields = false;
        private bool verbose = false;
        private bool updateFields = false;

        private bool UpdateFields
        {
            get { return updateFields; }
            set { updateFields = value; }
        }

        private bool Verbose {
            get { return verbose; }
            set { verbose = value; }
        }

        private bool RemoveFields {
            get { return removeFields; }
            set { removeFields = value; }
        }

        private string ContentTypeName {
            get { return contentTypeName; }
            set { contentTypeName = value; }
        }

        private string ProvidedUrl {
            get { return providedUrl; }
            set { providedUrl = value; }
        }
        #endregion

        /// 
        /// Runs the specified command. Called by STSADM.
        /// 
        /// The command.
        /// The key values.
        /// The output.
        /// 
        public int Run(string command, StringDictionary keyValues, 
                       out string output) {
            //Parse input                        
            // make sure all settings are valid
            if (!GetSettings(keyValues)) {
                Console.Out.WriteLine(GetHelpMessage(string.Empty));
                output = "Required parameters not supplied or invalid.";
            }

            SPSite siteCollection = null;
            SPWeb rootWeb = null;

            try {
                // get the site collection specified
                siteCollection = new SPSite(ProvidedUrl);
                rootWeb = siteCollection.RootWeb;

                //Get the source site content type
                SPContentType sourceCT = 
                             rootWeb.AvailableContentTypes[ContentTypeName];
                if (sourceCT == null) {
                    throw new ArgumentException("Unable to find " 
                        + "contenttype named \"" + ContentTypeName + "\"");
                }

                // process the root website
                ProcessWeb(rootWeb, sourceCT);

                output = "Operation successfully completed.";
                Log( output, false );
                return 0;
            }
            catch (Exception ex) {
                output = "Unhandled error occured: " + ex.Message;
                Log(output, false);
                return -1;
            }
            finally {
                if (rootWeb != null) {
                    rootWeb.Dispose();
                }
                if (siteCollection != null) {
                    siteCollection.Dispose();
                }
            }
        }

        /// 
        /// Go through a web, all lists and sync with the source content 
        /// type.
        /// Go recursively through all sub webs.
        /// 
        /// 
        /// 
        private void ProcessWeb(SPWeb web, SPContentType sourceCT) {
            //Do work on lists on this web
            Log("Processing web: " + web.Url);

            //Grab the lists first, to avoid messing up an enumeration 
            // while looping through it.
            List lists = new List();
            foreach (SPList list in web.Lists) {
                lists.Add(list.ID);
            }

            foreach (Guid listId in lists) {
                SPList list = web.Lists[listId];

                if (list.ContentTypesEnabled) {
                    Log("Processing list: " + list.ParentWebUrl + "/" 
                         + list.Title);

                    SPContentType listCT = 
                                         list.ContentTypes[ContentTypeName];
                    if (listCT != null) {
                        Log("Processing content type on list:" + list);

                        if (UpdateFields) {
                          UpdateListFields(list, listCT, sourceCT);
                        }

                        //Find/add the fields to add
                        foreach (SPFieldLink sourceFieldLink in 
                                               sourceCT.FieldLinks) {
                          if (!FieldExist(sourceCT, sourceFieldLink)) {
                            Log(
                              "Failed to add field " 
                              + sourceFieldLink.DisplayName + " on list " 
                              + list.ParentWebUrl + "/" + list.Title 
                              + " field does not exist (in .Fields[]) on " 
                              + "source content type", false);
                          }
                          else {
                            if (!FieldExist(listCT, sourceFieldLink)) {
                              //Perform double update, just to be safe 
                              // (but slow)
                              Log("Adding field \"" 
                                 + sourceFieldLink.DisplayName 
                                 + "\" to contenttype on " 
                                 + list.ParentWebUrl + "/" + list.Title, 
                                   false);
                              if (listCT.FieldLinks[sourceFieldLink.Id] 
                                                                != null) {
                                listCT.FieldLinks.Delete(sourceFieldLink.Id);
                                listCT.Update();
                              }
                              listCT.FieldLinks.Add(new SPFieldLink(
                                      sourceCT.Fields[sourceFieldLink.Id]));
                              listCT.Update();
                            }
                          }
                        }


                      if (RemoveFields) {
                            //Find the fields to delete
                            //WARNING: this part of the code has not been 
                            // adequately tested (though
                            // what could go wrong? ;-) ... )

                            //Copy collection to avoid modifying enumeration
                            // as we go through it
                            List listFieldLinks = 
                                                  new List();
                            foreach (SPFieldLink listFieldLink in 
                                                     listCT.FieldLinks) {
                                listFieldLinks.Add(listFieldLink);
                            }

                            foreach (SPFieldLink listFieldLink in 
                                                        listFieldLinks) {
                                if (!FieldExist(sourceCT, listFieldLink)) {
                                    Log("Removing field \"" 
                                       + listFieldLink.DisplayName 
                                       + "\" from contenttype on :" 
                                       + list.ParentWebUrl + "/" 
                                       + list.Title, false);
                                    listCT.FieldLinks.Delete(
                                                        listFieldLink.Id);
                                    listCT.Update();
                                }
                            }
                        }
                    }
                }
            }


            //Process sub webs
            foreach (SPWeb subWeb in web.Webs) {
                ProcessWeb(subWeb, sourceCT);
                subWeb.Dispose();
            }
        }


      /// 
      /// Updates the fields of the list content type (listCT) with the 
      /// fields found on the source content type (courceCT).
      /// 
      /// 
      /// 
      /// 
      private void UpdateListFields(SPList list, SPContentType listCT, 
                                    SPContentType sourceCT) {
        Log("Starting to update fields ", false);
        foreach (SPFieldLink sourceFieldLink in sourceCT.FieldLinks) {
          //has the field changed? If not, continue.
          if (listCT.FieldLinks[sourceFieldLink.Id]!= null 
               && listCT.FieldLinks[sourceFieldLink.Id].SchemaXml 
                  == sourceFieldLink.SchemaXml) {
            Log("Doing nothing to field \"" + sourceFieldLink.Name 
                + "\" from contenttype on :" + list.ParentWebUrl + "/" 
                + list.Title, false);
            continue;
          }
          if (!FieldExist(sourceCT, sourceFieldLink)) {
            Log(
              "Doing nothing to field: " + sourceFieldLink.DisplayName 
               + " on list " + list.ParentWebUrl 
               + "/" + list.Title + " field does not exist (in .Fields[])"
               + " on source content type", false);
            continue;
                              
          }

          if (listCT.FieldLinks[sourceFieldLink.Id] != null) {

            Log("Deleting field \"" + sourceFieldLink.Name 
                + "\" from contenttype on :" + list.ParentWebUrl + "/" 
                + list.Title, false);

            listCT.FieldLinks.Delete(sourceFieldLink.Id);
            listCT.Update();
          }

          Log("Adding field \"" + sourceFieldLink.Name 
              + "\" from contenttype on :" + list.ParentWebUrl 
              + "/" + list.Title, false);

          listCT.FieldLinks.Add(new SPFieldLink(
                                     sourceCT.Fields[sourceFieldLink.Id]));
          //Set displayname, not set by previus operation
          listCT.FieldLinks[sourceFieldLink.Id].DisplayName 
                      = sourceCT.FieldLinks[sourceFieldLink.Id].DisplayName;
          listCT.Update();
          Log("Done updating fields ");
        }
      }

      private static bool FieldExist(SPContentType contentType, 
                                                     SPFieldLink fieldLink)
        {
            try
            {
                //will throw exception on missing fields
                return contentType.Fields[fieldLink.Id] != null;
            }
            catch(Exception)
            {
                return false;
            }
        }

        private void Log(string str, bool verboseLevel) {
            if (Verbose || !verboseLevel) {
                Console.WriteLine(str);
            }
        }

        private void Log(string str) {
            Log(str, true);
        }


        /// 
        /// Parse the input settings
        /// 
        /// 
        /// 
        private bool GetSettings(StringDictionary keyValues) {
            try {
                ProvidedUrl = keyValues["url"];
                //test the url
                new Uri(ProvidedUrl);

                ContentTypeName = keyValues["contenttype"];
                if (string.IsNullOrEmpty(ContentTypeName)) {
                    throw new ArgumentException("contenttype missing");
                }

                if (keyValues.ContainsKey("removefields")) {
                    RemoveFields = true;
                }

                if (keyValues.ContainsKey("verbose")) {
                    Verbose = true;
                }

                if (keyValues.ContainsKey("updatefields"))
                {
                    UpdateFields = true;
                }
                return true;
            }
            catch (Exception ex) {
                Console.Out.WriteLine("An error occuring in retrieving the"
                    + " parameters. \r\n(" + ex + ")\r\n");
                return false;
            }
        }

        /// 
        /// Output help to console
        /// 
        /// 
        /// 
        public string GetHelpMessage(string command) {
            StringBuilder helpMessage = new StringBuilder();

            // syntax
            helpMessage.AppendFormat("\tstsadm -o {0}{1}{1}", command, 
                                                 Environment.NewLine);
            helpMessage.Append("\t-url " + Environment.NewLine);
            helpMessage.Append("\t-contenttype " + Environment.NewLine);
            helpMessage.Append("\t[-removefields]" + Environment.NewLine);
            helpMessage.Append("\t[-updatefields]" + Environment.NewLine);
            helpMessage.Append("\t[-verbose]" + Environment.NewLine);

            // description
            helpMessage.AppendFormat("{0}This action will propagate a site"
                + " content type to all list content types within the "
                + "site collection.{0}Information propagated is field "
                + "addition/removal.{0}{0}", Environment.NewLine);
            helpMessage.AppendFormat("{0}SΓΈren Nielsen (soerennielsen." 
                + "wordpress.com){0}{0}", Environment.NewLine);

            return helpMessage.ToString();
        }
    }
}

Final Comments

I’ve made zero attempts to optimize the code. It doesn’t really matter how long it takes, does it? Give it 10 minutes till a couple of hours for huge site collection (I’ve tested with about 400 sub sites).

I recommend that you use the verbose flag and pipe the output to a file, so that you can review that it did everything correctly.

The code does not handle site content types on sub sites I’ll probably add it fairly soon if I need it or time permits (does it ever?)

License

Use the above code freely, with two conditions:

  1. Leave my name and link in there πŸ˜‰
  2. Report bug and improvements back to me
Advertisements

Convert “virtual” content types to “physical”


What do you do, if you in a fit of madness/desperation/stupidity created the content types used throughout your site, through the web interface and you now want to do the “right” thing and place them in xml files packaged as a feature?

Well this is description on how to convert the existing “virtual” content type to that xml file, while maintaining the integrity of your existing site and content. Warning: I’m modifying the SharePoint content database directly – use at your own risk!

The basic idea:

  1. Create a content type xml file and package it in a feature (don’t deploy it yet) as you would if you started in a blank environment
  2. “Steal or copy” the content id for “virtual” content type from the database and use it in your xml files. In other words the existing content id that is used throughout your existing SharePoint database in the inheritance hierarchy, will remain unchanged
  3. Modify the database so that SharePoint sees your content type as being feature based instead of “database based”
  4. Deploy your new content type feature. You can now update that content type as if you had started it out xml based to begin with

It seems fairly straightforward doesn’t it? It actually is.

Howto

Information on creating xml based content type can be found here (and on many other sources), it’s really not that hard. Your deployment will be much easier after this.

Right about now would be a good time to do a backup of your content database πŸ˜‰

Step 1: Steal the Content Type ID

Your content type will need a very specific ID that the SharePoint created for you when you created your new content type in the first place (either through the web frontend or API). It looks like “0x0101……” and will probably be a rather long string. You need to grab this id from the content database:

  1. Connect to the content database in question, probably named wss_content_XXXX (if you didn’t choose a database name the XXXX will be a guid)
  2. Execute the following query to find the right content typeselect ResourceDir, ContentTypeId, Definition

    from dbo.ContentTypes

    where ResourceDir like ‘%Article Page%’

    Obviously substitute your own content type name, note that the web interface might have appended some trailing numbers to the name, so you’ll have to do a “like” selection

  3. Copy the ContentTypeID and insert it into the xml file. You might also want to verifiy that the definition corresponds to your that in your xml file (or just copy it over)

Step 2: Connect the Content Type to the XML File

Now you need to go into the database and modify the ContentType table to make SharePoint see it as a feature based content type as opposed to those solely in the database.

  1. Connect to the content database again (you might just have kept the window open)
  2. Execute begin tran once, just to give you an undo option
  3. Execute the following SQL statementUpdate dbo.ContentTypes

    Set Size = 16, Definition = null, IsFromFeature = true

    where ContentTypeId = 0x010100C5…..

    It should only modify one row

  4. If the name “ResourceDir” has been mangled by the web interface, you might want to take the opportunity to fix that too now
  5. If you are satisfied with the update execute commit tran, otherwise rollback tran, do not forget this as you are locking the table for the duration (btw: Isn’t that a neat trick?)

Caveats

I will not take any responsibility if you lose your databases, however I would like to know if you find flaws with the procedure πŸ˜‰

If you have many environments this technique only works if they have the same content type id for the same type across the farms. They will have if you did a backup/restore or content deployment from one to the other. They won’t if you created them through the web on both servers. Then you either choose which one is the master of the content or you are out of luck.

Note that if you update/change the content type xml files at a later time, the changes will only apply to the site scoped content type, not the actual list content types that the system created for every list where the type is enabled. This is very bad news, but not to worry I’ll post the fix for that in a few days (give me a bit of time).

If you modify the content type through the web interface after deployment it will once again be disconnected from the xml source, and you’ll have to complete Step 2 (only) to reconnect it.

Top 10 Indispensible Tools


Every blog is only complete with some sort of top 10 list present – this is my version , listing ten (and then some) of the applications I use and value the most. Some are freeware, most are not. I don’t care I’ll happily pay for any of the applications listed here.

I don’t list Word, Excel, Outlook or Visual Studio here as they come with the furniture, I can’t do my job without any of them, but what makes the difference and provide the extra 50% efficiency are these tools:

  1. AutoHotkey
    This absolutely invaluable tool is a hotkey and macro management program. You can literally program it to anything you can think of, though the syntax is a bit archaic, the functionality is staggering. I constantly modify my hotkey file to suit me you can download my version here (rename file extension to “ini”. read through the file to find keys it defines).
    Example 1: Ctrl + w: Bring firefox to the front if it’s running, otherwise start it.
    Example 2: Ctrl + Alt + a: Remember current window, Check if KeePass (see below) is running, If not start it and wait the main window to show up, Switch back to the original application and send the shortcut to KeePass that will autotype the password for the current window (KeePass will look at the window title and find the correct password in the database if possible).
    Example 3: Ctrl + G: Generate and insert a Guid into the active window.The list goes on and on … Brilliant.
  2. OneNote (2007)
    This is part of MS Office 2003 and 2007 in some of the licenses (please don’t ask me to explain the licensing model – I don’t think that anyone understands it). As the name implies this is a program for notetaking. It replaces the numerous little txt files, notes within outlook or however you prefer to do it. After a few test runs you are addicted to it. I think I spend at least 30% of my time here.Brief list of the features I use:Deep linking – You can create links directly to specific pages within OneNote (e.g. your main todo page) and save them as windows shortcuts. That’ll enable you to create hotkeys through windows to find the page quickly. Of course creating that hotkey through Autohotkey is a lot faster and the way I do it :-)Outlook integration – Create tasks in OneNote and have them synchronized with outlook (status info is synced two-ways, description only one way)

    Aggregation of flags – You can use a number of flags/tags for your notes that you can aggregate in a task pane, i.e. “show me all todo tagged notes in all pages”

    Cached file mode – This program probably features the most advanced file system available in any program I’ve ever seen. It basically opens your onenote database from whatever location you point it to and creates a cached version of the file. Whenever the original file is available it’ll sync your changes with that file. You can compare it to the way outlook works in offline mode, this time there’s no exchange/mail server, just the actual file itself. I place my notebooks on a USB stick to carry between computeres and I can still use my laptop without the USB most of the time – it just works πŸ™‚

    Calculations – Simple but clever. It can do simple calculations, pretty much like google does. Write “Sin(Pi/2)=” and it will calculate that value for you. In effect replaces the calculator (which I used all the time)

    Copy text from pictures – The screenshot capturing is nice, but one really cool feature is that it has a function to “copy text from picture”, i.e. it’ll perform OCR on any picture you paste into it. It’s pretty far from perfect, but works reasonably well for ordinary screenshots, i.e. not for a scan of a traffic sign

    Drawing/handwriting recognition – This is the feature that Microsoft primarily highlights which enables you to use a Tablet PC with OneNote to do handwritten notes that are searchable etc. We all know the success of the Tablet PC and this particular feature seems useless to the 99% of us that prefer to use a reliable keyboard instead. Whether it actually works or not I can’t say.

    Search – obviously…

    To finish off I’ll recommend reading David Rasmussens blog about OneNote he has some valuable technical insights.

  3. Google Desktop
    Can’t find a thing on my computer without this one being enabled, I primarily use it to search my emails as the amount quickly goes beyond normal search/categorizing within Outlook.Simple, fast, efficient and elegant, hit Ctrl twice, enter “from:torben SLA SharePoint” and the mail Torben send with that content is found. Google Desktop is a simpler product than MS desktop search offering, it has fewer advanced options, but I find it to be quicker to search and especially a lot faster to actually use.It comes with a number of extra crap, like Google Toolbar, Google Sidebar/gadgets/widgets/whatever they are called. Don’t care, don’t steal my screen real estate, just disable it πŸ˜‰
  4. FireFox
    No one can do without FireFox. It’s simply a faster, better browser that you can tweak to your liking. I use the following extensions all the time:FireBug – Absolutely invaluable tool for every developer that are forced to look into html/css/javascript once in a while. Was hell before this extention

    Del.icio.us – It handles my bookmarks and syncs them between all my various machines. I don’t care much about the much touted social bookmarking aspect, I don’t really find it all that useful

    All in One Mouse Gestures – Web browsing is usually best done with a mouse, this alleviates you from having to touch the keyboard too often during browsing. Simple saves Β½ a second when you need to navigate, open links in new windows etc. I just use the 4-6 simplest gestures, there are lots defined, most are impossible to remember or actually perform

    Selenium studio – I haven’t actually used Selenium studio for anything yet, but it seems an invaluable tool for doing web based unit testing, that is, recording them for later execution in a proper environment. I think I’ll try to use this in my next project. Everyone loves unit testing I just haven’t seen that many good tools for doing browser based unittesting, though it’s by far the most interesting to do

  5. KeePass
    Just a small simple password remember program. I gave up on remembering all my passwords six months ago, there was simply too many. I’ve used KeePass since and it’s now contains around 100 passwords for various systems (many are web sites). Now I can use all that extra brainpower for something useful ;-)I’ll argue that a program like KeePass actually increases the security as it enables you to use/generate distinct random password for every system instead of reusing a couple of passwords for all of them.One useful feature is the autotype feature where it’ll type the appropriate password into your current window with a hotkey (it figures out what password you need based on the title of the current window). Of course I extended this functionality a bit with AutoHotkey, so my shortcut actually starts the program if it’s not running, wait for it to be ready and then executes autotype.The one thing I miss is actually a FireFox extension to integrate the two – replace FireFox’ insecure password remember with KeyPass automatically (sorry, I don’t have the time to build it).
    Note: I’ve only used version 1.xx not 2.xx – it’s a complete redesign where the new version is based on .Net.
  6. Ceedo
    This is an application that will revolutionize your usage of USB drives. Nothing less. It enables you to install and execute applications on the USB disk on all host machines. So you can have your favorite text editor installed on the USB stick and be able to use it on any computer, with no installation on the host machine.The unique thing with ceedo (over competitors like MojoPac and U3) is that it enables you to install practically anything (with exceptions of course) also applications that were never designed for portability. It creates a “virtual windows environment” that the applications execute within, so when the applications writes to the registry that is stored in Ceedo’s registry not the host machine and when it reads from it Ceedo will overlay the host registry with its own. Works great for most applications, specific exceptions are Java, .Net., Visual Studio and MS Office – I suppose they change too much in windows for Ceedo to capture it all.This tool is indispensible when you routinely work with security restricted windows computers – now I don’t need to install programs all my programs on the host machines to be able to use my favorites.
    Note: On my installation AutoHotkey is of course added to Ceedo’s startup folder (yup it emulates normal windows behavior). Incidentally if another version of AutoHotkey is already running, the new one takes over (both run, the latest temporarily overwrites the first hotkey definition). That’s exactly what you want so the version on the USB stick can add hotkeys for the programs installed on that disk.
  7. VMWare Workstation
    Well you can’t really develop any serious SharePoint solutions without using virtual machines, often lots of them. I routinely use VMWare Workstation/Player/Server where the last two are freeware. Workstation is brilliant, fast, reliable and a snapshot tree feature to kill for (Player and Server does not really support snapshots properly – but they are free(!) ).I believe VPC is also a usable tool, though I feel that VMWare are still a couple of years ahead in terms of features and support for many operating systems. You can find many blogs with performance comparison between the two and the one clear conclusion is that the difference is negligible; you should base your choice on the features needed and pricing.
  8. Daemon tools
    Mount ISO files (many types) as virtual CD/DVDs. It works.I still don’t know why this is not a standard windows feature, it really should be. The funny thing is that MS distributes many of their products as ISO files, but they have no proper support for it (there is actually one MS program for it, lousy by the hearsay).
    Note: Be sure to deselect the Daemon tools search bar during installation. You don’t want another commercial internet explorer bar, do you?
  9. ReSharper
    An add-in to Visual Studio that will make everything better, easier and faster. I’m never going back. When Visual Studio 2005 was released a number of the refactorings supported by ReSharper were also supported by the base product. ReSharper still does it better though, so the refactoring capabilities are still a selling point along with countless other improvements.Very soon you’ll code both much faster and less error-prone (it catches a number of possible bugs even without compilation). The simplest feature I use the most is for to automatically add using statements when you start using a new class within your code, just type “Hashtable”, hit Ctrl+Alt, and continue coding knowing that the using statement has now been added (color coding will help you here).
    Note: I have only tested version 1.x and 2.x, not the “new” version 3. Yet.
  10. Reflector for .Net
    An excellent tool for disassembling .Net code. Let’s face it sometimes we all learn better with some inspiration from others’ code. To me it’s an indispensible source of information for SharePoint 2007 in figuring out what some of the errors/exception actually mean and how to counter them. Most of SharePoint 2007 can be disassembled, which is huge improvement over the locked down SharePoint 2003. Bits and pieces are still “obfuscated” and cannot be disassembled – obviously some of the best parts that we need the most.

Runner ups

While trying to keep the list short I also considered two more:

  1. FastStone capture
    If you ever need to do screen capture this program is your friend. It supports capturing a window with scrolling and some simple drawing on the captured image. An ellipses around the button to press is always nice… OneNote also has a nice screen capture utility, it’s only lacking the easy editing capabilities
  2. Paint.Net
    Delete and bury windows Paint once and for all. Finally. This is a high quality freeware image handling program that falls into the category of people that needs some basic image handling (transparent background for instance) for which Paint doesn’t cut it and PhotoShop is complete overkill (not to mention the learning curve and pricing).
    And yes: There is a file plugin that will read and write photoshop files (not working for huge files though – 100mb and above is not a good idea)

Final words

This is my list of must have applications that I use all the time. Your mileage may vary. In two months time some of them will have changed again – I always look for ways to improve my setup (and I also change the primary tasks in my job once in a while).

To that end, feel free to share your favorites in the comments.