Script to provision SharePoint 2010 publishing pages

In this post, I’d like to share a method of provisioning many publishing pages automatically using a PowerShell script.
Page Content

 

In this post, I’d like to share a method of provisioning many publishing pages automatically using a PowerShell script.

An example of when this is handy, is when you need to migrate old company press releases to an archive section. Another bulk provisioning scenario may be when you need to provision a site structure along with pages so end users populate the content on pages when they’re ready. You could do the same with a SharePoint 2010 solution package and deploy your pages as modules. In this example, however, we’ll just be using a script, so no deployment or down time required on the server while our provisioning happens.

I’ll assume you’re running afully configured development environment with SharePoint 2010 publishing infrastructure enabled.

I will be using http://www.contoso.com as my test URL, so if you’re using another host name – replace the respective variables in the script.

Open SharePoint 2010 Management Shell from the Start menu of your development server and execute the following script which provisions one publishing page to the root of your site:

01 $SiteUrl = "http://www.contoso.com"
02 $pageFileName = "TestPage.aspx"
03 Add-PSSnapin "Microsoft.SharePoint.Powershell"
04
05 $SPSite = Get-SPSite | Where-Object {$_.Url -eq $SiteUrl}
06   if($SPSite -ne $null)
07   {
08   $RootWeb = $SPSite.RootWeb
09   $pubWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($RootWeb)
10
11   $pageLayout = $pubWeb.GetAvailablePageLayouts() | Where-Object {$_.Title -eq "Blank Web Part page"}
12
13   $page = $pubWeb.GetPublishingPages().Add($pageFileName, $pageLayout)
14
15   $page.Title = $PressReleaseFileName
16   $pageItem = $page.ListItem
17   $pageItem["Comments"]="New page description"
18   $pageItem["PublishingContactName"]="Brad"
19   $pageItem["PublishingContactEmail"]="brads@contoso.com"
20   $page.Update()
21
22   $page=$pubWeb.GetPublishingPages() | Where-Object {$_.Name -eq $pageFileName}
23   $page.CheckOut()
24   $pageItem = $page.ListItem
25   $pageItem["Comments"]="Changed description"
26   $page.Update()
27
28   $page.CheckIn("Checked in by PowerShell script")
29   $page.listItem.File.Publish("Published by PowerShell script")
30   }
31 $SPSite.Dispose()

We start with defining variables and loading a PowerShell snap in.

Next, we get a hold of the current site and create an instance of a publishing site object to work with. We retrieve the Blank Web Part page page layout to create a new page with. Once created, we make changes to some of the page’s properties such as contact name of the creator, etc.

Next, we look at how you can get a hold of the existing page to make changes to it. In our case we connect to the same page instance, which looks a bit redundant.

Advertisements

Adding Items to the User/Welcome Menu in SharePoint 2010

Adding a new menu item to the SharePoint welcome/user menu is fairly straight forward.
Page Content

 

Adding a new menu item to the SharePoint welcome/user menu is fairly straight forward.

Thanks to Sohels blog for pointing me in the right direction.

First create a new empty SharePoint project in Visual Studio 2010, call it “MenuItemProject”.

2011-08-30-AddingItems-01.jpg

Deploy as a farm solution and click finish.

2011-08-30-AddingItems-02.jpg

Add a feature to the project. In the solutions explorer box, right click on the feature node and add feature.

2011-08-30-AddingItems-03.jpg

Also in the solution explorer, right click the top menuItemProject node and add > new item.

2011-08-30-AddingItems-04.jpg

Add an empty Element called “MenuAdditionElement”.

2011-08-30-AddingItems-05.jpg

Once added, open the menuAdditionElement > Elements.xml file.

2011-08-30-AddingItems-06.jpg

Replace the Elements.xml code with the following:

01 <?xml version="1.0" encoding="utf-8"?>
03    <CustomAction
04    Id="myCustomAction"
05    GroupId="PersonalActions"
06    Location="Microsoft.SharePoint.StandardMenu"
07    Sequence="1000"
08    Title="Google"
09    Description="Search away">
10      <UrlAction Url="http://www.google.com"/>
11    </CustomAction>
12 </Elements>

Save the Elements.xml file and then in the solutions explorer double click on the feature1.feature node.

2011-08-30-AddingItems-07.jpg

Ensure the MenuAddition Element is included in the items in the feature.

