sitecore-powershell-yin-yangReading some time ago the Item Buckets documentation I discovered something really cool called code data sources. We delivered something similar in our internal libraries and it proved super useful ever since. I’ve also recently read a nice article by Ronald Nieuwenhuis on NewGuid about their approach to the subject.

So what a PowerShell and Sitecore nut does when he sees stuff like that? Obviously delivers a scripted data source!

Why do that?

Just to prove that both Sitecore and PowerShell are infinitely malleable and mixable, is good enough for me, but that’s not really the reason someone other than me would be interested in it.

  • Delivering complex functionality based on multiple criteria. e.g. your field may need to provide different set of items to choose from based on:
    • user name or role (in simplest case this can be done using right management, but maybe not always possible in a more elaborate scenario)
    • current day or month?
    • In a multisite/multimarket scenario you may want to show different items for each site
    • based on engagement analytics parameters of the page
    • based on where in the tree an item exist (some of it can be done with use of a “query:”)
    • anything you might want to build the code data source for…

Something that would be beyond the reach of a regular Sitecore query and potentially something that you would really need to deliver code source for. But maybe you’re not in a position to deploy that on your environment?

Field data source

Field data source is something you might want to have the most flexibility with. This includes scripting fields like:

  • Checklist
  • Droplist
  • Grouped Droplink
  • Grouped Droplist
  • Multilist
  • Name Lookup Value List
  • Droplink
  • even with 3rd party fields like “Google Maps Multilist”

Generally everything that uses the getLookupSourceItems pipeline should work with the scripting solution.

Interestingly the following fields do not use the pipeline:

  • DropTree
  • Treelist
  • TreelistEx

But that’s understandable after you read John’s article on how the Data source is defined in those fields. Basically there’s plenty of flexibility there already.

How to define my scripts?

Using the PowerShell ISE:

Launching Sitecore PowerShell Console ISE

Define your script like follows:

Script editing in the Sitecore PwoerShell Console ISE

The script should be returning items like the script above. Don’t format your results with format-list, format-table etc. The output needs to be items themselves.

Save your script in the Script library by using the Save button in ISE:

Saving script in Script library

How do I use the script now?

find your script in the script library which is located in your Master database under:

/sitecore/system/Modules/PowerShell/Script Library/

and copy the script item path. Now in your template definition paste the path in the and prepend it with a “script:” statement like follows:

Using a scripted datasources on a templates

and then effect as you would expect:

image

How does it work?

The solution hooks into the getLookupSourceItems pipeline like follows:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <getLookupSourceItems>
        <processor 
          patch:before="*[@type='Sitecore.Pipelines.GetLookupSourceItems.ProcessQuerySource, Sitecore.Kernel']"
          type="Cognifide.PowerShell.Processors.ScriptedDataSource, Cognifide.PowerShell" />
      </getLookupSourceItems>
    </pipelines>
  </sitecore>
</configuration>

And the class that takes the pipeline input and executes PowerShell accordingly can look like the following:

public void Process(GetLookupSourceItemsArgs args)
{
    Assert.ArgumentNotNull(args, "args");

    //should we care?
    if (IsScripted(args.Source))
    {
        var items = new List<item>();

        // parse my sources and get me the items
        var source = GetScriptedQueries(args.Source, args.Item, items);
        args.Result.AddRange(items.ToArray());

        // if no other queries to be served, no need to continue pipeline
        if (string.IsNullOrEmpty(source))
        {
            args.AbortPipeline();
        }
    }
}

protected bool IsScripted(string dataSource)
{
    return dataSource.IndexOf("script:" ,
        StringComparison.OrdinalIgnoreCase) > -1;
}

