Rules in the Page Editor

This is a different way of disabling buttons on fields in Sitecore’s Page Editor, based on field values and the actual field, as was described in this post. This time I will be using the Sitecore Rules engine rather than removing items from a list.

The inspiration from this blog post came from @KevinObee, who put in this answer to the same question:

In short the answer he gave is: Use the Rules Engine as described here (a blog post by Adam Conn).

This sounded like a very interesting (not to mention powerful and elegant) solution, so here goes.

As Kevin gave a link to Sitecore’s blog, this was my starting point. I roughly followed Step 1 and Step 2 – but made my own Step 3. The only thing I didn’t follow is Step 2.4. I didn’t create the Command field.

I did this because I didn’t want to execute this rule for only my own command – the StackOverflow question specified it’s an existing (i.e. Sitecore out-of-the-box) command. This was my first issue – how do I get to the CommandStates of all buttons?

It turns out one can use the getQueryState pipeline. Found this out through this excellent post by @MikeReynolds. Thanks for that ;-)

For my solution I created an include file:

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <getQueryState>
        <processor type="TestApplication.HidePageEditorButtonsUsingRules.RunRules, TestApplication" />
      </getQueryState>
    </pipelines>
  </sitecore>
</configuration>

My RunRules class:

public class RunRules
{
  public void Process(GetQueryStateArgs args)
  {
    Assert.ArgumentNotNull(args, "args");
    args.CommandState = QueryState(args.CommandContext);
  }

  public CommandState QueryState(CommandContext context)
  {
    Assert.ArgumentNotNull(context, "args");
    var ruleContext = new CommandRuleContext();
    ruleContext.Item = context.Items[0];
    var ruleItems = context.Items[0].Database.SelectItems("/sitecore/system/Settings/Rules/Command Rules/Rules/*");

    foreach (var ruleItem in ruleItems)
    {
      var rules = RuleFactory.ParseRules<CommandRuleContext>(ruleItem.Database, ruleItem["Rules"]);
      if (rules != null && rules.Count > 0)
      {
        rules.Run(ruleContext);
      }
    }

    return ruleContext.CommandState;
  }
}

So now I can put in a rule like:

initialrule

So far so good. The only problem is that the question also specified that there might be multiple fields on the page, some of which would have the buttons, some wouldn’t.

First of all, I created a new Condition to be able to facilitate this requirement:

newcondition

This is my SpecificField class:

public class SpecificField<T> : StringOperatorCondition<T> where T : CommandRuleContext
{
  public string Value { get; set; }
  protected override bool Execute(T ruleContext)
  {
    Assert.ArgumentNotNull(ruleContext, "ruleContext");
    var item = ruleContext.Item;
    if (item == null)
    {
      return false;
    }

    string fieldName = ruleContext.FieldName;
    if (string.IsNullOrEmpty(fieldName))
    {
      return false;
    }

    return base.Compare(fieldName.ToUpperInvariant(), Value.ToUpperInvariant());
  }
}

Now I can change my Rule field to add this condition:

updatedrule

I’ll need to change some other things as well in our previous code.

The Process method in the RunRules class now looks like this:

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

  if (args.CommandContext.Parameters["field"] != null)
  {
    args.CommandState = QueryState(args.CommandContext);
  }
}

The updated lines of code are highlighted.

In the QueryState method of the RunRules class, I add the field to the commandContext like this:

public CommandState QueryState(CommandContext context)
{
  Assert.ArgumentNotNull(context, "args");
  var ruleContext = new CommandRuleContext();
  ruleContext.Item = context.Items[0];
  ruleContext.FieldName = context.Parameters["field"];
  var ruleItems = context.Items[0].Database.SelectItems("/sitecore/system/Settings/Rules/Command Rules/Rules/*");

  foreach (var ruleItem in ruleItems)
  {
    var rules = RuleFactory.ParseRules<CommandRuleContext>(ruleItem.Database, ruleItem["Rules"]);
    if (rules != null && rules.Count > 0)
    {
      rules.Run(ruleContext);
    }
  }

  return ruleContext.CommandState;
}

The added line is highlighted.

After building this and running it, the result for the title field is:

titlefield

And the subtitle field:

subtitlefield

Of course, all commands are now removed. This could be customized relatively easy by adding a new Condition to the Rule field, which would check the Command – very similar to the SpecificField class above.

Also, you might notice the Personalize button still displays. I’m not entirely sure why, but I’m thinking it’s a timing issue – it might be because the GetRenderingTestVariations gets added after the getQueryState pipeline runs. I haven’t really looked into this. If anyone can shed a light on this, please let me know in the comments.

Page Editor explorations

I read this StackOverflow question, and that made me wonder where the field buttons actually come from. I’d never looked into this before, so here goes.

The summary of the question is:

Can we disable the buttons on fields in Sitecore’s Page Editor, not only based on some field on some item, but also based on which field we have?

The buttons are added in the <getChromeData> pipeline. My initial answer to the question was that one could override the Process method in the GetFieldChromeData class, but that struck me as a little extreme. Especially because it’s a Sitecore processor, so if the Sitecore code gets upgraded, the override has to be changed as well.

So then I figured one could add a processor to the pipeline. I added:

<processor type=TestApplication.RemoveButtons, TestApplication/>

Directly after:

<processor type=Sitecore.Pipelines.GetChromeData.GetFieldChromeData, Sitecore.Kernel/>

My RemoveButtons class looks like this:

public class RemoveButtons : GetChromeDataProcessor
{
    public override void Process(GetChromeDataArgs args)
    {
Assert.ArgumentNotNull(args, “args”);
Assert.IsNotNull(args.ChromeData, “Chrome Data”);

        if (“field”.Equals(args.ChromeType, StringComparison.OrdinalIgnoreCase))
{
Field argument = args.CustomData["field"] asField;
Assert.ArgumentNotNull(argument, “CustomData[\"{0}\"]“.FormatWith(new object[] { “field” }));

            if (argument.Name == “Title” && MainUtil.GetBool(argument.Item["NeedsToBeCheckedToPersonalize"], false))
            {
args.ChromeData.Commands.RemoveAll(delegate(WebEditButton b)
{
return b.Header == “Personalize”;
});
}
}
}
}

First I’m checking whether it’s a field or placeholder or rendering. After that, I instantiate the field from the CustomData. Then, if the current field is the Title field and the checkbox NeedsToBeCheckedToPersonalize (which is, just to be clear, a random checkbox I added) is ticked, remove all buttons that are Personalize.

The result for the Title field:

Image

And my subtitle field:

Image

The two fields are on the same item, and of the same type (both are single-line text).

Placeholder Settings

I was playing around with Placeholder Settings, and thought it would be a nice topic for my next blog post – especially since I wanted to customize it a bit.

Adding placeholder settings

Placeholder settings can be added in different ways. The placeholder settings items are saved in the /sitecore/layout/Placeholder Settings folder.

The ‘global’ placeholder settings need a placeholder settings item to be created, then assigned a placeholder key (which needs to match the placeholder key in the sublayout) and can be assigned its allowed controls.

Another way is to add placeholder settings on the Layout details. The placeholder settings item will end up in the Placeholder Settings folder, but will not have a Placeholder Key set in the item itself. It will be bound to a placeholder on the layout.

Now, I had an article page, on which people could leave their comments through a Comment widget. This widget is enabled on some articles, but Content Editors can also remove it if they don’t want this functionality. This means they’d also have to be able to add it back again if they’ve removed it and changed their minds later – but I only want it on the page once, since it doesn’t make too much sense to have the same widget on the page twice.

When looking in the Core database (We can see all things Page Editor in /sitecore/content/Applications/WebEdit/Ribbons/WebEdit), we can see which command gets triggered by clicking the ‘new component’ button:

Image

Using the Commands.config file, this translates to the following command:

<command name=webedit:addrendering“ type=Sitecore.Shell.Applications.WebEdit.Commands.AddRendering,Sitecore.Client/>.

In the AddRendering class there’s a private static void defined called RunGetPlaceholderRenderingsPipeline, which kicks off a pipeline like this:

CorePipeline.Run(“getPlaceholderRenderings”, args);


If we then look in the web.config and see what the getPlaceholderRenderings pipeline does:

<getPlaceholderRenderings>

<processor type=Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings, Sitecore.Kernel/>
<processor type=Sitecore.Pipelines.GetPlaceholderRenderings.GetPredefinedRenderings, Sitecore.Kernel/>
<processor type=Sitecore.Pipelines.GetPlaceholderRenderings.RemoveNonEditableRenderings, Sitecore.Kernel/>
<processor type=Sitecore.Pipelines.GetPlaceholderRenderings.GetPlaceholderRenderingsDialogUrl, Sitecore.Kernel/>

</getPlaceholderRenderings>

The easiest solution would then be to add a processor to the pipeline.

A processor needs a Process method, and in our case will expect GetPlaceholderRenderingsArgs as well.

I figured I would start off simple and just make it so I can’t add any control to the placeholder if that control is already there.

Here’s the code I wrote for this:

publicvoid Process(GetPlaceholderRenderingsArgs args)
{
    Assert.IsNotNull(args, “args”);
    List<Item> placeholderRenderings = args.PlaceholderRenderings;

    if (placeholderRenderings != null)
    {
        placeholderRenderings.RemoveAll(x => args.LayoutDefinition.Contains(x.ID.ToString()));
    }
}

Relatively easy – go get PlaceholderRenderings, remove all renderings that are already in the LayoutDefinition. Note that this means we can now only have the control once per page as we are looking at the complete LayoutDefinition and not just the controls added to a specific placeholder.

Then I added my processor before

<processor type=Sitecore.Pipelines.GetPlaceholderRenderings.GetPlaceholderRenderingsDialogUrl, Sitecore.Kernel/> in the <getPlaceholderRenderings>node

in the <getPlaceholderRenderings> node:

<processor type=TestApplication.PlaceholderSettingsTest, TestApplication/>

I then set up my page. In my page I can add either Random Sublayout or Random Rendering.

Image

I added Random Rendering to the page, so I would expect Random Sublayout to be the only addable control.

So – Add component -> Add to here…
Image

Perfect!

Of course, there should be some modification, such as having the ability it have controls only once per placeholder rather than once per page like this example.

Rules rule

My content editors usually use the Content Editor to edit their items. For some reason, if there’s errors (i.e. required fields or items on the same level with the same name) they don’t always notice. I decided to make it a bit more noticeable, by creating one of those yellow bars on top of the item if there’s errors leading to not being able to go through workflow.

I figured there’d be something like Sitecore.Context.Item.Validation.IsValid or similar, but that wasn’t the case. There also wasn’t an existing Rule I could find that would give me whether the item was valid. Time to do some customization then!

Using Sitecore Rocks I created a class based on a Rule (Sidenote: Don’t have Sitecore Rocks yet? Get it here, now! It makes things a lot easier).

Insert through Rocks

Supposedly Sitecore would automatically make the Sitecore Item as well, but that didn’t happen in my case. That’s fine, I just created a Rule Item linking it up to my code.

rule item

As you can see, I could make an easy rule as I don’t need any variables apart from the item. I just want it to tell me when it’s either a critical error, fatal error or error.

The actual condition code I created was very easy, as I decided to make an extension method for the Item. This is the code for the condition:

publicclassItemValidCondition<T> : WhenCondition<T> where T : RuleContext
{
    protectedoverridebool Execute(T ruleContext)
    {
// Return true, if the condition is satisfied.
        return ruleContext.Item.IsValid();
    }
}

Then all I need to do is create the extension method. This was more tricky than I would’ve liked at first (although it ended up being fairly simple). I came across this blogpost by Aaron Blake:

That all seemed a bit too much to just validate an item. I ended up with something very similar – I just removed a lot of code, so I’d end up with the following:

var formValue = WebUtil.GetFormValue(“scValidatorsKey”);

if (!string.IsNullOrEmpty(formValue))
{
    var validators = ValidatorManager.GetValidators(ValidatorsMode.ValidatorBar, formValue);
    var options = newValidatorOptions(true);
    ValidatorManager.Validate(validators, options);

    foreach (BaseValidator baseValidator in validators)
    {
        if (baseValidator.Result == ValidatorResult.CriticalError ||
            baseValidator.Result == ValidatorResult.FatalError ||
baseValidator.Result == 
ValidatorResult.Error)
        {

            return false;
        }
    }
}

Initially, this seemed to work, but I ended up having 2 issues.

1 – Doesn’t show the errors in Sitecore Rocks – Rocks doesn’t have the scValidatorsKey since it’s not using a form to get the key out of.
2 – When browsing around I noticed it had a ‘delay’ of 1 item. When I selected an item I knew had an error it showed me that it was fine, when I then browsed to a different item (which I knew to be correct) it displayed my warning. I don’t know whether Aaron’s code also has this issue as I haven’t tested that, I just started modifying it.

What I thought was interesting was the ValidatorManager. I figured I could get the validators from the item that way then, instead of using the key.

That meant the GetValidators() method was out, since that requires a key. Other options are:
- GetGlobalValidatorsForItem
- GetItemValidators
- GetFieldsValidators

The GlobalValidatorsForItem returned some for me, but not the ones I expected. GetItemValidators returned none, which I kind of expected as I didn’t have any validators on the item – just on the fields.

I couldn’t figure out the second parameter in GetFieldsValidators though.

GetFieldsValidators

It doesn’t take in the actual fields, but an enumeration of FieldDescriptor. I didn’t want to create a FieldDescriptor for each field, so I decided to look further. If you have any information on this FieldDescriptor or how the GetFieldsValidator works, please leave it in a comment below.

I started some Googling again, and came across this StackOverflow question in which Stephen Pope mentions the ValidatorManager.BuildValidator method. That kind of does what I want, but I want to validate all rules, not just the one (or ones) I select. I want to keep in mind that if a validator gets added, I still want my validator to work. Turns out, there’s also a ValidatorManager.BuildValidators() method.

My final code for the extension method:

public static class ItemExtensions
{
    ///<summary>
    /// Check if the validators of the Workflow validators are valid
    ///</summary>
    ///<param name=”item”>The item</param>
    ///<returns>True if all validators return ValidatorResult.Valid. False otherwise</returns>
    public static bool IsValid(thisItem item)
    {
        var validators = ValidatorManager.BuildValidators(ValidatorsMode.Workflow, item);
        var validatorOptions = newValidatorOptions(true);
        ValidatorManager.Validate(validators, validatorOptions);

        foreach (BaseValidator baseValidator in validators)
        {
            if (baseValidator.Result == ValidatorResult.CriticalError ||
baseValidator.Result ==
ValidatorResult.FatalError ||
baseValidator.Result == 
ValidatorResult.Error)
            {
                return false;
            }
        }

        return true;
    }
}

Notice I’m using the ValidatorMode.Workflow, which is how I only validate the items in the worflow validation that’s been applied.

So now I could create my custom rule:

 rule

rule text

Which uses the existing action to show the warning.

And this is what it looks like in Sitecore Rocks:

result

Changing icons for insert options

Since my StackOverflow answer for that question seemed to be very popular, I figured I’d dedicate a blog post to the topic. 
The question basically boils down to “How can I change the icons for the insert options?”.

Image

Instead of the default images for the sublayout  and renderings when inserting a new component we’d like to see something more descriptive. This way it makes it a bit easier on content editors when they’re looking for a control in a long list – even if the name doesn’t make a lot of sense to them.

So what we can do is go to the sublayout or rendering, find the Appearance  section and select the Thumbnail image.

Image

‘Take screenshot’ opens a new window in which we can tell Sitecore to either use an item to browse to by selecting the item and the device or put in a URL. Sitecore then shoots off a request to the selected item, and renders it in the new window.

Image

Then, after selecting the proper area of the website, click OK. This is now selected as the image (which you can preview on the right-hand side).

Of course, if you already have images in your media library which you’d like to use, you can just select it using the Browse button. If the size isn’t correct, it’ll shrink/ stretch the image.

When we now try and insert a new component we get the following:

Image

Way better, I think.

Those screenshots will be saved in the Media Library, under the System node, then folders for the first 4 characters of it’s GUID:

Image

Sitecore Content Editor Woes

I decided to write this post after struggling with this for a while, and not being able to find a lot of help.

When a regular user (i.e. not a user with Admin rights) opened the Content Editor and clicked any of the options on the View tab they’d a completely white screen (in IE) or yellow screen with some XML error (FireFox), and the following error would show in the logfile:

ERROR Application error.
Exception: System.Web.HttpUnhandledException
Message: Exception of type ‘System.Web.HttpUnhandledException’ was thrown.
Source: System.Web
  at System.Web.UI.Page.HandleError(Exception e)
  at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
  at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
  at System.Web.UI.Page.ProcessRequest()
  at System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context)
  at System.Web.UI.Page.ProcessRequest(HttpContext context)
  at ASP.sitecore_shell_applications_content_manager_default_aspx.ProcessRequest(HttpContext context)
  at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
  at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Nested Exception

Exception: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
Source: mscorlib
  at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
  at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
  at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
  at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
  at Sitecore.Reflection.ReflectionUtil.InvokeMethod(MethodInfo method, Object[] parameters, Object obj)
  at Sitecore.Reflection.ReflectionUtil.CallMethod(Type type, Object obj, String methodName, Boolean includeNonPublic, Boolean includeInherited, Boolean includeStatic, Object[] parameters)
  at Sitecore.Reflection.ReflectionUtil.CallMethod(Object obj, String methodName, Boolean includeNonPublic, Boolean includeInherited, Object[] parameters)
  at Sitecore.Shell.Applications.ContentManager.ContentEditorPage.OnPreRender(EventArgs e)
  at System.Web.UI.Control.PreRenderRecursiveInternal()
  at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

Nested Exception

Exception: System.ArgumentNullException
Message: Null ids are not allowed.
Parameter name: displayName
Source: Sitecore.Kernel
  at Sitecore.Diagnostics.Assert.ArgumentNotNullOrEmpty(String argument, String argumentName)
  at Sitecore.Shell.Applications.ContentEditor.EditorFormatter.RenderSectionBegin(Control parent, String controlId, String sectionName, String displayName, String icon, Boolean isCollapsed, Boolean renderFields)
  at Sitecore.Shell.Applications.ContentEditor.EditorFormatter.RenderSection(Section section, Control parent, Boolean readOnly)
  at Sitecore.Shell.Applications.ContentEditor.EditorFormatter.RenderSections(Control parent, Sections sections, Boolean readOnly)
  at Sitecore.Shell.Applications.ContentEditor.Pipelines.RenderContentEditor.RenderStandardContentEditor.Process(RenderContentEditorArgs args)
  at (Object , Object[] )
  at Sitecore.Pipelines.PipelineMethod.Invoke(Object[] parameters)
  at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
  at Sitecore.Pipelines.CorePipeline.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists)
  at Sitecore.Pipelines.CorePipeline.Run(String pipelineName, PipelineArgs args, String pipelineDomain)
  at Sitecore.Pipelines.CorePipeline.Run(String pipelineName, PipelineArgs args)
  at Sitecore.Shell.Applications.ContentManager.Editor.Render(RenderContentEditorArgs args, Control parent)
  at Sitecore.Shell.Applications.ContentManager.Editor.Render(Item item, Item root, Hashtable fieldInfo, Control parent, Boolean showEditor)
  at Sitecore.Shell.Applications.ContentManager.ContentEditorForm.RenderEditor(Item item, Item root, Control parent, Boolean showEditor)
  at Sitecore.Shell.Applications.ContentManager.ContentEditorForm.UpdateEditor(Item folder, Item root, Boolean showEditor)
  at Sitecore.Shell.Applications.ContentManager.ContentEditorForm.Update()
  at Sitecore.Shell.Applications.ContentManager.ContentEditorForm.OnPreRendered(EventArgs e)

A message that’s very long, but also not very helpful in finding a solution.

The only helpful thing I found was http://egointeractive.posterous.com/argumentnullexception-null-ids-are-not-allowe

Which mentions the same thing happening because of denied read rights on templates.

That did fix the problem, giving me another one – some fields on some templates shouldn’t be visible for specific users. Luckily the answer to that one was an easy one: All I needed to do was deny the Field Read right, rather than the Read right.

The Field Read right can be altered by clicking the Columns button in the Security Editor, then ticking the Field Read box. After denying Field Read rights, the users now can view the standard values, but not the fields they’re not supposed to see.

Contextual Ribbons

In the Core database you can find the contextual ribbons item under /sitecore/content/Applications/Content Editor/Ribbons/Contextual Ribbons. The way I first understood it was that those ribbons will be based on some different template. Well, they don’t.

We can create a toolbar based on the /sitecore/templates/System/Ribbon/Toolbar template, create a Strip underneath (/sitecore/templates/System/Ribbon/Strip), create a Chunk underneath that (/sitecore/templates/System/Ribbon/Chunk) and finally a button (in my example a large button: /sitecore/templates/System/Ribbon/Large Button).

Let’s see how the ‘Images’ contextual ribbon works:
Image

This corresponds to the active menu below, when an image is selected in the media library:
Image

Note that because the Header field of the Image chunk is set to Media, both the strip and the chunk are called Media. These values will be taken from the Header field, not the name.

And this is selected in the Jpeg template (/sitecore/templates/System/Media/Unversioned/Jpeg), in the Ribbon field (in the Appearance section) :
Image

To create your own contextual ribbon, you can do the following:

Create a toolbar, strip, chunk and button:

Image

Toolbar only has the field ‘Sticky Chunk’. This seems to always be the first chunk in the tab – in this case the Save button in the Write chunk.
Image

In both the Strip item and Chunk item I’ve just put in the Header field (TestStrip and TestChunk in my case), and an ID identical to the Header.

For the Button I’ve copied the Click event and the icon from the View button of the Images ribbon, and set the header and ID to TestButton.

Now we can go into the Master database and go to the Standard Values of the template we want to add the contextual ribbon on. We can now either go to the Configure tab and select ‘Contextual Tab’ in the Appearance chunk, or go to the Ribbon field in the Appearance section.

This will give the following output:
Image

Of course we can create our own click event as well. All we need to do is define the command in the Commands.config file. For instance: <command name=”test:example” type=”Your.Example.Class, Your.Assembly” />

The class does need to inherit from Sitecore.Shell.Framework.Commands.Command, and implement the Execute method.

