This post is all about creating a Helix based solution from scratch. Please watch the video for additional information and a demo of the script. I really discourage people from using Habitat as the base to create new solutions. Hopefully this script will help.
Also refer to Marc Duiker’s Hands-on with Sitecore Helix: Anatomy of the Add-HelixModule.ps1 PowerShell script.
So in order to create a Helix solution from scratch, we do not have any tool at this moment. We either manually build it or use Habitat as a base. I do not recommend using Habitat and modifying that solution to fit your needs.
To start of with, the best way to automate this is to use PowerShell scripts inside of the Visual Studio IDE via the Package Manager Console. We need to use DTE (EnvDTE).
Download the HabitatSolutionScript.ps1 from https://gist.github.com/akshaysura/184132f67decb749ae235b602351832c
Open Visual Studio IDE
Open Package Manager Console
Type in $profile
Open that location in Windows explorer. You might not have NuGet_profile.ps1 at that location. Create one.
Open the NuGet_profile.ps1 and insert the following line and modify the path to your HabitatSolutionScript.ps1
. “D:\Projects\PowerShell\HabitatSolutionScript.ps1”
Save and close NuGet_profile.ps1
Restart your Visual Studio IDE and open Package Manager Console
Type in let and tab or you could type in the whole command Lets-Rumble
Follow the on screen prompts and enjoy your solution. I would recommend watching the video for additional information.
The script is not tested so please use it at your own risk. Since it required an empty folder, it won’t affect your existing code/solutions.
Please contribute to this script and hopefully we can make it better for the entire community.
#paths to the project templates on your system $global:classTemplate = "D:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\ProjectTemplatesCache\CSharp\Windows Root\Windows\1033\ClassLibrary\csClassLibrary.vstemplate" $global:webTemplate = "D:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\ProjectTemplatesCache\CSharp\Web\1033\WebApplicationProject40\EmptyWebApplicationProject40.vstemplate" #variable used to store the path for the empty Habitat folder $global:helixPath ="" #empty variable used to store the solution name $global:solutionName = "" #empty variable used to store the project names like Habitat to which we will add .Website $global:projectNames = New-Object System.Collections.ArrayList #empty variable used to store the feature name prefix like Sitecore.Foundation to which we will add foundation projects $global:foundationNamePrefix = "" #list of foundation projects to create $global:foundationNames = New-Object System.Collections.ArrayList #empty variable used to store the feature name prefix like Sitecore.Feature to which we will add feature projects $global:featureNamePrefix = "" #list of features to create $global:featureNames = New-Object System.Collections.ArrayList #Setup the folders to create $global:folderPaths = @("\etc","\lib","\scripts","\specs","\src\Feature","\src\Foundation","\src\Project","\src\Project\Common\code","\src\Project","\src\Project\Common\code","\src\Project\Common\serialization") $global:projectPath = "\src\Project\" $global:foundationPath = "\src\Foundation\" $global:featurePath = "\src\Feature\" $global:srcFolder = "\src" $global:codeFolder = "\code" $global:rolesFolder = "\roles" $global:serializationFolder = "\serialization" $global:testsFolder = "\Tests" $global:testProjectName = "Tests" $global:websiteName = "Website" $global:ConfigFolderName = "Configuration" $global:FeatureFolderName = "Feature" $global:FoundationFolderName = "Foundation" $global:ProjectFolderName = "Project" $global:solution = $null <# Prompt to confirm solution creation. #> function Confirm-Creation { param ( [string]$Title = 'Create Helix Solution?' ) Write-Host "================ $Title ================" Write-Host "1: Continue" Write-Host "q: Press 'q' to Quit" } <# Prompt to accept Projects #> function Accept-Projects { param ( [string]$Title = 'Enter Project Names/Tenants/Sites - Site1 etc' ) cls Write-Host "================ $Title ================" Write-Host "1: Create a Project." Write-Host "q: Press 'q' to exit this prompt and continue to create Foundation projects." } <# Prompt to accept Foundation projects #> function Accept-FoundationProjects { param ( [string]$Title = 'Enter Foundation Project Name(s)' ) cls Write-Host "================ $Title ================" Write-Host "1: Create a Foundation Project." Write-Host "q: Press 'q' to exit this prompt and continue to create Feature projects." } <# Prompt to accept Feature projects #> function Accept-FeatureProjects { param ( [string]$Title = 'Enter Feature Project Name(s)' ) cls Write-Host "================ $Title ================" Write-Host "1: Create a Feature Project." Write-Host "q: Press 'q' to exit this prompt and continue." } <# Purpose is get the information for the folder in which we are going to create the folder structure #> function Get-HabitatFoundationFolder { #Get path and validate the path $global:helixPath = $input = Read-Host "Please specify the path to an empty folder" if($global:helixPath -eq "") { Write-Host "Select a valid folder path." exit } if ( -not (Test-Path $global:helixPath -PathType Container) ) { Write-Host "Select a valid folder." exit } $directoryInfo = Get-ChildItem $global:helixPath | Measure-Object if ($directoryInfo.count -gt 0) { Write-Host "Folder is not empty." exit } } <# Purpose is to create the base folder structure of the Habitat solution. Step 1. #> function Create-HabitatFoundationFolders { foreach ($path in $global:folderPaths) { $newFolder = $global:helixPath + $path New-Item -ItemType Directory -Path $newFolder -Force } $solution = Get-Interface $dte.Solution ([EnvDTE80.Solution2]) $parentProject = $solution.AddSolutionFolder($global:FoundationFolderName) $parentSolutionFolder = Get-Interface $parentProject.Object ([EnvDTE80.SolutionFolder]) foreach ($foundation in $global:foundationNames) { $newFolder = $global:helixPath + $global:foundationPath + $foundation + $global:codeFolder $projectName = $global:foundationNamePrefix + "." + $foundation $testsProjectName = $global:foundationNamePrefix + "." + $foundation + "." + $global:testProjectName $childProject = $parentSolutionFolder.AddSolutionFolder($foundation) $childSolutionFolder = Get-Interface $childProject.Object ([EnvDTE80.SolutionFolder]) $projectFile = $childSolutionFolder.AddFromTemplate($global:webTemplate,$newFolder, $projectName); $newFolder = $global:helixPath + $global:foundationPath + $foundation + $global:rolesFolder New-Item -ItemType Directory -Path $newFolder -Force $newFolder = $global:helixPath + $global:foundationPath + $foundation + $global:serializationFolder New-Item -ItemType Directory -Path $newFolder -Force $newFolder = $global:helixPath + $global:foundationPath + $foundation + $global:testsFolder $projectFile = $childSolutionFolder.AddFromTemplate($global:classTemplate,$newFolder, $testsProjectName); } $solution = Get-Interface $dte.Solution ([EnvDTE80.Solution2]) $parentProject = $solution.AddSolutionFolder($global:FeatureFolderName) $parentSolutionFolder = Get-Interface $parentProject.Object ([EnvDTE80.SolutionFolder]) foreach ($feature in $global:featureNames) { $newFolder = $global:helixPath + $global:featurePath + $feature + $global:codeFolder $projectName = $global:featureNamePrefix + "." + $feature $testsProjectName = $global:featureNamePrefix + "." + $feature + "." + $global:testProjectName $childProject = $parentSolutionFolder.AddSolutionFolder($feature) $childSolutionFolder = Get-Interface $childProject.Object ([EnvDTE80.SolutionFolder]) $projectFile = $childSolutionFolder.AddFromTemplate($global:webTemplate,$newFolder, $projectName); $newFolder = $global:helixPath + $global:featurePath + $feature + $global:rolesFolder New-Item -ItemType Directory -Path $newFolder -Force $newFolder = $global:helixPath + $global:featurePath + $feature + $global:serializationFolder New-Item -ItemType Directory -Path $newFolder -Force $newFolder = $global:helixPath + $global:featurePath + $feature + $global:testsFolder $projectFile = $childSolutionFolder.AddFromTemplate($global:classTemplate,$newFolder, $testsProjectName); } $solution = Get-Interface $dte.Solution ([EnvDTE80.Solution2]) $parentProject = $solution.AddSolutionFolder($global:ProjectFolderName) $parentSolutionFolder = Get-Interface $parentProject.Object ([EnvDTE80.SolutionFolder]) foreach ($project in $global:projectNames) { $newFolder = $global:helixPath + $global:projectPath + $project + $global:codeFolder $projectName = $global:solutionName + "." + $project + "." + $global:websiteName $parentSolutionFolder = Get-Interface $parentProject.Object ([EnvDTE80.SolutionFolder]) $childProject = $parentSolutionFolder.AddSolutionFolder($project) $childSolutionFolder = Get-Interface $childProject.Object ([EnvDTE80.SolutionFolder]) $projectFile = $childSolutionFolder.AddFromTemplate($global:webTemplate,$newFolder, $projectName); $newFolder = $global:helixPath + $global:projectPath + $project + $global:rolesFolder New-Item -ItemType Directory -Path $newFolder -Force $newFolder = $global:helixPath + $global:projectPath + $project + $global:serializationFolder New-Item -ItemType Directory -Path $newFolder -Force } Write-Host "Done creating Habitat Foundation Structure." } <# Purpose is to accept inputs #> function Accept-VSSolutionNames { do { cls $input = Read-Host "Please specify you solution name. This could be the company name or overall solution name. e.g. Acme or MSFT " if([string]::IsNullOrWhiteSpace($input)) { Write-Host "Empty solution name. Try again." } else { $global:solutionName = $input } } until (-not([string]::IsNullOrWhiteSpace($global:solutionName))) do { $global:foundationNamePrefix = Read-Host "Please specify you foundation name prefix. e.g."$global:solutionName".Foundation" if([string]::IsNullOrWhiteSpace($global:foundationNamePrefix)) { Write-Host "Empty foundation name prefix. Try again." } } until (-not([string]::IsNullOrWhiteSpace($global:foundationNamePrefix))) do { $global:featureNamePrefix = Read-Host "Please specify you feature name prefix. e.g."$global:solutionName".Feature" if([string]::IsNullOrWhiteSpace($global:featureNamePrefix)) { Write-Host "Empty feature name prefix. Try again." } } until (-not([string]::IsNullOrWhiteSpace($global:featureNamePrefix))) do { Accept-Projects $input = Read-Host "Enter your Project name/Tenant/Site name or q to continue. (Habitat or Site1)" if ($input -eq 'q') { break } elseif (-not([string]::IsNullOrWhiteSpace($input))) { $global:projectNames.Add($input) #create foundation project } } until ($input -eq 'q') do { Accept-FoundationProjects $input = Read-Host "Enter your Foundation project name or q to continue." if ($input -eq 'q') { break } elseif (-not([string]::IsNullOrWhiteSpace($input))) { $global:foundationNames.Add($input) #create foundation project } } until ($input -eq 'q') do { Accept-FeatureProjects $input = Read-Host "Enter your Feature project name or q to continue." if ($input -eq 'q') { break } elseif (-not([string]::IsNullOrWhiteSpace($input))) { $global:featureNames.Add($input) #create feature project } } until ($input -eq 'q') } <# Confirm selections/input #> function Confirm-VSSolutionNames { cls Write-Host "Solution Name:" $global:solutionName Write-Host "Project Names:" $global:projectNames Write-Host "Foundation Name Prefix:" $global:foundationNamePrefix Write-Host "Feature Name Prefix:" $global:featureNamePrefix Write-Host "Foundation Projects:" $global:foundationNames Write-Host "Feature Projects:" $global:featureNames Confirm-Creation $input = Read-Host "Enter 1 to continue or q to exit." if ($input -eq 'q') { exit } elseif ($input -eq '1') { $dte.Solution.Create($global:helixPath, $global:solutionName) $global:solution = $dte.Solution $dte.ExecuteCommand("File.SaveAll") Create-HabitatFoundationFolders $solution = Get-Interface $dte.Solution ([EnvDTE80.Solution2]) $parentFolder = $solution.AddSolutionFolder($global:ConfigFolderName) $dte.ExecuteCommand("File.SaveAll") } } <# Start function #> function Lets-Rumble { Write-Host " ) ( ( " Write-Host " ( /( ( )\ ) ( ) )\ ) ) " Write-Host " )\()) ( )\( ) (()/( )\ ( ( /(( (()/( ( ( ( /( " Write-Host "((_)\ ))((_)\ ( /( /(_)) ( ((_)))\ )\())\ ( ( /(_)) ( )( )\ ` ) )\()) " Write-Host " _((_)/((_)((_))\()) (_)) )\ _ /((_|_))((_) )\ )\ ) (_)) )(()((_)/(/( (_))/ " Write-Host "| || (_))| |(_|(_)\ / __| ((_) (_))(| |_ (_)((_)_(_/( / __| ((_|(_|_|(_)_\| |_ " Write-Host "| __ / -_) || \ \ / \__ \/ _ \ | || | _|| / _ \ ' \)) \__ \/ _| '_| | '_ \) _| " Write-Host "|_||_\___|_||_/_\_\ |___/\___/_|\_,_|\__||_\___/_||_| |___/\__|_| |_| .__/ \__| " Write-Host " |_| " Get-HabitatFoundationFolder Accept-VSSolutionNames Confirm-VSSolutionNames }
If you have any questions or concerns, please get in touch with me. (@akshaysura13 on twitter or on Slack).
Nice one Akshay!!! Thanks for sharing!!!
Hi Akshay,
Please update the command name in Article from “Lets_Rumble” to “Lets-Rumble”. It seems a typo.
Updated. Thank you.
Ho Akshay very nice script!! next step integrate with Sitecore Rocks to generate Data templates and rendering and it will rocks 🙂
Hi Akshay, I have followed your instruction above, but i have an issue ‘Lets-Rumble’ is not recognized as the name of a cmdlet. i have followed instruction from https://nshackblog.wordpress.com/tag/powershell/ but the error still same. any advice?
It sounds like its not registering with in your Visual Studio. I wouldn’t know unless I troubleshoot it.
Anyone using VS 2017 can use these paths to update the script
$global:classTemplate = “C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\ProjectTemplates\CSharp\Windows\1033\ClassLibrary\csClassLibrary.vstemplate”
$global:webTemplate = “C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\ProjectTemplates\CSharp\Web\1033\WebApplicationProject40\EmptyWebApplicationProject40.vstemplate”
@Rayo you might have missed a dot “.”
Open the NuGet_profile.ps1 and insert the following line and modify the path to your HabitatSolutionScript.ps1
. “D:\Projects\PowerShell\HabitatSolutionScript.ps1”
I am getting this error “Lets-Rumble : The term ‘Lets-Rumble’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again”. I tried to troubleshoot as suggested here https://nshackblog.wordpress.com/tag/powershell/ but still it is not working
Thank you for the comment. Please make sure you follow the process of adding the scripts at the right location. Most of the time the issue is how they are installed. Thanks.
I did npot understand the following line.
Where should i insert the following line? Is it at the top of Nuget_profile.ps1?Can you be m,ore clear on that?
Open the NuGet_profile.ps1 and insert the following line and modify the path to your HabitatSolutionScript.ps1
. “D:\Projects\PowerShell\HabitatSolutionScript.ps1”