Sitecore by default is only installed to the production slot (ie. the web app itself), and installing it again in the slot will mean either pointing the installation at a second DB (which you could do), restoring the dacpacs to the live DB a second time (which you don't want to do), or creating a custom Sitecore package without the dacpac files (painful when Sitecore upgrades or changes need to be made).
I was tempted to create some DB-less Sitecore packages, but I knew there would have to be a better way. There looks to be some options if you upgrade to Premium, but for those of us in Standard I figured there should be a way to copy the files from slot to slot without downloading them locally and uploading them again, via FTP. After a lot of hunting and a promising upcoming solution from Microsoft, I stumbled across this azure-clone-webapps repo in Github. This was almost exactly what I was after (massive thanks to the author), I just needed to convert it to Powershell so that I could run it as part of my ARM template deployment script.
I've included my final Powershell script here and uploaded it as a Gist, feel free to use it as-is or tweak it to suit your needs. Since our client's Sitecore host is Rackspace they've got NewRelic installed, and I've included a skip statement to ignore the newrelic folder inside the website. Other than that it will copy all the site files from your given web app to the given slot. Enjoy!
Gist of SyncFilesToSlot.ps1
<# .SYNOPSIS Copies all of a web app's files to a given slot .DESCRIPTION Copies all of a web app's files to a given slot. Skips "newrelic" folder as the files are in use. .PARAMETER SubscriptionId The subscription id where the resources reside. .PARAMETER ResourceGroupName The resource group where the resources reside. .PARAMETER WebAppName Name of the web app containing files for the slot. .PARAMETER SlotName Name of the slot to fill with files from web app. #> param( [string] $SubscriptionId, [Parameter(Mandatory = $True)] [string] $ResourceGroupName, [Parameter(Mandatory = $false)] [string] $WebAppName, [Parameter(Mandatory = $True)] [string] $SlotName ) [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Web.Deployment") function Get-AzureRmWebAppPublishingCredentials($ResourceGroupName, $WebAppName, $SlotName = $null){ if ([string]::IsNullOrWhiteSpace($SlotName)) { $resourceType = "Microsoft.Web/sites/config"; $resourceName = "$WebAppName/publishingcredentials"; } else { $resourceType = "Microsoft.Web/sites/slots/config"; $resourceName = "$WebAppName/$SlotName/publishingcredentials"; } $publishingCredentials = Invoke-AzureRmResourceAction -ResourceGroupName $ResourceGroupName -ResourceType $resourceType -ResourceName $resourceName -Action list -ApiVersion 2016-08-01 -Force; return $publishingCredentials; } function GetScmUrl($ResourceGroupName, $WebAppName, $SlotName) { # revert to this when MS fixes https://social.msdn.microsoft.com/Forums/expression/en-US/938e59f6-6a83-4640-a423-26fe91d66cf3/scm-uri-for-web-app-deployment-slots #$scmUrl = $publishingCredentials.properties.scmUri #$scmUrlNoCreds = $scmUrl.Replace($scmUrl.Substring($scmUrl.IndexOf('$'), ($scmUrl.IndexOf('@')-$scmUrl.IndexOf('$')+1)), '') # ugh this version of substring sucks sooo much :'( #$apiUrl = "$scmUrl/api/command" # revert below if($SlotName) { $slot = Get-AzureRmWebAppSlot -ResourceGroupname $ResourceGroupName -Name $WebAppName -Slot $SlotName; $scmUrl = $slot.EnabledHostNames | where { $_.Contains('.scm.') }; } else { $scmUrl = "$WebAppName.scm.azurewebsites.net"; } # revert above return "https://$scmUrl"; } function SyncWebApps($srcUrl, $srcCredentials, $destUrl, $destCredentials) { $syncOptions = New-Object Microsoft.Web.Deployment.DeploymentSyncOptions; #$syncOptions.DoNotDelete = $true; $appOfflineRule = $null; $availableRules = [Microsoft.Web.Deployment.DeploymentSyncOptions]::GetAvailableRules(); if (!$availableRules.TryGetValue('AppOffline', [ref]$appOfflineRule)) { Write-Host "Failed to find AppOffline Rule"; } else { $syncOptions.Rules.Add($appOfflineRule); Write-Host "Enabled AppOffline Rule"; } $skipNewRelic = New-Object Microsoft.Web.Deployment.DeploymentSkipDirective -ArgumentList @("skipNewRelic", 'objectName=dirPath,absolutePath=.*\\newrelic', $true); $sourceBaseOptions = New-Object Microsoft.Web.Deployment.DeploymentBaseOptions; $sourceBaseOptions.ComputerName = $srcUrl + "/msdeploy.axd"; $sourceBaseOptions.UserName = $srcCredentials.properties.PublishingUserName; $sourceBaseOptions.Password = $srcCredentials.properties.PublishingPassword; $sourceBaseOptions.AuthenticationType = "basic"; $sourceBaseOptions.SkipDirectives.Add($skipNewRelic); $destBaseOptions = New-Object Microsoft.Web.Deployment.DeploymentBaseOptions; $destBaseOptions.ComputerName = $destUrl + "/msdeploy.axd"; $destBaseOptions.UserName = $destCredentials.properties.PublishingUserName; $destBaseOptions.Password = $destCredentials.properties.PublishingPassword; $destBaseOptions.AuthenticationType = "basic"; $destBaseOptions.SkipDirectives.Add($skipNewRelic); $destProviderOptions = New-Object Microsoft.Web.Deployment.DeploymentProviderOptions -ArgumentList @("contentPath"); $destProviderOptions.Path = "/site"; $sourceObj = [Microsoft.Web.Deployment.DeploymentManager]::CreateObject("contentPath", "/site", $sourceBaseOptions); $sourceObj.SyncTo($destProviderOptions, $destBaseOptions, $syncOptions); } if($SubscriptionId) { try { Set-AzureRmContext -SubscriptionID $SubscriptionId; } catch { Login-AzureRmAccount; Set-AzureRmContext -SubscriptionID $SubscriptionId; } } $srcCreds = Get-AzureRmWebAppPublishingCredentials $ResourceGroupName $WebAppName; $srcUrl = GetScmUrl $ResourceGroupName $WebAppName; $destCreds = Get-AzureRmWebAppPublishingCredentials $ResourceGroupName $WebAppName $SlotName; $destUrl = GetScmUrl $ResourceGroupName $WebAppName $SlotName; SyncWebApps $srcUrl $srcCreds $destUrl $destCreds;
Good!
ReplyDelete