After this we can put the value of the click field to test:example

As always, comments and improvements are very welcome!

Sitecore Rocks, PowerShell and SVN

Rather than creating a new website from the Sitecore zipfile, I wanted to create a new Sitecore website based on the files we have in SVN. After the Sitecore Rocks demo on Sitecore Symposium Amsterdam, I decided to create something like that, using Rocks. 

To get started, I first needed to create a new task list. This can be done through the Sitecore Rocks extension in Visual Studio. It was fairly similar to the ‘Install Sitecore Website’ that comes with Sitecore Rocks, so I decided to copy that task list and change it for my needs. By going to ‘Manage Task Lists’ (under Sitecore->Task Lists->Manage Task Lists), right clicking on the task list and selecting ‘Open in Windows Explorer’ the path of all task lists is easily found.

I just copied the InstallSitecore task list, gave it a new name and edited the tasks.

First point of interest here is the *taskname*.tasklist.xml file. The value of the <name> node is very important, since this is what dictates which tasks get added to this list.

The XML files are the ‘layout’ of the tasks – they define the title, description and to which taskslist they belong to. They also contain the fields needed (if they are necessary). I just needed the fields which are in fieldtype ‘installationFolders’: 

  • Base folder
  • Project name
  • Project folder
  • Website folder
  • Database folder
  • Data folder

 

