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.