Feature for adding a Icon to your SharePoint farm
On the internet you can find several articles about how to add an icon to the SharePoint farm.. Microsoft also has a KB article that describes how you can accomplish it.
The steps that have to be taken to add the a icon are (example with a pdf icon):
- Copy the .gif file that you want to use for the icon to the following folder on the server:
Drive:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\60\Template\Images
- Edit the Docicon.xml file to include the .pdf file name extension. To do so:
a. Start Notepad, and then open the Docicon.xml file. The Docicon.xml file is located in one of the following folders on the server:
Drive:\Program Files\Common Files\Microsoft Shared\Web server extensions\12\Template\Xml
b. In the section of the Docicon.xml file, add an entry for the .pdf file name extension. To do so, add the following line, where NameofIconFile is the name of the .gif file:
1: <Mapping Key="pdf" Value="NameofIconFile.gif"/>
- Restart Microsoft Internet Information Services (IIS).
Reading those articles I thought by myself that there must be a way to take these steps without manually editing the files within the 12 hive.
To accomplish you have to create a wsp package that exists out of an image and a feature. The feature will have a receiver that kicks off a timer job. The timer job will add or delete the mapping section depending on the activation or deactivation of the feature.
The feature.xml will have to look something like this and has to have a scope of farm because you will change a file that is used by the complete farm.:
1: <?xml version="1.0" encoding="utf-8"?>
2: <Feature Id="5dfefa72-4a9b-4320-af45-03758c755079"
3: Title="motion10 PDF Integration"
4: Description="This feature will add the pdf icon to the farm."
5: Version="12.0.0.0"
6: Hidden="FALSE"
7: Scope="Farm"
8: DefaultResourceFile="core"
9: ReceiverAssembly="Motion10.SharePoint.IconIntegration, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d7298385728e744a"
10: ReceiverClass="Motion10.SharePoint.IconIntegration.IconIntegration"
11: xmlns="http://schemas.microsoft.com/sharepoint/"
12: Creator="Maik van der Gaag"
13: ImageUrl="motion10/FeaturesIcon.png"
14: ImageUrlAltText="http://www.motion10.com">
15: <Properties>
16: <Property Key="IconExtension" Value="pdf"/>
17: <Property Key="IconUrl" Value="pdf_icon.png"/>
18: </Properties>
19: </Feature>
In the feature.xml file I defined two properties with these properties we create the mapping element in the docicon.xml file.
Note: This feature can be used to add all kinds of different icons to the farm. If you would like to add another icon you have to edit the two properties.
Like we discussed the feature will have a receiver that kicks of the timer job. To accomplish this we have to implement the FeatureActivated and FeatureDeactivating. On the activation we will start the timer job to add the mapping and on the deactivation we will delete it.
1: //-----------------------------------------------------------------------
2: // <copyright file="IconIntegration.cs" company="motion10">
3: // Copyright (c) motion10. All rights reserved.
4: // </copyright>
5: //-----------------------------------------------------------------------
6:
7: using System;
8: using Microsoft.SharePoint;
9: using Microsoft.SharePoint.Administration;
10:
11: namespace Motion10.SharePoint.IconIntegration
12: {
13: class IconIntegration: SPFeatureReceiver
14: {
15: /// <summary>
16: /// The name of the WSS Administration service
17: /// </summary>
18: private static readonly string serviceName = "WSS_Administration";
19:
20: public override void FeatureActivated(SPFeatureReceiverProperties properties)
21: {
22: RunJobNow(false, properties);
23: }
24:
25: public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
26: {
27: RunJobNow(true, properties);
28: }
29:
30: public override void FeatureInstalled(SPFeatureReceiverProperties properties)
31: {
32: }
33:
34: public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
35: {
36: }
37:
38: private void RunJobNow(bool delete, SPFeatureReceiverProperties prop)
39: {
40: if (prop == null)
41: {
42: throw new ArgumentNullException("prop", "Argument 'prop' cannot be 'null'");
43: }
44:
45: SPFarm farm = prop.Definition.Farm;
46:
47: InstallIconTimerJob newJob = null;
48:
49: foreach (SPService service in farm.Services)
50: {
51: if (service.Name == serviceName)
52: {
53:
54: newJob = new InstallIconTimerJob(service, prop.Definition.Id, delete);
55:
56: SPJobDefinition def = service.GetJobDefinitionByName(newJob.Name);
57: if (def != null)
58: {
59: def.Delete();
60: }
61:
62: break;
63: }
64: }
65:
66: newJob.Schedule = new SPOneTimeSchedule(DateTime.Now.AddHours(-2.0));
67: newJob.Title = string.Format("{0} {1} Icon for Feature {2}", delete ? "Delete" : "Install", prop.Definition.Properties["IconExtension"], prop.Definition.Id);
68: newJob.Update();
69: }
70: }
71: }
In the RunJobNow() method we create a new instance of our timer job by creating it with two properties. These properties are our feature id and a boolean whether we want to delete the mapping or add it. We also created an extension method on the SPService for retrieving a job definition by name. Here we will retrieve our own job definition if it is available and delete it if it is because we can’t create a new instance of the timer job when it is already available.
1: //-----------------------------------------------------------------------
2: // <copyright file="SPServiceExtensions.cs" company="motion10">
3: // Copyright (c) motion10. All rights reserved.
4: // </copyright>
5: //-----------------------------------------------------------------------
6:
7: using System;
8: using System.Linq;
9: using Microsoft.SharePoint.Administration;
10:
11: namespace Motion10.SharePoint.IconIntegration{
12: /// <summary>
13: /// SPService extension methods
14: /// </summary>
15: public static class SPServiceExtensions {
16:
17: /// <summary>
18: /// Gets the jobdefintions by name.
19: /// </summary>
20: /// <param name="service">The service.</param>
21: /// <param name="name">The name.</param>
22: /// <returns>SPJobDefinition</returns>
23: /// <exception cref="System.ArgumentNullException">Exception is thrown when the service or name equal null</exception>
24: public static SPJobDefinition GetJobDefinitionByName(this SPService service, string name) {
25: if (service == null) {
26: throw new ArgumentNullException("service", "Argument 'service' cannot be 'null'");
27: }
28:
29: if (String.IsNullOrEmpty(name)) {
30: throw new ArgumentNullException("name", "Argument 'name' cannot be 'null' or 'String.Empty'");
31: }
32:
33: var query = from SPJobDefinition job in service.JobDefinitions
34: where job.Name == name
35: select job;
36:
37: return query.FirstOrDefault();
38: }
39: }
40: }
Now that the feature is finished we can create the timer job (if you want to read more about the creation of a timer job you can read the following article http://msdn.microsoft.com/en-us/library/cc406686.aspx ). The timer job will have five properties.
1: private bool _delete
2: {
3: get
4: {
5: if (this.Properties.ContainsKey(deleteKey))
6: {
7: return Convert.ToBoolean(this.Properties[deleteKey]);
8: }
9: else
10: {
11: return false;
12: }
13: }
14: set
15: {
16: if (this.Properties.ContainsKey(deleteKey))
17: {
18: this.Properties[deleteKey] = value.ToString();
19: }
20: else
21: {
22: this.Properties.Add(deleteKey, value.ToString());
23: }
24: }
25: }
26:
27: private Guid _featureID
28: {
29: get
30: {
31: if (this.Properties.ContainsKey(featureKey))
32: {
33: return new Guid(this.Properties[featureKey].ToString());
34: }
35: else
36: {
37: return Guid.Empty;
38: }
39: }
40: set
41: {
42: if (this.Properties.ContainsKey(featureKey))
43: {
44: this.Properties[featureKey] = value.ToString();
45: }
46: else
47: {
48: this.Properties.Add(featureKey, value.ToString());
49: }
50: }
51:
52: }
53:
54: private string IconExtension
55: {
56: get
57: {
58: SPFeatureDefinition def = Farm.FeatureDefinitions[_featureID];
59: return def.Properties["IconExtension"].Value;
60: }
61: }
62:
63: private string IconUrl
64: {
65: get
66: {
67: SPFeatureDefinition def = Farm.FeatureDefinitions[_featureID];
68: return def.Properties["IconUrl"].Value;
69: }
70: }
71:
72:
73: public string SPDocIconFile
74: {
75: get
76: {
77: return SPUtility.GetGenericSetupPath(filePath);
78: }
79: }
We save the value of the _featureid and _delete property in the property bag of our timer job because we have to access the properties after we created a new instance of the timer job.
The other properties will retrieve the attributes for the mapping from the feature definition based on the feature id we saved in the property bag. The SPDocIconFile will create the complete path to the file we need.
1: //-----------------------------------------------------------------------
2: // <copyright file="InstallIconTimerJob.cs" company="motion10">
3: // Copyright (c) motion10. All rights reserved.
4: // </copyright>
5: //-----------------------------------------------------------------------
6:
7: using System;
8: using System.IO;
9: using System.Linq;
10: using System.Xml.Linq;
11: using Microsoft.SharePoint.Administration;
12: using Microsoft.SharePoint.Utilities;
13:
14: namespace Motion10.SharePoint.IconIntegration
15: {
16: public class InstallIconTimerJob : SPJobDefinition
17: {
18: /// <summary>
19: /// Filepath for the spthemes.xml file
20: /// </summary>
21: private readonly string filePath = "TEMPLATE\\XML\\DOCICON.XML";
22:
23: /// <summary>
24: /// The property key for the delete property
25: /// </summary>
26: private readonly string deleteKey = "Icon_Installation_Deletekey";
27:
28: /// <summary>
29: /// The property key for the feature property
30: /// </summary>
31: private readonly string featureKey = "Icon_Installation_Featurekey";
32:
33: private bool _delete
34: {
35: get
36: {
37: if (this.Properties.ContainsKey(deleteKey))
38: {
39: return Convert.ToBoolean(this.Properties[deleteKey]);
40: }
41: else
42: {
43: return false;
44: }
45: }
46: set
47: {
48: if (this.Properties.ContainsKey(deleteKey))
49: {
50: this.Properties[deleteKey] = value.ToString();
51: }
52: else
53: {
54: this.Properties.Add(deleteKey, value.ToString());
55: }
56: }
57: }
58:
59: private Guid _featureID
60: {
61: get
62: {
63: if (this.Properties.ContainsKey(featureKey))
64: {
65: return new Guid(this.Properties[featureKey].ToString());
66: }
67: else
68: {
69: return Guid.Empty;
70: }
71: }
72: set
73: {
74: if (this.Properties.ContainsKey(featureKey))
75: {
76: this.Properties[featureKey] = value.ToString();
77: }
78: else
79: {
80: this.Properties.Add(featureKey, value.ToString());
81: }
82: }
83:
84: }
85:
86: private string IconExtension
87: {
88: get
89: {
90: SPFeatureDefinition def = Farm.FeatureDefinitions[_featureID];
91: return def.Properties["IconExtension"].Value;
92: }
93: }
94:
95: private string IconUrl
96: {
97: get
98: {
99: SPFeatureDefinition def = Farm.FeatureDefinitions[_featureID];
100: return def.Properties["IconUrl"].Value;
101: }
102: }
103:
104:
105: public string SPDocIconFile
106: {
107: get
108: {
109: return SPUtility.GetGenericSetupPath(filePath);
110: }
111: }
112:
113: public InstallIconTimerJob()
114: : base()
115: {
116: }
117:
118: public InstallIconTimerJob(SPService service, Guid featureID, bool delete)
119: : base("Icon Installer", service, null, SPJobLockType.None)
120: {
121: this._delete = delete;
122: this._featureID = featureID;
123: }
124:
125: public override void Execute(Guid targetInstanceId)
126: {
127: base.Execute(targetInstanceId);
128:
129: this.ChangeDocIconFile();
130: }
131:
132: private void ChangeDocIconFile()
133: {
134: string spThemesContent = string.Empty;
135:
136: if (File.Exists(SPDocIconFile))
137: {
138: XDocument doc = XDocument.Load(SPDocIconFile);
139:
140: var element = from b in doc.Element("DocIcons").Element("ByExtension").Elements("Mapping")
141: where b.Attribute("Key").Value.ToLower() == this.IconExtension.ToLower()
142: select b;
143:
144: bool containsElement = (element != null && element.Count() > 0);
145:
146: if (_delete)
147: {
148:
149: if (containsElement)
150: {
151: element.Remove();
152: doc.Save(SPDocIconFile);
153: }
154:
155: }
156: else
157: {
158: if (!containsElement)
159: {
160: XElement iconElement = new XElement("Mapping");
161: iconElement.SetAttributeValue("Key", IconExtension);
162: iconElement.SetAttributeValue("Value", IconUrl);
163:
164:
165: doc.Element("DocIcons").Element("ByExtension").Add(iconElement);
166: doc.Save(SPDocIconFile);
167: }
168: }
169: }
170: else
171: {
172: throw new FileNotFoundException("The DocIcon file does not exist on the server");
173: }
174: }
175: }
176: }
This TimerJob looks a lot like the TimerJob I created for installing a theme in the SharePoint farm (here). It basically does the same only with another file.
In the ChangeDocIconFile() we load the document if it exists and search for the mapping we want by using a linq query.
If the linq query has more than zero items we can delete it or we do not have to add it. When we are adding the section and the linq query does not return any value we create a XElement mapping and add two attributes based on the properties in the feature definition.
If you would like to try this out you can download the wsp package with a installer.
Download: