In late-2019, EpiServer released one of their Beta programs, which would allow partners and developers the ability to control the DXP environment deployments via an API.
There have been a few blog posts (like this one by Anders Wahlqvist) on how to use these APIs in one-off instances, but none in how to make these into reusable generic PowerShell scripts.
In the next part of this series, we will cover how to set up a manual Release Pipeline to deploy into Integration.
This will eventually lead us to where we will be able to streamline the creation of a CI/DI Release Pipeline that publish all the way from Integration, to Preproduction, and finally to Production in an automated fashion that uses EpiServers deployment process.
Deployment Workflow
The workflow displayed below is the ideal workflow for setting up an EpiServer release pipeline.
As noted above, the scope of this post is going be the creation of PowerShell scripts that will eventually be used in a release pipeline, so we will be covering what is colored in blue.
PowerShell Scripts
As seen in the EpiServer documentation, there are a bunch of PowerShell commands that can be used to control these deployments.
As seen in the workflow above, there are five major events that are part of this API:
- Uploading the nupkg file to the Epi deployment staging area
- Deploying the uploaded nupkg file to an environment
- Deploying to an environment from a source environment
- Completing a Deployment
- Resetting a deployment
Upload Package to Staging Area
This script is going to be using the following of the API calls:
- Get-EpiDeploymentPackageLocation
- Add-EpiDeploymentPackage
The flow is going to look something like the following:
-
- Invoke script with 4 mandatory Parameters
- Client Key for intended environment
- Client Secret for intended environment
- Project ID (global for subscription)
- Artifact Path (Where is the Nupkg file located)
- Invoke script with 4 mandatory Parameters
param ( [Parameter(Position=0, Mandatory)] [ValidateNotNullOrEmpty()] [string]$ClientKey, [Parameter(Position=1, Mandatory)] [ValidateNotNullOrEmpty()] [string]$ClientSecret, [Parameter(Position=2, Mandatory)] [ValidateNotNullOrEmpty()] [string]$ProjectID, [Parameter(Position=3, Mandatory)] [ValidateNotNullOrEmpty()] [string]$ArtifactPath )
-
- Validate the parameter
- Not Empty/Null
- Not Whitespace
- Validate the parameter
if([string]::IsNullOrWhiteSpace($ClientKey)){ throw "A Client Key is needed. Please supply one." } ...... if([string]::IsNullOrWhiteSpace($ArtifactPath)){ throw "A path for the NUPKG file location is needed. Please supply one." }
-
- Ensure Nupkg file exists at the Artifact Path
$packagePath = Get-ChildItem -Path $ArtifactPath -Filter *.nupkg if($packagePath.Length -eq 0){ throw "No NUPKG files were found. Please ensure you're passing the correct path." }
-
- Check to see that the EpiCloud powershell module is installed
- If not, install it
- Check to see that the EpiCloud powershell module is installed
if (-not (Get-Module -Name EpiCloud -ListAvailable)) { Install-Module EpiCloud -Scope CurrentUser -Force }
-
- Create Object and get the Cloud Package Location
$getEpiDeploymentPackageLocationSplat = @{ ClientKey = "$ClientKey" ClientSecret = "$ClientSecret" ProjectId = "$ProjectID" } Write-Host "Finding deployment location..." $packageLocation = Get-EpiDeploymentPackageLocation @getEpiDeploymentPackageLocationSplat
-
- Start the upload into the Cloud Path for the nupkg file
$deploy = Add-EpiDeploymentPackage -SasUrl $packageLocation -Path $packagePath.FullName
Invoking the script looks something like the following:
.Perficient_UploadEpiPackage.ps1 -ClientKey "****" -ClientSecret "****" -ProjectId "****" -ArtifactPath "C:PackageLocation"
Publish to Environment via Code Package
- Start-EpiDeployment
- Get-EpiDeployment
-
- Invoke script with 6 mandatory Parameters
- Client Key for intended environment
- Client Secret for intended environment
- Project ID (global for subscription)
- Artifact Path (Where is the Nupkg file located – We need the name)
- Target Environment (Integration, Preproduction, Production)
- Use Maintenance Page (True/1, False/0)
- Invoke script with 6 mandatory Parameters
param ( [Parameter(Position=0, Mandatory)] [ValidateNotNullOrEmpty()] [string]$ClientKey, ...... [Parameter(Position=3, Mandatory)] [ValidateNotNullOrEmpty()] [string]$ArtifactPath, [Parameter(Position=4, Mandatory)] [ValidateNotNullOrEmpty()] [ValidateSet("Integration", "Preproduction", "Production")] [string]$TargetEnvironment, [Parameter(Position=5, Mandatory)] [ValidateNotNullOrEmpty()] [ValidateSet($true, $false, 0, 1)] [bool]$UseMaintenancePage )
-
- Validate the parameters
- Not Empty/Null
- Not Whitespace
- Target Environment has a valid environment name
- Use Maintenance Page has a valid boolean value
- Validate the parameters
if([string]::IsNullOrWhiteSpace($ClientKey)){ throw "A Client Key is needed. Please supply one." } ...... if([string]::IsNullOrWhiteSpace($UseMaintenancePage)){ throw "Please provide an option for if the maintenance page should be shown. Correct values are true or false." }
-
- Ensure Nupkg file exists at the Artifact Path
$packagePath = Get-ChildItem -Path $ArtifactPath -Filter *.nupkg if($packagePath.Length -eq 0){ throw "No NUPKG files were found. Please ensure you're passing the correct path." }
-
- Check to see that the EpiCloud powershell module is installed
- If not, install it
- Set up and Start the deployment
- When setting up the object, the Wait param can be set to true or false, but within my scenario, I would like to be able to report on the progress, so I set it to false. If it is set to true, the command will not return any value until it is done.
- The reason we set this to a variable is because we want the Deploy ID from the object, which will allow us to efficiently get the Epi Deployment status in the next bit of code.
- Check to see that the EpiCloud powershell module is installed
$startEpiDeploymentSplat = @{ DeploymentPackage = $packagePath.Name ProjectId = "$ProjectID" Wait = $false TargetEnvironment = "$TargetEnvironment" UseMaintenancePage = $UseMaintenancePage ClientSecret = "$ClientSecret" ClientKey = "$ClientKey" } Write-Host "Starting the Deployment to" $TargetEnvironment $deploy = Start-EpiDeployment @startEpiDeploymentSplat
-
- Set up the object and Get the current deployment
$deployId = $deploy | Select -ExpandProperty "id" $getEpiDeploymentSplat = @{ ProjectId = "$ProjectID" ClientSecret = "$ClientSecret" ClientKey = "$ClientKey" Id = "$deployId" } $percentComplete = 0 $currDeploy = Get-EpiDeployment @getEpiDeploymentSplat | Select-Object -First 1 $status = $currDeploy | Select -ExpandProperty "status" $exit = 0
-
- Set up a loop to query and print to the screen the current status level
- The Powershell Write-Progress command will not work in the Azure DevOps output screen
- Set up a loop to query and print to the screen the current status level
while($exit -ne 1){ $currDeploy = Get-EpiDeployment @getEpiDeploymentSplat | Select-Object -First 1 $currPercent = $currDeploy | Select -ExpandProperty "percentComplete" $status = $currDeploy | Select -ExpandProperty "status" if($currPercent -ne $percentComplete){ Write-Host "Percent Complete: $currPercent%" $percentComplete = $currPercent } if($percentComplete -eq 100){ $exit = 1 } if($status -ne 'InProgress'){ $exit = 1 } start-sleep -Milliseconds 1000 }
-
- If the deployment fails, throw an error
- This will also fail the Azure DevOps Release Task if this process fails
- If the deployment succeeds, set an Azure DevOps Output Variable
- This has been demonstrated in previous examples but using the output variables hasn’t been the easiest process to set up and use, as it has changed with different scenarios
- In the Reset/Complete scripts, we will look at a way to get around having to use the output variable, which will make it easier for these scripts to run independently
- If the deployment fails, throw an error
if($status -eq "Failed"){ throw "Deployment Failed. Errors: n" + $deploy.deploymentErrors } Write-Host "##vso[task.setvariable variable=DeploymentId;]'$deployId'"
Invoking the script looks something like the following:
.Perficient_DeployToEnvironment.ps1 -ClientKey "****" -ClientSecret "****" -ProjectId "****" -ArtifactPath "C:FilePath" -TargetEnvironment "Integration" -UseMaintenancePage 0
Publish to Environment via Source Environment
- Start-EpiDeployment
- Get-EpiDeployment
-
- Invoke script with 8 mandatory Parameters
- Client Key for intended environment
- Client Secret for intended environment
- Project ID (global for subscription)
- Source Environment (Integration, Preproduction, Production)
- Target Environment (Integration, Preproduction, Production)
- Use Maintenance Page (True/1, False/0)
- Include Blobs (True/1, False/0)
- Include DB (True/1, False/0)
- Invoke script with 8 mandatory Parameters
param ( [Parameter(Position=0, Mandatory)] [ValidateNotNullOrEmpty()] [string]$ClientKey, ...... [Parameter(Position=3, Mandatory)] [ValidateNotNullOrEmpty()] [ValidateSet("Integration", "Preproduction", "Production")] [string]$SourceEnvironment, [Parameter(Position=4, Mandatory)] [ValidateNotNullOrEmpty()] [ValidateSet("Integration", "Preproduction", "Production")] [string]$TargetEnvironment, [Parameter(Position=5)] [ValidateSet($true, $false, 0, 1)] [bool]$UseMaintenancePage, [Parameter(Position=6)] [ValidateSet($true, $false, 0, 1)] [bool]$IncludeBlobs, [Parameter(Position=7)] [ValidateSet($true, $false, 0, 1)] [bool]$IncludeDb [Parameter(Position=8, Mandatory)] [ValidateSet('cms','commerce')] [String] $SourceApp )
-
- Validate the parameters
- Not Empty/Null
- Not Whitespace
- Source and Target Environment has a valid environment name
- Source and Target Environment are not the same value
- Use Maintenance Page, Include Blobs, and Include DB have a valid boolean value
- Validate the parameters
if([string]::IsNullOrWhiteSpace($ClientKey)){ throw "A Client Key is needed. Please supply one." } ...... if($SourceEnvironment -eq $TargetEnvironment){ throw "The source environment cannot be the same as the target environment." }
-
- Ensure Nupkg file exists at the Artifact Path
$packagePath = Get-ChildItem -Path $ArtifactPath -Filter *.nupkg if($packagePath.Length -eq 0){ throw "No NUPKG files were found. Please ensure you're passing the correct path." }
-
- Check to see that the EpiCloud powershell module is installed
- If not, install it
- Set up and Start the deployment
- When setting up the object, the Wait param can be set to true or false, but within my scenario, I would like to be able to report on the progress, so I set it to false. If it is set to true, the command will not return any value until it is done.
- The reason we set this to a variable is because we want the Deploy ID from the object, which will allow us to efficiently get the Epi Deployment status in the next bit of code.
- Check to see that the EpiCloud powershell module is installed
$startEpiDeploymentSplat = @{ DeploymentPackage = $packagePath.Name ProjectId = "$ProjectID" Wait = $false TargetEnvironment = "$TargetEnvironment" UseMaintenancePage = $UseMaintenancePage ClientSecret = "$ClientSecret" ClientKey = "$ClientKey" IncludeBlob = $IncludeBlobs IncludeDb = $IncludeDb SourceApp = "$SourceApp" } Write-Host "Starting the Deployment to" $TargetEnvironment $deploy = Start-EpiDeployment @startEpiDeploymentSplat
-
- Set up the object and Get the current deployment
$deployId = $deploy | Select -ExpandProperty "id" $getEpiDeploymentSplat = @{ ProjectId = "$ProjectID" ClientSecret = "$ClientSecret" ClientKey = "$ClientKey" Id = "$deployId" } $percentComplete = 0 $currDeploy = Get-EpiDeployment @getEpiDeploymentSplat | Select-Object -First 1 $status = $currDeploy | Select -ExpandProperty "status" $exit = 0
-
- Set up a loop to query and print to the screen the current status level
- The Powershell Write-Progress command will not work in the Azure DevOps output screen
- Set up a loop to query and print to the screen the current status level
while($exit -ne 1){ $currDeploy = Get-EpiDeployment @getEpiDeploymentSplat | Select-Object -First 1 $currPercent = $currDeploy | Select -ExpandProperty "percentComplete" $status = $currDeploy | Select -ExpandProperty "status" if($currPercent -ne $percentComplete){ Write-Host "Percent Complete: $currPercent%" $percentComplete = $currPercent } if($percentComplete -eq 100){ $exit = 1 } if($status -ne 'InProgress'){ $exit = 1 } start-sleep -Milliseconds 1000 }
-
- If the deployment fails, throw an error
- This will also fail the Azure DevOps Release Task if this process fails
- If the deployment succeeds, set an Azure DevOps Output Variable
- This has been demonstrated in previous examples but using the output variables hasn’t been the easiest process to set up and use, as it has changed with different scenarios
- In the Reset/Complete scripts, we will look at a way to get around having to use the output variable, which will make it easier for these scripts to run independently
- If the deployment fails, throw an error
if($status -eq "Failed"){ throw "Deployment Failed. Errors: n" + $deploy.deploymentErrors } Write-Host "##vso[task.setvariable variable=DeploymentId;]'$deployId'"
Invoking the script looks something like the following:
.Perficient_PromoteToEnvironment.ps1 -ClientKey "****" -ClientSecret "****" -ProjectID "****" -SourceEnvironment Integration -TargetEnvironment Preproduction -UseMaintenancePage 1 -IncludeBlobs $false -IncludeDb 0 -SourceApp cms
Complete Deployment
- Get-EpiDeployment
- Complete-EpiDeployment
-
- Invoke script with 3 mandatory Parameters and 1 optional Parameter
- Client Key for intended environment
- Client Secret for intended environment
- Project ID (global for subscription)
- Deployment ID (Optional)
- Invoke script with 3 mandatory Parameters and 1 optional Parameter
param ( [Parameter(Position=0, Mandatory)] [ValidateNotNullOrEmpty()] [string]$ClientKey, ...... [Parameter(Position=3)] [string]$DeploymentId )
-
- Validate the parameters
- Not Empty/Null
- Not Whitespace
- Validate the parameters
if([string]::IsNullOrWhiteSpace($ClientKey)){ throw "A Client Key is needed. Please supply one." } ...... if([string]::IsNullOrWhiteSpace($ProjectID)){ throw "A Project ID GUID is needed. Please supply one." }
-
- Check to see that the EpiCloud powershell module is installed
- If not, install it
- Set up the object to get the current deployment details
- If the Deployment ID is passed in, use that (This can be obtained from a previous deployment via an Output Variable)
- Otherwise, I have written logic that will use the Get-EpiDeployment command to look up the most recent release.
- Check to see that the EpiCloud powershell module is installed
if([string]::IsNullOrWhiteSpace($DeploymentId)){ $getEpiDeploymentSplat = @{ ProjectId = "$ProjectID" ClientSecret = "$ClientSecret" ClientKey = "$ClientKey" } }else{ $getEpiDeploymentSplat = @{ ProjectId = "$ProjectID" ClientSecret = "$ClientSecret" ClientKey = "$ClientKey" id = "$DeploymentId" } } $currDeploy = Get-EpiDeployment @getEpiDeploymentSplat | Select-Object -First 1 if([string]::IsNullOrWhiteSpace($DeploymentId)){ Write-Host "No Deployment ID Supplied. Searching for In-Progress Deployment..." $DeploymentId = $currDeploy | Select -ExpandProperty "id" Write-Host "Deployment ID Found: $DeploymentId" }
-
- Set up and run the command to Complete the deployment
$completeEpiDeploymentSplat = @{ ProjectId = "$ProjectID" Id = "$DeploymentId" Wait = $false ClientSecret = "$ClientSecret" ClientKey = "$ClientKey" } $deploy = Complete-EpiDeployment @completeEpiDeploymentSplat
-
- Set up a loop to query and print to the screen the current status level
$percentComplete = 0 $status = $currDeploy | Select -ExpandProperty "status" $exit = 0 Write-Host "Percent Complete: $percentComplete%" while($exit -ne 1){ $currDeploy = Get-EpiDeployment @getEpiDeploymentSplat | Select-Object -First 1 $currPercent = $currDeploy | Select -ExpandProperty "percentComplete" $status = $currDeploy | Select -ExpandProperty "status" if($currPercent -ne $percentComplete){ Write-Host "Percent Complete: $currPercent%" $percentComplete = $currPercent } if($percentComplete -eq 100){ $exit = 1 } if($status -ne 'Completing'){ $exit = 1 } start-sleep -Milliseconds 1000 }
Invoking the script looks something like the following:
.Perficient_CompleteDeployment.ps1 -ClientKey "****" -ClientSecret "****" -ProjectId "****" [-DeploymentId "****"]
Reset Deployment
- Get-EpiDeployment
- Reset-EpiDeployment
-
- Invoke script with 3 mandatory Parameters and 1 optional Parameter
- Client Key for intended environment
- Client Secret for intended environment
- Project ID (global for subscription)
- Deployment ID (Optional)
- Invoke script with 3 mandatory Parameters and 1 optional Parameter
param ( [Parameter(Position=0, Mandatory)] [ValidateNotNullOrEmpty()] [string]$ClientKey, ...... [Parameter(Position=3)] [string]$DeploymentId )
-
- Validate the parameters
- Not Empty/Null
- Not Whitespace
- Validate the parameters
if([string]::IsNullOrWhiteSpace($ClientKey)){ throw "A Client Key is needed. Please supply one." } ...... if([string]::IsNullOrWhiteSpace($ProjectID)){ throw "A Project ID GUID is needed. Please supply one." }
-
- Check to see that the EpiCloud powershell module is installed
- If not, install it
- Set up the object to get the current deployment details
- If the Deployment ID is passed in, use that (This can be obtained from a previous deployment via an Output Variable)
- Otherwise, I have written logic that will use the Get-EpiDeployment command to look up the most recent release.
- Check to see that the EpiCloud powershell module is installed
if([string]::IsNullOrWhiteSpace($DeploymentId)){ $getEpiDeploymentSplat = @{ ProjectId = "$ProjectID" ClientSecret = "$ClientSecret" ClientKey = "$ClientKey" } }else{ $getEpiDeploymentSplat = @{ ProjectId = "$ProjectID" ClientSecret = "$ClientSecret" ClientKey = "$ClientKey" id = "$DeploymentId" } } $currDeploy = Get-EpiDeployment @getEpiDeploymentSplat | Select-Object -First 1 if([string]::IsNullOrWhiteSpace($DeploymentId)){ Write-Host "No Deployment ID Supplied. Searching for In-Progress Deployment..." $DeploymentId = $currDeploy | Select -ExpandProperty "id" Write-Host "Deployment ID Found: $DeploymentId" }
-
- Set up and run the command to Reset the deployment
$completeEpiDeploymentSplat = @{ ProjectId = "$ProjectID" Id = "$DeploymentId" Wait = $false ClientSecret = "$ClientSecret" ClientKey = "$ClientKey" } $deploy = Reset-EpiDeployment @completeEpiDeploymentSplat
-
- Set up a loop to query and print to the screen the current status level
$percentComplete = 0 $status = $currDeploy | Select -ExpandProperty "status" $exit = 0 Write-Host "Percent Complete: $percentComplete%" while($exit -ne 1){ $currDeploy = Get-EpiDeployment @getEpiDeploymentSplat | Select-Object -First 1 $currPercent = $currDeploy | Select -ExpandProperty "percentComplete" $status = $currDeploy | Select -ExpandProperty "status" if($currPercent -ne $percentComplete){ Write-Host "Percent Complete: $currPercent%" $percentComplete = $currPercent } if($percentComplete -eq 100){ $exit = 1 } if($status -ne 'Resetting'){ $exit = 1 } start-sleep -Milliseconds 1000 }
Invoking the script looks something like the following:
.Perficient_ResetDeployment.ps1 -ClientKey "****" -ClientSecret "****" -ProjectId "****" [-DeploymentId "****"]
Conclusion
- Upload a nupkg file to the staging location
- Deploy a previously uploaded nupkg file to an environment
- Deploy from one environment to another
- Complete a deployment
- Reset a deployment
Great stuff! Thanks for putting this together!