Creating Search Content Sources with a PowerShell CmdLet

In this blog I already posted many tips on how to use PowerShell on SharePoint. PowerShell has been developed by Microsoft for performing Windows Administration tasks. You can even automate tasks by writing PowerShell scripts. PowerShell works with the .NET object model. This means that you need an in-depth knowledge of the object model of the technology you want to manage with PowerShell. In the case of SharePoint this is a really big beast with a lot of facets. As you or your administrator wants to perform more advanced management tasks your PowerShell commands and scripts will become a lot more complicated. In that case you can extend PowerShell functionality with PowerShell CmdLets.

In PowerShell version 1.0 CmdLets are written in managed code, meaning in languages like VB.NET and C#. There exist Visual Studio templates that can used to develop your custom PowerShell CmdLets.

Now I hear you think: “Wait! Why bother writing PowerShell CmdLets if you can solve this issue by developing custom STSADM commands?”. At this point you can start a discussion on what the best option is. I advise the following rule of thumb: if you or your administrator only has to manage SharePoint, than you go for custom STSADM commands because in that case STSADM is part of your daily job. If you or your administrator uses PowerShell to perform administrative tasks on different technologies (like SQL Server, Exchange and SharePoint), your best option is PowerShell CmdLets.

The base principles of writing PowerShell CmdLets are explained in this MSDN article.

In this post I will explain how you can manage Search content sources using PowerShell and how you can develop custom PowerShell CmdLets to create a new content source.

You can download the source code for the PowerShell CmdLet here. Be aware that this is demo code and that it need to be extended with solid exception handling.

When you want to work with Search content sources you first have to load the necessary assemblies:

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Search")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server.Search")

 

Then you have to retrieve an instance of the Shared Service Provider. As PowerShell doesn’t run in the context of SharePoint, you have to pass the name of the Shared Service Provider to the static method GetContext of the Microsoft.Office.Server.ServerContext class. In PowerShell you achieve this as follows:

#Instantiate Shared Service Provider
$serverctx = [Microsoft.Office.Server.ServerContext]::GetContext(“U2USharedServices”)

 

Once you have retrieved an instance of the Shared Service Provider you have access to the services that it hosts. The Search service is one of these services. You can instantiate the Search service by executing the GetContext static method of the Microsoft.Office.Server.Search.Administration.SearchContext class.

#instantiate the Search Service

£
$searchctx = [Microsoft.Office.Server.Search.Administration.SearchContext]::GetContext ($serverctx)

#display the name of the search service instance
$searchctx.Name

 

You can retrieve and display the Search content sources as follows:

#show the available content sources
$content = new-object Microsoft.Office.Server.Search.Administration.Content($searchctx)

foreach ($cs in $content.ContentSources)
{
    Write-Host "NAME: ", $cs.Name, " - ID: ", $cs.Id, " - CrawlStatus: ", $cs.CrawlStatus
    Write-Host "Full Crawl Schedule: ", $cs.FullCrawlSchedule.Description
    Write-Host "Incremental Crawl Schedule: ", $cs.IncrementalCrawlSchedule.Description
}

 

The trouble starts when you now want to create a new content source using PowerShell because you need to specify the type of content source you want to create. As you already know, you can create content sources of the following types:

  • WebContentSource                          
  • SharePointContentSource
  • FileShareContentSource
  • ExchangePublicFolderContentSource
  • BusinessDataContentSource

If you want to create a FileShare content source you can write this as follows in C#:

 

ContentSource newContentSource = content.ContentSources.Create(
     typeof(FileShareContentSource), nameContentSource);
newContentSource.StartAddresses.Add(new Uri(url));
FileShareContentSource fileShareContentSource =
     (FileShareContentSource)newContentSource;
fileShareContentSource.FollowDirectories = true;
fileShareContentSource.Update();

 

As you can see, the Create method asks for the type of the content source. The second parameter is the name for the new content type. You also have to specify the start address URL. In case of a file share  content source you can also specify whether you want to index the sub folders or not. After having set the  necessary properties you have to execute the Update method.h

Executing the correct Create method is rather difficult to achieve in PowerShell. It would be easier if you could execute something like this:

set-createcontentsource “name of the SSP” “name new content source” “type of content source” “start address”

 

In this case you can opt to write a PowerShell CmdLet to extend PowerShell functionality. PowerShell CmdLets are written in managed code. There exist a couple of Visual Studio project templates that you can download and use. (The link was down this weekend but I hope it will be restored).

Open Visual Studio 2005 and choose Windows PowerShell as project type in the left pane and the Windows PowerShell template in the right pane.

image

This creates a project skeleton consisting of a  PSSnapIn file and a class inheriting from. The project contains a reference to the System.Management.Automation assembly. This assembly comes with the PowerShell SDK.

Right-click the PSSnapIn file to view the code. The code contains 5 properties that need to be set by you. The most important one is  the Name property which will be used when you register your PowerShell CmdLet. The class inherits from PSSnapin which is the base class for creating snap-ins. Snap-ins are the deployment unit of Windows PowerShell and are tagged with a RunInstaller attribute for registering the Snap-in with PowerShell.

[RunInstaller(true)]
public class CreateContentSourceSnapIn : PSSnapIn
{
    public override string Name
    {
        get { return "CreateContentSourceCmdLet"; }
    }
    public override string Vendor
    {
        get { return "U2U"; }
    }
    public override string VendorResource
    {
        get { return "CreateContentSourceCmdLet,U2U"; }
    }
    public override string Description
    {
        get { return "Registers the CmdLets and Providers in this assembly"; }
    }
    public override string DescriptionResource
    {
        get { return "CreateContentSource,Registers the CmdLets and Providers in this assembly"; }
    }
}

 

The name will be used to register the CmdLet.

A Snap-in contains one or more CmdLets. A CmdLet is a class that inherits from the base class CmdLet residing in the System.Management.Automation namespace.

The class is preceded by the name of the CmdLet and consists of:

  • a verb that indicates the action of the CmdLet (get or set)
  • a noun that must be specific and describe what the action will perform

In this case the name of the CmdLet looks as follows:

[Cmdlet(VerbsCommon.Set, "CreateContentSource", SupportsShouldProcess = true)]
public class CreateContentSource : Cmdlet
{

}

 

You can define a number of parameters if the CmdLet needs some exra information before it can execute its extra functionality. As this sample CmdLet is going to create a new content source, it defines 4 parameters:

  • name shared service provider
  • name content source
  • type of the content source
  • start address of the content source

The first incoming parameter is the SharedServiceProvider name and is defined as follows:

private string nameSharedServiceProvider;

[Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true,
    HelpMessage = "The Name of the Shared Service Provider")]
[ValidateNotNullOrEmpty]
public string SharedServiceProviderName
{
    get { return nameSharedServiceProvider; }
    set { nameSharedServiceProvider = value; }
}

 

The Position attribute indicates that this parameter is the first one that need to be specified when the CmdLet is executed. The Mandatory attributes indicates that this attributes is required. The ValueFromPipelineByPropertyName attribute means that the value for this attribute can come from a property in the incoming pipeline object that has the same name as the parameter. You can specify a HelpMessage attribute that will display when help is asked for the CreateContentSource command. The ValidateNotNullOrEmpty is a validation rule indicating that the incoming value cannot be null or empty.

The other parameters are defined in a similar way.

There is one member to override: the ProcessRecord method. In this method you need to write the implementation code of your extra functionality. The implementation in the sample looks as follows:

protected override void ProcessRecord()
{
    Microsoft.Office.Server.ServerContext serverctx = null;
    SearchContext searchctx = null;

    try
    {
        // open the context of the Shared Service Provider
        serverctx = ServerContext.GetContext(nameSharedServiceProvider);
        if (serverctx != null)
        {
            searchctx = SearchContext.GetContext(serverctx);
            WriteObject("Connected to search context " + searchctx.Name);

            if (searchctx != null)
            {
                Content content = new Content(searchctx);

                switch (typeContentSource)
                {
                    case "sharepoint":
                        CreateSharePointContentSource(content);
                        break;

                    case "web":
                        CreateWebContentSource(content);
                        break;

                    case "fileshare":
                        CreateFileShareContentSource(content);
                        break;

                    case "exchange":
                        CreateExchangeContentSource(content);
                        break;

                    case "bdc":
                        CreateBDCContentSource(content);
                        break;
                }

            }
        }
    }
    catch (Exception ex)
    {
        WriteObject("ERROR: " + ex.Message);
    }
}

 

Each type of content source is created in a separate private method. When an exception occurs the error message is written to the console.

This is the sample code for the creation of the file share content source:

private void CreateFileShareContentSource(Content content)
{
    ContentSource newContentSource = content.ContentSources.Create(
        typeof(FileShareContentSource), nameContentSource);
    newContentSource.StartAddresses.Add(new Uri(url));
    FileShareContentSource fileShareContentSource =
        (FileShareContentSource)newContentSource;
    fileShareContentSource.FollowDirectories = true;
    fileShareContentSource.Update();
    fileShareContentSource.StartFullCrawl();
}

 

When the content source is created and the properties set, a full crawl on the new content source is started.

When the code is ready to deploy, build your project. To register the CmdLet with PowerShell open a Visual Studio command prompt, navigate to the bin\debug folder of your project and execute the installer by running the InstallUtil tool.

Installutill CreateContentSourceCmdLet.dll

 

Open Windows PowerShell and load the SharePoint assemblies if this is not yet done by your profile. Load also the PowerShell snap-in by executing the following:

Add-PSSnapIn CreateContentSourceCmdLet

 

This is the name you specified in the PSSnapIn installer class.

I can now create a new content source in PowerShell by executing the following:

set-createcontentsource “U2USharedServiceProvider” “Office Documents” “fileshare” “\\springfield\DocsToIndex”

 

When this command has executed successfully, the new content source will appear in the Search Settings pages of the SharePoint Central Administration.

image

image

A bit besides the subject of this post, but I read an excellent post on how to index and search your .NET source code and PowerShell scripts here.