Patrick's SharePoint Blog

SharePoint's Booming world

Archive for May, 2010

Using PowerShell to deploy SharePoint Solutions (WSP)

Posted by Patrick Boom on May 31, 2010

The STSADM command line application worked well with previous versions of SharePoint. But the world is progressing and PowerShell will be the new administrative tool for SharePoint. In previous articles, I already showed some amazing powerful scripts that otherwise would require more lines of code. PowerShell offers some great advantages in large farms, as they can be run remotely on target machines, can be signed and therefore be controlled. Building up a repository of scripts or cmdlets to execute common tasks would be worthwhile in the long run.

First off an important thing to note here that this will only work with SharePoint 2010. There are no PowerShell snapins available for 2007, although you could create your own off course. And when run from a SharePoint 2010 Management Shell, the snapins are loaded automatically. But what if you just use the ‘normal’ console? Well, then you would have to register the snapin yourself. Fortunately, MS has already created the PowerShell script that is needed, located at:

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Config\PowerShell\Registration\SharePoint.ps1.

You could include this script in your scripts to be run first, or just include the command that registers the snapin:

Add-PSSnapin Microsoft.SharePoint.PowerShell

This article covers one of the most basic tasks one would do when administrating SharePoint: Deploy SharePoint solutions (WSP) and enable/disable features.

Working with Solutions

In the ‘old’ days (let us not forget that the stsadm is still there and we have a lot of SharePoint 2007 installations across the globe), the following stsadm command could be used to add a SharePoint solution to SharePoint:

stsadm –o addsolution –filename “D:\Deploy\MySharePointSolution.wsp

We used the following command to deploy the solution once installed to a specific web application:

stsadm –o deploysolution –name MySharePointSolution.wsp –url http://myspwebappp –allowgacdeployment –immediate

If we would upgrade an existing solution, we would use the following:

stsadm –o upgradesolution –name MySharePointSolution.wsp –filename “D:\Deploy\MySharePointSolution.wsp” -immediate

And finally, we used the following commands to retract and delete a specific solution from the web application:

stsadm –o retractsolution –name MySharePointSolution.wsp –url http://myspwebapp –immediate
stsadm –o deletesolution –name MySharePointSolution.wsp

Now, let us see how we could do above operations with PowerShell. For this, we use the following PowerShell commands:

Add-SPSolutionD:\Deploy\MySharePointSolution.wsp
Install-SPSolution –Identity MySharePointSolution.wsp –WebApplication http://myspwebapp –GACDeployment

If you would like to add the solution as sandboxed, you would use the Install-SPUserSolution command instead. To upgrade a solution, we specify which solution is to be updated and with which new solution file:

Update-SPSolution –Identity MySharePointSolution.wsp –LiteralPath “D:\Deploy\MySharePointSolution.wsp” –GacDeployment

To retract and remove a solution, we use the following commands:

Uninstall-SPSolution –Identity MySharePointSolution.wsp –WebApplication http://myspwebapp
Remove-SPSolution–Identity MySharePointSolution.wsp

Working with features

Similarly, commands exist for working with features. The stsadm equivalents:

stsadm –o activatefeature –name MyFeatureName –url http://myspwebapp
stsadm –o deactivatefeature –name MyFeatureName –url http://myspwebapp

Needless to say, there are easy equivalents in PowerShell:

Enable-SPFeature –Identity MyFeatureNameOrGuid –url http://myspwebapp
Disable-SPFeature –Identity MyFeatureNameOrGuid –url http://myspwebapp

As you can see, PowerShell will completely replace stsadm as the administrative command line tool for SharePoint. Better to start using it as the next version of SharePoint will not have a stsadm command line tool I suspect. To conclude, let us take a look at a script that enables a certain feature across all site collections and sites in a farm. As an example, I have taken the SharePoint Server Enterprise Site Features feature with ID 0806d127-06e6-447a-980e-2e90b03101b8.

Add-PSSnapin Microsoft.SharePoint.PowerShell
$WebApplications = Get-SPWebApplication

foreach ($webapp in $WebApplications) {
  $Id = $webapp.Id
  Write-Host “Processing web application $Id …”
  $sites = $webapp.Sites
  foreach ($site in $sites) {
    Write-Host Processing site $site.Id
    $webs = $site.AllWebs
    foreach ($web in $webs) {
      Write-Host Processing web $web.Title
      if ($web.Features[“0806d127-06e6-447a-980e-2e90b03101b8”] -eq $null) {
        Enable-SPFeature -Identity 0806d127-06e6-447a-980e-2e90b03101b8 -url $web.Url -Confirm:$false
      } else {
        Disable-SPFeature -Identity 0806d127-06e6-447a-980e-2e90b03101b8 -url $web.Url -Confirm:$false
      }
    }
  }
}

Please note though that above script will work for small site collections. But for larger object hierarchies, you will need to include proper memory management (SPAssignment) to ensure proper release of memory.

As always, have fun coding.

Advertisements

Posted in PowerShell, SharePoint 2010 | Tagged: , , , , , , | 63 Comments »

Adding a custom company menu tab with dynamic menu on the ribbon

Posted by Patrick Boom on May 25, 2010

The move to include the ribbon in SharePoint provides additional challenges to site designers. They have to include it somehow in their designs, as the ribbon is here to stay. One of our clients had an own navigation menu that he wanted to integrate with the ribbon. The problem is that this menu is dynamic and can change at any moment in time. And the SharePoint 2010 ribbon is static by nature. It is defined in XML files on startup.

That made me wonder whether it would be possible to dynamically add menu items to controls, tabs and groups. I then stumbled upon a blog post by a company called ICC here that described a method of dynamic flyout anchors. Another post described here by Tom Wilson described how we could change the look and feel of the ribbon to fit the internal company design.

I basically used the method described by ICC to dynamically construct my menu items on a separate tab. The source of the menu is a web service that provides the menu items in XML. An XSL transformation then creates the necessary xml definition for SharePoint, disconnecting the web service from technology specific implementations. This resulted in a combination of custom defined tabs, server side command handlers, page components and the works, meaning quite a comprehensive piece of code and a lot for this article. Although I have tried to be as complete as possible, I might have described some parts in less detail then I should. If so, feel free to leave a comment. Some stuff that will be covered in this one example:

  1. Creating Tabs and Groups on the ribbon
  2. Populating FlyoutAnchor dynamically
  3. Registering commands on the server
  4. Calling server side code using ICallbackEventHandler interface

Quite some stuff to cover here. Let us first take a look at an architectural picture on what we are trying to accomplish here:

Define the Tab, Group and Control elements.

 So, let us start coding. Open Visual Studio 2010 and create a new empty SharePoint project. Add a new empty element and call it MyCompany. Also add a mapping to the Layouts folder by right clicking the project and select Add à SharePoint Mapped Layouts folder. This will host our custom JavaScript files. Open the Elements.xml file in the MyCompany empty element and populate it with the following XML:

<?xml version=1.0encoding=utf-8?>
<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
<Control 
 
Id=AdditionalPageHead
 
Sequence=200
 
ControlClass=$SharePoint.Project.FileNameWithoutExtension$.CompanyTabLoader
 
ControlAssembly=$SharePoint.Project.AssemblyFullName$>
</Control>
<CustomAction
  Id=Boom.Ribbon.EnterpriseTabExample
  Title=Enterprise Menu
  Location=CommandUI.Ribbon>
<CommandUIExtension>
 
<CommandUIDefinitions>
   
<CommandUIDefinition Location=Ribbon.Tabs._children>
     
<Tab
        Id=Boom.Ribbon.EnterpriseTab
        Sequence=250
        Title=Enterprise Menu>
        
<Scaling Id=Boom.Ribbon.Enterprise.Scaling>
         
<MaxSize Id=Boom.Ribbon.Enterprise.Scaling.MyCompany.MaxSizeSequence=20GroupId=Boom.Ribbon.Enterprise.MyCompanySize=LargeMedium />
         
<MaxSize Id=Boom.Ribbon.Enterprise.Scaling.MyJob.MaxSizeSequence=40GroupId=Boom.Ribbon.Enterprise.MyJobSize=LargeMedium />
          
<MaxSize Id=Boom.Ribbon.Enterprise.Scaling.MyHR.MaxSizeSequence=40GroupId=Boom.Ribbon.Enterprise.MyHR” Size=LargeMedium />
         
<Scale Id=Boom.Ribbon.Enterprise.Scaling.MyCompany.MediumSmallSequence=100GroupId=Boom.Ribbon.Enterprise.MyCompanySize=MediumSmall />
         
