For modern businesses, it’s extremely important to be flexible in order to satisfy the constantly changing customer needs. That’s why more and more companies turn to the evolving cloud computing services, which provide scalability, optimize connectivity and improve performance.
Sitecore is not an exception, as it allows you to deploy solutions on the Azure Platform as a Service (PaaS). For a new Sitecore environment, you can use out-of-the-box web deploy packages (WDP). But to deploy your existing (local) solution, you need to initially package it into a WDP.
In this article, I will tell you how you can package your local Sitecore Habitat into web deploy package (WDP). You will also see what issues you might run into during the process.
Guideline
Let’s imagine that you have a local Sitecore instance (in my case Sitecore Habitat) and you would like to deploy this instance into Microsoft Azure as PaaS.
For this purpose, you can use a special tool — Sitecore Azure Toolkit.
Main Sitecore Azure Toolkit features:
- Packaging of Sitecore instances into role-specific packages using PowerShell command line programs
- Integration with Microsoft Azure services
- ARM templates for topologies such as XM, XP, XPSingle
- Configuration for running on the Azure App Service
- Security features provided by HTTPS, low-privileged SQL access, Sitecore password
Download Sitecore Azure Toolkit current version 2.2 here.
Step 1
To start working on this tool, you just need to extract the downloaded archive to a folder. In my case “C:\Sitecore\Azure Toolkit”.
Also, don’t forget to check that you have all the required prerequisites:
- A cloud-hosted MongoDB cluster for the xDB Collection and Tracking databases that are used with XP and XP0. Note: this is a prerequisite for versions earlier than 9.0 only.
- .NET Framework 4.6
- PowerShell 4.0
- Microsoft Azure PowerShell 2.0.1 or later.
- Microsoft SQL Server Data-Tier Application Framework (DacFX) for SQL server 2012 or later. Note: this framework is usually installed with SQL Server or Visual Studio.
Step 2
Then, open the root folder where the toolkit is unpacked and load the main module into a PowerShell terminal :
Import-Module .\ tools \ Sitecore . Cloud.Cmdlets.psm1 -Verbose
This module has some commandlets responsible for:
- Packaging a module into WDP
- Packaging a local instance into WDP
- Deployment onto Azure
Our main goal is to make a Sitecore web deploy package ( SCWDP ) based on local Sitecore Habitat instance. Run the following commandlet to achieve this goal:
Start-SitecoreAzurePackaging
This command contains parameters required for executing:
- SitecorePath — the path to either a folder where the Sitecore installation should be packaged or to a folder in a zip file.
- DestinationFolderPath — the path to the folder for generated packages storage.
- CargoPayloadFolderPath — the path to the folder with the version-specific role and feature transformation files.
- CommonConfigPath — the path to the file with a list of the transformation files to be applied to all of the roles.
- SkuConfigPath — the path to the file with lists of role-specific transformations for the selected version and the Sitecore configuration on the Microsoft App Service.
- ParameterXmlPath — the path to the WebDeploy Sitecore version-specific archive manifest and parameter declaration files.
- FileVersion — an optional parameter for embedding a version marker in the generated WebDeploy packages.
I will note one more optional parameter a bit later.
Step 3
You should have your local instance on your dev machine. On my local machine, I have a Habitat Sitecore instance.
The website is located in C:\inetpub\wwwroot\habitat.dev.local and published on IIS as habitat.dev.local.
As for my local habitat SQL databases — they all are located in another VM machine.
Step 4
Now, you have to define the destination folder path. I would like to save the generated Sitecore WDP package into C:\Sitecore\WDPs.
I’ve already mentioned that the Azure toolkit is unpacked into:
Let’s populate all the parameters we need to run with Start-SitecoreAzurePackaging command:
1 | $sitecorePath = "C:\inetpub\wwwroot\habitat.dev.local" |
2 | $destinationFolderPath = "C:\Sitecore\WDPs" |
3 | $cargoPayloadFolderPath = "C:\Sitecore\Azure Toolkit\resources\9.1.1\CargoPayloads" |
4 | $commonConfigPath = "C:\Sitecore\Azure Toolkit\resources\9.1.1\Configs\common.packaging.config.json" |
5 | $skuConfigPath = "C:\Sitecore\Azure Toolkit\resources\9.1.1\Configs\XPSingle.packaging.config.json" |
6 | $parameterXmlPath = "C:\Sitecore\Azure Toolkit\resources\9.1.1\MsDeployXmls" |
And define these parameters with a command:
1 | Start -SitecoreAzurePackaging -sitecorePath $sitecorePath -destinationFolderPath $destinationFolderPath ` |
2 | -cargoPayloadFolderPath $cargoPayloadFolderPath -commonConfigPath $commonConfigPath ` |
3 | -skuConfigPath $skuConfigPath -parameterXmlPath $parameterXmlPath -fileVersion $fileVersion |
Below, you can see how it looks like in my PowerShell ISE terminal session.
Step 5
Finally, it seems that we are ready to click on the green button to run Sitecore Azure Packaging!
But take your time! I instantly caught an error:
The following is the error text:
1 | Connecting to database 'habitat_Core' on server 'test-server' . |
3 | Extracting schema from database |
4 | New -SCWebDeployPackage : Connecting to database 'habitat_Core' on server 'test-server' . |
6 | Extracting schema from database |
7 | At C:\Sitecore\Azure Toolkit\tools\Sitecore.Cloud.Cmdlets.psm1:195 char:49 |
8 | + ... ckagePath = New -SCWebDeployPackage -Path $SitecorePath -Destination $ ... |
9 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
10 | + CategoryInfo : NotSpecified: (Connecting to d...a from database:String) [New -SCWebDeployPackage ], Exception |
11 | + FullyQualifiedErrorId : Microsoft.Extensions.Logging.EventId,Sitecore.Cloud.Cmdlets.Packaging.NewSCWebDeployPackage |
12 | System.IO.FileNotFoundException: Could not find file 'C:\Users\vap\AppData\Local\Temp\tempDacPacs1d5912bf-c65a-4c53-8f70-e8a335d6ea37\Sitec |
14 | File name: 'C:\Users\vap\AppData\Local\Temp\tempDacPacs1d5912bf-c65a-4c53-8f70-e8a335d6ea37\Sitecore.core.dacpac' |
15 | at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) |
16 | at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 buff |
17 | erSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost) |
18 | at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Str |
19 | ing msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost) |
20 | at System.IO.File.InternalReadAllBytes(String path, Boolean checkHost) |
21 | at Sitecore.Cloud.Package.Common.FileSystemProvider.ReadFile(String fileName) |
22 | at Sitecore.Cloud.Packaging.WebDeployPackages.DatabasePackager.PackageDatabases(XDocument connStringFile, Boolean integratedSecurity) |
23 | at Sitecore.Cloud.Packaging.WebDeployPackages.WebDeployPackageBuilder.Build(SitecoreInstallationFolderTree scInstFoldTree, DirPath outpu |
24 | tDir, String targetFileName, Boolean force, String version, Boolean integratedSecurity) |
25 | at Sitecore.Cloud.Cmdlets.Packaging.NewSCWebDeployPackage.ProcessRecord() |
26 | New -SCWebDeployPackage : System.IO.FileNotFoundException: Could not find file 'C:\Users\vap\AppData\Local\Temp\tempDacPacs1d5912bf-c65a-4c |
27 | 53-8f70-e8a335d6ea37\Sitecore.core.dacpac'. |
28 | File name: 'C:\Users\vap\AppData\Local\Temp\tempDacPacs1d5912bf-c65a-4c53-8f70-e8a335d6ea37\Sitecore.core.dacpac' |
29 | at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) |
30 | at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 buf |
31 | ferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost) |
32 | at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, St |
33 | ring msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost) |
34 | at System.IO.File.InternalReadAllBytes(String path, Boolean checkHost) |
35 | at Sitecore.Cloud.Package.Common.FileSystemProvider.ReadFile(String fileName) |
36 | at Sitecore.Cloud.Packaging.WebDeployPackages.DatabasePackager.PackageDatabases(XDocument connStringFile, Boolean integratedSecurity) |
37 | at Sitecore.Cloud.Packaging.WebDeployPackages.WebDeployPackageBuilder.Build(SitecoreInstallationFolderTree scInstFoldTree, DirPath outp |
38 | utDir, String targetFileName, Boolean force, String version, Boolean integratedSecurity) |
39 | at Sitecore.Cloud.Cmdlets.Packaging.NewSCWebDeployPackage.ProcessRecord() |
40 | At C:\Sitecore\Azure Toolkit\tools\Sitecore.Cloud.Cmdlets.psm1:195 char:49 |
41 | + ... ckagePath = New -SCWebDeployPackage -Path $SitecorePath -Destination $ ... |
42 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
43 | + CategoryInfo : NotSpecified: (System.IO.FileN...ProcessRecord():String) [New -SCWebDeployPackage ], Exception |
44 | + FullyQualifiedErrorId : Microsoft.Extensions.Logging.EventId,Sitecore.Cloud.Cmdlets.Packaging.NewSCWebDeployPackage |
45 | Could not find file 'C:\Users\vap\AppData\Local\Temp\tempDacPacs1d5912bf-c65a-4c53-8f70-e8a335d6ea37\Sitecore.core.dacpac' . |
Hmm. Could not find the generated *.dacpac file in a temp folder. I’ve just checked this folder and found nothing. What could be a reason?
In a stack trace, I see that this happened during “ Extracting schema from database. ”
As I’ve mentioned before, all habitat.dev.local SQL dbs are located on another VM machine. That’s probably why the “sqlpackage.exe” (Microsoft SQL Server command-line tool used by Sitecore Azure toolkit to create a Sitecore database snapshot *.dacpac file from a live SQL Server) was not able to either connect SQL or generate the appropriate *.dacpac file.
I started “dotPeeking” (decompiling) Azure Toolkit binaries responsible for Start-SitecoreAzurePackaging to find the place where sqlpackage.exe joined the game. These are the most involved ones:
In a while, I figured out a method
Sitecore.Cloud.Packaging.WebDeployPackages.CreateDacPac(string dbConnectionString, string targetFilePath){…}
in the source code where the appropriate dacpac file is being generated.
To test this functionality, I created a simple Console application to check if sqlpackage.exe works as expected. When I executed this app, I got the following output log:
1 | Connecting to database 'habitat_Core' on server '<sql server name>' . |
3 | Extracting schema from database |
4 | *** Error extracting database:Could not extract package from specified database. |
5 | The reverse engineering operation cannot continue because you do not have View Definition permission on the 'habitat_Core' database. |
Aha, I see that a user defined in connection strings (connections that go from the App_Config/ConnectionStrings.config file in your instance) do not have appropriate grant permissions to be able to make a Sitecore.core.dacpac file.
So, I went to SQL server and noticed that “ coreuser” (this user is defined in a “core” connection string) does not belong to db_owner role:
I applied this role, rerun my Console App and got a Success output message:
1 | Connecting to database 'habitat_Core' on server 'test-server' . |
3 | Extracting schema from database |
4 | Resolving references in schema model |
5 | Validating schema model for data package |
7 | Exporting data from database |
10 | Processing Table '[dbo].[AccessControl]' . |
11 | Processing Table '[dbo].[Archive]' . |
12 | Processing Table '[dbo].[ArchivedFields]' . |
13 | Processing Table '[dbo].[ArchivedItems]' . |
14 | Processing Table '[dbo].[ArchivedVersions]' . |
15 | Processing Table '[dbo].[aspnet_Applications]' . |
16 | Processing Table '[dbo].[aspnet_Membership]' . |
17 | Processing Table '[dbo].[aspnet_Paths]' . |
18 | Processing Table '[dbo].[aspnet_PersonalizationAllUsers]' . |
19 | Processing Table '[dbo].[aspnet_PersonalizationPerUser]' . |
20 | Processing Table '[dbo].[aspnet_Profile]' . |
21 | Processing Table '[dbo].[aspnet_Roles]' . |
22 | Processing Table '[dbo].[aspnet_SchemaVersions]' . |
23 | Processing Table '[dbo].[aspnet_Users]' . |
24 | Processing Table '[dbo].[aspnet_UsersInRoles]' . |
25 | Processing Table '[dbo].[aspnet_WebEvent_Events]' . |
26 | Processing Table '[dbo].[Blobs]' . |
27 | Processing Table '[dbo].[ClientData]' . |
28 | Processing Table '[dbo].[Descendants]' . |
29 | Processing Table '[dbo].[EventQueue]' . |
30 | Processing Table '[dbo].[History]' . |
31 | Processing Table '[dbo].[IDTable]' . |
32 | Processing Table '[dbo].[Items]' . |
33 | Processing Table '[dbo].[Links]' . |
34 | Processing Table '[dbo].[Notifications]' . |
35 | Processing Table '[dbo].[Properties]' . |
36 | Processing Table '[dbo].[PublishQueue]' . |
37 | Processing Table '[dbo].[RolesInRoles]' . |
38 | Processing Table '[dbo].[SharedFields]' . |
39 | Processing Table '[dbo].[Tasks]' . |
40 | Processing Table '[dbo].[UnversionedFields]' . |
41 | Processing Table '[dbo].[UserLogins]' . |
42 | Processing Table '[dbo].[VersionedFields]' . |
43 | Processing Table '[dbo].[WorkflowHistory]' . |
44 | Successfully extracted database and saved it to file 'C:\Temp\Sitecore.core.dacpac' . |
Now, we can see that the “Sitecore.core.dacpac” is created:
Cool! Then, I applied the “db_owner” role to all users under appropriate dbs defined in:
C:\inetpub\wwwroot\habitat.dev.local\App_Config\ConnectionStrings.config
Note:
At the beginning of the article, I’ve mentioned one optional parameter for executing the command Start-SitecoreAzurePackaging:
By default, the value of this parameter is false , which means that the user creds will be used from ConnectionsStrings.config file.
If you specify the value true for this parameter, the process responsible for generating dacpac files will use the current Windows account. But there’s one condition — this account must have administrative rights to access the SQL server.
For some reason, I lost sight of this parameter at the very beginning and, therefore, I had to deal with the problem I described above.
Step 6
Then I executed
1 | Start -SitecoreAzurePackaging -sitecorePath $sitecorePath -destinationFolderPath $destinationFolderPath ` |
2 | -cargoPayloadFolderPath $cargoPayloadFolderPath -commonConfigPath $commonConfigPath ` |
3 | -skuConfigPath $skuConfigPath -parameterXmlPath $parameterXmlPath -fileVersion $fileVersion |
once again. The issue seems to be gone — the “*.dacpac” files started to appear in a temporary folder, as shown below:
In a few minutes, I got another exception message:
The error text:
1 | System.UnauthorizedAccessException: Access to the path 'C:\inetpub\wwwroot\habitat.dev.local\tempConnectionStringeb3ab08c-5c68-4d9d-bf05-8b72f845145c\' is denied. |
2 | at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) |
3 | at System.IO.Directory.InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj, Boolean checkHost) |
4 | at System.IO.Directory.InternalCreateDirectoryHelper(String path, Boolean checkHost) |
5 | at DotNet.Basics.IO.DirPath.CreateIfNotExists() |
6 | at DotNet.Basics.IO.FileExtensions.CopyTo(FilePath source, FilePath target, Boolean overwrite, Boolean ensureTargetDir) |
7 | at Sitecore.Cloud.Packaging.WebDeployPackages.WebDeployPackageBuilder.Build(SitecoreInstallationFolderTree scInstFoldTree, DirPath outputDir, String targetFileName, Boolean force, String version, B |
8 | oolean integratedSecurity) |
9 | at Sitecore.Cloud.Cmdlets.Packaging.NewSCWebDeployPackage.ProcessRecord() |
10 | New -SCWebDeployPackage : System.UnauthorizedAccessException: Access to the path 'C:\inetpub\wwwroot\habitat.dev.local\tempConnectionStringeb3ab08c-5c68-4d9d-bf05-8b72f845145c\' is denied. |
11 | at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) |
12 | at System.IO.Directory.InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj, Boolean checkHost) |
13 | at System.IO.Directory.InternalCreateDirectoryHelper(String path, Boolean checkHost) |
14 | at DotNet.Basics.IO.DirPath.CreateIfNotExists() |
15 | at DotNet.Basics.IO.FileExtensions.CopyTo(FilePath source, FilePath target, Boolean overwrite, Boolean ensureTargetDir) |
16 | at Sitecore.Cloud.Packaging.WebDeployPackages.WebDeployPackageBuilder.Build(SitecoreInstallationFolderTree scInstFoldTree, DirPath outputDir, String targetFileName, Boolean force, String version, |
17 | Boolean integratedSecurity) |
18 | at Sitecore.Cloud.Cmdlets.Packaging.NewSCWebDeployPackage.ProcessRecord() |
19 | At C:\Sitecore\Azure Toolkit\tools\Sitecore.Cloud.Cmdlets.psm1:195 char:49 |
20 | + ... ckagePath = New -SCWebDeployPackage -Path $SitecorePath -Destination $ ... |
21 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
22 | + CategoryInfo : NotSpecified: (System.Unauthor...ProcessRecord():String) [New -SCWebDeployPackage ], Exception |
23 | + FullyQualifiedErrorId : Microsoft.Extensions.Logging.EventId,Sitecore.Cloud.Cmdlets.Packaging.NewSCWebDeployPackage |
24 | Access to the path 'C:\inetpub\wwwroot\habitat.dev.local\tempConnectionStringeb3ab08c-5c68-4d9d-bf05-8b72f845145c\' is denied. |
The process tries to do some stuff with a temporary file in the root folder of the habitat.dev.local site.
To avoid UnauthorizedAccessException, I just stopped IIS and rerun the PowerShell command.
Success
One minute, two minutes, no errors, waiting…and I see a good message:
The goal is achieved — local Habitat Sitecore Web Deploy Package has been generated:
C:\Sitecore\WDPs\habitat.dev.local_single.scwdp.zip
This is an archive where you can see all local Sitecore instance DB dacpac files, base SQL scripts to be executed during future deployment, and parameters:
We can also see a Content directory in which our local Sitecore Habitat root site folder was copied:
It took 7 minutes for my local Habitat Sitecore instance process to be completed.
Now, we have our local Sitecore instance packed. That means you can deploy this SCWDP to Azure as Sitecore PaaS, but that’s another topic for discussion.
Summary
I’ve described one of the first steps you have to take to prepare your local Sitecore Habitat for Azure deployment — the creation of a Sitecore web deploy package. The following is a summary of the process:
- Download the Azure toolkit from the official portal
- Unzip the toolkit into your deployment folder, for instance — C:\Sitecore\Azure Toolkit\
- Check all prerequisites
- Execute the following Commandlets into PowerShell terminal run from the root Azure Toolkit folder:
1 | Import -Module .\ tools \ Sitecore . Cloud.Cmdlets.psm1 -Verbose |
3 | $sitecorePath = "C:\inetpub\wwwroot\habitat.dev.local" |
4 | $destinationFolderPath = "C:\Sitecore\WDPs" |
5 | $cargoPayloadFolderPath = "C:\Sitecore\Azure Toolkit\resources\9.1.1\CargoPayloads" |
6 | $commonConfigPath = "C:\Sitecore\Azure Toolkit\resources\9.1.1\Configs\common.packaging.config.json" |
7 | $skuConfigPath = "C:\Sitecore\Azure Toolkit\resources\9.1.1\Configs\XPSingle.packaging.config.json" |
8 | $parameterXmlPath = "C:\Sitecore\Azure Toolkit\resources\9.1.1\MsDeployXmls" |
11 | Start -SitecoreAzurePackaging -sitecorePath $sitecorePath -destinationFolderPath $destinationFolderPath ` |
12 | -cargoPayloadFolderPath $cargoPayloadFolderPath -commonConfigPath $commonConfigPath ` |
13 | -skuConfigPath $skuConfigPath -parameterXmlPath $parameterXmlPath -fileVersion $fileVersion |
If your Windows user has the Administrator role to access SQL server where your local Sitecore instance databases live — just add an additional parameter to avoid some issues I mentioned in this blog post:
-integratedSecurity 1 (means true)
5. Note that in some minutes you have a generated *.scwdp package in a folder you specified in the $destinationFolderPath parameter
6. To be more flexible, I suggest you save your PowerShell script and execute this script using Windows PowerShell ISE tool:
That’s all for today. If my experience comes in handy for you, I will be happy. If you have any questions, please contact me via Twitter and LinkedIn.
Amazing. This helped!