It's encouraging to see people thinking and acting on my presentation (encouraging? Heck, I'm smug about it!). Charles Cook noticed that a Visual Studio generated project has multiple ItemGroups in it (forgive me for stealing your example Charles!)

<Project DefaultTargets="Build"
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Reference Include="System" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

As Charles points out this is functionality equivilant to a single item group

<Project DefaultTargets="Build"
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Reference Include="System" />
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

So the question arises why have or allow multiple ItemGroups. First we need to look at what an Item is, in MSBuild Terms. Items are inputs into the build, grouped into collections based on the user defined collection name. So in the examples above we have two Item collections, Reference and Compile. You refer to items using @(CollectionName) and not the $(PropertyName) you would use for properties. An item, unlike a property, may also include meta data, this is commonly seen with localised resource files. MSDN has the official word.

Why multiple ItemGroups? Well there is the style argument, that it's nice to keep item definitions for each group separate but that's not really it. There are two advantages to allowing, and using separate ItemGroups, project file inclusions and conditional processing.

Remember that you can separate out build Targets into separate build files, so you could, for example, have a common library build script, which is included in the build process for all your products. You can also separate Item and Property declarations as well. Consider the multiple build types you would normally have, Developer, Clean, Daily, Release, each, usually, with a separate build script. You may want to share common items between all these, references, or a custom set of meta data for a custom task. Rather than duplicate the ItemGroup in each build script, pull it out, create a common.proj which sets your common items and let the individual scripts worry about just setting the things they need for their purpose.

The other advantage is conditional processing. Each Target, Task, ItemGroup and Property all can take a condition attribute. I'll admit the following example is contrived however; imagine you have a demo version of your software and a release version. The demo version does not have a save menu item, or indeed any save code. You could have item groups such that

<ItemGroup Condition="'$(Configuration)' == 'Demo'>
  <Compile Include="demo\demoToolbar.cs" />
</ItemGroup>

<ItemGroup Condition="'$(Configuration)' == 'Release'>
  <Compile Include="release\releaseToolbar.cs" />
</ItemGroup>

<ItemGroup>
  <Compile Include="everythingElse.cs" />
</ItemGroup>

I warned you it was contrived, but hopefully it illustrates the point that you can include items in a group based on any condition supported.