<Scale Id=Boom.Ribbon.Enterprise.Scaling.MyJob.MediumSmallSequence=120GroupId=Boom.Ribbon.Enterprise.MyJobSize=MediumSmall />
         
<Scale Id=Boom.Ribbon.Enterprise.Scaling.MyHR.MediumSmallSequence=120GroupId=Boom.Ribbon.Enterprise.MyHRSize=MediumSmall />
        </Scaling>
       
<Groups Id=Boom.Ribbon.Enterprise.Groups>
         
<Group
            Id=Boom.Ribbon.Enterprise.MyCompany
            Title=My Company Menu
            Template=Ribbon.Templates.Flexible2
            Sequence=100>
           
<Controls Id=Boom.Ribbon.Enterprise.MyCompany.Controls>
             
<FlyoutAnchor
                Id=Boom.Ribbon.Enterprise.MyCompany.Menu

                Command=Boom.Ribbon.Enterprise.MyCompany.Menu

                Sequence=10

                Image16by16=/_layouts/$Resources:core,Language;/images/formatmap16x16.png
                Image16by16Top=-16
                Image16by16Left=-88

                Image32by32=/_layouts/$Resources:core,Language;/images/formatmap32x32.png
                Image32by32Top=-128
                Image32by32Left=-448

                LabelText=My Company Menu

                TemplateAlias=o1

                PopulateDynamically=true

                PopulateOnlyOnce=false

                PopulateQueryCommand=PopulateDymamicMenuItemsQueryCommand

                ToolTipTitle=FlyoutAnchor Dymamic

                ToolTipDescription=FlyoutAnchor with dymamic menu items />
             
</Controls>
           
</Group>
         
</Groups>
       
</Tab>
     
</CommandUIDefinition>
   
</CommandUIDefinitions>
   
<CommandUIHandlers>
     
<CommandUIHandler
        Command=Boom.Ribbon.Enterprise.MyCompany.Menu
       
CommandAction=“”
        EnabledScript=true />
     
<CommandUIHandler
        Command=DynamicButtonCommand

        CommandAction=JavaScript:alert(‘Dynamic Button ‘ + arguments[2].MenuItemId + ‘ clicked.’);

        EnabledScript=true />
    
</CommandUIHandlers>
  
</CommandUIExtension>
</CustomAction>
</Elements>
 

So, what did we just include? In this XML, we define a custom action that will add a Tab to our ribbon called Boom.Ribbon.EnterpriseTab. In there, we define some scaling on how the controls in the groups should be rendered and obviously, the groups itself. In this example, I only defined one group called MyCompany for simplicity, although I did added scaling for additional groups. For the purpose of this blog however, we keep it with one group. In the group, we define a single FlyoutAnchor control that will be the focus subject on this blog. For additional information about the Tab and Group definition, there are many articles out there that describe these. For instance, refer to the blogs by Chris O’Brien on this subject.

So let us take a closer look at the FlyoutAnchor node. The Id and Command attributes follow the naming convention (project-ribbon-tab-group) as best practice, although they can be anything you like. The following attributes are of more interest: PopulateDynamically, PopulateOnlyOnce and PopulateQueryCommand. These three attributes determine that the controls in this menu will be populated dynamically. PopulateOnlyOnce determines that this will only happen the first time it is loaded, which we set to false. The PopulateQueryCommand specifies the command that will be called to populate the control. The command has to set the properties.PopulationXml property with the exact declarative XML as we would use when not loading dynamically. We will get to that part later.

But, as one can see, the command mentioned in the PopulateQueryCommand is not registered in the CommandUIHandlers section of the XML. This is because we will do this registration on the server side. We do however register two commands that make the control enabled (Boom.Ribbon.Enterprise.MyCompany.Menu) and the command that each button will execute when clicked (DynamicButtonCommand). So, how do we now wire the commands to this definition? We will get to that when we solve another problem. The above declared tab will not show up in our interface if we deploy this solution now. That is because we have omitted the RegistrationId and RegistrationType attributes in our custom action, meaning it is not tied to any context yet. As we want the tab to appear always, we have to make sure that the tab is made visible when each page loads. We do this by inserting a custom control that will do this in the pre-render phase of the page, using the AdditionalPageHead control placeholder. And since we then have a control anyway, we can also use that to wire the commands of our tab. So, add a custom class to the project. I have not included it in its own directory, but you could. I called this class CompanyTabLoader.cs. How do we ensure this is loaded on each page? Well, that is accomplished by adding the Control element at the beginning of above XML (or end if you like).

Adding a Control to the AdditionalPageHead on each page

 If fact, this is quite simple to accomplish. The following declaration adds our control to the AdditionalPageHead section of the page:

<Control
 
Id=AdditionalPageHead
 
Sequence=200
 
ControlClass=$SharePoint.Project.FileNameWithoutExtension$.CompanyTabLoader
 
ControlAssembly=$SharePoint.Project.AssemblyFullName$>

Some nice bonus in above declaration. We use the VS 2010 placeholders for adding our complete assembly name upon deployment, so we do not have to fill that in ourselves ;-). By far, this is the easiest way to get code running on each page. As shown, this declaration tells SharePoint to include our custom control in each page by adding it to the AdditionalPageHead container. Now, we can add code to our custom control to complete the wiring of all our events.

Show the custom tab each time the page is loaded

 To make sure our tab is visible on each page we need to call some methods in the PreRender event of our control. Override the OnPreRender event of our base class and add the following code. (Make sure CompanyTabLoader.cs inhereits from System.Web.UI.WebControls.WebControl. Also add references to our project to the following assemblies: Microsoft.SharePoint.dll, System.Web.dll, Microsoft.Web.CommandUI.dll.

protected override void OnPreRender(EventArgs e)
{
  SPRibbon ribbon = Microsoft.SharePoint.WebControls.SPRibbon.GetCurrent(this.Page);

 
  if
(ribbon != null)
{
    const string initialTabId = “Boom.Ribbon.EnterpriseTab”;

    if (!ribbon.IsTabAvailable(initialTabId))
     
ribbon.MakeTabAvailable(initialTabId);
  
}
}

We first get a reference to the SPRibbon by calling the static GetCurrent method and passing the current page. We then call the MakeTabAvailable method of the SPRibbon class and pass the ID of the Tab to make available. If you do not have this method, then add the reference to the Microsoft.Web.CommandUI assembly. This will ensure our tab is shown on each page in the site collection. We can now proceed to creating our PageComponent JavaScript to handle the custom commands that we will create.

Registering and creating a PageComponent

 The page component is a JavaScript object that we will use to assign are commands to in code. This component will be the bridge between your ribbon and your commands. This component can be very confusing and complex; I know it was for me. But then again, I am no JavaScript guru. In any way, this component tends to be generic and can be extended to fit multiple needs and commands. Let me show you mine PageComponent.js in the basis. I have added this JavaScript file to the Layouts mapped folder in our solution, in its own subdirectory.

function ULS_SP() {
  if (ULS_SP.caller) {
   
ULS_SP.caller.ULSTeamName = “Windows SharePoint Services 4”;
   
ULS_SP.caller.ULSFileName = “/_layouts/Boom.DynamicMenuSample/PageComponent.js”;
  
}
}

Type.registerNamespace(‘Boom.DynamicMenuSample’);

// RibbonApp Page Component
Boom.DynamicMenuSample.PageComponent = function () {
 
ULS_SP();
 
Boom.DynamicMenuSample.PageComponent.initializeBase(this);
}

Boom.DynamicMenuSample.PageComponent.initialize = function () {
 
ULS_SP();
 
ExecuteOrDelayUntilScriptLoaded(Function.createDelegate(null, Boom.DynamicMenuSample.PageComponent.initializePageComponent), ‘SP.Ribbon.js’);
}

Boom.DynamicMenuSample.PageComponent.initializePageComponent = function () {
 
ULS_SP();
  var ribbonPageManager = SP.Ribbon.PageManager.get_instance();

  if (null !== ribbonPageManager) {
   
ribbonPageManager.addPageComponent(Boom.DynamicMenuSample.PageComponent.instance);
   
ribbonPageManager.get_focusManager().requestFocusForComponent(Boom.DynamicMenuSample.PageComponent.instance);
 
}
}

Boom.DynamicMenuSample.PageComponent.refreshRibbonStatus = function () {
 
SP.Ribbon.PageManager.get_instance().get_commandDispatcher().executeCommand(Commands.CommandIds.ApplicationStateChanged, null);
}

Boom.DynamicMenuSample.PageComponent.prototype = {
 
getFocusedCommands: function () {
   
ULS_SP();
    return [];
 
},
 
getGlobalCommands: function () {
   
ULS_SP();
    return getGlobalCommands();
 
},
  
isFocusable: function () {
   
ULS_SP();
  
return true;
 
},
  
receiveFocus: function () {
   
ULS_SP();
   
return true;
 
},
 
yieldFocus: function () {
   
ULS_SP();
    return true;
 
},
 
canHandleCommand: function (commandId) {
   
ULS_SP();
    return commandEnabled(commandId);
 
},
 
handleCommand: function (commandId, properties, sequence) {
   
ULS_SP();
    return handleCommand(commandId, properties, sequence);
 
}
}

// Register classes
Boom.DynamicMenuSample.PageComponent.registerClass(‘Boom.DynamicMenuSample.PageComponent’, CUI.Page.PageComponent);
Boom.DynamicMenuSample.PageComponent.instance = new Boom.DynamicMenuSample.PageComponent();

// Notify waiting jobs
NotifyScriptLoadedAndExecuteWaitingJobs(“/_layouts/Boom.DynamicMenuSample/PageComponent.js”);

No real specifics here. As you can see, I declare a namespace and a class (prototype) to handle various events on the ribbon. Also some constructors (initializers) are declared here. The actual wiring to our custom controls will be done in code. You can reuse this page component for other extensions to the ribbon if desired.

Registering our commands and the PageComponent

 Now we need to register our page component and our custom commands with the page. We move back to our custom control called CompanyTabLoader.cs. In the OnPreRender event, we create a new generic list of IRibbonCommand that will hold our custom commands. We add a new SPRibbonCommand to that list that wires our PopulateDynamicMenuItemsQueryCommand to a command on the ribbon.

var commands = new List<IRibbonCommand>();

// register the command at the ribbon. Include the callback to the server to generate the xml
commands.Add(new SPRibbonCommand(“PopulateDymamicMenuItemsQueryCommand”, “CreateServerMenu(”,”); properties.PopulationXML = menuXml;”));

As you can see in above code sample, we create a new SPRibbonCommand object with two arguments. The first is the name of the command to register, in our case the command mentioned in the declarative XML above. The second argument specifies the code to execute when this command is called. We have included two statements here. First we call CreateServerMenu with two empty arguments. That will be our server side method that will be wired in a few moments. Secondly, we set the properties.PopulationXML to the result of the server call that is loaded in the menuXml variable. We now need to register this command collection (with only one command in our case) with our page component. That is done using the following code:

//Register initialize function
var manager = new SPRibbonScriptManager();

var methodInfo = typeof(SPRibbonScriptManager).GetMethod(

“RegisterInitializeFunction”,

BindingFlags.Instance | BindingFlags.NonPublic);
methodInfo.Invoke(manager, new object[] {Page, “InitPageComponent”, “/_layouts/Boom.DynamicMenuSample/PageComponent.js”, false, “Boom.DynamicMenuSample.PageComponent.initialize()”});


// Register ribbon scripts
manager.RegisterGetCommandsFunction(Page, “getGlobalCommands”, commands);
manager.RegisterCommandEnabledFunction(Page, “commandEnabled”, commands);
manager.RegisterHandleCommandFunction(Page, “handleCommand”, commands);

We create an instance of the SPRibbonScriptManager class and add the initialize function of our page component. After that, we register our commands with the three events of our page component that return an instance of the event, getGlobalCommands, commandEnabled and handleCommand. The last will be called when a command is executed on the ribbon in our page component. We should now wire our server side method mentioned in the command.

Add a callback eventhandler for server side processing

 For this, we implement the ICallbackEventHandler on our class. That will add two methods called GetCallbackResult and RaiseCallbackEvent. The latter will be called by the JavaScript, the first will be processed prior to returning to the client. In our example, we just add the menu XML needed for our FlyoutAnchor in the first method. Using variables, we can pass values from one method to another. For simplicity sake, I did not. Here is the contents of our GetCallbackResult method.

/// <summary>
/// This method will return the caller with the menu. It will now return a static menu, but you can call any webservice from here or the

/// RaiseCallbackEvent event and perform transformation there.

/// </summary>

/// <returns></returns>

public string GetCallbackResult()
{
  string dynamicMenuXml =
“<Menu Id=’Boom.Ribbon.Enterprise.MyCompany.Menu.Menu’>”
  
+ “<MenuSection Id=’Boom.Ribbon.Enterprise.MyCompany.Menu.Section1′ DisplayMode=’Menu16′>”
 
+ “<Controls Id=’Boom.Ribbon.Enterprise.MyCompany.Menu.Section1.Controls’>”;

  string
buttonXML = String.Format(

    “<Button Id=’DynamicButton{0}’ “
   
+ “Command=’DynamicButtonCommand’ “
    
+ “MenuItemId='{0}’ “
   
+ “LabelText=’My Custom menu 1′ “
   
+ “ToolTipTitle=’My Custom menu 1′ “
   
+ “ToolTipDescription=’Dynamic Button’ />”, 0);

   buttonXML = buttonXML + String.Format(

     “<Button Id=’DynamicButton{0}’ “
    
+ “Command=’DynamicButtonCommand’ “
    
+ “MenuItemId='{0}’ “
     
+ “LabelText=’My Custom menu 2′ “
    
+ “ToolTipTitle=’My Custom menu 2′ “
    
+ “ToolTipDescription=’Dynamic Button’ />”, 1);

   buttonXML = buttonXML + String.Format(
     “<Button Id=’DynamicButton{0}’ “
     + “Command=’DynamicButtonCommand’ “
    
+ “MenuItemId='{0}’ “
    
+ “LabelText=’My Custom menu 3′ “
    
+ “ToolTipTitle=’My Custom menu 3′ “
    
+ “ToolTipDescription=’Dynamic Button’ />”, 2);

   buttonXML = buttonXML + String.Format(
     “<Button Id=’DynamicButton{0}’ “
    
+ “Command=’DynamicButtonCommand’ “
    
+ “MenuItemId='{0}’ “
    
+ “LabelText=’My Custom menu 4′ “
    
+ “ToolTipTitle=’My Custom menu 4′ “
    
+ “ToolTipDescription=’Dynamic Button’ />”, 3);

  dynamicMenuXml += buttonXML;
 
dynamicMenuXml += “</Controls>” + “</MenuSection>” + “</Menu>”;
 
  return
dynamicMenuXml;
}

The method creates the menu XML as expected by the control. One could also call a web service in the RaiseCallbackEvent method to get the basic menu xml and execute a XSL transformation in this method to construct this XML. I kept it simple and just did it in code here. The objective (have it done on the server), was already achieved. Once here, you can do whatever you like. Now we should wire these methods to a callback reference, so that our JavaScript can call this.

Wire server side callback to JavaScript

 So, we return to our OnPreRender event. In there, we add the following:

// register the client callbacks so that the JavaScript can call the server.
ClientScriptManager cm = this.Page.ClientScript;

String cbReference = cm.GetCallbackEventReference(this, “arg”, “ReceiveServerMenu”, “”);

String callbackScript = “function CreateServerMenu(arg, context) {“ + cbReference + “; }”;
cm.RegisterClientScriptBlock(this.GetType(), “CreateServerMenu”, callbackScript, true);


//Register script files

ScriptLink.RegisterScriptAfterUI(Page, “/_layouts/Boom.DynamicMenuSample/Boom.DynamicMenuSample.js”, false, true);

We instantiate the ClientScriptManager and add a callback reference to our class. The third argument in this reference is the name of the JavaScript function that will receive the response from our server, in our case ReceiveServerMenu. We then construct the client side script that wires our CreateServerMenu function to the callback and register it all as a client script block. Pffff, what a lot of wiring 😉 But we are almost there. Just hang on for a couple of more lines 😉

Add the JavaScript that will receive the server response

 Final thing we should do is write the client side JavaScript that receives the response and assign the response to the menuXml variable, as mentioned in our custom command. For this, I have added yet another .js (Boom.DynamicMenuSample.js) file in our layouts folder, just to separate the page component from other scripts. To use this extra JavaScript file, we need to register it. This is done using the last line in the previous code sample. In the Boom.DynamicMenuSample.js file, add the following couple of (simple) lines :

// variable to hold the server menu
var menuXml;

// This function will receive the callback from the server with the menu items.
function ReceiveServerMenu(arg, context) {
 
menuXml = arg;
}

The menuXml variable is used to assign it to the properties.PopulationXML in our command declaration. The ReceiveServerMenu function is referenced in our callback reference declaration above. This is the callback function that will be called when the server completes its request. The return value of that function will be assigned to the ‘arg’ argument passed. In the method, we then assign the return value to the menuXml variable (which in turn will be assigned to the properties.PopulationXML in the command), which completes our circle.

Build and deploy

 Now that we have completed our coding, all we have to do is click and deploy and watch the fruits of our labor. If you are lucky, you will see something similar to below. Be patient on the first load though, as the web application still needs to spin up.

Some considerations

Now that we have completed this exercise, one could argue whether this would be the approach to create a menu. Some advantages:
Although I have not tried it, the controls that are dynamically created do not have to be buttons. It could also be FlyoutAnchor’s again, which creates nested menus.
Because we can customize also the look and feel of the ribbon, it would be possible to make it look like a normal menu, as seen many times on sites.
Menu items could easily be added or removed, without the need to redeploy code.

Disadvantages can primarily be found on networks with high latency, where this approach would not be valuable. The menu would simple take too long to load.
Secondly, this approach would be rather ‘chatty’, meaning a lot of request would go back and forth. Some kind of caching mechanism on the server side is preferred.

Anyway, it does show all the possibilities you have with the ribbon. Dynamic controls, server side processing, dynamic command registration on the server, declarative commands, tabs and groups. Have fun playing around with this.

The example project with code can be downloaded here.

UPDATE 24-7-2013: 

It seems like multiple people have issues with the pageComponent.js class as at regular times, it throws a JS error. This is also true for SP 2013. It occurs with getGlobalCommands() function. In this post people solved it by implementing the exact commands to execute. Unfortunately, I have no other solution.

Posted in SharePoint 2010 | Tagged: , , , , , , , , | 47 Comments »

Using Powershell to change registry keys for search crawler

Posted by Patrick Boom on May 17, 2010

In one of my earlier posts, I described a way to use PowerShell to change settings in a SharePoint 2007 farm.  That started out as my first PowerShell script and I have witnessed the power behind it all.

In our project, we needed a server wide adjustment of the registry for the SharePoint index servers across all farms. Of course, multiple ways to do this, but here is where PowerShell shows its power. I needed to change two registry settings, both covering the maximum file size that is crawled by the indexer. By default, this is 16 MB (mutiplied by 4), leaving a total of 64 MB indexed in each file. As we now enlarged trhe maximum upload size to 100 MB, we needed the crawl settings to be adjusted. To be honest, I was quite surprised it would only take me two lines of script to make this work. The registry keys in question were those below:

HKLM\SYSTEM\CURRENTCONTROLSET\
HKLM\SOFTWARE\MICROSOFT\OFFICE SERVER\12.0\SEARCH\GLOBAL\GATHERING MANAGER

And to change these, the following lines of script would suffice. I have also added some additional lines to check whether we actually are at the index server, otherwise, these changes would have no effect. Pay special attention to the way the registry is accessed. In principle, it is accessed as a file path.

Set Search Crawler Settings

Write-Host “Set registry values for search”
Write-Host “=======================================================================”
Write-Host “”

if ( (Get-Itemproperty  -Path “hklm:\SOFTWARE\Microsoft\Office Server\12.0\Search\Global”).Index -eq 1 ) {
    Write-Host -f green “Validated that this server is the index Server ” 
} else  {
    throw “This server is the not the SharePoint index Server ” 
}

Write-Host “Processing MaxTextFilterBytes”
set-itemproperty “hklm:\system\currentcontrolset\control\contentindex” MaxTextFilterBytes -type “DWord” -value “104857600” -force

Write-Host -f green “Done…”
Write-Host “Processing MaxDownloadSize”
set-itemproperty “hklm:\software\microsoft\office server\12.0\search\global\gathering manager” MaxDownloadSize -type “DWord” -value “26214400” -force

Write-Host -f green “Done…”
Write-Host -f green “Script completed”
Write-Host “=======================================================================”
Write-Host “”

In addition to these registry changes, a couple of more settings need to be set to get the entire maximum upload size working. Above registry changes instruct the search crawler to enlarge the crawled file size. But this still does not allow SharePoint to upload larger files.  Besides SharePoint changes, OS level changes also need to be make on the client side to allow the WebDAV protocol to upload larger files. These are however out of scope of this blog post. In short, three steps are needed to support larger files on SharePoint:

1. Change the crawler settings to support larger files in the registry.
2. Change the Maximum File Size for the Web Application.
3. Change the connection time out setting on the SharePoint search service. (OSearch)

To change the crawler settings, the PowerShell script is located above. To execute steps 2 and 3 in above list, the following PowerShell scripts do the job:

Set Maximum File Size for Web Application

[Void][System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.SharePoint”)
$farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
if ($farm -eq $null ) {
    throw “Could not connect to the SharePoint farm”
}

Write-Host “”
Write-Host “====================================================================”
Write-Host “”
$websvcs = new-object  Microsoft.SharePoint.Administration.SPWebServiceCollection($farm)
if ($websvcs.Length -eq 0 ) {
    throw “Could not connect to any SharePoint Web Service in the farm”
}
$FileSize = 100
$MaximumFileSize = 0

foreach ($websvc in $websvcs) {
    foreach ($webapp in $websvc.WebApplications) {
 if (($webapp.MaximumFileSize -ne $FileSize) -and ($webapp.MaximumFileSize -lt $FileSize))
     {
  Write-Host “Set file size for web application $webapp.Name”
  $webapp.MaximumFileSize = $FileSize
  $MaximumFileSize = $webapp.MaximumFileSize
  $webapp.Update()
  Write-Host -f green “New file size $MaximumFileSize MB”
 } else {
         Write-Host -f green “Maximum file size for $($webapp.Name) was already set to or larger than $FileSize MB”
 }
    }
}
Write-Host -f green “Script completed”
Write-Host “====================================================================”

Set Connection TimeOut for Search Crawler

[Void][System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.SharePoint”)
$farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
if ($farm -eq $null ) {
    throw “Could not connect to the SharePoint farm”
}

Write-Host “====================================================================”
Write-Host “”

$searchsvcs = @($farm.Services | where -FilterScript {$_.GetType() -eq [Microsoft.Office.Server.Search.Administration.SearchService]})
if ($searchsvcs.Length -eq 0 ) {
    throw “Could not connect to any SharePoint Search Service in the farm”
}

$TimeOut = 90
foreach ($searchsvc in $searchsvcs) {
    if (($searchsvc.ConnectionTimeOut -ne $Timeout) -or ($searchsvc.AcknowledgementTimeout -ne $Timeout))
    {
        Write-Host “Set connection and acknoledgement timeouts for $($searchsvc.Name) to $Timeout”
        $searchsvc.ConnectionTimeOut = $Timeout;
        $searchsvc.AcknowledgementTimeout = $Timeout;
        $searchsvc.Update()
 Write-Host -f green “Done…”

    } else {
        Write-Host -f green “Connection timeout for $($searchsvc.Name) was already set to $Timeout”
    }
}

Write-Host -f green “Script completed”
Write-Host “====================================================================”

Thats it! Using three simple scripts, we can change the settings in our entire farm to use new file upload settings and have it supported by the sarch crawler.

CU!

Posted in PowerShell, SharePoint 2007 | Tagged: , , , , , | 5 Comments »

Migrating to SharePoint 2010 – Some Thoughts Part 2

Posted by Patrick Boom on May 6, 2010

This week, we got one of our customized applications from SharePoint 2007 to work on a SharePoint 2010 machine. As as described in my previous post this does not go without problems. We tried the content database move method, meaning we made a backup of one of our 2007 content databases and restored that database on the 2010 SQL server.

We then attached the database to the SharePoint 2010 farm by using the following command:

stsadm -o addcontentdb -url http://sp2010 -databasename SP2007_Content_DB -preserveolduserexperience true

This command will initiate an upgrade of the database. Once upgraded, the sites are accessible from SharePoint 2010. There are a couple of things we noticed when we restored sites this way.

1. We can upgrade their look and feel using the Visual Upgrade settings from the site settings menu. This will however overwrite any custom look and feel you might have enabled for your site.
2. Document libraries that came out of the box in the 2007 site will easily migrate to 2010, including all its features, like multiple select and the ribbon interaction. However, custom document libraries based on their own shema.xml, will continue to have the 2007 functionality, even if visually upgraded. So the multiple select, ribbon interaction and so on will not work in these libraries. They are usable though, but not with all 2010 features.

Prior to this exercise, we needed to make sure all necessary code and customizations were installed on the system. But our solution that created our own look and feel (like custom master pages and stylesheets) really messed up the entire environment. I would enable alright, but your site would be crippled. Worst thing though, it would not reset to its original setting, meaning our site was dead in the water. So be very carefull when upgrading to 2010 with visual elements like master pages and layouts.

Posted in SharePoint 2007, SharePoint 2010 | Tagged: , , , | Leave a Comment »

 
%d bloggers like this: