CHUVASH.eu

CHunky Universe of Vigouros Astonishing SHarepoint :)

Creating custom powershell cmdlet

Why

I need to to activate a feature in PowerShell and specify some properties. Simple? Yes. Possible? No. In the default Enable-SPFeature cmdlet you can’t specify any properties:

Enable-SPFeature –Identity "b5eef7d1-f46f-44d1-b53e-410f62032846" -URL http://dev

We can of course easily add properties when activating features in onet.xml:

<!-- Publishing Resources -->
<Feature ID="AEBC918D-B20F-4a11-A1DB-9ED84D79C87E">
  <Properties xmlns="http://schemas.microsoft.com/sharepoint/">
    <Property Key="AllowRss" Value="false" />
    <Property Key="SimplePublishing" Value="false" />
  </Properties>
</Feature>

So I went to SharePoint StackExchange and asked the question. Then I realized: the standard Sharepoin API doesn’t support this neither. The methods of SPFeatureCollection which have SPFeatureProperties as parameter are internal. So the only way is to use Reflection like Hristo Pavlov (2008) and Yaroslav Pentsarsky (2010) suggest. So why not to try to create a cmdlet?

What

I want to create a custom cmdlet: Enable-SPFeatureWithProperties. It should look like this:

$properties = @{"Test" = "SPLENDID" }
Enable-SPFeatureWithProperties `
      –Identity "b5eef7d1-f46f-44d1-b53e-410f62032846" `
      -URL http://dev `
      -Properties $properties

The cmdlet code will be written in C# and published on github. It is a part of my project called sp-lend-id and like all other parts in sp-lend-id it will have a Chuvash word as its name: Taprat (/taprat/ meaning “Enable” :) )

How

First I create a simple cmdlet just to get started. Then I test the reflection code to activate a feature with properties. Then, if this works, I will bring the pieces together and create my cmdlet.

Simple cmdlet

The best cmdlet tutorial (text, images, code and videocast) is created by Saveen Reddy. I just followed his sample. I created a new class library project and a class for the demo cmdlet: Get_DemoNames.cs.

By the way, if you don’t have th powershell dll as Saveen Reddy describes, add manually the reference to the csproj-file:

<Reference Include="System.Management.Automation" />

Build and test the cmdlet:

Activating the feature

Create a very simple Feature and Feature Receiver which takes properties and does something with them, like Yaroslav suggested:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
	var web = properties.Feature.Parent as SPWeb;
	if (web != null)
	{
		var allow = web.AllowUnsafeUpdates;
		web.AllowUnsafeUpdates = true;
		if (properties.Feature.Properties["Test"] != null)
		{
			web.Title = properties.Feature.Properties["Test"].Value;
			web.Update();
		}
		web.AllowUnsafeUpdates = allow;
	}
}

After deploying this feature we can create a simple console application to test the reflection code. First, the extension for SPFeature to be able to invoke the internal methods:

public static class SPFeatureExtensions
{
    public static SPFeature ActivateFeature(this SPFeatureCollection features, Guid featureId, Dictionary&lt;string, string&gt; activationProps)
    {
        var propCollConstr = typeof(SPFeaturePropertyCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
        var properties = (SPFeaturePropertyCollection) propCollConstr.Invoke(new object[] { null });
        foreach (var key in activationProps.Keys)
        {
            properties.Add(new SPFeatureProperty(key, activationProps[key]));
        }           
        return ActivateFeature(features, featureId, properties);
    }
    private static SPFeature ActivateFeature(this SPFeatureCollection features, Guid featureId, SPFeaturePropertyCollection properties)
    {          
        if (features[featureId] != null)
        {
            // The feature is already activated. No action required
            return null;
        }
        var type = typeof(SPFeatureCollection);

        // now we have to get "AddInternal" Method with this signature:
        //internal SPFeature AddInternal(Guid featureId, Version version, SPFeaturePropertyCollection properties, bool force, bool fMarkOnly, SPFeatureDefinitionScope featdefScope)

        var param = new[]
                        {
                            typeof (Guid), typeof (Version), typeof (SPFeaturePropertyCollection), typeof (bool),
                            typeof (bool), typeof (SPFeatureDefinitionScope)
                        };
        var addInternal = type.GetMethod("AddInternal", BindingFlags.Instance | BindingFlags.NonPublic, null, param, null);
        if (addInternal == null)
        {
            // failed to find the method
            return null;
        }
        var result = addInternal.Invoke(features, new object[] { featureId, null, properties, false, false, SPFeatureDefinitionScope.Farm });
        return result as SPFeature;
    }
}

There are some changes compared to Yaroslav’s code:

  • The testing if feature is enabled happens without internal Get Method, just by (features[featureId] != null)
  • The internal “Add” method now is called “AddInternal”
  • The parameters which have to be passed to the internal add method are changed, too.

I found the signature of the “AddInternal” using Reflector.NET 6:

internal SPFeature AddInternal(Guid featureId, 
				Version version, 
				SPFeaturePropertyCollection properties, 
				bool force, 
				bool fMarkOnly, 
				SPFeatureDefinitionScope featdefScope)
{
    return this.AddInternalWithName(featureId, null, 
            version, properties, force, fMarkOnly, featdefScope);
}
Testing the code in a console application

Just a few lines of code to test and debug the code:

static void Main(string[] args)
{
	using (var site = new SPSite("http://dev"))
	{
		using (var web = site.OpenWeb())
		{
			var activationProps = new Dictionary
				{{"Test", "Title changed by feature in activation"}};
			web.Features.ActivateFeature(
                          new Guid("b5eef7d1-f46f-44d1-b53e-410f62032846"), 
                          activationProps);
		}
	}
}

The pre-condition is that the test feature is deployed, then it works:

Running it in Powershell

Now, when we know the code works, we can test it in powershell. We create three params:

[System.Management.Automation.Parameter(Position = 0, Mandatory = true)] 
public string Identity;

[System.Management.Automation.Parameter(Position = 1, Mandatory = true)]
public string Url;

[System.Management.Automation.Parameter(Position = 2, Mandatory = false)]
public System.Collections.Hashtable Properties;

The third parameter is Hashtable because the main “dictionary” object in PowerShell is Hashtable. So we must convert the passed hashtable into a Dictionary:

private Dictionary<string, string> GetProperties()
{
    var dictionary = new Dictionary<string, string>();
    if (Properties != null)
    {
        foreach (var key in Properties.Keys)
        {
            dictionary.Add((string)key, (string)Properties[key]);
        }
    }
    return dictionary;
}

Then the only thing we have to do is to copy the code from the Main method in console app into the ProcessRecord method:

protected override void ProcessRecord()
{
    var properties = GetProperties();
    WriteObject(properties);
    using (var site = new SPSite(Url))
    {
        using (var web = site.OpenWeb())
        {
            var features = web.Features;
            var id = new Guid(Identity);
            features.ActivateFeature(id, properties);
        }
    }
}

Now we can rebuild the solution and run the powershell by importing the module:

$p = @{"Test" = "Tjena" }
Enable-SPFeatureWithProperties -Identity "b5eef7d1-f46f-44d1-b53e-410f62032846" -Url "http://dev" -Properties $p
Creating PSSnapin

So the cmdlet is completed. There is one thing left which I want to try: creating pssnapin which one can add in an easy way to a powershell session. To create a pssnapin we have to create class in the project and extend it from PSSnapin. This class just has some properties: Name, Description and Vendor:

[RunInstaller(true)]
public class TapratInstaller : PSSnapIn
{
    public override string Name
    {
        get
        {
            return "sp-lend-id.taprat";
        }
    }

    public override string Vendor
    {
        get 
        { 
            return "Anatoly Mironov";
        }
    }

    public override string Description
    {
        get
        {
            return
                "This includes an experimental cmdlet to activate sharepoint features and specify custom properties";
        }
    }
}

To install the pssnapin, run these commands in powershell as Administrator:

Set-Alias installutil $env:windir\Microsoft.NET\Framework64\v2.0.50727\installutil.exe
installutil C:\code\sp-lend-id\taprat\cmdlet\sp-lend-id.taprat\bin\Debug\sp-lend-id.taprat.dll

Pay attention that we have to run installutil.exe from Framework64, not Framework. Otherwise the pssnapin won’t be accessible.

After the pssnapin has been installed we can see it by running:

Get-PSSnapin -registered

Now all we have to is to add the newly created pssnapin and run the commands:

Add-PSSnapin sp-lend-id.taprat
Get-DemoNames -Prefix "Hello "

And the desired:

$properties = @{"Test" = "SPLENDID" }
Enable-SPFeatureWithProperties `
      –Identity "b5eef7d1-f46f-44d1-b53e-410f62032846" `
      -URL http://dev `
      -Properties $properties

Of course, in order this to work, we have to deactivate our test feature first. Then the cmdlet activates our test feature which only updates web title in this experiment. It works. Splendid!

Code

The code can be found on sp-lend-id project’s code repository on github: https://github.com/mirontoli/sp-lend-id

DISCLAIMER:

This code is just a lab code for experimenting and learning purposes. Don’t use it in production environments unless you test it yourself. As Per Jacobsen pointed in his answer on Sharepoint StackExchange, the internal methods can vary in different releases and patches of SharePoint, which we can see here (Yaroslav’s code can’t be run anymore), and it can break your solution deployment.

About these ads

2 responses to “Creating custom powershell cmdlet

  1. Suman 2013-05-14 at 23:32

    You are the man…!! The line “Pay attention that we have to run installutil.exe from Framework64, not Framework. Otherwise the pssnapin won’t be accessible.” from your blog made my day. I was struggling since a while and finally after reading here, I was able to access it

    • Anatoly Mironov 2013-05-15 at 10:21

      Hi Suman. Thank you for your feedback. I understand your joy when things work.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Discovering SharePoint

And having fun doing it

Bram de Jager's SharePoint blog

My view and thoughts on SharePoint.

My programming life

and everything in between

SharePoint Development Lab by @avishnyakov

It is a good place to share some SharePoint stories and development practices.

SharePoint Dragons

Nikander & Margriet on SharePoint

The Zuul Cat Idea Brewery

Where ideas on software development and entrepreneurship brew.

Paul J. Swider

Inspire! Teach! Awe!

Mai Omar Desouki - Avid SharePointer

Egyptian & Vodafoner - Senior SharePoint Consultant

Alexander Ahrens

MCPD | SharePoint | Web Development | JavaScript | .NET

Cameron Dwyer | SharePoint, Outlook, OnePlaceMail

OnePlaceMail, SharePoint, Outlook & Life's Other Little Wonders

.:paul.tavares:.

Me and My doings!

Share SharePoint Points

By Mohit Vashishtha

Jimmy Janlén "Den Scrummande Konsulten"

Erfarenheter, synpunkter och raljerande om Scrum från Jimmy Janlén

SPJoel

SharePoint for everyone

SharePointRyan.com

Ryan Dennis is a SharePoint Solutions Architect with a passion for SharePoint and PowerShell

SharePoint 2020

The Vision for a Future of Clarity

Aharoni in Unicode, ya mama

Treacle tarts for great justice

Follow

Get every new post delivered to your Inbox.

Join 232 other followers

%d bloggers like this: