Back

Hands-on with Sitecore Helix: Using PowerShell to add a new module

PowerShell plus Visual Studio equals Sitecore Helix

Embracing Sitecore Helix

Ever since I attended Anders Laub his presentation at SUGCON Europe 2015 about component based architecture in Sitecore solutions I have been a strong advocate of these modular principles. I even went to Pentia to learn about this in full detail.

I was very happy to see that Sitecore finally got their act together and published their Helix guidelines and recommended practices on the web.

For the last half year my team is using the modular Helix style architecture with success and it's time to share some experiences.

Go straight to the TLDR

Adding a new module with ease

Seeing a Sitecore Helix solution can be a bit daunting at first. The folder structure is quite deeply nested and developers need to have a good understanding what a module is composed of. Adding a new Feature or Foundation module to your Sitecore Helix solution is a time consuming and error prone task if you do it manually over and over again. Just as a learning experience it's good to know what needs to be done so please do have a look.

Since adding a module is such a repetative task it is a perfect candidate for automation. Currently there are two Yeoman based solutions to create modules; generator-habitat and generator-prodigious-helix.

Pros

Both these Yeoman generators allow you to create a new Feature or Foundation module with ease. Both create the folder structure and the Visual Studio projects. The main difference is that generator-habitat works with Unicorn and generator-prodigious-helix works with TDS.

Cons

The only drawback of both generators is that they don't update the solution file. So you manually need to add the generated projects into he solution (and create the module specific solution folder as well).

Because I'm lazy and don't want to do repetitive work I set out to find another solution for this.

PowerShell

Since I'm more familiair with PowerShell I used that instead of the Yeoman generators (I already invested quite some time in my own solution before I became aware of the Yeoman generators). Fairly quickly I had a script that would copy a template folder to the desired destination and replace tokens for the module name, namespaces and GUIDs.

The only thing left was adding the projects to the solution. First I took a very basic approach and started parsing the sln file since it's all plain text anyway. But it became quite a hassle to manage project relations and nesting of projects and solution folders with only GUIDs to work with. I needed a better solution. And then I met DTE.

DTE to the rescue

DTE (Development Tools Environment) or EnvDTE is the Visual Studio automation model and is used for Visual Studio extensions to manipulate the solution and it's projects. The DTE framework (COM based) is implemented across several EnvDTE*.dll and VSLangProj*.dll libraries depending on the version of Visual Studio you're running.

The SolutionFolder interface in the EnvDTE80 assembly captured my interest with the following methods:

In my initial attempts of using DTE I experienced quite some difficulties in creating the right types of objects and interfaces. This was probably due to not having the correct EnvDTE*.dll and VSLangProj*.dll assemblies loaded in my PowerShell script.

I found that the NuGet Package Manager Console in Visual Studio already has the proper assemblies loaded since it's also using DTE when adding new NuGet packages to the solution. Now I only needed to find a way to call my PowerShell script from the Package Manager Console.

NuGet profile

The PowerShell commands that can be used in the Package Manager Console are stored in a Profile.ps1 script located at C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\<someweirdID>\Modules\NuGet. Since that profile belongs to the console it's probably best to not touch that one because it could be overwritten during an update.

When you type $profile in the Package Manager Console you'll get the location of a user profile that can be used to extend the default one. In my case I got the following:

NuGet Profile Path

If you go to that location there might not be a profile file at all. You can then create an empty file and name it NuGet_profile.ps1.

When you make changes to this user profile while Visual Studio is open Visual Studio will not detect any changes. You can type & $profile in the Package Manager Console to reload the profile.

TLDR: My Add-HelixModule Solution

Add-HelixModule

  1. module-template, a folder containing code and config template files. This template is based on a Sitecore Habitat Feature which is stripped down significantly.
  2. add-helixmodule.ps1, a PowerShell script which creates a new Feature or Foundation module AND adds this to the current solution in Visual Studio.
  3. NuGet_profile.ps1, a PowerShell NuGet profile that is used by the Visual Studio Package Manager Console. This profile only loads the add-helixmodule.ps1.
  4. add-helix-module-configuration.json, a config file containing values for namespaces, location of the module-template and more.

You can see it working in my fork of Sitecore Habitat:

  • Clone my Sitecore Habitat fork.
  • Verify that you have a NuGet_profile.ps1 (use $profile to check the location).
  • Add the following to this profile and update the path to point to the add-helixmodule.ps1 file on your disk:

NuGet_profile.ps1

<# 
    Loads the add-helixmodule.ps1 script to enable the creation of Feature and Foundation project in Sitecore Helix solutions.
    
    You need to change this path to the location where the script is located on your local machine. 
    
    Once the script is loaded the Add-Feature and Add-Foundation methods are available in the Package Manager Console in Visual Studio.
#>
. "C:\dev\git\HabitatFork\scripts\add-helixmodule.ps1"
  • Open the Sitecore Habitat solution in Visual Studio.
  • Open the add-helix-module-configuration.json file.
  • Update the following values in that configuration file:
    • moduleTemplatePath. This is the absolute path to the module-template folder.
    • featureNamespacePrefix. This is the namespace prefix for new Feature modules (e.g. CompanyName.ClientName).
    • foundationNamespacePrefix. This is the namespace prefix for new Foundation modules (e.g. CompanyName).
    • sourceFolderName. This is the relative path to the location where the Feature/Foundation and Project folders are. For Sitecore Habitat this is //src.

add-helix-module-configuration.json

{
    "__comment": "This configuration file is used by the add-helix-module.ps1 script which creates modules for Sitecore Helix solutions.",
    "config": {
        "__comment__moduleTemplatePath": "Update the moduleTemplatePath property to point to your module-template location.",
        "moduleTemplatePath": "C:\\dev\\git\\HabitatFork\\module-template",
        "__comment__featureNamespacePrefix": "Replace the value for featureNamespacePrefix with a suitable namespace prefix. The Feature.<ModuleName> will be appended by the script.",
        "featureNamespacePrefix": "CompanyNamespace.ClientNamespace",
        "__comment__foundationNamespacePrefix": "Replace the value for featureNamespacePrefix with a suitable namespace prefix. The Foundation.<ModuleName> will be appended by the script.",
        "foundationNamespacePrefix": "CompanyNamespace",
        "__comment__sourceFolderName": "The sourcefolder should contain the relative path (from the sln file folder) where the Feature, Foundation and Project folders are located. The Sitecore Habitat default is '\\src'.",
        "sourceFolderName": "\\src",
        "__comment__fileExtensionsToUpdateContentRegex": "The regex in the fileExtensionsToUpdateContentRegex property is used to find the files which contain tokens which will be replaced with new values by the script.",
        "fileExtensionsToUpdateContentRegex": "(.config|.csproj|.cs|.cshtml|.feature|.js|.nuspec|.role|.sitecore|.targets)$",
        "__comment__fileExtensionsToUpdateProjectGuidsRegex" : "The regex in the fileExtensionsToUpdateProjectGuidsRegex property is used to find the files which need to have VS project GUIDs inserted.",
        "fileExtensionsToUpdateProjectGuidsRegex": "(.csproj|AssemblyInfo.cs)$",
        "__comment__": "The templateNamespacePrefix property contains the token which will be replaced with the value from either featureNamespacePrefix or foundationNamespacePrefix.",
        "templateNamespacePrefix": "_NamespacePrefix_",
        "__comment__templateModuleType": "The templateModuleType property contains the token variable which will be replaced with Feature or Foundation.",
        "templateModuleType": "_ModuleType_",
        "__comment__templateModuleName": "The templateModuleName property contains the token which will be replace with the actual ModuleName.",
        "templateModuleName": "_Name_",
        "__comment__templateProjectGuid": "The templateProjectGuid property contains the token which will be replaced by a new GUID and used as VS module project identifier.",
        "templateProjectGuid": "_ProjectGuid_",
        "__comment__templateTestProjectGuid": "The templateTestProjectGuid property contains the token which will be replaced by a new GUID and used as VS test project identifier.",
        "templateTestProjectGuid": "_TestProjectGuid_"
    }
}
  • Type Add-Feature or Add-Foundation in the Package Manager Console followed by the name of the module and hit enter.

Add-Feature Completed

Solution Explorer showing the added feature

In my next post I'll dig deeper into the inner workings of the add-helixmodule.ps1 PowerShell script.

So are we done now?

No. Currently the script only works with an existing Visual Studio solution that uses the Helix/Habitat folder structure so it heavily relies on folder names called Feature and Foundation.

Improvements I can think of now:

  • Make the script more robust/configurable so it works with other naming conventions instead on Feature/Foundation.
  • Add yml files for rendering and template folders similar as generator-habitat is doing.
  • Extend the script so it could also create a whole new Sitecore Helix based solution.
  • Use separate template structures for Feature and Foundation modules.

A whole different approach will be to investigate the experimental vs-net-dte and gulp-notify-dte projects in order to get DTE to work with Gulp. Please do let me know if you have plans to dig into this :).