2011-08-30-AddingItems-08.jpg

Deploy the solution to your SharePoint server.

Once deployed, refresh your site home page and the new menu item should be visible.

2011-08-30-AddingItems-09.jpg

Using minified CSS and JavaScript files everywhere in SharePoint 2010

Find out how to leverage the extensibility capabilities of the SharePoint 2010 platform and support using minified CSS and JavaScript files everywhere.
Page Content

2011-11-27-UsingMinifiedCSS-01.jpg

In my previous post I showed you how you can automate minifying JavaScript and CSS files in Visual Studio 2010. As I also mentioned, SharePoint has limited support for using minified files. But just because something is not available out of the box doesn’t mean it’s not possible. Find out how to leverage the extensibility capabilities of the SharePoint 2010 platform and support using minified CSS and JavaScript files everywhere.

Minified files and the ScriptLink control

Using minified files is a great and easy technique for optimizing your SharePoint solution for performance. Out-of-the-box SharePoint 2010 provides the ScriptLink control which allows you to automatically switch between raw and minified versions of your files. However, it has two flaws. First of all it supports only JavaScript files and just as you can minify JavaScript files, you can also minify your CSS files. Another thing that you have to keep in mind, if you want to use the standard ScriptLink control with minified files, is that it works only for JavaScript files deployed to the LAYOUTS folder on the file system.

Using minified files everywhere

If you want to use minified CSS and JavaScript files everywhere you will need a solution other than the standard ScriptLink control. One solution that you could consider is creating a custom control that you would add to your Master Page and which would automatically switch between the raw and minified version of the asset file depending on the mode in which the Web Application is working. Such control could look as follows:

