This is post 5 of 7 in a series titled Development environments with PowerShell DSC
- Development environments with PowerShell DSC – Introduction to DSC
- Development environments with PowerShell DSC – Windows Features
- Development environments with PowerShell DSC – Mongo DB
- Development environments with PowerShell DSC – SQL Server
- Development environments with PowerShell DSC – Sitecore
- Development environments with PowerShell DSC – Coveo CES
- Development environments with PowerShell DSC – Coveo REST API & Coveo for Sitecore
So, finally, we've got the prerequisites (Windows,
Mongo,
SQL) out of the way, we can get to installing Sitecore in this post. There are a load of ways of going about this, but my usual choice is automating the Sitecore
.exe
installer. Doing this via DSC gives you the basis of an installation which can be used across all your platforms. The process below is based on the approach I've used with ordinary PowerShell in the past, but adapted for DSC:
Before I get going, couple of notes about the examples I'm including in this series of posts: They all have a call to `Start-DSCConfiguration` using the "-Force" flag to make DSC run this configuration immediately in "push" mode. However, the `Configuration` blocks declared should all work in pull configurations. Writing the scripts this way just makes them easier for people to try out without any other setup effort. Also the examples are all pretty much self-contained, so the overall configuration of a server using them would rely on many separate scripts. You don't necessarily need to have as many as I'm showing here. It's more to keep the examples clear and usable than to represent the "right" architecture. You're free to merge things together, or even break them apart further if that suits you.
@{ AllNodes = @( @{ NodeName = "WIN-AQEKG7L9SE8" Role = "Setup, WindowsFeatures, IE, SqlServer, MongoDB, Sitecore" TempFolder = "c:\dsc" WWWRoot = "C:\inetpub\wwwroot" Sitecore = @{ Installer = "Sitecore 8.0 rev. 150812.exe" License = "PartnerLicense-2015.xml" InstanceName = "eight" SQLServer = "localhost" SQLUser = "sa" SQLPassword = "p@55w0rd" PackageInstallFile = "PackageDeploy.aspx" } } ); }
This adds the disk path to the WWWRoot folder. This could be part of the Sitecore config block itself, but it struck me that this was something I'd end up using for things other than just Sitecore. Inside the Sitecore config block, the properties specify the name of the installer to use, the license file we're using and an instance name to install. It also specifies the SQL server details to be used by Sitecore, and the ASPX file that will be used to enable us to install packages.
Configuration SitecoreInstall { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $PackagePath ) Node $AllNodes.where{ $_.Role.Contains("Sitecore") }.NodeName { # # We'll put the resources here # } }
The first thing we need to do is copy over the Sitecore installer and license files, which can be done with the
File
resource:
File SitecoreInstaller { SourcePath = "$PackagePath\$($Node.Sitecore.Installer)" DestinationPath = "$($Node.TempFolder)\$($Node.Sitecore.Installer)" Type = "File" Ensure = "Present" } File SitecoreLicense { SourcePath = "$PackagePath\$($Node.Sitecore.License)" DestinationPath = "$($Node.TempFolder)\$($Node.Sitecore.License)" Type = "File" Ensure = "Present" }
As discussed in
my previous posts about automating Sitecore installs, we need to get the
.MSI
package out of the Sitecore installer in order to automate it. We can do that with a script resource. I've not implemented the
GetScript
property for this resource, as it wasn't immediately obvious what it should return. The
TestScript
just checks if our temp folder already includes the "SupportFiles" folder that this process should create:
Script ExtractSC8 { DependsOn = "[File]SitecoreInstaller" GetScript = { } TestScript = { $tmp = $using:Node.TempFolder Test-Path "$using:tmp\SupportFiles" } SetScript = { $tmp = $using:Node.TempFolder $installer = $using:Node.Sitecore.Installer Push-Location $tmp $path = "$tmp\$installer" &$path /q /ExtractCab | out-null Pop-Location } }
The
SetScript
is pretty simple - it works out the location of our configured temp folder and the Sitecore installer. Then it changes directory to the temp folder and executes the installer passing in flags for "quiet" and "extract". That results in a folder under temp containing the
.MSI
as well as a few other bits which we can ignore.
So the next step is to run the install. Again the
GetScript
is blank here. I think it should probably return something about any instance installed with our defined name - but that's a task for another day. For the
TestScript
we're checking if an instance is already installed with the specified name by checking if a folder exists for it under the website root folder we configured. That's enough for us to decide whether to proceed with the install.
The
SetScript
for the install is a bit more complex here. This resource has been written to install v8 of Sitecore, but this could be easily adjusted to work with other versions.
Script SC8 { DependsOn = "[Script]ExtractSC8, [file]SitecoreLicense" GetScript = { } TestScript = { $wwwRoot = $using:Node.WWWRoot $instance = $using:Node.Sitecore.InstanceName $iisFolder = "$using:wwwRoot\$using:instance" Test-Path $iisFolder } SetScript = { $registryPath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\" if(-not (Test-path $registryPath)){ throw "The instance-testing path in the registry could not be found - not a 64bit machine?" } $scRegistryPath = "$registryPath\Sitecore CMS\" $instanceNumber = "1" if( Test-path $scRegistryPath ){ Push-Location $scRegistryPath $lastID = dir | Get-ItemProperty -Name InstanceID | Sort-Object -property InstanceID -Descending | Select-Object -ExpandProperty InstanceID -First 1 if( -not [string]::IsNullOrWhiteSpace($lastID)){ Write-Verbose "Previous instance found: $lastID " $instanceString = $lastID.Remove(0,10) $instanceNumber = [int]::Parse($instanceString) $instanceNumber = $instanceNumber + 1 } else { Write-Verbose "No previous instance found" } Pop-Location } else { Write-Verbose "No previous instance found" } Write-Verbose "Using instance number: $instanceNumber" $instanceID = "InstanceId$instanceNumber" $tmp = $using:Node.TempFolder $licenseFile = $using:Node.Sitecore.License $license = "$tmp\$licenseFile" $site = $using:Node.Sitecore.InstanceName $siteAppPool = "$($site)_AppPool" $sitePrefix = "$($site)_" $sqlServer = $using:Node.Sitecore.SQLServer $sqlUser = $using:Node.Sitecore.SQLUser $sqlPassword = $using:Node.Sitecore.SQLPassword $wwwRoot = $using:Node.WWWRoot msiexec.exe /qn /i "$tmp\SupportFiles\exe\Sitecore.msi" "TRANSFORMS=:$instanceID;:ComponentGUIDTransform5.mst" "MSINEWINSTANCE=1" "LOGVERBOSE=1" "SC_LANG=en-US" "SC_FULL=1" "SC_INSTANCENAME=$site" "SC_LICENSE_PATH=$license" "SC_SQL_SERVER_USER=$sqlUser" "SC_SQL_SERVER=$sqlServer" "SC_SQL_SERVER_PASSWORD=$sqlPassword" "SC_DBPREFIX=$sitePrefix" "SC_PREFIX_PHYSICAL_FILES=1" "SC_SQL_SERVER_CONFIG_USER=$sqlUser" "SC_SQL_SERVER_CONFIG_PASSWORD=$sqlPassword" "SC_DBTYPE=MSSQL" "INSTALLLOCATION=$wwwRoot\$site" "SC_DATA_FOLDER=$wwwRoot\$site\Data" "SC_DB_FOLDER=$wwwRoot\$site\Databases" "SC_MDF_FOLDER=$wwwRoot\$site\Databases\MDF" "SC_LDF_FOLDER=$wwwRoot\$site\Databases\LDF" "SC_NET_VERSION=4" "SITECORE_MVC=0" "SC_INTEGRATED_PIPELINE_MODE=1" "SC_IISSITE_NAME=$site" "SC_IISAPPPOOL_NAME=$siteAppPool" "SC_IISSITE_HEADER=$site" "SC_IISSITE_PORT=80" "SC_IISSITE_ID=" "/l*+v" "$tmp\Install.log" | out-null } }
The first job it needs to do is work out the "instance number" property that needs to be passed to the installer command. The process for working this out here is just a DSC translation of the process discussed in my previous post on this parameter. (This post also explains why we need it)
The code checks for the existence of the registry keys that the Sitecore installer creates when it runs. If the registry path exists, then the largest instance ID can be extracted and incremented to get the one for us to use. If the registry path doesn't exist, then we can assume no previous installs have happened and we can default to instance one.
Once the instance number has been found, the code extracts all the config variables that the install requires.
Then finally, it runs
msiexec.exe
, passing in the
.MSI
file we generated and all the relevant properties to let Windows run the installation. Once this completes, Sitecore is ready to run.
File AddRemoteDeploy { DependsOn = "[script]SC8" SourcePath = "$PackagePath\$($Node.Sitecore.PackageInstallFile)" DestinationPath = "$($Node.WWWRoot)\$($Node.Sitecore.InstanceName)\website\$($Node.Sitecore.PackageInstallFile)" Type = "File" Ensure = "Present" }
Now, one key aspect to getting packages installed correctly via this route is the response time of Sitecore when you make one of these requests. Especially with large packages and Sitecore 8, it's quite possible to end up with your request for the package to be installed timing out. Hence it's sensible to make sure Sitecore is started up before you try to install anything. And this can be done with another script resource:
Script WarmSitecore { DependsOn = "[script]SC8" GetScript = { } TestScript = { $false } SetScript = { $url = "http://$($using:Node.Sitecore.InstanceName)/" Write-Verbose "Starting Sitecore..." $result = $null do { Write-Verbose " -- trying to start Sitecore -- " try { $result = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 0 } catch { Write-Verbose " -- start failed - retying [$($result.StatusCode)] -- " } } while($result.StatusCode -ne 200) Write-Verbose "Public site started..." $url = $url + "sitecore/" do { Write-Verbose " -- Retry starting Admin -- " try { $result = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 0 } catch { Write-Verbose " -- start failed - retying [$($result.StatusCode)] -- " } } while($result.StatusCode -ne 200) Write-Verbose "Admin site started..." } }
The
GetScript
and
TestScript
properties aren't filled in here, as they don't really make sense to me in this context. The
SetScript
makes HTTP requests to the sitecore public site until we get back a "success" response. (Whilst we're telling
Invoke-WebRequest
to apply no timeout, you do still get timeout exceptions from this code - hence the try/catch block) We then repeat the same process to load the Sitecore admin site as well.
And that's Sitecore ready to go for your development.
↑ Back to top