TDS Post-Deploy PowerShell Step

I’ve recently been spending some time to upgrade our solution as well as perform some tweaks, optimizations and clean up on our deployment process. Even though I am the creator of UniCorn, I’ve almost exclusively been using Team Development for Sitecore in all the projects which I have worked on 🐘

The release of TDS 5.5 allowed developers to add their own custom deploy steps. Previously these might have been conducted manually (urgh!) or we had deployment steps added to our process in Octopus Deploy to conduct these. The post deploy steps however are better since they can be developer controlled and added to source control. Win!

The Hedgehog Team has been asking for the community to share their post deploy scripts. You can find a number of scripts here. There are some great scripts there, similar to what we currently have. But the way that script are added and managed in the TDS projects was a bit annoying to me…

So it got me thinking about that great quote by J.R.R. Tolkien:

One Script to rule them all, One Script to find them,
One Script to bring them all and using SPE bind them

One Script To Rule Them All

Problem

The same as everyone else really, to run some post deploy actions, but I didn’t want to manage scproj files manually. I also wanted to make it easy to add additional post deploy steps whenever needed, using PowerShell scripts. This would also have the advantage that the scripts can be managed and deployed using TDS itself as well.

Solution

Enter Sitecore PowerShell Extensions. We use PowerShell a lot, although most of my SPE scripts are for ah-hoc tasks, SPE can pretty much do everything including slice bread, make coffee and even do Mike Reynolds laundry!

The idea was a post deploy step, which in turn would run whatever SPE scripts we wanted.

The post deploy step was actually very simple, since Adam Najmanowicz has already done most of the work for us and SPE will already react to events being raised. The only thing we need to do is wire in a custom event and then add whatever SPE scripts we need.

Installation and Usage

Make sure you read post on post on creating TDS Post Deploy Steps. Add this custom step in to raise a custom event, patch in the event into the Sitecore config and then add the correct folder structure into the events section of SPE. Create whatever scripts you need in this folder to run on post deploy.

Code

We simply need to raise a custom event and make sure the folder structure is created to reflect it. Then create your SPE scripts in that folder.

public class NotifyPostDeploy : IPostDeployAction
{
	[Description("Raises event after a successful TDS Package installation")]
	public void RunPostDeployAction(XDocument deployedItems, IPostDeployActionHost host, string parameter)
	{
		host.LogMessage("Raising TDS PostDeploy Event...");
		Event.RaiseEvent("packageinstall:tds:postdeploy", new EventArgs());
	}
}
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
	  <event name="packageinstall:tds:postdeploy">
        <handler type="Cognifide.PowerShell.Integrations.Tasks.ScriptedItemEventHandler, Cognifide.PowerShell" method="OnEvent" />
      </event>
    </events>
  </sitecore>
</configuration>

spe-events-tree-structure

Post-Deploy PowerShell Scripts

Here are some sample SPE scripts to replicate the functionality of some of the other community developed post deploy steps. The originals can be found in this article: TDS Post Deploy Steps

Trigger Site Publish

Publish-Item -Path master:\content -Recurse -Target web -PublishMode Incremental -AsJob
Write-Log "[TDSPostDeploy::SPE] Site publish started."

This will trigger a publish of all content, but be careful. If you want to rebuild indexes or do anything with the web database after, you should wait for the publish to finish first. You may wish to use this script instead.

Rebuild Content Search Indexes

$indexes = @("sitecore_master_index","sitecore_web_index")

$indexes | ForEach {

    $index = Get-SearchIndex -Name $_
    Write-Log "[TDSPostDeploy::SPE] Rebuilding the search index $($index.Name)"
    $time = Measure-Command {
        $index.Rebuild()
    }
    Write-Log "[TDSPostDeploy::SPE] Completed rebuilding the search index $($index.Name) in $($time.TotalSeconds) seconds."
}

Rebuild Link databases

$databases = @("core","master","web")

$databases | ForEach {
    $db = Get-Database -Name $_
    Write-Log "[TDSPostDeploy::SPE] Rebuild of index '$db' started."
    $time = Measure-Command {
      [Sitecore.Globals]::LinkDatabase.Rebuild($db)
    }
    Write-Log "[TDSPostDeploy::SPE] Completed rebuilding the $($db) link database in $($time.TotalSeconds) seconds."
}

The File Remover

$files = @(
    "Files\file1.txt", 
    "Files\Folder1\file2.txt", 
    "Files\Folder1\Subfolder2\file3.txt", 
    "Files\Folder2\*.config"
)
$folderTimestamp = Get-Date -Format yyyyMMdd\\HHmmss
$archiveFolder = "Archive\" + $folderTimestamp

cd $AppPath
$archiveFolder = md -d -Force -Path ..\$archiveFolder -Confirm:$false
Write-Log "[TDSPostDeploy::SPE] Archive folder created: $archiveFolder"

Foreach ($file in $files)
{
    Get-ChildItem -Path $file -Recurse | ForEach {
        if (!$_.DirectoryName) { Return }
        $newpath = Join-Path $archiveFolder $_.DirectoryName.Replace($AppPath, '')
        New-Item $newpath -Type Directory -ErrorAction SilentlyContinue | Out-Null
        Move-Item $_.FullName -Destination $newpath
        Write-Log "[TDSPostDeploy::SPE] Archived file: $_"
    }
}

The above will move all files specified to an Archive folder, and will retain the original folder structure. This last part is important if you have multiple files with the same name in different folders.

TODO: Error check that the files actually exists before acting on it. Also, a 2nd pass to delete empty folders once files have been moved.

This step would only work if you installed the TDS package on the CD servers as well. We only create item packages so they are never deployed to the CD servers. You could utilize SPE Remoting, but it is against our security policy to expose this kind on functionality on a hardened CD environment.

Configuring Sitecore Role Members

$roleMappings = @(
	@("sitecore\JediTeam", "sitecore\Developer"),
	@("sitecore\VaderTeam", "sitecore\Author")
)

$roleMappings | ForEach {
	$role = Get-Role -Identity $_[0]
	if (!$role) {
	    Write-Log [TDSPostDeploy::SPE] "Role" $_[0] "not found. Cannot add role as member of" $_[1]
	} else {
	    Add-RoleMember -Identity $_[1] -Members $_[0]
    	Write-Log "[TDSPostDeploy::SPE] Successfully added role" $_[1] "to" $_[0]
	}
}

Slack Notifications

Last but not least, who doesn’t love a good automated Slack Notification!

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="Slack.WebHook.URL" value="https://hooks.slack.com/services/replace-me/with/your-webhook-url" />
    </settings>
  </sitecore>
</configuration>
$slackWebHook = [Sitecore.Configuration.Settings]::GetSetting("Slack.WebHook.URL")
$payload = 'payload={"text": "Why can''t you be my friend? What did I ever do to you? I even posted this message to Slack from SPE to let you know that TDS PostDeploy tasks have completed."}'

Invoke-WebRequest -Uri $slackWebHook `
                  -Method Post `
                  -UseBasicParsing `
                  -Body $payload

Which results in the following notification:

spe-slack-notification

Leave a comment