001 using System; 
002 using System.IO; 
003 using System.Web; 
004 using System.Web.UI; 
005 using Microsoft.SharePoint.Utilities; 
006   
007 namespace Mavention.SharePoint.Controls { 
008     public enum AssetType { 
009         CSS, 
010         JavaScript, 
011         Other 
012    
013   
014     public class AssetLinkControl : Control { 
015         public string Href { get; set; } 
016         public bool WithDebug { get; set; } 
017   
018         protected override void Render(HtmlTextWriter writer) { 
019             if (!String.IsNullOrEmpty(Href)) { 
020                 AssetType assetType = AssetType.Other; 
021                 string href = GetAssetUrl(Href, WithDebug, out assetType); 
022   
023                 switch (assetType) { 
024                     case AssetType.CSS: 
025                         RenderCssLink(href, writer); 
026                         break
027                     case AssetType.JavaScript: 
028                         RenderJsLink(href, writer); 
029                         break
030                
031            
032        
033   
034         public static string GetAssetUrl(string url, bool withDebug, out AssetType assetType) { 
035             string assetUrl = url; 
036             assetType = AssetType.Other; 
037   
038             if (!String.IsNullOrEmpty(assetUrl)) { 
039                 assetUrl = SPUtility.GetServerRelativeUrlFromPrefixedUrl(assetUrl); 
040                 assetType = GetAssetType(assetUrl); 
041   
042                 if (withDebug && HttpContext.Current.IsDebuggingEnabled) { 
043                     assetUrl = GetDebugUrl(assetUrl, assetType); 
044                
045            
046   
047             return assetUrl; 
048        
049   
050         private static AssetType GetAssetType(string url) { 
051             AssetType assetType = AssetType.Other; 
052   
053             if (!String.IsNullOrEmpty(url)) { 
054                 string extension = Path.GetExtension(url); 
055                 switch (extension) { 
056                     case ".css"
057                         assetType = AssetType.CSS; 
058                         break
059                     case ".js"
060                         assetType = AssetType.JavaScript; 
061                         break
062                
063            
064   
065             return assetType; 
066        
067   
068         private static string GetDebugUrl(string url, AssetType assetType) { 
069             string debugUrl = url; 
070   
071             if (!String.IsNullOrEmpty(debugUrl)) { 
072                 string extension = Path.GetExtension(debugUrl); 
073                 switch (assetType) { 
074                     case AssetType.CSS: 
075                         extension = ".css"
076                         break
077                     case AssetType.JavaScript: 
078                         extension = ".js"
079                         break
080                
081   
082                 debugUrl = String.Concat(debugUrl.Replace(extension, String.Format(".debug{0}", extension))); 
083            
084   
085             return debugUrl; 
086        
087   
088         private void RenderJsLink(string href, HtmlTextWriter writer) { 
089             writer.AddAttribute(HtmlTextWriterAttribute.Src, href); 
090             writer.RenderBeginTag(HtmlTextWriterTag.Script); 
091             writer.RenderEndTag(); 
092        
093   
094         private void RenderCssLink(string href, HtmlTextWriter writer) { 
095             writer.AddAttribute(HtmlTextWriterAttribute.Rel, "stylesheet"); 
096             writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/css"); 
097             writer.AddAttribute(HtmlTextWriterAttribute.Href, href); 
098             writer.RenderBeginTag(HtmlTextWriterTag.Link); 
099             writer.RenderEndTag(); 
100        
101    
102 }

How it works

We start with retrieving the URL of the asset (line 21). To provide flexibility when defining URLs we first translate the URL to a server-relative URL by parsing tokens if applicable (line 39). Next we get the type of the asset that we are working with (line 40). While we could create two separate controls: one for CSS files and one for JavaScript files, in this sample we use one control for both types. The last step is to check whether the asset link points to an asset that is provided in two versions (raw and minified) and if the Web Application is in debug mode (line 42).

Using the WithDebug property allows us to use the AssetLinkControl both for referencing files that have already been minified (like the jQuery library) as well as files that we have created ourselves and that are available both as raw and minified files.

If the Web Application is in debug mode we convert the asset link to point to the debug version of the asset (line 43). Finally we return the URL and render it depending on the asset type (lines 23-30).

Using the AssetLinkControl

The AssetLinkControl can be used the best for declaratively registering assets in the Master Page and Page Layouts. To register an asset, all you have to do is to include the following snippet:

1 <Mavention:AssetLinkControl Href="~SiteCollection/Style Library/mavention/css/mavention.css" WithDebug="true" runat="server" />

Depending on the mode in which your Web Application is running, this control will either point to the minified or the debug version of the CSS file.

What the AssetLinkControl is not

Although the AssetLinkControl provides you with great flexibility, it is by no means a replacement for dynamically registering JavaScript or CSS files. The standard SharePoint 2010 CssRegistration and ScriptLink controls provide much more flexibility allowing you to determine how and when your assets should be loaded. Whenever you need to register a CSS or a JavaScript file on runtime you should use those standard controls instead. The great thing about how the AssetLinkControl is built, is that you can use its GetAssetUrl method to build a URL to your asset and then pass it to the standard SharePoint controls. This approach allows you to extend the standard functionality of the SharePoint platform with additional flexibility and optimization for performance.

CAML Query and SharePoint EcmaScript

In this post I will explain, how to deal with CAML in EcmaScript, and how to form the CAML-query to get selected items.
Page Content

 

Extending list functionality by using SharePoint ribbon buttons is a wide-spread practice. At work, we are using this scenario extensively.

And I’ve found out that one of the first problems, which every developer faces when working with such tasks, is the neccessity of fetching information of currently selected list items. Partially, it can be satisfied by methods of SP.ListOperation.Selection SharePoint EcmaScript class. Unfortunately, the getSelectedItems() method, which pretends to fit our needs, provides only IDs (not even Guids), and FSObjType values for the selected items.

And if you want more data – you will need to deal with SharePoint Client Object Model, and produce a corresponding CAML query.

In this post I will explain, how to deal with CAML in EcmaScript, and how to form the CAML-query to get selected items.

The problem

Ok, so let’s clarify what do we need to get finally?

The logic of selected items query is: ((id==selectedId1) || (id==selectedId2) || … || (id==selectedIdN)). So, I bet your first try would look similar to this:

01 <Where>
02   <Or>
03     <Eq>
04       <FieldRefName="ID" />
05       <ValueType="Integer">1</Value>
06     </Eq>
07     <Eq>
08       <FieldRefName="ID" />
09       <ValueType="Integer">2</Value>
10     </Eq>
11     <Eq>
12       <FieldRefName="ID" />
13       <ValueType="Integer">3</Value>
14     </Eq>
15   </Or>
16 </Where>

Of course, it would not work 🙂 After googling a bit, you could then come to the following code:

01 <Where>
02   <Or>
03     <Eq>
04       <FieldRefName="ID" />
05       <ValueType="Integer">1</Value>
06     </Eq>
07     <Or>
08       <Eq>
09         <FieldRefName="ID" />
10         <ValueType="Integer">2</Value>
11       </Eq>
12       <Eq>
13         <FieldRefName="ID" />
14         <ValueType="Integer">3</Value>
15       </Eq>
16     </Or>
17   </Or>
18 </Where>

Ok, this will work, but maybe there is a better way to achieve our goal?

Yes, there is!

01 <Where>
02   <In>
03     <FieldRef Name="ID" />
04     <Values>
05       <Value Type="Integer">1</Value>
06       <Value Type="Integer">2</Value>
07       <Value Type="Integer">3</Value>
08     </Values>  
09   </In>
10 </Where>

You know, that is the catch of SharePoint, actually! You could write a huge amount of code, invent some complicated algorythms, search for workarounds, suffer from bugs, dispute with collegues for hours… And after all is done, several weeks later you suddenly stumble upon a simpliest solution, which would take less than an hour to implement!!…

SP.XmlWriter

Next our question is, “How to deal with XML in SharePoint EcmaScript?”. To start with, I’d like to introduce a little known SharePoint EcmaScript class, SP.XmlWriter, which is implemented in SP.Core.js file. It’s functionality is very similar to the functionality of server-side XmlWriter. I regret, it is the only class, provided OOTB, to deal with XML including CAML in SharePoint EcmaScript but it’s better than nothing. And if you’re creating some client-side libs, it would be a good way to improve readability of your functions to use this class.

The SP.XmlWriter contains following functions:

  • create(stringBuilder)
  • writeStartElement(tagName)
  • writeElementString(tagName, value)
  • writeEndElement()
  • writeAttributeString(localname, value)
  • writeStartAttribute(localname)
  • writeEndAttribute()
  • writeString(value)
  • writeRaw(xml)

Elementary example of using of the SP.XmlWriter:

01 var camlQuery = new SP.CamlQuery();
02 var sb = new Sys.StringBuilder();
03 var writer = SP.XmlWriter.create(sb);
04
05 writer.writeStartElement("Where");
06
07 writer.writeStartElement("Eq");
08
09 writer.writeStartElement("FieldRef");
10 writer.writeAttributeString("Name", "StatusID");
11 writer.writeEndElement();
12
13 writer.writeStartElement("Value");
14 writer.writeAttributeString("Type", "Integer");
15 writer.writeString("1");
16 writer.writeEndElement();
17
18 writer.writeEndElement();
19
20 writer.writeEndElement();
21
22 writer.close();
23
24 camlQuery.set_viewXml(sb.toString());
25
26 // Result:  
27 // <Where> 
28 // <Eq> 
29 // <FieldRef Name='StatusID' /> 
30 // <Value Type='Integer'>1</Value> 
31 // </Eq> 
32 // </Where>

The code:

Ok, now we are ready to implement the main algorithm, using SP.XmlWriter.

Let’s try:

01 function getSelectedItemsQuery() {
02     /// <summary>
03     /// This function returns string which contains CAML query for retrieving all selected list items
04     /// </summary>
05     var sb = new Sys.StringBuilder();
06     var writer = new SP.XmlWriter.create(sb);
07     writer.writeStartElement("Where");
08     writer.writeStartElement("In");
09
10     // FieldRef element
11     writer.writeStartElement("FieldRef");
12     writer.writeAttributeString("Name", "ID");
13     writer.writeEndElement();
14
15     // Values element
16     var items = SP.ListOperation.Selection.getSelectedItems();
17     var itemsCount = items.length;
18     writer.writeStartElement("Values");
19     while (items.length > 1) {
20         writer.writeStartElement("Value");
21         writer.writeAttributeString("Type", "Integer");
22         writer.writeString(items.pop().id);
23         writer.writeEndElement();
24     }
25     writer.writeEndElement(); // Values
26     writer.writeEndElement(); // In
27     writer.writeEndElement(); // Where
28     writer.close();
29     return sb.toString();
30 }

It turns out to be not so complicated after all, isn’t it? 2011-09-01-SelectedItems-03.gif

Ok, very good. And again, it’s the time for the essential question: “Is there any way to make it even better?”.

And yes!! There is!

I’m happy to introduce a very useful open source project, SharePoint EcmaScript CAML Builder. It does the same thing for client-side code, as famous Camlex does for server-side.

Rewritten using Caml Builder, the code will look as simple, as 1-2-3:

1 var camlBuilder = new CamlBuilder();
2 var caml = camlBuilder.Where().IntegerField("ID").In(itemIds).ToString();

And, by the way, you can have intellisense and documentation tooltips for it:

2011-09-01-SelectedItems-02a.png

Example of usage

Drawing a line, I’d like to provide an example of usage. The idea is to get e-mails for all selected users in a standard Contacts list, using SharePoint Client Object Model in conjunction with our new-born getSelectedItemsQuery function, from withing a ribbon button, and displaying all these e-mails in a modal dialog.

The main javascript code will look like this:

01 function PopupEmailsDialog() {
02     var context = new SP.ClientContext.get_current();
03     var web = context.get_web();
04     var list = web.get_lists().getById(SP.ListOperation.Selection.getSelectedList());
05     
06     var items = SP.ListOperation.Selection.getSelectedItems();
07     var itemIds = new Array();
08     for (var i = 0; i < items.length; i++) {
09         itemIds.push(items[i].id);
10     }
11
12     var query = new SP.CamlQuery();
13     var camlBuilder = new CamlBuilder();
14     var caml = camlBuilder.Where().IntegerField("ID").In(itemIds).ToString();
15     query.set_viewXml("<View><Query>" + caml + "</Query></View>");
16     var items = list.getItems(query, 'Include(Email)');
17     context.load(items);
18
19     context.executeQueryAsync(
20         function (sender, args) {
21             var enumerator = items.getEnumerator();
22             var emails = new Array();
23             while (enumerator.moveNext()) {
24                 var item = enumerator.get_current();
25                 emails.push(item.get_item('Email'));
26             }
27
28             var div = document.createElement('div');
29             div.innerHTML = emails.join('<br />');
30             var options =
31             {
32                 html: div,
33                 title: "Emails button test"
34             };
35
36             SP.UI.ModalDialog.showModalDialog(options);
37         },
38         function (sender, args) {
39             alert("Error! " + args.Message + "\nStack trace: " + args.StackTrace);
40         }
41         );
42 }

This script normally should be placed into a Ribbon CommandUIHandler element. And also, a link to our js should be deployed. For example, using a CustomAction with ScriptLink location approach, as it is described in Jan Tielens’ blog: Deploying and using jquery with a Sharepoint Sandboxed solution.

But for this article, I will use the open source project “SharePoint 2010 Fluent Ribbon API”, which allows us to simplify the ribbon creation. The Fluent Ribbon code should be placed in Feature Event Receiver class, and will look like this:

01 public override void FeatureActivated(SPFeatureReceiverProperties properties)
02 {
03     var web = properties.Feature.Parent as SPWeb;
04     var button = new ButtonDefinition()
05     {
06         Id = "EmailsButton",
07         Title = "Emails button",
08         Image = ImageLibrary.GetStandardImage(14, 4),
09         CommandJavaScript = "PopupEmailsDialog();",
10         CommandEnableJavaScript = "SP.ListOperation.Selection.getSelectedItems().length > 0",
11         TemplateAlias = "o1"
12     };
13     var ribbonCustomAction = new RibbonCustomAction();
14     ribbonCustomAction.AddControl(button, SPRibbonIds.ListItem.Groups.Actions.Id, 10);
15     ribbonCustomAction.Provision(properties.Definition.Id, web, ListTypes.ContactsList);
16 }
17 public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
18 {
19     var web = properties.Feature.Parent as SPWeb;
20     RibbonCustomAction.RemoveAllCustomizations(web, properties.Definition.Id);
21 }

So, we got a very pretty code with no magic strings! Cute!

In addition, I have following custom actions to add links to my js files:

02   <CustomAction
03     ScriptSrc="~SiteCollection/SiteAssets/CamlHelper.js"
04     Location="ScriptLink"
05     Sequence="10">
06   </CustomAction>
07   <CustomAction
08     ScriptSrc="~SiteCollection/SiteAssets/EmailsButton.js"
09     Location="ScriptLink"
10     Sequence="11">
11   </CustomAction>
12 </Elements>

Finally, I’ve got this screenshot:

2011-09-01-SelectedItems-01.png

Full sources for this example are included in the Fluent Ribbon Source Code (project named EmailsButton).