 |
|
If you have a question or suggestion, please contact me through Windows Live Messenger. My status:
 . If I am not online, please send me an e-mail. |
|
|
|
|
 |
|
|
|
|
|
|
|
|
3/27/2010We are rebuilding our SharePoint 2007 environment that has a number of different farms: - 5 development servers
- 1 test server
- 1 staging server
- 1 production environment with 1 application server, 2 web frontends and 1 database server
Because we want the SharePoint installation and configuration to be the same on every environment, we decided to script the installation. In this post I will describe some of the choices we have made and what concepts we used to meet specific requirements. Our final script is based on a number of samples that can be found at several locations on the internet (Ben Curry/Mindsharp, automossinstaller on CodePlex and Gary Lapointe). I won’t post all our scripts, but just some relevant snippets. STSADM vs PowerShell First choice to make was the choice between using STSADM and PowerShell. Because most samples out there (that are very good) use STSADM, and our PowerShell knowledge is still a bit basic, we decided to go for the STDADM option. Most of the commands we use are out of the box SharePoint, but for some specific tasks we use custom command extensions by Gary Lapointe. We realize that we will have to re-do our scripts for SharePoint 2010, because there PowerShell probably is the better option. Parameters After creating our scripts, we ended up with a number of scripts files for specific servers, and steps in the process. All these files have parameters to make it do what we want it to do. The header of each file defines all parameters used in that file, which looks like this: @set psconfig="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\bin\psconfig.exe" @set stsadm="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\bin\stsadm.exe" @set MOSSSP2_Path=C:\Install\MOSS2007X64\MOSS2007_with_SP2_X64 @set WSSSP2_LP_DutchPath=C:\Install\WSS30X64\WSS30_LP_with_SP2_X64 @set MOSSSP2_LP_DutchPath=C:\Install\MOSS2007X64\MOSS2007_LP_with_SP2_X64 @set OfficeServerConfigFilesPath=C:\Install\Deployment\Unattended_MOSS @rem ==================== GENERAL PARAMETERS =========================== @set Domain=TSTMSS @set Environment=o @set SetupUserAccount=%Domain%\svcmoss-install-%Environment% @set Farmserviceaccount=%Domain%\svcmoss-admin-%Environment%
To manage these parameters easier, I created a simple WinForms application that extracts every parameter and it’s value in a CSV file. We manage the parameters for every environment in an Excel workbook, with a sheet for every server.
By exporting a sheet to a CSV file and feed that to the custom tool, we can easily create an installation set. This simply replaces the parameter values in the script files and copies the set of files to a new folder.
Passwords
One of the requirements we have is to keep the passwords for the service account very secure. We started off putting all parameters in the same Excel sheets as described above, but this is not exactly secure... We ended up creating a password text file that contains all service accounts used (per farm) and their passwords. Nobody has the permissions to read the file, except the SharePoint installer account. And of course our AD admins who create and manage service accounts and publish the text files.
In the script we are using the sample below to read a password from the password file:
@set EnvironmentSuffix=o @set Domain=TSTMSS @set Company=TSTTESTCOMPANY @set SSPAccount=%Domain%\svc-ssp%Company%-%EnvironmentSuffix% @set PasswordFile=C:\mosspasswords.txt @for /F "usebackq tokens=1,2" %%i in (`type "%PasswordFile%"`) do IF %SSPAccount% == %%i set SSPPassword=%%j @%stsadm% -o createssp ^ -title "%SSPNAME%" ^ -url %SSPAdminURL% ^ -mysiteurl %MySiteHostURL% ^ -indexserver "%indexserver%" ^ -indexlocation "%defaultindexlocation%" ^ -sspdatabaseserver "%DatabaseServer%" ^ -sspdatabasename "%SSPDatabaseName%" ^ -searchdatabaseserver "%DatabaseServer%" ^ -searchdatabasename "%SharedServicesSearchDB%" ^ -ssplogin %SSPAccount% ^ -ssppassword %SSPPassword% ^ >>"%date% - MOSS_Installation_Log.txt"
The first part of the script sets all parameters and reads a password, in this case for the account that runs the shared services provider. The second part of the script shows the stsadm command that creates the SSP, and uses the password setting.
This way SharePoint admins who install SharePoint do not need to know the password for the service accounts.
Install SharePoint
To install SharePoint on the web servers and on the application server, we first prepared a slipstreamed installation of SharePoint and SP2. A manual on how to slipstream SP2 into the installer can be found in this blog post on the Sean Blog. Following the same procedure, we also prepared slipstream packages for the Dutch language packs (WSS and MOSS) and SP2.
Next step was to prepare the configuration XML files. Technet describes the options. We created 3 files; 1 for the application server, 1 for the web frontends and 1 for installing the language pack(s). Next action was to run the installation scripts on the app server and the WFE’s. Please note we only installed the bits at this point. The configuration wizard still has to do it’s work, but first we need to create databases.
Databases
In our environment, databases need to be created and managed by our DBAs. They decide on what drive the data and log files end up, and we want to specify initial database sizes. All of our SharePoint databases are created by the DBAs using a script. Including the SharePoint configuration database and the search databases. They were all created even before we started installing SharePoint. In our parameter Excel sheet (see above) we gathered the settings for every databases we need. The script generation tool simply copies them in the SQL script files that are sent to the DBA.
The TechNet ‘Deploy using DBA-created databases’ article summarizes which databases (and their collation) you will need, who needs to be the owner and what account you need to give permissions. The following snippet shows the SQL script file that creates a new database:
DECLARE @db_id smallint; SET @db_id = DB_ID(N'$(Databasename)'); IF @db_id IS NULL BEGIN print 'Create database $(Databasename)'; CREATE DATABASE $(Databasename) ON (NAME = '$(Databasename)_Data', FILENAME = '$(DataFilePath)$(Databasename)_Data.mdf', SIZE=$(DatabaseSize), MAXSIZE=$(DatabaseMaxSize), FILEGROWTH=$(DatabaseFileGrowth)) LOG ON (NAME = '$(Databasename)_Log', FILENAME = '$(LogFilePath)$(Databasename)_Log.ldf', SIZE= 5, MAXSIZE=25, FILEGROWTH=5) COLLATE Latin1_General_CI_AS_KS_WS; USE master; EXEC sp_dboption '$(Databasename)', 'autoclose', 'FALSE'; END; ELSE BEGIN print 'Database $(Databasename) already exists. Skip creation step.'; END;
The print statements in this script write some information to the log file. This snippet is used as in the sample below. It shows how this script (stored in CreateDatabase.sql) is used to create the content database for one of the web applications:
@rem ==================== TEAMS PARAMETERS =========================== @set TeamsDatabasename=WSS_Content_Teams @set TeamsDataFilePath=C:\SQLData\Data\ @set TeamsLogFilePath=C:\SQLData\Log\ @set TeamsDatabaseSize=10MB @set TeamsDatabaseMaxSize=50MB @set TeamsDatabaseFileGrowth=5MB @set TeamsLogSize=5MB @set TeamsLogMaxSize=25MB @set TeamsLogFileGrowth=5MB @set TeamsApplicationPoolName=%Company%_CollaborationAppPool @set TeamsApplicationPoolAccount=%Domain%\svc-teams-%Company%-%EnvironmentSuffix% @echo %date% - %time% Create database for Teams >>"%date% - MOSS_Installation_Log.txt" @echo Maak database voor Teams @sqlcmd -iSQL_CreateDatabase.sql ^ -vDatabasename=%TeamsDatabasename% ^ -vDataFilePath="%TeamsDataFilePath%" ^ -vLogFilePath="%TeamsLogFilePath%" ^ -vDatabaseSize=%TeamsDatabaseSize% ^ -vDatabaseMaxSize=%TeamsDatabaseMaxSize% ^ -vDatabaseFileGrowth=%TeamsDatabaseFileGrowth% ^ -vLogSize=%TeamsLogSize% ^ -vLogMaxSize=%TeamsLogMaxSize% ^ -vLogFileGrowth=%TeamsLogFileGrowth% ^ -S%DatabaseServer% ^ -vServername=%DatabaseServer% ^ >>"%date% - MOSS_Installation_Log.txt"
After creating the database you need to set the owner for the new database and grant a number of accounts permissions to the database (depending on which database; see the TechNet article above). The script below allows the accounts that are specified in the parameters CentralAdministrationApplicationPoolAccount and ApplicationPoolAccount access to the database. The first account gets to be the owner of the database and the second user is added to the db_owner role.
DECLARE @db_id smallint; SET @db_id = DB_ID(N'$(Databasename)'); IF @db_id IS NOT NULL BEGIN print 'Set permissions for database $(Databasename)'; USE $(Databasename); EXEC sp_grantlogin '$(CentralAdministrationApplicationPoolAccount)'; EXEC sp_grantlogin '$(ApplicationPoolAccount)'; print 'Change owner to $(CentralAdministrationApplicationPoolAccount)'; EXEC sp_changedbowner '$(CentralAdministrationApplicationPoolAccount)'; IF NOT EXISTS (SELECT * FROM sysusers WHERE name='$(ApplicationPoolAccount)') EXEC sp_grantdbaccess '$(ApplicationPoolAccount)'; EXEC sp_addrolemember 'db_owner', '$(ApplicationPoolAccount)'; END;
This script is saved in the file called SQL_SetOwner_AddOwnerRole.sql and called from the sample script below. This sets the correct permissions for the content database we just created in the previous script.
@set EnvironmentSuffix=o @set Domain=TSTMSS @set Company=TSTTESTCOMPANY @set CentralAdministrationApplicationPoolAccount=%Domain%\zsvcMSS @set DatabaseServer=TSTMSS\SQLEXPRESS @rem ==================== TEAMS PARAMETERS =========================== @set TeamsDatabasename=WSS_Content_Teams @set TeamsApplicationPoolName=%Company%_CollaborationAppPool @set TeamsApplicationPoolAccount=%Domain%\svc-teams-%Company%-%EnvironmentSuffix% @echo %date% - %time% START - Office Server Configuration >>"%date% - MOSS_Installation_Log.txt" @set stsadm="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\bin\stsadm.exe" @echo %date% - %time% Permissions for Teams database >>"%date% - MOSS_Installation_Log.txt" @echo Zet de rechten voor Teams database @sqlcmd -iSQL_SetOwner_AddOwnerRole.sql ^ -vDatabasename=%TeamsDatabasename% ^ -S%DatabaseServer% ^ -vServername=%DatabaseServer% ^ -vCentralAdministrationApplicationPoolAccount=%CentralAdministrationApplicationPoolAccount% ^ -vApplicationPoolAccount=%TeamsApplicationPoolAccount% ^ >>"%date% - MOSS_Installation_Log.txt" @if not errorlevel 0 goto errorhandler
These SQL scripts are used for every database we needed in our SharePoint environment:
- Configuration database
- Content database for Central Administration web application
- Search admin database
- Content database for the SSP Admin web applications
- Content database(s) for the MySites
- SSP databases (for 2 SSPs)
- SSP Search databases
- Content database for collaboration sites
- Content database for intranet
- Content databases for a number of SharePoint applications
Web applications
To create web applications, we use the STSADM command extension gl-createwebapp by Gary Lapointe. The main reason for that is that we did not want our virtual directory path of the IIS website in the default location C:\inetpub. These home directories needed to be moved to another drive and the extension by Gary allowed us to do that. Except for the IIS website for the Central Administration sites. This is created automatically by PSConfig, and it does not allow you o specify the path. So this is the only website that still lives in c:\inetpub.
@rem ==================== GENERAL PARAMETERS =========================== @set EnvironmentSuffix=o @set Domain=TSTMSS @set Company=TSTTESTCOMPANY @set CentralAdministrationApplicationPoolAccount=%Domain%\zsvcMSS @set DatabaseServer=TSTMSS\SQLEXPRESS @rem ==================== TEAMS PARAMETERS =========================== @set TeamsDatabasename=WSS_Content_Teams @set TeamsURL=http://teams.tstlocal.nl @set TeamsHostHeader=teams.tstlocal.nl @set TeamsDescription=Team sites voor %Company% @set TeamsApplicationPoolName=%Company%_CollaborationAppPool @set TeamsApplicationPoolAccount=%Domain%\svc-teams-%Company%-%EnvironmentSuffix% @set TeamsVirtualDirectory=C:\inetpub\mossroot\teams @rem ==================== GET PASSWORDS =========================== @set PasswordFile=C:\mosspasswords.txt @echo off @for /F "usebackq tokens=1,2" %%i in (`type "%PasswordFile%"`) do IF %TeamsApplicationPoolAccount% == %%i set TeamsApplicationPoolPassword=%%j @echo on @rem =============================================================== @echo %date% - %time% START - Office Server Configuration >>"%date% - MOSS_Installation_Log.txt" @set stsadm="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\bin\stsadm.exe" @echo %date% - %time% Set default SSP to the SSP for %Company% >>"%date% - MOSS_Installation_Log.txt" @%stsadm% -o setdefaultssp ^ -title "Shared Services %Company%" ^ >>"%date% - MOSS_Installation_Log.txt" @if not errorlevel 0 goto errorhandler @echo %date% - %time% Make Teams web application on url %TeamsURL% >>"%date% - MOSS_Installation_Log.txt" @%stsadm% -o gl-createwebapp ^ -url %TeamsURL% ^ -directory "%TeamsVirtualDirectory%" ^ -port 80 ^ -sethostheader %TeamsHostHeader% ^ -databaseserver %DatabaseServer% ^ -databasename "%TeamsDatabasename%" ^ -description "%TeamsDescription%" ^ -apidname "%TeamsApplicationPoolName%" ^ -donotcreatesite ^ -apidtype configurableid ^ -apidlogin %TeamsApplicationPoolAccount% ^ -apidpwd %TeamsApplicationPoolPassword% ^ >>"%date% - MOSS_Installation_Log.txt" @if not errorlevel 0 goto errorhandler
The sample script above shows how a new, empty, web application is created. Site collections will be added by another scripts. This script uses the content database that we had created in previous steps. In our SharePoint farm we have 2 SSPs. Before the web application is created, we make sure the default SSP is the SSP that we need to associate the web application with. This way the script ensures that all web applications will be associated with the right SSP.
Summary
In this post I have described the most important concepts we have applied when scripting our SharePoint installation. Instead of posting the full script, I have posted only relevant snippets. Hope you can use these snippets to make your own scripts do they need to do. 3/17/2010With the release date of SharePoint 2010 approaching rapidly, I decided that it was about time to make my Content By Type web part available for SharePoint 2010. In this small series, I will describe my adventures to the first release of the web part. Part zero of this series is this blog post, where I described how I configured my development environment (for usage on Windows 2008 Server R2). In this post I wrote down the steps to get to the first working prototype. The screenshot below shows the web part in SharePoint 2010. Recompile versus new Visual Studio Project The easiest way to get the web part working in 2010, would be to recompile the solution in Visual Studio 2010 with updated references to the 2010 assemblies. Because I wanted to take advantage of the new SharePoint development tools in Visual Studio 2010, I decided to create a new Visual Studio project instead. It is a bit more work, but you end up with a Visual Studio solution that is using the latest and greatest in SharePoint development. So here we go: - Create a new project in Visual Studio using the “Empty SharePoint Project” template
- Use the .NET Framework 3.5:
- In the ‘SharePoint Customization Wizard’, I selected Deploy as a farm solution. In the project that I am migrating from SharePoint 2007, I am using some features that will not work in a sandboxed solution (like custom aspx pages in the LAYOUTS folder and custom ascx controls). In one of the next steps of this project I will see if I can get rid of this and deploy Content By Type as a sandbox solution.
- Set the references to the SharePoint 14 assemblies.
Add ASPX and ASCX files The editor uses an ASPX page and a number of ASCX controls. In Visual Studio 2008 these files were added to the Layouts folder of the ‘12’ folder in the project. Visual Studio now has the concept of SharePoint mapped folders. One of them is the Layouts folder. I wanted to stick to Visual Studio as much as possible, so I decided to leave the custom ‘12’, ‘12HIVE’ or ‘SharePointRoot’ folders as I used to call them. - Right click your project file
- Select Add and select the “ShrePoint Layouts Mapped Folder”
- This mapped folder maps to a physical folder on your hard drive, in the normal project structure. I copied all files to this folder and added them to the project:
- In the ASPX and ASCX files I checked all the references to the 2007 versions of the SharePoint assemblies and updated them to the 2010 version:
I just updated the version number from 12 to 14. The public key token remains the same. Add the web part code Visual Studio 2010 organizes web parts a bit different than you are used to. I decided not just to copy my webpart files to the new VS project, but to do it the Visual Studio 2010 way. - To organize things a bit, I created a normal project folder for the ContentByType web part.
- On the new folder, right click, select Add and add a web part. I choose not to create a Visual Web Part, because they did not exists in VS2008, and therefore my web part is an ‘ordinary’ web part:
- Visual Studio just added a number of items to your project, all packaged as 1 item:
- The web part CS file.
- The .webpart file containing the metadata.
- The elements.xml file for the feature.
- Copy the web part code. I manually copied the code from my web part CS file into the new CS file just created by Visual Studio.
- In the ContentByTypeWebPart.webpart file, I copied all the metadata about the web part from the properties in the old version of the web part file.
- In the Elements.xml file, I changed the group in which the web part will be displayed in the web part gallery.
- Copy the other CS files to the project. I created a number of folders and added all other necessary CS files.
- Now all code is in my project, I renamed the namespaces I used in the 2007 project to my new namespace.
Replaceable parameters Another cool new feature of Visual Studio 2010 is Replaceable parameters. These are tokens that you can add to any file that gets packaged by Visual Studio. These tokens are replaced by Visual Studio automatically. An example of such a parameter is $SharePoint.Project.AssemblyFullName$. This gets replaced by the full 4 part assembly name. These parameters are used by Visual Studio itself when you for example add a new web part to your project and look in the .webpart file: <webPart xmlns="http://schemas.microsoft.com/WebPart/v3"> <metaData> <type name="TST.SharePoint2010.WebParts.ContentByType.ContentByTypeWebPart.ContentByTypeWebPart, $SharePoint.Project.AssemblyFullName$" /> <importErrorMessage>$Resources:core,ImportErrorMessage;</importErrorMessage> </metaData>
So you now never have to lookup your public key token again! I changed all the assembly references in the ASPX and ASCX files used by the Content By Type web part to the parameter $SharePoint.Project.AssemblyFullName$:
<%@ Control Language="C#" Inherits="TST.SharePoint2010.WebParts.ContentByType.ContentTypeConfiguration, $SharePoint.Project.AssemblyFullName$" %> <%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register TagPrefix="TST" Assembly="$SharePoint.Project.AssemblyFullName$" Namespace="TST.SharePoint2010.Shared.WebControls" %>
The magical moment: Hit F5
When everything compiled, the magical moment came. I pressed F5. And of course it worked! Nice work, with just a bit of work I had the first version running on 2010. Next step will be to get rid of the popup dialog for the editor. I want it to use the ribbon and the out of the box SharePoint 2010 dialog look and feel. And I might go for a Silverlight editor also.
Summary
In this post I have described my first steps on the road of code migration to SharePoint 2010. Instead of doing a recompile I decided to upgrade my Visual Studio solution to the 2010 way to do things. Hope this gives you a starting point if you are planning to do the same for your solutions. Although it is a bit of manual work, I would recommend to go for it, and also upgrade your solution to take advantage of the new tooling. 3/11/2010
We are currently breaking down our current SharePoint environment and moving to a new farm. In the current farm we are using a custom SharePoint theme. This has to be installed on the new farm also. This custom theme was created and installed in the early SharePoint 2007 days, when we copied themes by doing an xcopy deployment and registered the theme manually in spthemes.xml. For our new farm we wanted to do this the proper way. To register the themes in the file, I have created a farm scoped feature that registers the theme(s) upon installation of the feature. They are unregistered when the solution is uninstalled.
This feature is based on the work done by DevExpert in this blogpost: http://www.devexpertise.com/2009/02/11/installing-a-theme-as-a-sharepoint-feature/
Please Note: This solution uses Linq, and therefore you need to have the .NET Framework 3.5 installed on your SharePoint server(s).
Feature XML
The contents of the feature.xml file:
<?xml version="1.0" encoding="utf-8"?> <Feature Id="c98fb223-7344-446f-a3ac-bf43708fee1a" Title="Theme registration feature" Description="Feature that (un)registers a number of themes." Version="1.0.0.0" Hidden="FALSE" Scope="Farm" ReceiverAssembly="TST.DemoCode, Version=1.0.0.0, Culture=neutral, PublicKeyToken=503edd7b21a430b3" ReceiverClass="TST.DemoCode.FeatureReceivers.RegisterThemes" xmlns="http://schemas.microsoft.com/sharepoint/"> <Properties> <Property Key="FileName" Value="themes.xml"></Property> </Properties> </Feature>
Nothing special here. It is a Farm scoped feature. In the properties we add the name of the XML file that contains the theme definitions.
Sample XML
In my version, you can create a xml file with the themes to be registered and deploy that with your feature. The sample XML that comes with the solution (see links at the bottom):
<?xml version="1.0" encoding="utf-8" ?> <Themes xmlns="http://schemas.tonstegeman.com/themes/2010/03"> <Theme> <LCID>1033</LCID> <TemplateID>DemoTheme</TemplateID> <DisplayName>English demo theme</DisplayName> <Description>Dummy demo theme registration for testing the RegisterThemes feature.</Description> <Thumbnail>images/thDemoTheme.gif</Thumbnail> <Preview>images/thDemoTheme.gif</Preview> </Theme> <Theme> <LCID>1043</LCID> <TemplateID>DemoTheme</TemplateID> <DisplayName>Nederlands demo thema</DisplayName> <Description>Dummy demo thema registratie voor het testen van de RegisterThemes feature.</Description> <Thumbnail>images/thDemoTheme.gif</Thumbnail> <Preview>images/thDemoTheme.gif</Preview> </Theme> </Themes>
In this sample you see a theme getting registered for 2 languages, English and Dutch.
Feature receiver events
The code of the feature receiver that does the work:
public override void FeatureActivated(SPFeatureReceiverProperties properties) {} public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {} public override void FeatureInstalled(SPFeatureReceiverProperties properties) { Trace.WriteLine("TST.DemoCode.FeatureReceivers.RegisterTheme.FeatureInstalled"); _properties = properties; try { Themes themes = ValidateProperties(); if (themes != null) { ModifySPTheme(themes, ModificationType.Add); } } catch (Exception ex) { Trace.WriteLine(String.Format("ERROR: {0}", ex.Message)); throw; } } public override void FeatureUninstalling(SPFeatureReceiverProperties properties) { Trace.WriteLine("TST.DemoCode.FeatureReceivers.RegisterTheme.FeatureUninstalling"); _properties = properties; try { Themes themes = ValidateProperties(); if (themes != null) { ModifySPTheme(themes, ModificationType.Remove); } } catch (Exception ex) { Trace.WriteLine(String.Format("ERROR: {0}", ex.Message)); throw; } }
Reading themes from the XMLfile
Themes are read from the xml file by the function ‘ValidateProperties’. The code is shown in the snippet below:
private Themes ValidateProperties() { String fileName = GetSettingFromProperties("FileName", true); String fileDir = _properties.Definition.RootDirectory; if (!fileDir.EndsWith(@"\")) { fileDir += @"\"; } fileName = fileDir + fileName; Trace.WriteLine(String.Format("File name xml file: {0}.", fileName)); if (!File.Exists(fileName)) { throw new SPException(String.Format("File not found: {0}.", fileName)); } Trace.WriteLine(String.Format("Load XML: {0}.", fileName)); XDocument xmlFile = XDocument.Load(fileName); XNamespace nmsp = XNamespace.Get(Theme.XmlNameSpaceThemes); var query = from c in xmlFile.Elements(nmsp + "Themes") select c; Themes themes = new Themes(); if (themes.GetFromXml(query.FirstOrDefault())) { Trace.WriteLine(String.Format("Themes found: {0}.", themes.ThemeList.Count)); return themes; } return null; }
This function creates a ‘Themes’ object, and populates it with a list of ‘Theme’ objects. You can find this Theme class in the zip file referenced at the bottom. The GetXML method that reads the items from the XML file and creates the objects:
public Boolean GetFromXml(XElement xmlNode) { Trace.WriteLine("Read themes from XML."); if (xmlNode == null) { Trace.WriteLine("No themes found."); return false; } XNamespace nmsp = XNamespace.Get(Theme.XmlNameSpaceThemes); var themes = from t in xmlNode.Elements(nmsp + "Theme") select new Theme { TemplateID = t.Element(nmsp + "TemplateID").Value, DisplayName = t.Element(nmsp + "DisplayName").Value, Description = t.Element(nmsp + "Description").Value, Thumbnail = t.Element(nmsp + "Thumbnail").Value, Preview = t.Element(nmsp + "Preview").Value, LCID = t.Element(nmsp + "LCID").Value }; _themes = themes.ToList(); return true; }
Register the themes
The method ‘ModifySPTheme’ (which is based on the work by DevExpert, as stated above) does all the work and is displayed below:
private void ModifySPTheme(Themes themes, ModificationType type) { XDocument doc = null; XNamespace ns = "http://tempuri.org/SPThemes.xsd"; foreach (Theme theme in themes.ThemeList) { // path to the SPTHEMES.XML file string spthemePath = Path.Combine(SPUtility.GetGenericSetupPath(String.Format(@"TEMPLATE\LAYOUTS\{0}", theme.LCID)), "SPTHEMES.XML"); Trace.WriteLine(String.Format("Themes xml file: {0}.", spthemePath)); if (!File.Exists(spthemePath)) { continue; } // read the contents of the SPTHEMES.XML file Trace.WriteLine("Read Themes XML"); string contents = string.Empty; using (StreamReader streamReader = new StreamReader(spthemePath)) { contents = streamReader.ReadToEnd(); streamReader.Close(); } using (StringReader stringReader = new StringReader(contents.Trim())) { // create a new XDocument from the contents of the file doc = XDocument.Load(stringReader); // retrieve all elements with the TemplateID. At most, there should only be one var element = from b in doc.Element(ns + "SPThemes").Elements(ns + "Templates") where b.Element(ns + "TemplateID").Value == theme.TemplateID select b; // determine if the theme element already exists bool exists = (element != null && element.Count() > 0); if (type == ModificationType.Add) { Trace.WriteLine(String.Format("Check if theme by ID exists: {0} (LCID={1}).", theme.TemplateID, theme.LCID)); if (!exists && theme.Validate()) { Trace.WriteLine(String.Format("Theme {0} does not exist. Add", theme.TemplateID)); // create an XElement that defines our custom theme XElement xml = new XElement(ns + "Templates", new XElement(ns + "TemplateID", theme.TemplateID), new XElement(ns + "DisplayName", theme.DisplayName), new XElement(ns + "Description", theme.Description), new XElement(ns + "Thumbnail", theme.Thumbnail), new XElement(ns + "Preview", theme.Preview)); // add the element to the file and save doc.Element(ns + "SPThemes").Add(xml); Trace.WriteLine("Save Themes XML"); doc.Save(spthemePath); } } else { if (exists) { // if the element exists, remove it and save Trace.WriteLine(String.Format("Remove theme {0}.", theme.TemplateID)); element.Remove(); doc.Save(spthemePath); } } stringReader.Close(); } } }
Testing
After installing the feature to our farm, the themes are registered in the appropriate XML files:
Summary
You can use this generic feature to register all your SharePoint themes, by creating a separate feature for every SharePoint project, or by adding all themes in one XML file and install that feature to your farm(s). You can download a ZIP file with all source code and a sample WSP package that you can install.
http://sharepointobjects.codeplex.com/releases/view/41777 3/7/2010I just finished rebuilding my SharePoint 2010 development environment and started to build a new version of the Content By Type web part for SharePoint 2010. I decided to do a “Hello World” first to make sure everything works as expected. Some details on my dev environment: - Machine1: Windows 2008 R2
- Machine2: Windows 2008 R2
- SQL Server 2008 Express
- SharePoint 2010
- Visual Studio 2010
When I am developing, I am logged on to Machine2 as a domain user, who is a member of the local Administrators group. The application pool used by SharePoint run using different service accounts. I switched off User Account Control, because it is a virtual machine used for development purposes, and it saves me a lot of messages and restarting Visual Studio. To find the settings, type ‘UAC’ in the start menu search box. In Visual Studio I created a new empty SharePoint project. I pointed it to my development test site in SharePoint and added a web part. Wrote the code to display the “Hello World” message and hit F5. The solution compiled fine, but deployment failed: Error occurred in deployment step 'Recycle IIS Application Pool': The local SharePoint server is not available. Check that the server is running and connected to the SharePoint farm. This error occured because my domain user does not have the correct permissions in these databases: - SharePoint_Config
- SharePoint_AdminContent_[guid]
I added my domain to the user to the database (db_owner) and that fixed the problem. Next error message I got is this: Error occurred in deployment step 'Recycle IIS Application Pool': Cannot connect to the SharePoint site: http://intranet/. Make sure that this is a valid URL and the SharePoint site is running on the local computer. If you moved this project to a new computer or if the URL of the SharePoint site has changed since you created the project, update the Site URL property of the project. Same issue here, the domain user does not have the correct permissions for the content database of the web application. I added the domain user to the db_owner role of this database and things started to work: If you start running into issues like this, the SharePoint ULS logs turn out to be a very good starting point to find out what is wrong. Hope this saves some of you some time if you are using a similar setup. It will probably same myself some time next time I setup a new environment. 3/2/2010A new release (version 1.1) of SharePoint Objects is available on CodePlex. In this new release Features and SharePoint groups are added. If you are interested in a general overview of SharePoint Objects, read this blog post, or watch this screencast. The same principles showed here now also apply to features and group. Feature definitions After configuring a number of feature definitions in SharePoint Objects, all sites, site collections, web applications and the farm are added to the database if they are using the selected feature definition. The screenshot below shows all sites that have the “CustomList” feature activated. You can filter the list of feature definitions that are installed by scope. In my Farm there are 4 site collections based on the Collaboration Portal template. They all 4 have a Document Center site, which has the CustomList feature activated. Therefore the list shows 4 document centers. The link directly takes you to the Manage Features page for that site (depending of the scope of the selected feature definition). The link in the Site column leads you to the site itself. SharePoint Objects for Feature Definitions is available from Central Administration. In the SharePoint Objects section, click the link Feature definitions: SharePoint Groups SharePoint Objects now can also give you insight into the usage of your SharePoint groups, even across site collections! All securable objects in SharePoint (list item, folder, list/document library, site) are scanned. If the securable object has unique permissions, and the selected group has a role assigned, the securable object is added to the SharePoint Objects index and associated with that group. This allows you to quickly find all SharePoint artifacts that have unique permissions for the group “Home Members” for example. If you are using this group name across multiple site collections and web applications, and you index by Internal name, SharePoint Objects will show these artifacts across the boundaries of the current site collection. The screenshot below shows all SharePoint artifacts that have a role assignment for the group Home Members. In the list you see a number of different object types. A number of sites (in multiple site collections) have an assignment. You will also find a number of lists/document libraries, folders and even list items. The List column shows a direct link to the default view of the list or document library in case of a list, folder or item. If you navigate to the site settings page, the SharePoint Objects section now contains a new menu option, called SharePoint Groups: Summary The new release is available on CodePlex. If you are upgrading from the first version, let me know and I will send you the upgrade instructions. 2/28/2010For the new release of SharePoint Objects I am working on indexing the references to SharePoint groups. By using SharePoint Objects you can easily see which securable objects in SharePoint (site, list, folder, item) have a role assignment for a specific SharePoint group. I want the url for the reference to point directly to the Edit Permissions page in SharePoint for every type of object: The Edit Permissions page is the editprms.aspx page in the layouts folder. This page takes a number of parameters to load the permission settings for a specific object. The sections below describe the format of the url for each securable object type: SharePoint site (SPWeb) The Edit Permissions url for a SharePoint site looks like this: /_layouts/editprms.aspx?obj=[OBJECTID]%2C[OBJECTTYPE]&sel=[GROUPID] where: [OBJECTID] is the encoded full url of the site. [OBJETTYPE] is ‘WEB’. [GROUPID] is the ID of the group The code to construct the url: refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2CWEB&sel={2}", web.Url, HttpUtility.UrlEncode(web.Url), group.ID);
Example:
http://tstmss/_layouts/editprms.aspx?obj=http%3a%2f%2ftstmss%2CWEB&sel=5
SharePoint List (SPList)
The Edit Permissions link for a list object:
/_layouts/editprms.aspx?obj=[OBJECTID]%2C[OBJECTTYPE]&sel=[GROUPID] where: [OBJECTID] is the encoded ID url of the list. [OBJETTYPE] is ‘LIST’ for a list and ‘DOCLIB’ for a document library. [GROUPID] is the ID of the group
The code to construct the url:
if (list is SPDocumentLibrary) { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2CDOCLIB&sel={2}", web.Url, HttpUtility.UrlEncode(list.ID.ToString("B")), group.ID.ToString());} else { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2CLIST&sel={2}", web.Url, HttpUtility.UrlEncode(list.ID.ToString("B")), group.ID.ToString());}
Example:
http://tstmss/_layouts/editprms.aspx?obj=%7ba17a2cb1-a103-41fe-af46-5785c4838e6f%7d%2CDOCLIB&sel=5
SharePoint folder (SPFolder)
The Edit Permissions link for a folder in a document library of a SharePoint list:
/_layouts/editprms.aspx?obj=[OBJECTID]%2C[OBJECTTYPE]%2C[ITEMID]&sel=[GROUPID] where: [OBJECTID] is the encoded ID url of the list that contains the folder. [OBJETTYPE] is ‘FOLDER’ for a folder in a list and ‘DOCLIBFOLDER’ for folder in a document library. [ITEMID] is the ID of the listitem for the folder. [GROUPID] is the ID of the group
The code to construct the url:
if (folder.Item.ParentList is SPDocumentLibrary) { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2C{2}%2CDOCLIBFOLDER&sel={3}", web.Url, HttpUtility.UrlEncode(folder.ParentListId.ToString("B")), folder.Item.ID, group.ID);} else { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2C{2}%2CFOLDER&sel={3}", web.Url, HttpUtility.UrlEncode(folder.ParentListId.ToString("B")), folder.Item.ID, group.ID);}
Example:
http://tstmss/_layouts/editprms.aspx?obj=%7ba17a2cb1-a103-41fe-af46-5785c4838e6f%7d%2C4%2CDOCLIBFOLDER&sel=5
SharePoint list item (SPListItem)
The Edit Permissions link for an item in a SharePoint list or document library:
/_layouts/editprms.aspx?obj=[OBJECTID]%2C[OBJECTTYPE]%2C[ITEMID]&sel=[GROUPID] where: [OBJECTID] is the encoded ID url of the list that contains the item or document. [OBJETTYPE] is ‘LISTITEM’ for an item in a list and ‘DOCUMENT’ for an item in a document library. [ITEMID] is the ID of the listitem or document. [GROUPID] is the ID of the group
The code to construct the url:
if (item.ParentList is SPDocumentLibrary) { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2C{2}%2CDOCUMENT&sel={3}", web.Url, HttpUtility.UrlEncode(item.ParentList.ID.ToString("B")), item.ID, group.ID);} else { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2C{2}%2CLISTITEM&sel={3}", web.Url, HttpUtility.UrlEncode(item.ParentList.ID.ToString("B")), item.ID, group.ID);}
Example:
http://tstmss/_layouts/editprms.aspx?obj=%7bd8a32282-cc74-4635-89a2-06270f50f6b8%7d%2C3%2CLISTITEM&sel=5
Summary
This post describes what parameters you need to pass to the Edit Permissions page, for the different types of objects that you can assign permissions to.
If you are interested in SharePoint Objects in general, watch this screencast or visit my CodePlex site. 2/23/2010In this screencast I will give you an overview of my new CodePlex solution, SharePoint Objects. Click the link below to watch the video (I am working on how to embed it in this post). Introduction SharePoint Objects from Ton Stegeman on Vimeo. Links to other relevant SharePoint Objects content: 1/30/2010In one of the projects we are working on, we created a number of Reporting Services and integrated them in SharePoint 2007. This report displays SharePoint data in a printable, exportable report on a SharePoint page. We started of with using a report deployed to our Reporting Services server. In this proof of concept stage, we looked at using the Enesys RS Data Extension. A pretty clever datasource for SharePoint. It did not exactly meet our requirements, so we had to look at another solution. One of my colleagues pointed me to the ReportViewer controls in Visual Studio. This includes an ASP.NET control that is able to display reports in a web page. In this article I will describe how I created a web part that displays an aggregation of SharePoint tasks in a report. For the end user the report looks like this: The user has a dropdown box in the web part that left him/her select a task status. After clicking the button, the reporting web part queries the site collection for all Task list items with that status and puts them in the report. A big pro that we got by using this control is that we now bypass the Report Server. The report itself is deployed as a resource in the assembly. Therefore we do not need to deploy the report and its permissions. And we bypass the double hop. Nice. By using our Reporting Services server, we would go from SharePoint to the Report Server to get the report. The report server would need to connect back to the SharePoint server to get the data. By using the ReportViewer, we bypass this, because everything happens on the server. As you can see from the screenshot above, this article is not about my skills to build a nice report. I just want to show you how to display a report in SharePoint that is using SharePoint data. At the end of this post you will find a link to a ZIP file containing all sources used in this article. Step 1 – Setup In this article I am using Visual Studio 2008, the .NET Framework 3.5 SP1 (this also needs to be available on the SharePoint servers). After creating a new Visual Studio project, (Class library) I targeted the .NET framework to 3.5. Next thing to do is add a reference to the reporting assembly. This control is available in the dll Microsoft.ReportViewer.WebForms.dll, that can be found in folder ‘C:\Program Files\Microsoft Visual Studio 9.0\ReportViewer’. Your solution installer also needs to make the assembly available on the SharePoint server(s). The the reportview is not yet available on the SharePoint servers, you need to deploy these assemblies: - Microsoft.ReportViewer.WebForms.dll
- Microsoft.ReportViewer.Common.dll
- Microsoft.ReportViewer.ProcessingObjectModel.dll
In the web.config of your SharePoint web application(s) you will need to make some changes. First thing to do is to register the HttpHandler. Add this snippet in the HttpHandlers section: 1: <add verb="*" 2: path="Reserved.ReportViewerWebControl.axd" 3: type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
In the appSettings section, you need to remove this element:
1: <add key="ReportViewerMessages" 2: value="Microsoft.SharePoint.Portal.Analytics.UI.ReportViewerMessages, Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
If you do not remove the element, your reporting web part will display this error message at runtime: “The type Microsoft.SharePoint.Portal.Analytics.UI.ReportViewerMessages, Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c does not implement IReportViewerMessages or could not be found”. Now your Visual Studio project and your SharePoint are ready to get started.
Step 2 – Create the business objects
The report will use an ObjectDataSource as the datasource. In this step we will create the businessobjects to be used as the datasource. Visual Studio can help you pretty good if you want to connect to SQL Server to get the data for your report. Unfortunately, Visual Studio does not know about SharePoint, and therefore you will have to do the coding around the datasource yourself. First object to create is the Task object. This object is very simple:
1: public class Task 2: { 3: public Task() 4: { 5: } 6: 7: public String Title { get; set; } 8: public String Status { get; set; } 9: public String Priority { get; set; } 10: public String AssignedTo { get; set; } 11: public DateTime DueDate { get; set; } 12: 13: public int DueDays 14: { 15: get 16: { 17: return DueDate.Subtract(DateTime.Now).Days; 18: } 19: } 20: }
The TaskList object is an object that holds a number of Task objects and is the object that will be used as ObjectDataSource. Therefore this class is decorated with the DataObjectAttribute attribute (from the System.ComponentModel namespace). This class has a parameterless constructor and a public method that returns a List populated with Task items.
1: [DataObjectAttribute(true)] 2: public class TaskList 3: { 4: public TaskList() 5: { 6: } 7: 8: [DataObjectMethod(DataObjectMethodType.Select)] 9: public List<Task> LoadByStatus(String status) 10: { 11: List<Task> taskList = new List<Task>(); 12: SPSiteDataQuery taskQuery = new SPSiteDataQuery(); 13: taskQuery.Lists = "<Lists ServerTemplate=\"107\" />"; 14: taskQuery.RowLimit = 10000; 15: String query = String.Format(@"<Where><Eq><FieldRef Name='Status' /><Value Type='Text'>{0}</Value></Eq></Where>", status); 16: taskQuery.Query = query; 17: taskQuery.ViewFields = "<FieldRef Name=\"Title\" /><FieldRef Name=\"AssignedTo\" /><FieldRef Name=\"Priority\" /><FieldRef Name=\"DueDate\" Type=\"DateTime\"/>"; 18: taskQuery.Webs = "<Webs Scope=\"SiteCollection\" />"; ; 19: DataTable projectsTable = SPContext.Current.Web.GetSiteData(taskQuery); 20: foreach (DataRow row in projectsTable.Rows) 21: { 22: Task task = new Task(); 23: task.Title = GetFieldValue(row, "Title"); 24: task.Priority = GetFieldValue(row, "Priority"); 25: task.AssignedTo = GetFieldValueUser(row, "AssignedTo"); 26: task.DueDate = GetFieldValueDateTime(row, "DueDate"); 27: taskList.Add(task); 28: } 29: return taskList; 30: } 31: }
The function that is called by the ObjectDataSource is LoadByStatus. This takes a status string as a parameter and returns a List of Task items. The class also contains a number of helper methods to get the data from the DataTable. They are irrelevant for this blog post, but they can be found in the source code that you will find in the ZIP file at the bottom of this article.
Step 3 – Create the report
Now that we have our business objects ready, we can create the report. In your Visual Studio project, click “Add new item” and select the “Report” from the Reporting page:
Select the rdlc file you just added in the Solution Explorer and set the Build Action to Embedded Resource.
In the Datasources windows, click the Add New Data Source button:
In the next dialog, select Object as the Data Source Type:

The next step in the wizard asks you to which object you with to bind. Select the Task object, because that is the object we want to use in our report:
In the Report menu of Visual Studio, select Data Sources.
In the dialog select the datasource object you just added, and click Add to report.

Add a table to the report and drag one of the fields to the detail row:
From this point on, it is business as usual (if you are used to building reports in Reporting Services).
Step 4 – Create the reporting web part
Last thing to do it to create a new web part that will render the report. The snippet below shows the part of the CreateChildControls method of the task reporting web part that created the objects we need. Full code (that also creates the dropdownlist and the button) can be found in the ZIP file at the bottom of this post.
1: _reportViewer = new ReportViewer(); 2: _reportViewer.Width = new Unit(100, UnitType.Percentage); 3: _reportViewer.Height = new Unit(100, UnitType.Percentage); 4: _reportViewer.Visible = false; 5: _reportViewer.LocalReport.ReportEmbeddedResource = "TST.SharePoint2007.Reporting.TaskReport.rdlc"; 6: Controls.Add(_reportViewer); 7: 8: _datasourceTaken = new ObjectDataSource(); 9: _datasourceTaken.ID = "datasourceTasks"; 10: _datasourceTaken.SelectMethod = "LoadByStatus"; 11: _datasourceTaken.SelectParameters.Add(new Parameter("status", System.Data.DbType.String)); 12: _datasourceTaken.TypeName = "TST.SharePoint2007.Reporting.TaskList, TST.SharePoint2007.Reporting, Version=1.0.0.0, Culture=neutral, PublicKeyToken=503edd7b21a430b3"; 13: _datasourceTaken.Selecting += new ObjectDataSourceSelectingEventHandler(datasourceTasks_Selecting); 14: Controls.Add(_datasourceTaken);
After creating the ReportView control, the reference to the report file needs to be set. Our report is an embedded resource in the assembly. Therefore we seet the ReportEmbeddedResource property of the LocalReport (in line 5). The full name to load the report is the name of the assembly (TST.SharePoint2007.Reporting) followed by the name of the report (TaskReport.rdlc).
After creating the viewer control, the ObjectDataSource is created. The TypeName property in line 12 is the reference to the business object we created in step 2. The SelectMethod and SelectParameters need to match a public function decorated with the DataObjectMethod attribute in that classe. In my case that is the function LoadByStatus, which has a parameter called status of type String (see step 2). The snippet below shows the implementation of the Selecting event.
1: private void datasourceTasks_Selecting(object sender, ObjectDataSourceSelectingEventArgs e) 2: { 3: e.InputParameters["status"] = _getStatus.SelectedValue; 4: }
In this event we get the value of the status dropdownlist (see the first screenshot) and pass the value to the inputparameter of the datasource. Last thing to do it to implement the event handler for the button. When a user clicks the “Generate Report” button, this method is called:
1: private void search_Click(object sender, EventArgs e) 2: { 3: ReportDataSource dataSource = new ReportDataSource("TST_SharePoint2007_Reporting_Task", _datasourceTaken); 4: _reportViewer.LocalReport.DataSources.Clear(); 5: _reportViewer.LocalReport.DataSources.Add(dataSource); 6: _reportViewer.Visible = true; 7: _reportViewer.LocalReport.Refresh(); 8: } 9:
In this method, a new ReportDataSource object is instantiated and added to the report. Please note that the first parameter of the constructor, the name parameter, needs to match the name of the datasource in the report. This is the name of the data source you selected in step 3 in the Report Data Sources dialog:

The second parameter of the ReportDataSource constructor is a reference to the ObjectDataSource we created in CreateChildControls.
After building and deploying your web part, you should now be able to run the report.
Conclusion
The .NET ReportViewer control is a powerful control to use if you need to do reporting on SharePoint data in SharePoint. It saves you from managing a report server. You don’t need to deploy the report, because it it part of the assembly and you do not have to worry about the double hop issue. Another option would be to run Reporting Services in integrated mode with SharePoint. In our case, that was not an option. We had very specific requirements for the data retrieval process and therefore we moved in this direction.
Full source code for this sample can be found in this ZIP file. 1/13/2010
Introduction
[Update 28-02-2010]: Watch the introduction screencast at vimeo.
This blog post is the introduction of my new CodePlex project, SharePoint Objects. SharePoint Objects is built to give you insight in usage of SharePoint 2007 artifacts in your farm. You probably have seen the message “The content type is in use” a number of times when you tried to delete a content type. Then the big search for all other content types, lists and items that are using the content type began and probably ended in frustration. Or you are going to make a change to one of the site columns and you want to understand the impact of this change. The only way to do this in SharePoint is by manually going through all your sites and lists.
This is where SharePoint Objects is going to help you. It creates a dedicated database in your farm. You can instruct SharePoint Objects to index specific objects and all references to that object into the database. You can use the SharePoint Objects pages to display the information in this database. This will help you to quickly find all places in SharePoint 2007 where your artifact is used. The solution comes with a timer job that automatically keeps this information up to date, based on the schedule that you specify. The screenshot below shows the overview of the usage of the out of the box “Article Date” field.
It shows three different types of objects that are using the site column in the site collection. The list shows the name of the referencing object, including a link to the details page of that referencing object. It also shows the site and the list that contain the object. You can filter the list by using the search options just above the list, or by using column filtering just as in ordinary SharePoint lists.
The release that I am now introducing is the first beta release. This version indexes site columns and content types. For content types these types of object references are indexed:
- Content type associated to a list: SharePoint lists that are using the selected content type.
- Content type of a list item: List items of the selected content type.
- Content type is parent of a content type: Other content types that are using the selected content type as parent.
For site columns these types of usage of the site column are indexed:
- Site column associated to a list: SharePoint lists that have the selected field associated.
- Site column used by a list item: list items that have a value for the selected field.
- Site column associated to a content type: Content types that have an association with the Article Date field.
At the bottom of this post, you will find my ideas about the future of SharePoint Objects. After installation of SharePoint Objects (you can download installer and the installation guide here) you need to specify which objects will be indexed by SharePoint Objects.
Specifying objects to index
After installing the solution, you need to tell SharePoint Objects which objects to index. Before you do this, make sure you completed all configuration steps that are described in the Installation Guide. In these steps you will setup the database and configure menu options and the timer job. The site settings menu of all SharePoint sites now have a new section called SharePoint Objects. The page that you select will show you all objects that are defined in that site. Please be aware that it only shows the objects that are defined in the current site. It will not show all available site columns or content types. Therefore the list of objects will be long in the root site of your site collections. In the sample screenshots below, I am using an example of a site column, but the same principles apply to content types.
The left side of the screen shows information about the current site. It shows when the site was last indexed. Just below that you will find the section that shows all artifacts, defined in this site, that are currently monitored by SharePoint objects. In my case, this list is empty, because we are going to configure the first object. Use the group dropdown or the search box to find your site column and click the radiobutton.
The top right of your screen will now show the options for the site column. First you need to select the Index scope. You can choose from one of these 4 scopes:
- Site – Just objects in the current site are indexed.
- Site Collection – The indexer will iterate through all sites in the current site collection to find objects that use the site column.
- Web Application – All site collections in the current web application will be monitored for references to the Article Date site column.
- Farm – All artifacts in all web applications in your farm are checked for references to the selected site column.
The second option is to select Index by property. The indexer uses this value to decide if a SharePoint artifact has a reference to the selected site column. The dropdown has these options:
- Id – Checks the reference of for example a site column association to a list based on the ID (the GUID) of the site column. If the GUID of the Article Date field matches the guid of any of the list fields, the list is included in the index. This is a useful option for the out of the box site columns and the site columns that you have deployed through features, in which you specified the ID for a field.
- Display name – If you manually created the same site columns across multiple site collections and web applications, the ID for these site columns will be different. You can then instruct the indexer to decide whether or not it is a reference by looking at the display name of the field. In my example, a
- Internal name – Same as display name, but now the internal name of the field is used. If you promote your users to categorize their documents using a Category field, you can use SharePoint Objects to get an idea of user groups in your organization that are using this.
If you want to index the selected site column, you check the box Include in index. If you want SharePoint Objects to include references to list items in the index, you tick the Index list items box. If this is not selected, list items in the lists and libraries are not checked for references.
After setting the options, click the Save button. You can now wait for the timer job to kick in and index the objects you configured, or you can click the “Index now” link in the Site information section.
Finding references to your object
After setting up a number of objects and running the timer job, you can use the SharePoint Objects pages to find the references. You use the same page as you used to setup your artifacts in the previous chapter. The Indexed Site columns section now shows the objects that are monitored by the solution.
Select one of the site colums. The right side of the screen now again shows the options you just set for this object. Below that you will find the list of objects that have a reference to the Article Date field. You can use the dropdown boxes and the textboxes in the Objects using this site column section to find the references that you are looking for.
Please notice the 2 scope dropdowns in the screenshot above. The display scope is set to Site. So the list currently only shows all artifacts that use Article Date in the current site. The references are indexed using the web application scope, so I can change the dropdown and get an overview of all objects using Article Date, even in other site collections. If I had set the Index scope option to Site Collection however, the indexer just looked in the current site collection and selecting Web Application as the display scope would not show me more references. Just because they are not indexed.
You can also filter the list by using the column headers:
Running SharePoint Objects in your farm(s)
There are 2 ways to run the SharePoint Indexer. The first option is to configure the timerjob and specify a schedule. Please be aware that indexing is a process that uses a lot of system resources and is something that you would not want to do during business hours. In your Central Administration, the Application Management page has 2 new menu options:
Select the option Configure timerjob for the index job of SharePoint Objects. If you do not see these options, the web application feature is not activated for your web application. Check the Installation Guide for instructions. Specify a schedule for the timerjob and click OK.
Please note: the indexer runs in the context of a web application. That means that if you want to index content from multiple web applications, you need to activate the feature and configure the timerjob for every web application.
The future
As I said in the introduction, this is the first beta release. I hope you are going to give it a go, but please give it a pretty good test in a lab environment, before throwing it on the production server directly. I am interested to hear feedback from you how installation and configuration was. You are also invited to give me suggestions on how to improve SharePoint Objects. Am I missing references for site columns and content types, does everything work as expected, are all links shown in the overview correct? Things like that. I am also working on new artifacts to include in a next release. The list can get pretty long, I am currently thinking about:
- SharePoint groups
- Web parts
- Feature Definitions
- Page Layouts and master pages
- List schema’s
- List views
If you have artifacts that you want to index in the next release, please let me know and I will prioritize.
Here is the link to CodePlex: http://sharepointobjects.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=38702
Enjoy SharePoint Objects and hope you like it! 12/30/2009This post is a wrap up of previous blog posts that I have written on creating a custom list schema. I find myself searching for these posts every time I need to do this, so I thought it would be handy to have it all in one place (at least for myself). In one of our current projects, we were creating custom list schemas like this. we tried to query the lists (using Content Query Web Part) based on the custom content type that is associated with these list. At first this did not work and this post also shows how we fixed that. At the end of this article you will find the link to the ZIP file containing all code. This article shows how to: - Create a custom site column from a feature
- Create a custom content type using that site column from a feature
- Create a custom list schema using that content type
- Rename the Title field to a custom display name
- Add a new view to the list, to show the new site column
- Create a new feature that creates a new instance of the new list template
- Populate the list with default items
- Show items from lists based on this template using the Content Query Web Part
My solution has 2 features: - site collection feature for registering site column, content type and list template.
- web feature for creating a list and populating it with data.
Step 1 – The Site Collection Feature The XML snippet below is the XML from my feature.xml file that ends up in folder <SharePoint Root>\TEMPLATE\FEATURES\TST.CustomListTemplate. Nothing special to mention here, except that you can register multiple site columns, content types and list templates by creating 1 feature. You don’t need to create a new feature for every list template. SharePoint allows you to do that, but you end up with having a lot of features. If you want to have a feature for each list template, I would recommend creating hidden features and creating 1 wrapper features with activation dependencies. 1: <?xml version="1.0" encoding="utf-8"?> 2: <Feature Id="23ddef43-9d01-4127-9903-beeb77c28c5d" 3: Title="TST Custom List Template demo" 4: Description="Custom list template with a custom content type associated (Ton Stegeman)" 5: Version="1.0.0.0" 6: Scope="Site" 7: Hidden="FALSE" 8: xmlns="http://schemas.microsoft.com/sharepoint/"> 9: <ElementManifests> 10: <ElementManifest Location="Fields.xml"/> 11: <ElementManifest Location="Contenttype.xml"/> 12: <ElementManifest Location="ListTemplates\Template.xml" /> 13: </ElementManifests> 14: </Feature>
The sample above activates three element manifests. These can be found in the next paragraphs.
Step 2 – The Site Column
In this XML snippet below, the new site column is created.
1: <?xml version="1.0" encoding="utf-8"?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <Field 4: Type="Text" 5: DisplayName="Demo Text Field" 6: Required="TRUE" 7: MaxLength="255" 8: Group="TST Demo Fields" 9: ID="{ee69b92a-02cd-4a5d-b6bd-d1fec35c301f}" 10: StaticName="DemoTextField" 11: Name="DemoTextField"/> 12: </Elements>
Most important thing to notice here is that I removed the spaces that are used in the displayname of the field from the internalname attributes of the field. I would recommend doing this for all special characters, otherwise you end up with encoded internal field names using ‘_x0020’. My custom site column would have had internal name ‘TST_x0020_Demo_x0020_Field’ if I did not remove the spaces from the internal names.
Step 3 – The Content Type
Below you can find the contents of Contenttype.xml. In the <FieldRefs> element you will find all fields that are used by the content type. You see a reference to our custom site column (it has the same ID and Name). The first field used in this sample is the title field that is renamed to have another displayname.
1: <?xml version="1.0" encoding="utf-8"?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <ContentType ID="0x01008c43d032ae654d6e8b965c45acc4af3a" 4: Name="Demo Contenttype" 5: Description="Content type with a custom text field and a renamed Title field." 6: Group="TST Demo Fields" 7: Version="0" 8: Sealed="FALSE" 9: ReadOnly="FALSE" 10: BaseType="0x01"> 11: <FieldRefs> 12: <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" 13: DisplayName="Demo Title Field" 14: Name="Title" 15: Required="TRUE" 16: ShowInNewForm="TRUE" 17: ShowInEditForm="TRUE"/> 18: <FieldRef ID="{ee69b92a-02cd-4a5d-b6bd-d1fec35c301f}" 19: Name="DemoTextField" 20: Required="TRUE" 21: ShowInNewForm="TRUE" 22: ShowInEditForm="TRUE"/> 23: </FieldRefs> 24: </ContentType> 25: </Elements>
There is something special about the ID of a custom content type. If you haven’t seen this before, you better read the something of the backgrounds, to understand this mechanism. Brett Maytom explains how it works. This MSDN article by Scot Hillier also explains it briefly.
Step 4 – The List Schema
Next thing to create is the list schema that is using our custom content type. I won’t publish the full content of the schema.xml file. You can find this in the attached ZIP file (see the end of this article). Important in the schema is that you will need to have the XML definition of the Field elements that are in your content type.
1: <ContentTypes> 2: <ContentTypeRef ID="0x01008c43d032ae654d6e8b965c45acc4af3a"> 3: <Folder TargetName="Item" /> 4: </ContentTypeRef> 5: </ContentTypes> 6: <Fields> 7: <Field 8: Name="LinkTitle" 9: ID="{82642ec8-ef9b-478f-acf9-31f7d45fbc31}" 10: DisplayName="Demo Title Field" 11: Sealed="TRUE" 12: SourceID="http://schemas.microsoft.com/sharepoint/v3" 13: StaticName="LinkTitle"> 14: </Field> 15: <Field 16: Name="LinkTitleNoMenu" 17: ID="{bc91a437-52e7-49e1-8c4e-4698904b2b6d}" 18: DisplayName="Demo Title Field" 19: Sealed="TRUE" 20: SourceID="http://schemas.microsoft.com/sharepoint/v3" 21: StaticName="LinkTitleNoMenu"> 22: </Field> 23: <Field 24: ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" 25: Type="Text" 26: Name="Title" 27: ShowInNewForm="TRUE" 28: DisplayName="Demo Title Field" 29: Sealed="TRUE" 30: SourceID="http://schemas.microsoft.com/sharepoint/v3" 31: StaticName="Title"> 32: </Field> 33: <Field 34: Type="Text" 35: DisplayName="Demo Text Field" 36: Required="TRUE" 37: MaxLength="255" 38: Group="TST Demo Fields" 39: ID="{ee69b92a-02cd-4a5d-b6bd-d1fec35c301f}" 40: StaticName="DemoTextField" 41: Name="DemoTextField"/> 42: </Fields>
The snippet above shows how to associate the custom content type we created in Step 3 with the list. The Fields elements contains all fields that we will use in our list (also our custom field that is part of the content type). If you don’t put the XML for your fields here, the content type will be associated with the list, but the fields will not be available in the list items. The snippet also shows how to rename the Title field to have a custom display name. It also renames the out of the box computed fields that use this Title field (like ‘Title (linked to item with edit menu)’).
Step 5 – The View
In the schema.xml file we also add the new view to our list. The snippet below just shows the <View> element.
1: <View 2: BaseViewID="2" 3: Type="HTML" 4: WebPartZoneID="Main" 5: DisplayName="TST Custom View" 6: DefaultView="FALSE" 7: SetupPath="pages\viewpage.aspx" 8: ImageUrl="/_layouts/images/generic.png" 9: Url="DemoView.aspx">
Important attributes of this element are BaseViewID, SetupPath and Url. BaseViewID must be a unique ID for all views in your schema.xml file. SetupPath is a reference to a page that is used as template for your view page. If you don’t want to use custom form, set it to ‘pages\viewpage.aspx’. The Url will be the name of the aspx of the View itself. If you don’t specify the SetupPath, you will have to make sure your list schema folder has an aspx for the view page.
Step 6 – The List Template
After creating the list schema, next thing to do is to define the list template. This is the 3rd XML file that is referenced from our feature.xml file in step 1. The contents of my example is shown in the snippet below.
1: <?xml version="1.0" encoding="utf-8"?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <ListTemplate 4: Name="List" 5: Type="90010" 6: BaseType="0" 7: OnQuickLaunch="TRUE" 8: SecurityBits="11" 9: Sequence="410" 10: DisplayName="Demo list template with custom content type (Ton Stegeman)" 11: Description="Custom list with a custom content type associated, a renamed title field and an extra view." 12: Image="/_layouts/images/itgen.gif"/> 13: </Elements>
Make sure that the Name attribute matches the name of the folder that contains the schema.xml file. In my case, the feature folder has a subfolder called ‘List’ that holds the schema.xml file. Second thing to look at is to define a unique number for your list template and use that in the Type attribute.
Step 7 – The Web Feature
In the previous steps we have completed our site collection feature that registers the schema for our custom list. In this step, we will create a Web scoped feature that creates a new instance based on this list schema and adds some items to it.
1: <?xml version="1.0" encoding="utf-8"?> 2: <Feature Id="05fd17d4-5f70-40ab-a5e9-975ba15df371" 3: Title="TST Custom List Instance demo" 4: Description="Custom list instance based on a custom schema (Ton Stegeman)" 5: Version="1.0.0.0" 6: Scope="Web" 7: Hidden="FALSE" 8: xmlns="http://schemas.microsoft.com/sharepoint/"> 9: <ElementManifests> 10: <ElementManifest Location="Lists.xml" /> 11: </ElementManifests> 12: <ActivationDependencies> 13: <ActivationDependency FeatureId="23ddef43-9d01-4127-9903-beeb77c28c5d"/> 14: </ActivationDependencies> 15: </Feature>
The above snippet shows the XML file the feature. Make sure the Id for your feature is unique. Create a new GUID using http://www.newguid.com, use GuidGen in Visual Studio, or use the CodeRush/Refactor tools for SharePoint Developers by Andrew Connell.
The feature has a dependency on our site collection feature. SharePoint will first check if this feature is activated before activating the web feature.
Step 8 – The List
The feature in step 7 uses Lists.xml as element manifest. The contents of this file is shown in the snippet below. The FeatureId attribute is a reference to the feature that registered the list template. This is our site collection feature from Step 1, not the web feature from step 7!
1: <?xml version="1.0" encoding="utf-8" ?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <ListInstance 4: FeatureId="23ddef43-9d01-4127-9903-beeb77c28c5d" 5: Title="Demo list" 6: Description="Demo list with custom schema and items." 7: Id="90010" 8: TemplateType="90010" 9: Url="Lists/Demolist"> 10: </ListInstance> 11: </Elements>
Make sure that the Id attribute matches the Type attribute of the ListTemplate element in Step 6. If you have spaces in the title of your list, it might be good to remove them from the url for your list. The Url attribute allows you to specify the url for your list. This will be the name of the rootfolder of the list.
Step 9 – The Data
In this step, we populate the list with some data once the list is created. Add the snippet below as a child element of the ListInstance element in the XML file of step 8.
1: <Data> 2: <Rows> 3: <Row> 4: <Field Name="ID">1</Field> 5: <Field Name="Title">Title of the first item (created in the feature)</Field> 6: <Field Name="DemoTextField">Demo text first item</Field> 7: </Row> 8: <Row> 9: <Field Name="ID">2</Field> 10: <Field Name="Title">Title of the first item (created in the feature)</Field> 11: <Field Name="DemoTextField">Demo text first item</Field> 12: </Row> 13: </Rows> 14: </Data>
For every Row, the ID field is set explicitly. It is not necessary to do this, but if you don’t do it and your feature gets re-activated, you end up with duplicated content in your list.
Step 9 – The Deployment
Deployment of our 2 features is done using a WSP package. In my Visual Studio solution, the folder TSTCustomListTemplate contains the site collection feature and all content. The folder TSTCustomListInstance contains all content for the web feature. The package is created using the DDF file below:
1: ; 2: .OPTION EXPLICIT ; Generate errors 3: .Set CabinetNameTemplate=TST.TestList2007.ListTemplate.wsp 4: .set DiskDirectoryTemplate=CDROM ; All cabinets go in a single directory 5: .Set CompressionType=MSZIP;** All files are compressed in cabinet files 6: .Set UniqueFiles="ON" 7: .Set Cabinet=on 8: .Set DiskDirectory1=Package 9: manifest.xml manifest.xml 10: 11: ; Include files for the Feature folder 12: ..\TSTCustomListTemplate\Feature.xml TST.CustomListTemplate\Feature.xml 13: ..\TSTCustomListTemplate\Contenttype.xml TST.CustomListTemplate\Contenttype.xml 14: ..\TSTCustomListTemplate\Fields.xml TST.CustomListTemplate\Fields.xml 15: ..\TSTCustomListTemplate\ListTemplates\Template.xml TST.CustomListTemplate\ListTemplates\Template.xml 16: ..\TSTCustomListTemplate\List\schema.xml Features\TST.CustomListTemplate\List\schema.xml 17: 18: ..\TSTCustomListInstance\Feature.xml TST.CustomListInstance\Feature.xml 19: ..\TSTCustomListInstance\Lists.xml TST.CustomListInstance\Lists.xml 20: ;
The manifest of the solution:
1: <?xml version="1.0" encoding="utf-8" ?> 2: <Solution SolutionId="56e1580d-7eb8-4428-adea-1a17c120389c" xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <TemplateFiles> 4: <TemplateFile Location="Features\TST.CustomListTemplate\List\schema.xml"/> 5: </TemplateFiles> 6: <FeatureManifests> 7: <FeatureManifest Location ="TST.CustomListTemplate\Feature.xml"/> 8: <FeatureManifest Location ="TST.CustomListInstance\Feature.xml"/> 9: </FeatureManifests> 10: </Solution> 11:
Deploying the solution is done using SharePoint Solution Installer from CodePlex. The snippet below shows the configuration for the installer, from setup.exe.config. The SolutionId needs to be the same value as the value of the SolutionId attribute in the manifest file.
1: <?xml version="1.0" encoding="utf-8" ?> 2: <configuration> 3: <appSettings> 4: <add key="BannerImage" value="Default"/> 5: <add key="LogoImage" value="None"/> 6: <add key="EULA" value=""/> 7: <add key="SolutionId" value="56e1580d-7eb8-4428-adea-1a17c120389c"/> 8: <add key="FarmFeatureId" value=""/> 9: <add key="SolutionFile" value="TST.TestList2007.ListTemplate.wsp"/> 10: <add key="SolutionTitle" value="TST Demo List Template"/> 11: <add key="SolutionVersion" value="1.0.0.0"/> 12: <add key="UpgradeDescription" value="Upgrades {SolutionTitle} on all frontend web servers in the SharePoint farm."/> 13: <add key="RequireDeploymentToCentralAdminWebApplication" value="false"/> 14: <add key="RequireDeploymentToAllContentWebApplications" value="false"/> 15: </appSettings> 16: </configuration>
Step 10 – The Test
After installing and deploying the solution, you can test the new list. First activate the site collection feature:
In the site(s) where you want to use your new list, activate the web feature:
If all is well, you now have a new list in your site called ‘Demo list’:
This list should have a custom view (TST Custom View), the Title field is renamed to ‘Demo Title Field’ and it has 2 items, as shown in the screenshot above.
Step 11 – Querying The Content
Last step in this article is to show how you can query for content in your new list(s) using the Content Query Web Part. Drag the web part onto a page and modify the properties. In the Query section you can select your new content type, as is shown in this screenshot:
The issue we have here is that you also need to specify the list type. In some cases, our custom list is not available in the dropdown. I was unable to figure out why the custom list template was not available in the dropdown, but I have seen it a few times. The way to solve this is to full configure your web part. Then save the changes. This will result in the message ‘This query has returned no items’. Export your web part and delete it from the page. Open the file in an editor. Find the property where that attribute Name has value ‘ServerTemplate’. Change the value of this property to the value of your Type attribute in the ListTemplate regristration in Step 6. In my case this is 90010.
1: <webParts> 2: <webPart xmlns="http://schemas.microsoft.com/WebPart/v3"> 3: <data> 4: <properties> 5: <property name="ServerTemplate" type="string">90010</property> 6: </properties> 7: </data> 8: </webPart> 9: </webParts>
Save the .webpart file , import it onto your page and that’s it. Your web part will show the results from your custom lists:
This ZIP file contains all the files I described in this article. http://www.tonstegeman.com/Blog/Documents/TST.TestList2007.zip
|
|
|
|
|
|
|
|