I recently did a project where my client needed several calendars provisioned via a Feature Receiver when a particular type of Site Collection was created; they had one primary calendar and they wanted all the other calendars to be overlaid onto the primary one using SharePoint 2010’s Calendar overlay capabilities.

Here’s a quick summary of this feature if you’re not familiar with it. When you are looking at a calendar you should notice that there is a ribbon button titled “Calendar Overlay”:

Clicking this button brings you to the Calendar Overlay Settings page; from this page you can add or edit the overlay calendars. Clicking “New Calendar” brings you to an application page where you can define what calendar to overlay:

As you can see from the above screenshot, you can add not just a SharePoint calendar as an overlay but also an Exchange calendar. After configuring calendar overlays you will see overlaid items when looking at the month view for the calendar:

In this example, the pink item is coming from the overlay calendar. Overlay items are added dynamically after the page loads (some JavaScript and Ajax take care of loading the items and rendering them on the page).

So that’s the basics from the end-users perspective. Now what about the technical details? Well, there’s not a lot of information out there that describes how this is done but here’s the gist of it – there’s a simple XML structure that is stored in the SPView’s CalendarSettings property; this property defines the overlays. Once you know that the rest is just a matter of figuring out what that structure looks like. The easiest way to start is to simply go through the browser and set up one or two overlays and then use some simple PowerShell to dump out that XML:

Here’s a better view of what that XML looks like:

 1<AggregationCalendars>
 2    <AggregationCalendar Id="{26ddb82c-9e2b-4c5d-9b7e-4ee25cf5c357}" 
 3                         Type="SharePoint" 
 4                         Name="My Overlay Calendar" 
 5                         Description="" 
 6                         Color="5" 
 7                         AlwaysShow="False" 
 8                         CalendarUrl="/Lists/MyOverlayCalendar/calendar.aspx">
 9        <Settings WebUrl="http://demo" 
10                  ListId="{428bd2cb-a32d-4867-b658-6498158636a8}" 
11                  ViewId="{09928cd4-9a5e-44ed-9bf2-dfe1fc85661b}" 
12                  ListFormUrl="/Lists/MyOverlayCalendar/DispForm.aspx" />
13    </AggregationCalendar>
14</AggregationCalendars>

I want to call particular attention to the WebUrl attribute of the Settings element; this value must be the full URL of the SPWeb object that contains the overlay calendar list. Okay, you’re thinking, not a huge deal, SharePoint stores the full URL for lots of things and it doesn’t really pose issues right? WRONG! Think about a scenario where you have an extended web application. So in my example I have an authoring site located at http://demo and I’ve extended this site for anonymous access under the URL http://demo.aptillon.com. Due to what I consider a design flaw with the overlays, the overlay feature will only work when the web application you are accessing the site as matches the web application defined for the WebUrl attribute. So if I were to try and access my overlay using the anonymous site I’d get the following error:

