Most of the documentation I've seen out there around creating publishing items such as master pages and page layouts has developers using SharePoint Designer to create them. This is ok, but it only gets you so far, and I see problems with the approach when you require more robust development. Also, when it came to using SharePoint Designer, I always had the question - 'how do you develop a master page or page layout locally in development, and deploy it to a completely different farm?'
As my past couple of posts might point to, I've been working a lot with WSS Solutions lately, and I'm currently working with a client who's doing a migration from MCMS 2002 to MOSS. The solution is completely custom, and needs to be as robust as possible. Part of the project has been documenting the best possible configuration for MOSS CMS development, so I figured what I've found would be something good to share.
Essentially, there's two features in the solution. One feature for custom resource files (RESX) and another feature for all the custom publishing items. I'm using the full Telerik Editor for MOSS - which seems to have all the kinks worked out from the earlier versions - along with some other Telerik ASP.NET controls. I've heard that Andrew Connell is working with Telerik on releasing a whitepaper at the end of this month that will provide the needed guidance for leveraging best practices in implementing all of the Telerik ASP.NET components into MOSS and WSS v3, and I'll most likely touch on it in the future, as well.
I don't want to really cover the details of the MOSS resource feature in this post, but Mikhail Dikov has a great one on creating custom resource files in MOSS, so make sure to check it out. I will, however, give you what you need to get it up and running.
So - let's start from scratch!
Create a new Class Library project in Visual Studio 2005:
Once it's created, delete Class1.cs.
Add a reference to Windows SharePoint Services, Microsoft Content Publishing and Management, and System.Web.
I really like to mirror the installation directory on the server, so I normally set up a folder structure in my projects to match what the file structure on the server looks like, along with the folders that correspond with the locations of the files I'm going to add..
The first thing I'm going to do is create the resource feature along with the needed feature receivers to deploy the resource files to the App_GlobalResources directory of the web application it's installed on.
This is code I mainly obtained from Mikhail's post, but here's how you set up the resource feature:
If you set up the folder structure just like I did in the image above, create a class in the 'Objects' directory called DeployCustomResourceJob.cs, with the following code:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using System.Diagnostics;
using System.IO;
namespace MOSSFeatures.Objects
{
public class DeployCustomResourcesJob : SPJobDefinition
{
#region Fields
[Persisted]
private string _SourcePath;
private string _SomethignElse;
#endregion
#region Constructors
public DeployCustomResourcesJob()
: base()
{
}
public DeployCustomResourcesJob(string jobName, SPWebApplication webApp, string featureName)
: base(jobName, webApp, null, SPJobLockType.Job)
{
//get the path to the feature directory
this._SourcePath = SPUtility.GetGenericSetupPath("Template");
//uncomment the following line (commented to fix a bug in livewriter)
// + "\\FEATURES\\" + featureName;
}
#endregion
#region Job Handling
public override void Execute(Guid targetInstanceId)
{
try
{
//get the web application for the current job - I'm pretty sure this is the same as calling 'base.WebApplication'
SPWebApplication webApp = this.Parent as SPWebApplication;
foreach (SPUrlZone zone in webApp.IisSettings.Keys)
{
//the settings of the IIS application to update
SPIisSettings settings = webApp.IisSettings[zone];
//determine the destination path
string destPath = Path.Combine(settings.Path.ToString(), "App_GlobalResources");
//get the RESX files from the feature directory
string[] filePaths = Directory.GetFiles(this._SourcePath, "*.resx");
//copy/overwrite the RESX files into the App_GlobalResource directory
foreach (string filePath in filePaths)
{
string fileName = Path.GetFileName(filePath);//get the filename of the file to copy
File.Copy(filePath, Path.Combine(destPath, fileName), true);//copy it over the file in the Resource directory
}
}
}
catch (Exception ex)
{
Debug.WriteLine("Failed to copy global resource");
Debug.WriteLine(ex);
throw;
}
}
#endregion
}
}
Next - create a class in the 'FeatureReceivers' directory called CustomFeatureReceiver.cs. This is the feature receiver which will move the resource files once the feature is activated. Here's the code:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using MOSSFeatures.Objects;
namespace MOSSFeatures.FeatureReceivers
{
public class CustomResourceReceiver : SPFeatureReceiver
{
#region Constants
const string JOB_TITLE = "Deploy Custom Resources";
const string JOB_NAME = "job-deploy-custom-resources";
#endregion
#region Events
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPWeb web = properties.Feature.Parent as SPWeb;
//check for an exisiting instance of the job - if we find it, delete it
foreach (SPJobDefinition job in web.Site.WebApplication.JobDefinitions)
{
if (job.Name == JOB_NAME && job.WebApplication.Name == web.Site.WebApplication.Name)
{
job.Delete();
string something = "nothing";
}
}
//create a new deployment job
DeployCustomResourcesJob deployJob = new DeployCustomResourcesJob(JOB_NAME,
web.Site.WebApplication,
properties.Definition.DisplayName);
deployJob.Title = JOB_TITLE;
deployJob.Schedule = new SPOneTimeSchedule(DateTime.Now);
deployJob.Update();
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
SPWeb web = properties.Feature.Parent as SPWeb;
//delete the job, if there is one
foreach (SPJobDefinition job in web.Site.WebApplication.JobDefinitions)
{
if (job.Name == JOB_NAME && job.WebApplication.Name ==
web.Site.WebApplication.Name)
{
job.Delete();
}
}
//we could delete the resource files here if we want to
}
public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
//throw new Exception("The method or operation is not implemented.");
}
public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
//throw new Exception("The method or operation is not implemented.");
}
#endregion
}
}
Create two RESX files in the TEMPLATE\FEATURES\CustomResources project directory - CustomResources.en-US.resx, and CustomResources.resx. According to Mikhail, one resource file is used during the provisioning process, and the other is used at run-time, but the rule I use is that the entries in both resource files should match. These two files will contains contain the resource strings used in the custom publishing feature, as well as anywhere else you might need them.
Create the Feature.xml file for the CustomResources feature.
<Feature
Title="CustomResourceFeature"
Id="AC3CB30C-905F-433f-A635-DA13291BCC08"
Description="Deploys localization resources used for MOSSFeatures to the App_GlobalResources directory"
Version="1.0.0.0"
Scope="Site"
xmlns="http://schemas.microsoft.com/sharepoint/"
ReceiverAssembly="MOSSFeatures, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ENTER_YOUR_PUBLIC_KEY_HERE"
ReceiverClass="MOSSFeatures.FeatureReceivers.CustomResourceReceiver" AlwaysForceInstall="true" ActivateOnDefault="false">
<ElementManifests>
<ElementFile Location="CustomResources.en-US.resx"/>
<ElementFile Location="CustomResources.resx"/>
</ElementManifests>
</Feature>
There - that's all you need to implement custom resource files in MOSS. Now we need to create the WSS Solution file (WSP) to deploy them.
At the root of the project, create a new file called CreateMOSSFeatures_WSP.ddf. This ddf file is used by makecab so it knows where to place files inside the cabinet (WSP) file.
Enter the following code in the ddf file
.OPTION EXPLICIT ;generate errors
.Set CabinetNameTemplate=MOSSFeatures.wsp
.Set DiskDirectoryTemplate=CDROM ;All cabinets go into a single directory
.Set CompressionType=MSZIP ;** All files are compressed in cabinet files
.Set UniqueFiles="ON"
.Set Cabinet=on
.Set DiskDirectory1=
;Here's the way it works...
;FileToCopyFromInProject NewFileInCABFile
;manifest file
manifest.xml manifest.xml
;assemblies
bin\debug\MOSSFeatures.dll MOSSFeatures.dll
;features
;FeatureName\feature.xml FeatureName\feature.xml
;FeatureName\elements.xml FeatureName\elements.xml
;CustomResources
TEMPLATE\FEATURES\CustomResources\Feature.xml CustomResources\Feature.xml
TEMPLATE\FEATURES\CustomResources\CustomResources.en-US.resx CustomResources\CustomResources.en-US.resx
TEMPLATE\FEATURES\CustomResources\CustomResources.resx CustomResources\CustomResources.resx
TEMPLATE\FEATURES\CustomResources\CustomResources.en-US.resx RESOURCES\CustomResources.en-US.resx
TEMPLATE\FEATURES\CustomResources\CustomResources.resx RESOURCES\CustomResources.resx
Now, we need to create the manifest file for the WSS Solution. The manifest file will tell the solution receiver in the MOSS farm where to put the files located inside the WSP cabinet once the solution is installed. It will also tell the receiver to place the assembly into the GAC, and deploy the code across all servers in the farm.
At the root of the project file, create a file named manifest.xml with the following code:
<?xml version="1.0" encoding="utf-8" ?>
<Solution SolutionId="ENTER_SOLUTION_ID_GUID_HERE" xmlns="http://schemas.microsoft.com/sharepoint/" ResetWebServer="TRUE">
<Assemblies>
<Assembly DeploymentTarget="GlobalAssemblyCache" Location="MOSSFeatures.dll">
<SafeControls>
<SafeControl Safe="True" Assembly="MOSSFeatures, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=ENTER_YOUR_PUBLIC_KEY_HERE"
Namespace="MOSSFeatures" TypeName="*"/>
</SafeControls>
</Assembly>
</Assemblies>
<Resources>
<Resource Location="CustomResources\CustomResources.en-US.resx"/>
<Resource Location="CustomResources\CustomResources.resx"/>
</Resources>
<FeatureManifests>
<FeatureManifest Location="CustomResources\Feature.xml"/>
</FeatureManifests>
<RootFiles>
<RootFile Location="Resources\CustomResources.en-US.resx"/>
<RootFile Location="Resources\CustomResources.resx"/>
</RootFiles>
</Solution>
Now that we have the ddf and the manifest files created, we can set up a post-build event in Visual Studio that will run makecab and create the WSP file for us. To add the post build event, right-click on the project in the Visual Studio Solution Explorer and choose 'Properties'
Enter the following lines in the Post-build event command line text box:
cd $(ProjectDir)
makecab /f CreateMOSSFeatures_WSP.ddf
We also need to make sure that we sign the assembly, so when the solution is deployed, it can be added to the GAC.
For ease in deployment, as I stated in a previous post, it's a good idea to create some command files to handle the solution deployment, update, and removal. Now's a good time to add the directory which contains stsadm to the Path Windows System Variable if you haven't done it already, and doing so will allow you to run stsadm from any directory. To set this up, right-click on My Computer, and choose Properties. On the Advanced tab, click the Environment Variables button, and append ';C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN' to the end of the Path variable.
Now, you can create the cmd files and refer to stsadm inside them. There's three files: Deploy.cmd, Update.cmd, and Remove.cmd, rather than explain what's going on in theme in exhausting detail, here's the code:
Deploy.cmd
@ECHO OFF
SET SITE_URL="http://mossdevelopment.richfinn.net"
SET SOLUTION_NAME=MOSSFeatures.wsp
REM ----- Deactivate the feature(s) -----
stsadm -o deactivatefeature -name CustomResources -url %SITE_URL% -force
REM ----- Remove the features and solution -----
stsadm -o retractsolution -name %SOLUTION_NAME% -immediate -url %SITE_URL%
ECHO .
ECHO .
ECHO -------------------------------------------------------------------------------------------
ECHO Verify that the correct solutions have been completly retracted and then press ENTER
ECHO -------------------------------------------------------------------------------------------
ECHO .
pause
ECHO ON
REM ----- Delete the solution -----
stsadm -o deletesolution -name %SOLUTION_NAME%
REM ----- Add and depoly the solution -----
stsadm -o addsolution -filename %SOLUTION_NAME%
stsadm -o deploysolution -n %SOLUTION_NAME% -immediate -allowgacdeployment -allowCasPolicies -url %SITE_URL%
ECHO OFF
ECHO .
ECHO .
ECHO -------------------------------------------------------------------------------------------
ECHO Solution deployment complete, press ENTER to activate features, if any
ECHO -------------------------------------------------------------------------------------------
pause
ECHO ON
REM ----- Active features -----
stsadm -o activatefeature -name CustomResources -url %SITE_URL% -force
REM ----- Remove the following comment if features are present
REM pause
Update.cmd
cls
ECHO OFF
SET SITE_URL="http://mossdevelopment.richfinn.net"
SET SOLUTION_NAME=MOSSFeatures.wsp
REM ECHO .
REM ECHO .
REM ECHO -------------------------------------------------------------------------------------------
REM ECHO Verify that the correct solutions have been completly retracted and then press ENTER
REM ECHO -------------------------------------------------------------------------------------------
REM ECHO .
pause
ECHO ON
stsadm -o upgradesolution -name %SOLUTION_NAME% -filename %SOLUTION_NAME% -immediate -allowgacdeployment
ECHO OFF
ECHO .
ECHO .
ECHO -------------------------------------------------------------------------------------------
ECHO Solution deployment complete, press ENTER to activate features, if any
ECHO -------------------------------------------------------------------------------------------
pause
ECHO ON
REM ----- Active features -----
stsadm -o activatefeature -name CustomResources -url %SITE_URL% -force
pause
Remove.cmd
ECHO OFF
SET SITE_URL="http://mossdevelopment.richfinn.net"
SET SOLUTION_NAME=MOSSFeatures.wsp
REM ----------- Remove the features ------------------
stsadm -o deactivatefeature -name CustomResources -url %SITE_URL% -force
REM ----------- Retract the solution ----------------
stsadm -o retractsolution -name %SOLUTION_NAME% -immediate -url %SITE_URL%
pause
REM ----------- Execute Time Jobs --------------------
REM stsadm execadmsvcjobs
REM ----------- Delete the solution -----------------
stsadm -o deletesolution -name %SOLUTION_NAME%
pause
*** IMPORTANT ***
Finally, here's a tricky part. You need to manually add your assembly to the <assemblies> block of each web.config file in your farm, or the code will bomb. Should track in the schema like this:
<configuration>
<system.web>
<compilation batch="false" debug="false">
<assemblies>
<add assembly="MOSSFeatures, Version=1.0.0.0, Culture=neutral, PublicKeyToken=YOUR_PUBLIC_KEY"/>
</assemblies>
</compilation>
</system.web>
</configuration>
OK! Now that the resources are out there, we can create the custom publishing feature using the resources just like you see in the out-of-box publishing layouts feature that came with MOSS. Creating this feature is kind of a long process, but it really explains solution development in MOSS being that it has so many different components. I feel that if you can get this, you can really do anything in the platform.
When it comes to web content publishing, there's three primary things needed, a content type, a master page, and a page layout. The content type defines the different columns - or in MCMS lingo 'content placeholders' - and custom meta-data which will be used in the page layouts (content templates) you'll have in your publishing site. The master page is the frame of the page - usually the top and left sides of the page - and is the container for the page layouts. Finally, the page layouts contains the physical content placeholders where the content editors enter their text, images, links, etc...
Just like with page layout development in SharePoint Designer, the first thing we need is the content type. Content types are defined by columns, so we set the columns up first in a file at the root FEATURES\CustomPublishingLayouts called CustomPublishingColumns.xml. I want to keep this example simple and clean, so I'm only going to create a single column for content. Here's the code:
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Field ID="ENTER_CUSTOM_GUID_HERE" Name="content"
Group="$Resources:CustomResources,ColumnGroup_CustomPublishingColumns_Title;"
DisplayName="$Resources:CustomResources,column_content_displayname;" Type="HTML"
Required="FALSE" Sealed="TRUE" RichText="TRUE" RichTextMode="FullHtml"></Field>
</Elements>
You can see how I'm referencing the entries in the custom resource files created previously. (The entries are already in the resource files in the downloadable code)
Now that we've got the content placeholder column created, we can create the content type. In the same directory as CustomPublishingColumns.xml, create another xml file called CustomPublishingContentType.xml with the following code:
<!-- _lcid="1033" _version="12.0.4518" _dal="1" -->
<!-- _LocalBinding -->
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentType ID="0x01010007FF3E057FA8AB4AA42FCB67B453FFC100E214EEE741181F4E9F7ACC43278EE811002D7E961EBCD042C6A40B97F107579F70"
Group="$Resources:cmscore,group_pagelayoutcontenttypes;"
Name="$Resources:CustomResources,ContentTypes_CustomPublishingContentType_Title;"
Description="$Resources:CustomResources,ContentTypes_CustomPublishingContentType_Description;">
<FieldRefs>
<FieldRef ID="ENTER_SAME_GUID_AS_COLUMN" Name="$Resources:CustomResources,column_content_displayname;"/>
</FieldRefs>
<DocumentTemplate TargetName="/_layouts/CreatePage.aspx" />
</ContentType>
</Elements>
You can see the custom content type ID here. Chris O'Brian has a nice post on deploying content types as features along with how to create a custom content type ID. Long story short, a custom content type id is created by taking the content type id of the content type you're inheriting from, adding two zeros, and then adding a new guid with no dashes or brackets.
Now, we need to create the master page. Heather Solomon has a great blank master page which I like using as a starting place for my master page development. Make sure you download it, or you can just get it from the solution I've uploaded (link's at the bottom of this post)
Add two new folders to the CustomPublishingLayouts feature - MasterPages, and PageLayouts.
In the MasterPages directory, create two files named blank.master, and blank.master.cs. Yes, that's right, code behind for the master page - something you don't get in Designer. One thing you'll notice is that you don't get the greatest visual studio support for the files, so they appear side-by-side rahter than in the nice hierarchy you see in a custom asp.net web application project.
Another thing - make sure you don't use the CodeBehind attribute in the page directive of the ASPX page. Note sure why, but it doesn't compile if it's there. Just use the Inherits attribute and you'll be fine...
blank.master (from Heather Solomon)
<%@Master language="C#" AutoEventWireup="true" Inherits="MOSSFeatures.MasterPages.blank"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages"
Assembly="Microsoft.SharePoint, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="wssuc" TagName="Welcome" src="~/_controltemplates/Welcome.ascx"
%>
<%@ Register TagPrefix="wssuc" TagName="DesignModeConsole" src="~/_controltemplates/DesignModeConsole.ascx"
%>
<HTML id="HTML1" dir="<%$Resources:wss,multipages_direction_dir_value%>" runat="server"
xmlns:o="urn:schemas-microsoft-com:office:office"
__expr-val-dir="ltr">
<HEAD id="HEAD1" runat="server">
<META Name="GENERATOR" Content="Microsoft SharePoint">
<META Name="progid" Content="SharePoint.WebPartPage.Document">
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
<META HTTP-EQUIV="Expires" content="0">
<SharePoint:RobotsMetaTag ID="RobotsMetaTag1" runat="server"/>
<Title ID=onetidTitle><asp:ContentPlaceHolder id=PlaceHolderPageTitle runat="server"/></Title>
<SharePoint:Theme ID="Theme1" runat="server"/>
<SharePoint:ScriptLink ID="ScriptLink1" language="javascript" name="core.js" Defer="true" runat="server"/>
<SharePoint:CustomJSUrl ID="CustomJSUrl1" runat="server"/>
<SharePoint:SoapDiscoveryLink ID="SoapDiscoveryLink1" runat="server"/>
<SharePoint:CssLink ID="CssLink1" runat="server"/>
<style type="text/css">
/**** Overriding styles for branding ~ located in the CSSSTyleLibrary ****/
@import url(/CSSStyleLibrary/name.css);
</style>
<asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server"/>
<SharePoint:DelegateControl ID="DelegateControl1" runat="server" ControlId="AdditionalPageHead" AllowMultipleControls="true"/>
</HEAD>
<BODY scroll="yes" onload="javascript:if (typeof(_spBodyOnLoadWrapper) != 'undefined') _spBodyOnLoadWrapper();">
<form id="Form1" runat="server" onsubmit="return _spFormOnSubmitWrapper();">
<WebPartPages:SPWebPartManager id="m" runat="Server"/>
<!--- --- --- Top Toolbar --- --- --->
<asp:ContentPlaceHolder id="PlaceHolderGlobalNavigation" runat="server">
<span id="TurnOnAccessibility" style="display:none">
<a href="#" class="ms-skip" onclick="SetIsAccessibilityFeatureEnabled(true);UpdateAccessibilityUI();return false;">
<SharePoint:EncodedLiteral ID="EncodedLiteral1" runat="server" text="<%$Resources:wss,master_turnonaccessibility%>"
EncodeMethod="HtmlEncode"/></a>
</span>
<A id="A1" href="javascript:;" onclick="javascript:this.href='#mainContent';" class="ms-skip"
AccessKey="<%$Resources:wss,maincontent_accesskey%>" runat="server">
<SharePoint:EncodedLiteral ID="EncodedLiteral2" runat="server" text="<%$Resources:wss,mainContentLink%>"
EncodeMethod="HtmlEncode"/></A>
<span id="TurnOffAccessibility" style="display:none">
<a href="#" class="ms-acclink" onclick="SetIsAccessibilityFeatureEnabled(false);UpdateAccessibilityUI();return false;">
<SharePoint:EncodedLiteral ID="EncodedLiteral3" runat="server" text="<%$Resources:wss,master_turnoffaccessibility%>"
EncodeMethod="HtmlEncode"/></a>
</span>
<!-- Global Breadcrumb -->
<asp:ContentPlaceHolder id="PlaceHolderGlobalNavigationSiteMap" runat="server">
<asp:SiteMapPath SiteMapProvider="SPSiteMapProvider" id="GlobalNavigationSiteMap"
RenderCurrentNodeAsLink="true" SkipLinkText="" NodeStyle-CssClass="ms-sitemapdirectional" runat="server"/>
</asp:ContentPlaceHolder>
<!-- Variations, Welcome Menu, My Site, My Links, Help -->
<SharePoint:DelegateControl ID="DelegateControl2" runat="server" ControlId="GlobalSiteLink0"/>
<wssuc:Welcome id="IdWelcome" runat="server" EnableViewState="false">
</wssuc:Welcome>
<SharePoint:DelegateControl ID="DelegateControl3" ControlId="GlobalSiteLink1" Scope="Farm" runat="server"/>
<SharePoint:DelegateControl ID="DelegateControl4" ControlId="GlobalSiteLink2" Scope="Farm" runat="server"/>
<a href="javascript:TopHelpButtonClick('NavBarHelpHome')" AccessKey="<%$Resources:wss,multipages_helplink_accesskey%>"
id="TopHelpLink" title="<%$Resources:wss,multipages_helplinkalt_text%>" runat="server">
<img id="Img1" align='absmiddle' border=0 src="/_layouts/images/helpicon.gif" alt="<%$Resources:wss,multipages_helplinkalt_text%>"
runat="server"></a>
</asp:ContentPlaceHolder>
<!--- --- --- End of Top Toolbar --- --- --->
<!--- --- --- Site Header --- --- --->
<SharePoint:SiteLogoImage id="onetidHeadbnnr0" LogoImageUrl="/_layouts/images/titlegraphic.gif" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderSiteName" runat="server">
<SharePoint:SPLinkButton runat="server" NavigateUrl="~site/" id="onetidProjectPropertyTitle">
<SharePoint:ProjectProperty ID="ProjectProperty1" Property="Title" runat="server" />
</SharePoint:SPLinkButton>
</asp:ContentPlaceHolder>
<!--- --- --- End of Site Header --- --- --->
<!--- --- --- Search --- --- --->
<asp:ContentPlaceHolder id="PlaceHolderSearchArea" runat="server">
<SharePoint:DelegateControl ID="DelegateControl5" runat="server" ControlId="SmallSearchInputBox"/>
</asp:ContentPlaceHolder>
<!--- --- --- End of Search --- --- --->
<!--- --- --- Horizontal Top Navigation Bar --- --- --->
<asp:ContentPlaceHolder id="PlaceHolderTopNavBar" runat="server">
<asp:ContentPlaceHolder id="PlaceHolderHorizontalNav" runat="server">
<SharePoint:AspMenu
ID="TopNavigationMenu"
Runat="server"
DataSourceID="topSiteMap"
EnableViewState="false"
AccessKey="<%$Resources:wss,navigation_accesskey%>"
Orientation="Horizontal"
StaticDisplayLevels="2"
MaximumDynamicDisplayLevels="1"
DynamicHorizontalOffset="0"
StaticPopoutImageUrl="/_layouts/images/menudark.gif"
StaticPopoutImageTextFormatString=""
DynamicHoverStyle-BackColor="#CBE3F0"
SkipLinkText=""
StaticSubMenuIndent="0"
CssClass="ms-topNavContainer">
<StaticMenuStyle/>
<StaticMenuItemStyle CssClass="ms-topnav" ItemSpacing="0px"/>
<StaticSelectedStyle CssClass="ms-topnavselected" />
<StaticHoverStyle CssClass="ms-topNavHover" />
<DynamicMenuStyle BackColor="#F2F3F4" BorderColor="#A7B4CE" BorderWidth="1px"/>
<DynamicMenuItemStyle CssClass="ms-topNavFlyOuts"/>
<DynamicHoverStyle CssClass="ms-topNavFlyOutsHover"/>
<DynamicSelectedStyle CssClass="ms-topNavFlyOutsSelected"/>
</SharePoint:AspMenu>
<SharePoint:DelegateControl ID="DelegateControl6" runat="server" ControlId="TopNavigationDataSource">
<Template_Controls>
<asp:SiteMapDataSource
ShowStartingNode="False"
SiteMapProvider="SPNavigationProvider"
id="topSiteMap"
runat="server"
StartingNodeUrl="sid:1002"/>
</Template_Controls>
</SharePoint:DelegateControl>
</asp:ContentPlaceHolder>
<!-- Site Actions Menu -->
<SharePoint:SiteActions runat="server" AccessKey="<%$Resources:wss,tb_SiteActions_AK%>" id="SiteActionsMenuMain"
PrefixHtml="<div><div>"
SuffixHtml="</div></div>"
MenuNotVisibleHtml="&nbsp;">
<CustomTemplate>
<SharePoint:FeatureMenuTemplate ID="FeatureMenuTemplate1" runat="server"
FeatureScope="Site"
Location="Microsoft.SharePoint.StandardMenu"
GroupId="SiteActions"
UseShortId="true"
>
<SharePoint:MenuItemTemplate runat="server" id="MenuItem_Create"
Text="<%$Resources:wss,viewlsts_pagetitle_create%>"
Description="<%$Resources:wss,siteactions_createdescription%>"
ImageUrl="/_layouts/images/Actionscreate.gif"
MenuGroupId="100"
Sequence="100"
UseShortId="true"
ClientOnClickNavigateUrl="~site/_layouts/create.aspx"
PermissionsString="ManageLists, ManageSubwebs"
PermissionMode="Any" />
<SharePoint:MenuItemTemplate runat="server" id="MenuItem_EditPage"
Text="<%$Resources:wss,siteactions_editpage%>"
Description="<%$Resources:wss,siteactions_editpagedescription%>"
ImageUrl="/_layouts/images/ActionsEditPage.gif"
MenuGroupId="100"
Sequence="200"
ClientOnClickNavigateUrl="javascript:MSOLayout_ChangeLayoutMode(false);"
/>
<SharePoint:MenuItemTemplate runat="server" id="MenuItem_Settings"
Text="<%$Resources:wss,settings_pagetitle%>"
Description="<%$Resources:wss,siteactions_sitesettingsdescription%>"
ImageUrl="/_layouts/images/ActionsSettings.gif"
MenuGroupId="100"
Sequence="300"
UseShortId="true"
ClientOnClickNavigateUrl="~site/_layouts/settings.aspx"
PermissionsString="EnumeratePermissions,ManageWeb,ManageSubwebs,
AddAndCustomizePages,ApplyThemeAndBorder,ManageAlerts,ManageLists,ViewUsageData"
PermissionMode="Any" />
</SharePoint:FeatureMenuTemplate>
</CustomTemplate>
</SharePoint:SiteActions>
</asp:ContentPlaceHolder>
<!--- --- --- End of Horizontal Top Navigation Bar --- --- --->
<!--- --- --- Edit Consoles --- --- --->
<asp:ContentPlaceHolder ID="WSSDesignConsole" runat="server">
<wssuc:DesignModeConsole id="IdDesignModeConsole" runat="server"/>
</asp:ContentPlaceHolder>
<asp:ContentPlaceHolder ID="SPNavigation" runat="server">
<SharePoint:DelegateControl ID="DelegateControl7" runat="server" ControlId="PublishingConsole"
PrefixHtml="<tr><td colspan="4" id="mpdmconsole" class="ms-consolemptablerow">"
SuffixHtml="</td></tr>">
</SharePoint:DelegateControl>
</asp:ContentPlaceHolder>
<!--- --- --- End of Edit Consoles --- --- --->
<!--- --- --- Page Header --- --- --->
<asp:ContentPlaceHolder id="PlaceHolderPageImage" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderTitleLeftBorder" runat="server"/>
<!--- Breadcrumb --->
<asp:ContentPlaceHolder id="PlaceHolderTitleBreadcrumb" runat="server">
<asp:SiteMapPath SiteMapProvider="SPContentMapProvider" id="ContentMap" SkipLinkText=""
NodeStyle-CssClass="ms-sitemapdirectional" runat="server"/>
</asp:ContentPlaceHolder>
<!--- Page Title - leave wrapped in table cell in order to show on sub pages and hide on home page -->
<table>
<tr>
<td height=100% valign=top ID=onetidPageTitle class="ms-pagetitle">
<h2 class="ms-pagetitle">
<asp:ContentPlaceHolder id="PlaceHolderPageTitleInTitleArea" runat="server" />
</h2>
</td>
</tr>
</table>
<!-- Mini Console - supplementary buttons for Site Map -->
<asp:ContentPlaceHolder id="PlaceHolderMiniConsole" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderTitleRightMargin" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderTitleAreaSeparator" runat="server"/>
<!--- --- --- End of Page Header --- --- --->
<!--- --- --- Left Navigation Bar --- --- --->
<asp:ContentPlaceHolder id="PlaceHolderLeftNavBarDataSource" runat="server" />
<!-- Calendar -->
<asp:ContentPlaceHolder id="PlaceHolderCalendarNavigator" runat="server" />
<asp:ContentPlaceHolder id="PlaceHolderLeftNavBarTop" runat="server"/>
<!-- Quick Launch -->
<asp:ContentPlaceHolder id="PlaceHolderLeftNavBar" runat="server">
<!-- View All Site Content -->
<label class="ms-hidden"><SharePoint:EncodedLiteral ID="EncodedLiteral4" runat="server"
text="<%$Resources:wss,quiklnch_pagetitle%>" EncodeMethod="HtmlEncode"/></label>
<Sharepoint:SPSecurityTrimmedControl ID="SPSecurityTrimmedControl1" runat="server" PermissionsString="ViewFormPages">
<SharePoint:SPLinkButton id="idNavLinkViewAll" runat="server" NavigateUrl="~site/_layouts/viewlsts.aspx"
Text="<%$Resources:wss,quiklnch_allcontent%>" AccessKey="<%$Resources:wss,quiklnch_allcontent_AK%>"/></div>
</SharePoint:SPSecurityTrimmedControl>
<!-- Quick Launch Items -->
<Sharepoint:SPNavigationManager
id="QuickLaunchNavigationManager"
runat="server"
QuickLaunchControlId="QuickLaunchMenu"
ContainedControl="QuickLaunch"
EnableViewState="false">
<SharePoint:DelegateControl ID="DelegateControl8" runat="server"
ControlId="QuickLaunchDataSource">
<Template_Controls>
<asp:SiteMapDataSource
SiteMapProvider="SPNavigationProvider"
ShowStartingNode="False"
id="QuickLaunchSiteMap"
StartingNodeUrl="sid:1025"
runat="server"
/>
</Template_Controls>
</SharePoint:DelegateControl>
<SharePoint:AspMenu
id="QuickLaunchMenu"
DataSourceId="QuickLaunchSiteMap"
runat="server"
Orientation="Vertical"
StaticDisplayLevels="2"
ItemWrap="true"
MaximumDynamicDisplayLevels="0"
StaticSubMenuIndent="0"
SkipLinkText=""
>
<LevelMenuItemStyles>
<asp:MenuItemStyle CssClass="ms-navheader"/>
<asp:MenuItemStyle CssClass="ms-navitem"/>
</LevelMenuItemStyles>
<LevelSubMenuStyles>
<asp:SubMenuStyle CssClass="ms-navSubMenu1"/>
<asp:SubMenuStyle CssClass="ms-navSubMenu2"/>
</LevelSubMenuStyles>
<LevelSelectedStyles>
<asp:MenuItemStyle CssClass="ms-selectednavheader"/>
<asp:MenuItemStyle CssClass="ms-selectednav"/>
</LevelSelectedStyles>
</SharePoint:AspMenu>
</Sharepoint:SPNavigationManager>
<!--- Tree View --->
<Sharepoint:SPNavigationManager
id="TreeViewNavigationManager"
runat="server"
ContainedControl="TreeView">
<SharePoint:SPLinkButton runat="server" NavigateUrl="~site/_layouts/viewlsts.aspx" id="idNavLinkSiteHierarchy"
Text="<%$Resources:wss,treeview_header%>" AccessKey="<%$Resources:wss,quiklnch_allcontent_AK%>"/>
<SharePoint:SPHierarchyDataSourceControl
runat="server"
id="TreeViewDataSource"
RootContextObject="Web"
IncludeDiscussionFolders="true"
/>
<SharePoint:SPRememberScroll runat="server" id="TreeViewRememberScroll"
onscroll="javascript:_spRecordScrollPositions(this);" Style="overflow: auto;height: 400px;width: 150px; ">
<Sharepoint:SPTreeView
id="WebTreeView"
runat="server"
ShowLines="false"
DataSourceId="TreeViewDataSource"
ExpandDepth="0"
SelectedNodeStyle-CssClass="ms-tvselected"
NodeStyle-CssClass="ms-navitem"
NodeStyle-HorizontalPadding="2"
SkipLinkText=""
NodeIndent="12"
ExpandImageUrl="/_layouts/images/tvplus.gif"
CollapseImageUrl="/_layouts/images/tvminus.gif"
NoExpandImageUrl="/_layouts/images/tvblank.gif"
>
</Sharepoint:SPTreeView>
</Sharepoint:SPRememberScroll>
</Sharepoint:SPNavigationManager>
<!-- Recycle Bin -->
<SharePoint:SPLinkButton runat="server" NavigateUrl="~site/_layouts/recyclebin.aspx"
id="idNavLinkRecycleBin" ImageUrl="/_layouts/images/recycbin.gif"
Text="<%$Resources:wss,StsDefault_RecycleBin%>" PermissionsString="DeleteListItems"/>
</asp:ContentPlaceHolder>
<!--- --- --- End of Left Navigation Bar --- --- --->
<asp:ContentPlaceHolder id="PlaceHolderLeftActions" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderNavSpacer" runat="server"><IMG SRC="/_layouts/images/blank.gif"
width=138 height=1 alt=""></asp:ContentPlaceholder>
<asp:ContentPlaceHolder id="PlaceHolderLeftNavBarBorder" runat="server"></asp:ContentPlaceHolder>
<asp:ContentPlaceHolder id="PlaceHolderBodyLeftBorder" runat="server"/>
<!--- --- --- Page Content --- --- --->
<PlaceHolder id="MSO_ContentDiv" runat="server">
<table id="MSO_ContentTable" width=100% height="100%" border="0" cellspacing="0" cellpadding="0"
class="ms-propertysheet">
<tr>
<td class='ms-bodyareaframe' valign="top" height="100%">
<A name="mainContent"></A>
<asp:ContentPlaceHolder id="PlaceHolderPageDescription" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderMain" runat="server">
</asp:ContentPlaceHolder>
</td>
</tr>
</table>
</PlaceHolder>
<!--- --- --- End of Page Content --- --- --->
<asp:ContentPlaceHolder id="PlaceHolderBodyRightMargin" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderFormDigest" runat="server">
<SharePoint:FormDigest ID="FormDigest1" runat=server/>
</asp:ContentPlaceHolder>
<input type="text" name="__spDummyText1" style="display:none;" size=1/>
<input type="text" name="__spDummyText2" style="display:none;" size=1/>
</form>
<asp:ContentPlaceHolder id="PlaceHolderUtilityContent" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderBodyAreaClass" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderTitleAreaClass" runat="server"/>
<!--- --- --- Hidden Placeholders --- --- --->
<asp:Panel ID="Panel1" visible="false" runat="server">
<!-- Place unused Content Placeholders here -->
</asp:Panel>
<!--- --- --- End of Hidden Placeholders --- --- --->
</BODY>
</HTML>
blank.master.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace MOSSFeatures.MasterPages
{
public class blank : System.Web.UI.MasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
Now for the Page Layout, create two files in PageLayouts named customPageLayout.aspx and customPageLayout.aspx.cs
customPageLayout.aspx
<%@ Page Language="C#" Inherits="MOSSFeatures.PageLayouts.customPageLayout" meta:progid="SharePoint.WebPartPage.Document" %>
<%@ Register Tagprefix="SharePointWebControls" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint,
Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint,
Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="PublishingWebControls" Namespace="Microsoft.SharePoint.Publishing.WebControls"
Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="PublishingNavigation" Namespace="Microsoft.SharePoint.Publishing.Navigation"
Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="radE" Namespace="Telerik.SharePoint.FieldEditor"
Assembly="RadEditorSharePoint,Version=4.1.0.0,Culture=neutral,PublicKeyToken=1f131a624888eeed"%>
<asp:Content ContentPlaceholderID="PlaceHolderPageTitle" runat="server">
<SharePointWebControls:FieldValue id="PageTitle" FieldName="Title" runat="server"/>
</asp:Content>
<asp:Content ContentPlaceholderID="PlaceHolderMain" runat="server">
<br/>
<radE:RadHtmlField runat="server" id="content" FieldName="content"></radE:RadHtmlField>
</asp:Content>
If you're not using the Telerik editor, you can use the out-of-box rich-text editor, but the Telerik editor is free - use it! I'm also assuming you've got it installed with this solution...
customPageLayout.aspx.cs
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint.Publishing;
using System.Web.UI.WebControls;
namespace MOSSFeatures.PageLayouts
{
public class customPageLayout : PublishingLayoutPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
Now that we've got the master page and page layout, we need to provision it to the master page gallery on the server when the feature is activated. This is accomplished by using a provisioning module.
In the same directory as the content type and column definition files, create another xml file named ProvisionedFiles.xml
ProvisionedFiles.xml
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="CustomPageLayouts" RootWebOnly="TRUE" Path="PageLayouts" Url="_catalogs/masterpage">
<File Url="customPageLayout.aspx" Type="GhostableInLibrary" IgnoreIfAlreadyExists="false">
<Property Name="Title" Value="$Resources:CustomResources,PageLayouts_handbookHome_Title;"/>
<Property Name="ContentType" Value="$Resources:cmscore,contenttype_pagelayout_name;"/>
<Property Name="PublishingAssociatedContentType"
Value=";#$Resources:CustomResources,ContentTypes_CustomPublishingContentType_Title;;
#0x01010007FF3E057FA8AB4AA42FCB67B453FFC100E214EEE741181F4E9F7ACC43278EE811002D7E961EBCD042C6A40B97F107579F70;#"/>
</File>
</Module>
<Module Name="CustomMasterPages" Url="_catalogs/masterpage" Path="MasterPages" RootWebOnly="TRUE">
<File Url="blank.master" Type="GhostableInLibrary">
<Property Name="ContentType" Value="$Resources:cmscore,contenttype_masterpage_name;" />
<Property Name="PublishingPreviewImage"
Value="~SiteCollection/_catalogs/masterpage/$Resources:core,Culture;/Preview Images/BlueBand.png,
~SiteCollection/_catalogs/masterpage/$Resources:core,Culture;/Preview Images/BlueBand.png" />
<Property Name="MasterPageDescription" Value="a blank master page used for testing purposes" />
</File>
</Module>
</Elements>
So - that's it for the basic elements needed for the custom publishing items. Now we just need the Feature.xml file for the publishing items. Create it in the root of the feature next to the content type and column definition files.
Feature.xml
<Feature xmlns="http://schemas.microsoft.com/sharepoint/" Title="Custom Publishing Feature"
Scope="Site" Id="ENTER_NEW_GUID"
AlwaysForceInstall="true" ActivateOnDefault="false" Version="1.0.0.0">
<ElementManifests>
<ElementManifest Location="CustomPublishingColumns.xml"/>
<ElementManifest Location="CustomPublishingContentType.xml"/>
<ElementManifest Location="ProvisionedFiles.xml"/>
</ElementManifests>
</Feature>
Now, we need to update the ddf, manifest, and Deploy/Update/Remove cmd files for the solution deployment
The ddf file should now look like this:
.OPTION EXPLICIT ;generate errors
.Set CabinetNameTemplate=MOSSFeatures.wsp
.Set DiskDirectoryTemplate=CDROM ;All cabinets go into a single directory
.Set CompressionType=MSZIP ;** All files are compressed in cabinet files
.Set UniqueFiles="ON"
.Set Cabinet=on
.Set DiskDirectory1=
;Here's the way it works...
;FileToCopyFromInProject NewFileInCABFile
;manifest file
manifest.xml manifest.xml
;assemblies
bin\debug\MOSSFeatures.dll MOSSFeatures.dll
;features
;FeatureName\feature.xml FeatureName\feature.xml
;FeatureName\elements.xml FeatureName\elements.xml
;CustomResources
TEMPLATE\FEATURES\CustomResources\Feature.xml CustomResources\Feature.xml
TEMPLATE\FEATURES\CustomResources\CustomResources.en-US.resx CustomResources\CustomResources.en-US.resx
TEMPLATE\FEATURES\CustomResources\CustomResources.resx CustomResources\CustomResources.resx
TEMPLATE\FEATURES\CustomResources\CustomResources.en-US.resx RESOURCES\CustomResources.en-US.resx
TEMPLATE\FEATURES\CustomResources\CustomResources.resx RESOURCES\CustomResources.resx
;CustomPublishingLayouts
TEMPLATE\FEATURES\CustomPublishingLayouts\Feature.xml CustomPublishingLayouts\Feature.xml
TEMPLATE\FEATURES\CustomPublishingLayouts\ProvisionedFiles.xml CustomPublishingLayouts\ProvisionedFiles.xml
TEMPLATE\FEATURES\CustomPublishingLayouts\CustomPublishingColumns.xml CustomPublishingLayouts\CustomPublishingColumns.xml
TEMPLATE\FEATURES\CustomPublishingLayouts\CustomPublishingContentType.xml CustomPublishingLayouts\CustomPublishingContentType.xml
;Publishing Layouts Page Layouts
TEMPLATE\FEATURES\CustomPublishingLayouts\PageLayouts\customPageLayout.aspx
FEATURES\CustomPublishingLayouts\PageLayouts\customPageLayout.aspx
;Publishing Layouts Master Pages
TEMPLATE\FEATURES\CustomPublishingLayouts\MasterPages\blank.master FEATURES\CustomPublishingLayouts\MasterPages\blank.master
Here's the updated manifest.xml file:
<?xml version="1.0" encoding="utf-8" ?>
<Solution SolutionId="ENTER_SOLUTION_ID_GUID_HERE" xmlns="http://schemas.microsoft.com/sharepoint/" ResetWebServer="TRUE">
<Assemblies>
<Assembly DeploymentTarget="GlobalAssemblyCache" Location="MOSSFeatures.dll">
<SafeControls>
<SafeControl Safe="True" Assembly="MOSSFeatures, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=ENTER_YOUR_PUBLIC_KEY_HERE" Namespace="MOSSFeatures" TypeName="*"/>
</SafeControls>
</Assembly>
</Assemblies>
<Resources>
<Resource Location="CustomResources\CustomResources.en-US.resx"/>
<Resource Location="CustomResources\CustomResources.resx"/>
</Resources>
<FeatureManifests>
<FeatureManifest Location="CustomResources\Feature.xml"/>
<FeatureManifest Location="CustomPublishingLayouts\Feature.xml"/>
</FeatureManifests>
<TemplateFiles>
<!-- Page Layouts-->
<TemplateFile Location="FEATURES\CustomPublishingLayouts\PageLayouts\customPageLayout.aspx"/>
<!-- Master Pages -->
<TemplateFile Location="FEATURES\CustomPublishingLayouts\MasterPages\blank.master"/>
</TemplateFiles>
<RootFiles>
<RootFile Location="Resources\CustomResources.en-US.resx"/>
<RootFile Location="Resources\CustomResources.resx"/>
</RootFiles>
</Solution>
Deploy.cmd
@ECHO OFF
SET SITE_URL="http://mossdevelopment.richfinn.net"
SET SOLUTION_NAME=MOSSFeatures.wsp
REM ----- Deactivate the feature(s) -----
stsadm -o deactivatefeature -name CustomResources -url %SITE_URL% -force
stsadm -o deactivatefeature -name CustomPublishingLayouts -url %SITE_URL% -force
REM ----- Remove the features and solution -----
stsadm -o retractsolution -name %SOLUTION_NAME% -immediate -url %SITE_URL%
ECHO .
ECHO .
ECHO -------------------------------------------------------------------------------------------
ECHO Verify that the correct solutions have been completly retracted and then press ENTER
ECHO -------------------------------------------------------------------------------------------
ECHO .
pause
ECHO ON
REM ----- Delete the solution -----
stsadm -o deletesolution -name %SOLUTION_NAME%
REM ----- Add and depoly the solution -----
stsadm -o addsolution -filename %SOLUTION_NAME%
stsadm -o deploysolution -n %SOLUTION_NAME% -immediate -allowgacdeployment -allowCasPolicies -url %SITE_URL%
ECHO OFF
ECHO .
ECHO .
ECHO -------------------------------------------------------------------------------------------
ECHO Solution deployment complete, press ENTER to activate features, if any
ECHO -------------------------------------------------------------------------------------------
pause
ECHO ON
REM ----- Active features -----
stsadm -o activatefeature -name CustomResources -url %SITE_URL% -force
stsadm -o activatefeature -name CustomPublishingLayouts -url %SITE_URL% -force
REM ----- Remove the following comment if features are present
REM pause
Update.cmd
cls
ECHO OFF
SET SITE_URL="http://mossdevelopment.richfinn.net"
SET SOLUTION_NAME=MOSSFeatures.wsp
REM ECHO .
REM ECHO .
REM ECHO -------------------------------------------------------------------------------------------
REM ECHO Verify that the correct solutions have been completly retracted and then press ENTER
REM ECHO -------------------------------------------------------------------------------------------
REM ECHO .
pause
ECHO ON
stsadm -o upgradesolution -name %SOLUTION_NAME% -filename %SOLUTION_NAME% -immediate -allowgacdeployment
ECHO OFF
ECHO .
ECHO .
ECHO -------------------------------------------------------------------------------------------
ECHO Solution deployment complete, press ENTER to activate features, if any
ECHO -------------------------------------------------------------------------------------------
pause
ECHO ON
REM ----- Active features -----
stsadm -o activatefeature -name CustomResources -url %SITE_URL% -force
stsadm -o activatefeature -name CustomPublishingLayouts -url %SITE_URL% -force
pause
Remove.cmd
ECHO OFF
SET SITE_URL="http://mossdevelopment.richfinn.net"
SET SOLUTION_NAME=MOSSFeatures.wsp
REM ----------- Remove the features ------------------
stsadm -o deactivatefeature -name CustomResources -url %SITE_URL% -force
stsadm -o deactivatefeature -name CustomPublishingLayouts -url %SITE_URL% -force
REM ----------- Retract the solution ----------------
stsadm -o retractsolution -name %SOLUTION_NAME% -immediate -url %SITE_URL%
pause
REM ----------- Execute Time Jobs --------------------
REM stsadm execadmsvcjobs
REM ----------- Delete the solution -----------------
stsadm -o deletesolution -name %SOLUTION_NAME%
pause
Now, deploying the solution will provision the new master page, page layout, and content type. Pages can now be created using the new layouts, as well. When you need to update the page layouts, just run the Update.cmd file, and the solution will be updated with the changes.
This was a fairly basic solution for deploying custom publishing items, but you can see how powerful and extensible it is, and leveraging resource files is pretty cool, too. Next I hope to show how you can create a custom site definition to provision sites using the custom page layouts rather than the out of box page layout.
Here's the source project for this solution: MOSSFeatures_CustomPublishingFeature.zip