For my project, I came up with the following steps:

  • Create project folder
  • Create a new branch in SVN
  • Get the new branch from SVN
  • Switch the working copy to the new branch
  • Copy the licence files
  • Copy some files and folders that aren’t in SVN
  • Set Security
  • Build solution
  • Create IIS Site
  • Add Site to Host File
  • Install Sitecore Rocks
  • Warm Up
  • Open Sitecore

I won’t go into the ‘default’ Sitecore Rocks steps (in the InstallSitecore task), since they seem fairly straightforward. I did need to change the license file copying step – instead of having that in a field in Sitecore Rocks, I decided to just copy it from one of our development servers.

The whole project was actually a lot easier than expected. The only dependency is that I needed a client which allowed command-line scripts without input. I only had TortoiseSVN installed, which does need input, so I installed Slik SVN (http://sliksvn.com/en/download/) instead.

Create new branch

The only difficulty here is we need to check whether the branch exists beforehand. The rest is just copying and pasting of the trunk to a new location
$svnBranchRoot = https://location/of/your/branches/
$svnNewBranchName = $svnBranchRoot + $parameters["projectName"]
$svnTrunk=https://location/of/your/trunk

#Check if branch exists
If(Test-Path $svnNewBranchName) {
Write-Host “Error: $svnNewBranchName already exists.”
Exit(-1)
}

#Create branch
svn copy $svnTrunk $svnNewBranchName -m “Created new branch $svnNewBranchName”

Get new branch from SVN

All I needed was 3 lines of code:

$svnTrunk=https://location/of/the/trunk
$folder = $parameters["projectFolder"] + “\”
svn co $svnTrunk $folder

(co is just shorthand for checkout.)

If it doesn’t matter that input is needed, it can be done with TortoiseSVN as well, instead of the last line put this:
TortoiseProc.exe /command:checkout /url:$svnTrunk /path:$folder /closeonend:0

Note, if on either of the two commands you get an error, make sure the paths to their exe are added to the Environment Variables. Figuring this out took me a little while (and the help of my colleague @_shriroop_ )

This can be found under the properties of My Computer, Advanced System Settings, Environment Variables. I added mine to the system variable Path, but I don’t know what the recommended practice here is.

Switch the working copy

This is also as easy as it sounds:

$svnBranchRoot= https://location/of/your/branches/
$workingCopySVN = $svnBranchRoot + $parameters["projectName"]
$workingCopyFileSystem = $parameters["projectFolder"] + “\”

#Switch the working copy
svn switch $workingCopySVN $workingCopyFileSystem

Write-Output “Switched working copy”

Build solution

Lastly, I wanted to build the solution, since it needs to be built before the site can actually be called.
Here I’m just using msbuild:

$SlnFilePath = $parameters["projectFolder"] + “\Product\Saga.Sitecore.sln”
msbuild $SlnFilePath /t:rebuild /p:Configuration=Debug /p:Platform=”any cpu”

I realize this could’ve been done a little more dynamic, for instance by adding a field where one can specify where the trunk is located, but I decided against it, since our trunk hasn’t had a location change since we put it in place.

Overall, it really surprised me how easy this was done. Comments on how things could’ve been done otherwise would be very welcome of course.