And of course, due to this error, the overlays will not show up. So even if I’ve only changed the protocol (http to https) I’d still get this same error. This means that, effectively, calendar overlays using SharePoint lists will only work under the context of the Web Application (and protocol) from which the overlay was defined. (I’ve torn through the code that does this and it’s something that Microsoft should be very embarrassed about – very poor performance and just flat out horribly implemented. Okay, I digress, let’s get back to the details.

Another thing you’ll want to do to understand this XML structure is to look at the code that constructs it. There’s two places to look and both require Reflector or some equivalent disassembler; the first is the SerializeAccessors() method of the Microsoft.SharePoint.ApplicationPages.Calendar.CalendarAccessorManagerImpl class. This method takes the properties provided to it and constructs the XML structure shown above. So where are these properties set? For that we need to look at the BtnOk_Click() method of the Microsoft.SharePoint.ApplicationPages.AggregationCustomizePage class. I’m not going to show the details of these methods here but suffice it to say these methods have everything you need to understand this structure.

As I previously noted, for my particular client I needed to set several overlays within a Feature Activated event; to make this easier (because there was technically several places I had to do this) I created a simple method that I could call; this method took in my target list and the list I wanted to overlay as well as several other properties. For this post I’ve taken that code and created a modified version of it which supports adding Exchange-based calendars. Here’s that code:

  1using System;
  2using System.Collections.Generic;
  3using System.Globalization;
  4using System.Linq;
  5using System.Text;
  6using System.Xml;
  7using Microsoft.SharePoint;
  8
  9namespace Lapointe.SharePoint2010.Automation.Common.Lists
 10{
 11    public enum CalendarOverlayColor
 12    {
 13        LightYellow = 1,
 14        LightGreen = 2,
 15        Orange = 3,
 16        LightTurquise = 4,
 17        Pink = 5,
 18        LightBlue = 6,
 19        IceBlue1 = 7,
 20        IceBlue2 = 8,
 21        White = 9
 22    }
 23
 24    public class SetListOverlay
 25    {
 26        public static void AddCalendarOverlay(SPList targetList, string viewName, string owaUrl, string exchangeUrl, string overlayName, string overlayDescription, CalendarOverlayColor color, bool alwaysShow, bool clearExisting)
 27        {
 28            AddCalendarOverlay(targetList, viewName, owaUrl, exchangeUrl, null, overlayName, overlayDescription, color, alwaysShow, clearExisting);
 29        }
 30        public static void AddCalendarOverlay(SPList targetList, string viewName, SPList overlayList, string overlayName, string overlayDescription, CalendarOverlayColor color, bool alwaysShow, bool clearExisting)
 31        {
 32            AddCalendarOverlay(targetList, viewName, null, null, overlayList, overlayName, overlayDescription, color, alwaysShow, clearExisting);
 33        }
 34        private static void AddCalendarOverlay(SPList targetList, string viewName, string owaUrl, string exchangeUrl, SPList overlayList, string overlayName, string overlayDescription, CalendarOverlayColor color, bool alwaysShow, bool clearExisting)
 35        {
 36            bool sharePoint = overlayList != null;
 37            string linkUrl = owaUrl;
 38            if (sharePoint)
 39                linkUrl = overlayList.DefaultViewUrl;
 40
 41            SPView targetView = targetList.DefaultView;
 42            if (!string.IsNullOrEmpty(viewName))
 43                targetView = targetList.Views[viewName];
 44
 45            XmlDocument xml = new XmlDocument();
 46            XmlElement aggregationElement = null;
 47            int count = 0;
 48            if (string.IsNullOrEmpty(targetView.CalendarSettings) || clearExisting)
 49            {
 50                xml.AppendChild(xml.CreateElement("AggregationCalendars"));
 51                aggregationElement = xml.CreateElement("AggregationCalendar");
 52                xml.DocumentElement.AppendChild(aggregationElement);
 53            }
 54            else
 55            {
 56                xml.LoadXml(targetView.CalendarSettings);
 57                XmlNodeList calendars = xml.SelectNodes("/AggregationCalendars/AggregationCalendar");
 58                if (calendars != null)
 59                    count = calendars.Count;
 60                aggregationElement = xml.SelectSingleNode(string.Format("/AggregationCalendars/AggregationCalendar[@CalendarUrl='{0}']", linkUrl)) as XmlElement;
 61                if (aggregationElement == null)
 62                {
 63                    if (count >= 10)
 64                        throw new SPException(string.Format("10 calendar ovarlays already exist for the calendar {0}.",targetList.RootFolder.ServerRelativeUrl));
 65                    aggregationElement = xml.CreateElement("AggregationCalendar");
 66                    xml.DocumentElement.AppendChild(aggregationElement);
 67                }
 68            }
 69            if (!aggregationElement.HasAttribute("Id"))
 70                aggregationElement.SetAttribute("Id", Guid.NewGuid().ToString("B", CultureInfo.InvariantCulture));
 71
 72            aggregationElement.SetAttribute("Type", sharePoint ? "SharePoint" : "Exchange");
 73            aggregationElement.SetAttribute("Name", !string.IsNullOrEmpty(overlayName) ? overlayName : (overlayList == null ? "" : overlayList.Title));
 74            aggregationElement.SetAttribute("Description", !string.IsNullOrEmpty(overlayDescription) ? overlayDescription : (overlayList == null ? "" : overlayList.Description));
 75            aggregationElement.SetAttribute("Color", ((int)color).ToString());
 76            aggregationElement.SetAttribute("AlwaysShow", alwaysShow.ToString());
 77            aggregationElement.SetAttribute("CalendarUrl", linkUrl);
 78
 79            XmlElement settingsElement = aggregationElement.SelectSingleNode("./Settings") as XmlElement;
 80            if (settingsElement == null)
 81            {
 82                settingsElement = xml.CreateElement("Settings");
 83                aggregationElement.AppendChild(settingsElement);
 84            }
 85            if (sharePoint)
 86            {
 87                settingsElement.SetAttribute("WebUrl", overlayList.ParentWeb.Site.MakeFullUrl(overlayList.ParentWebUrl));
 88                settingsElement.SetAttribute("ListId", overlayList.ID.ToString("B", CultureInfo.InvariantCulture));
 89                settingsElement.SetAttribute("ViewId", overlayList.DefaultView.ID.ToString("B", CultureInfo.InvariantCulture));
 90                settingsElement.SetAttribute("ListFormUrl", overlayList.Forms[PAGETYPE.PAGE_DISPLAYFORM].ServerRelativeUrl);
 91            }
 92            else
 93            {
 94                settingsElement.SetAttribute("ServiceUrl", exchangeUrl);
 95            }
 96            targetView.CalendarSettings = xml.OuterXml;
 97            targetView.Update();
 98            /*
 99            <AggregationCalendars>
100                <AggregationCalendar 
101                    Id="{cfc22c0b-688e-4555-b1d0-784081a91464}" 
102                    Type="SharePoint" 
103                    Name="My Overlay Calendar"
104                    Description="" 
105                    Color="1" 
106                    AlwaysShow="True" 
107                    CalendarUrl="/Lists/MyOverlayCalendar/calendar.aspx">
108                    <Settings 
109                        WebUrl="http://demo" 
110                        ListId="{4a15e596-674f-4af7-a548-0b01470e8d75}" 
111                        ViewId="{594c2916-14e7-4b08-ba36-1126b825bf45}" 
112                        ListFormUrl="/Lists/MyOverlayCalendar/DispForm.aspx" />
113                </AggregationCalendar>
114                <AggregationCalendar 
115                    Id="{cfc22c0b-688e-4555-b1d0-784081a91465}" 
116                    Type="Exchange" 
117                    Name="My Overlay Calendar"
118                    Description="" 
119                    Color="1" 
120                    AlwaysShow="True" 
121                    CalendarUrl="<url>">
122                    <Settings ServiceUrl="<url>" />
123                </AggregationCalendar>
124            </AggregationCalendars>
125            */
126        }
127    }
128}

I’m not going to bore you with the details of this code as all I’m doing is basic XML manipulation. I created a couple of method overloads to allow for creating SharePoint or Exchange-based overlays. So, did you notice the namespace? Yup, no point in releasing code here if I’m not going to turn it into a cmdlet

I’m not sure how useful this cmdlet will be in everyday use but imagine the scenario in which you have a primary calendar on your company portal and you want to add it as an overlay on every calendar throughout portal – you could easily do this using this cmdlet. Before we get to that, let’s see the full help for the cmdlet, which I called Set-SPListOverlay:

PS C:\Users\spadmin> help Set-SPListOverlay -full

NAME
    Set-SPListOverlay
    
SYNOPSIS
    Sets calendar overlays for the given list.
    
SYNTAX
    Set-SPListOverlay -Color  -OverlayTitle  [-OverlayDescription ] -OwaUrl  -WebServiceUrl  [-TargetList]  [-ViewName ] [-DoNotAlwaysShow ] [-ClearExisting ] [-AssignmentCollection ] []
    
    Set-SPListOverlay -Color  [-OverlayList]  [-OverlayTitle ] [-OverlayDescription ] [-TargetList]  [-ViewName ] [-DoNotAlwaysShow ] [-ClearExisting ] [-AssignmentCollection ] []
    
    Set-SPListOverlay [-OverlayLists]  [-TargetList]  [-ViewName ] [-DoNotAlwaysShow ] [-ClearExisting ] [-AssignmentCollection ] []
    
    
DESCRIPTION
    Sets calendar overlays for the given list.
    
    Copyright 2010 Falchion Consulting, LLC
    > For more information on this cmdlet and others:
    > http://www.falchionconsulting.com/
    > Use of this cmdlet is at your own risk.
    > Gary Lapointe assumes no liability.
    

PARAMETERS
    -Color 
        The color to use for the overlay calendar.
        
        Required?                    true
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -TargetList 
        The calendar list to add the overlays to.
        
        Required?                    true
        Position?                    1
        Default value                
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
        
    -ViewName []
        The name of the view to add the overlays to. If not specified the default view will be used.
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -OverlayList 
        The calendar list to add as an overlay.
        
        Required?                    true
        Position?                    2
        Default value                
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
        
    -OverlayLists 
        The calendar lists to add as an overlay.
        
        Required?                    true
        Position?                    2
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -OverlayTitle []
        The title to give the overlay calendar when viewed in the target calendar.
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -OverlayDescription []
        The description to give the overlay calendar when viewed in the target calendar.
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -OwaUrl 
        Outlook Web Access URL.
        
        Required?                    true
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -WebServiceUrl 
        Exchange Web Service URL.
        
        Required?                    true
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -DoNotAlwaysShow []
        Don't always show the calendar overlay.
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -ClearExisting []
        Clear existing overlays. If not specified then all overlays will be appended to the list of existing over        lays (up until 10 - anything after 10 will be ignored)
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -AssignmentCollection []
        Manages objects for the purpose of proper disposal. Use of objects, such as SPWeb or SPSite, can use large amounts of memory and use of these objects in Windows PowerShell scripts requires proper memory management. Using the SPAssignment object, you can assign objects to a variable and dispose of the objects after they are needed to free up memory. When SPWeb, SPSite, or SPSiteAdministration objects are used, the objects are automatically disposed of if an assignment collection or the Global parameter is not used.
        
        When the Global parameter is used, all objects are contained in the global store. If objects are not immediately used, or disposed of by using the Stop-SPAssignment command, an out-of-memory scenario can occur.
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
        
    
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer and OutVariable. For more information, type,
        "get-help about_commonparameters".
    
INPUTS
    
    
OUTPUTS
    
    
NOTES
    
    
        For more information, type "Get-Help Set-SPListOverlay -detailed". For technical information, type "Get-Help Set-SPListOverlay -full".
    
    ------------------EXAMPLE------------------
    
    PS C:\> Get-SPList "http://server_name/lists/MyCalendar" | Set-SPListOverlay -TargetList "http://server_name/lists/MyOverlayCalendar" -Color "Pink" -ClearExisting
    
    
    This example adds the MyOverlayCalendar calendar as an overlay to the MyCalendar list.
    
    
RELATED LINKS
    Get-SPList 

You can see from the different parameter sets that I’ve made provisions for setting the overlay as a SharePoint list or an Exchange calendar; additionally, I’ve made it so that if you have an array of lists that you wish to add as an overlay you can easily pass that array in as well. So now let’s look at that example:

 1$mainList = Get-SPList http://demo/lists/myCalendar
 2foreach ($site in (Get-SPSite http://demo -Limit All)) {
 3    foreach ($web in $site.AllWebs) {
 4        foreach ($list in ($web.Lists | ? {$_.BaseTemplate -eq "Events"})) {
 5            if ($list.ID -eq $mainList.ID) { continue }
 6            Set-SPListOverlay -TargetList $list `
 7                -OverlayList $mainList `
 8                -Color "Pink" `
 9                -OverlayTitle "Main Portal Calendar" `
10                -ClearExisting
11        }
12        $web.Dispose()
13    }
14    $site.Dispose()
15}

Pretty simple huh? I’m just grabbing the primary list and then I’m looping through all my Site Collections and Sites and then grabbing all the lists that have a base template of “Events”. Once I have the list then I simply call my cmdlet – that’s it – easy right?

Okay, so what if you have a calendar with a bunch of overlays and you need to grab those calendars and do something to them? Well, I threw in a bonus cmdlet called Get-SPListOverlays. This one is really simple – it merely takes in the primary list and then calls a simple helper method that parses the XML structure and grabs each list. I’m not going to bother showing the code as it’s real basic and this post is long enough (just download the source if you’d like to see it) but I will show the cmdlet help:

PS C:\Users\spadmin> help Get-SPListOverlays -Full
NAME
    Get-SPListOverlays
    
SYNOPSIS
    Retrieve all SPList objects set as a calendar overlay on the given list.
    
SYNTAX
    Get-SPListOverlays [-Identity]  [-Web ] [-AssignmentCollection ] []
    
    
DESCRIPTION
    Retrieve all SPList objects set as a calendar overlay on the given list.
    
    Copyright 2010 Falchion Consulting, LLC
    > For more information on this cmdlet and others:
    > http://www.falchionconsulting.com/
    > Use of this cmdlet is at your own risk.
    > Gary Lapointe assumes no liability.
    

PARAMETERS
    -Identity 
        The calendar whose calendar overlays will be retrieved.
        
        The value must be a valid URL in the form http://server_name/lists/listname or /lists/listname. If a server relative URL is provided then the Web parameter must be provided.
        
        Required?                    true
        Position?                    1
        Default value                
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
        
    -Web []
        Specifies the URL or GUID of the Web containing the calendar whose overlays will be retrieved.
        
        The type must be a valid GUID, in the form 12345678-90ab-cdef-1234-567890bcdefgh; a valid name of Microsoft SharePoint Foundation 2010 Web site (for example, MySPSite1); or an instance of a valid SPWeb object.
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
        
    -AssignmentCollection []
        Manages objects for the purpose of proper disposal. Use of objects, such as SPWeb or SPSite, can use large amounts of memory and use of these objects in Windows PowerShell scripts requires proper memory management. Using the SPAssignment object, you can assign objects to a variable and dispose of the objects after they are needed to free up memory. When SPWeb, SPSite, or SPSiteAdministration objects are used, the objects are automatically disposed of if an assignment collection or the Global parameter is not used.
        
        When the Global parameter is used, all objects are contained in the global store. If objects are not immediately used, or disposed of by using the Stop-SPAssignment command, an out-of-memory scenario can occur.
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
        
    
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer and OutVariable. For more information, type,
        "get-help about_commonparameters".
    
INPUTS
    
    
OUTPUTS
    
    
NOTES
    
    
        For more information, type "Get-Help Get-SPListOverlays -detailed". For technical information, type "Get-Help Get-SPListOverlays -full".
    
    ------------------EXAMPLE------------------
    
    PS C:\> $lists = Get-SPListOverlays "http://server_name/lists/mylist"
    
    
    This example retrieves the calendar overlays for the calendar at http://server_name/lists/mycalendar.
    
    
RELATED LINKS
    Get-SPList 
    Set-SPListOverlay 
    Get-SPWeb 

In the end this turned out to all be pretty simple to do but it was certainly a challenge trying to figure it all out as there’s no documentation (official or otherwise) that I’ve been able to find.