protected static string GetScriptedQueries(string sources, Item 
    contextItem, List<item> items)
{
    string unusedLocations = string.Empty;
    foreach (string location in new ListString(sources))
    {
        if (location.StartsWith("script:"))
        {
            var scriptLocation = location.Replace("script:", "").Trim();

            // for every scripted source, run it through powershell
            var itemArray = RunEnumeration(scriptLocation, contextItem);
            foreach (var item in itemArray)
            {
                items.Add(item);
            }
        }
        else
        {
            unusedLocations += 
                unusedLocations.Length > 0 
                    ? "|" + location 
                    : location;
        }
    }
    return unusedLocations;
}

protected static Item[] RunEnumeration(string scriptSource, Item item)
{
    // get my script item from the Script library
    scriptSource = scriptSource.Replace("script:", "").Trim();  
    Item scriptItem = item.Database.GetItem(scriptSource);

    // initialize the PowerShell session
    ScriptSession session = new ScriptSession(ApplicationNames.Default);

    // get the script from the script item
    String script = (scriptItem.Fields["Script"] != null) 
                        ? scriptItem.Fields["Script"].Value 
                        : string.Empty;

    // put the script in context of the current item
    script = String.Format(
        "cd \"{0}:{1}\"\n", 
        item.Database.Name, 
        item.Paths.Path.Replace("/", "\\").Substring(9)) + script;

    // execute the script
    // "false" value provided as the second parameter specifies that
    // session should return objects rather than text to be displayed
    return session.ExecuteScriptPart(script, false).Cast<item>().ToArray();
}

Word of caution is to make sure your script runs in reasonable time just as you would with your code solution, as while it does not impact the delivery side of Sitecore a poorly performing script might make your editors not send you a Christmas card. That said – from my tests I’ve not seen any noticeable slow down or any measurable impact where my script would be slower than something that you would solve using the code: datasource of the same complexity.

The solution is available with the Cognifide’s Sitecore PowerShell Console (starting with 2.0 RC2) already available on Sitecore Marketplace.

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...



This entry (Permalink) was posted on Wednesday, April 17th, 2013 at 5:04 pm and is filed under ASP.NET, PowerShell, Sitecore, Software Development, Solution, Web applications. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response , or trackback from your own site.



  • Pingback: PowerShell Scripted data sources in Sitecore (Part 2) | Codality()

  • Pierre Sapinault

    Hey Adam,
    Is there a way to get the current Sitecore Content Editor Language so that we can put that in the script?
    That way i could display in a multilist only the item that have a version for the current editing language that the user is in.
    Makes sense?
    cheers,
    Pierre

    PS(no pun intended): I already tried calling [Sitecore.Context]::Language.Name and [Sitecore.Sites.SiteContext]::Current.Language but it only returns “en”

  • Assuming you’re using the solution that plugs into the “getLookupSourceItems” pipeline I’ve created the following script:

    Get-ChildItem master:content -Language ([Sitecore.Context]::Language)
    Write-Log -Log Error "LANG-DIAG ContentLang: $([Sitecore.Context]::ContentLanguage) Lang: $([Sitecore.Context]::Language)"

    I’ve saved the script in: “/sitecore/system/Modules/PowerShell/Script Library/test/homes”

    And created a field of type “Multilist” in a sample item and set it’s data source as: “script:/sitecore/system/Modules/PowerShell/Script Library/test/homes”

    I’ve created a few Japanese only items and I can see them when I switch to Japanese but not when I have my edited version selected as English.

    Let me know if this is the scenario you want to accomplish (as it seems to be working for me) or is there anything I am missing.

  • Pierre Sapinault

    Hi Adam,
    Thanks a lot for the quick reply!
    It is indeed what I intended to do, what i was wrong about is that i don’t need the Sitecore Content Editor language but just the current language from the item the user is trying to edit.
    In the end i wrote this one and it works perfect for Multilist:

    $Language = (Get-Item .).Language.Name

    Get-ChildItem master:/sitecore/content/website/Data/Menu` items -Recurse -Language $Language |`

    Where-Object {$_.TemplateID -eq “{CC6E8A67-93DA-4D2A-BAEC-F60A3FB925B6}”}

    best,
    Pierre