Friday, November 9, 2018

Publish viewer/canceller using Sitecore Powershell Extensions

I have only just recently jumped the Sitecore Powershell Extensions (SPE) bandwagon, but I absolutely love it.
Most of the tasks I've used it for so far has been either very straight forward, or very client specific. However we were just asked by a client to make a publish viewer which would list details of current publish actions and queue, as well as being able to canceling queued items.
Similar applications has existed in the past, but it didn't seem like any of them worked properly on Sitecore 8.2 and 9.0 - SPE to the rescue!

First a couple of functions

Most of this could exist in one long script, I just really like splitting them up into individual scripts for easier reuse.

Get-PublishJobs

First off I made a function to get all jobs from the jobmanager, filter them to only see the PublishManager jobs, and then sorting them by PublishDate. This gives returns me a PowerShell array of relevant jobs.

function Get-PublishJobs() 
{
  $category = 'PublishManager'
  [Sitecore.Jobs.JobManager]::GetJobs() | 
           Where-Object -FilterScript { $_.Category -eq $category } | 
           sort @{expression={$_.Options.Parameters[0].PublishDate};Descending=$false}
}

Show-PublishStatus

This is all that's needed to see the current publish status. I just use Show-ListView and GetPublishJobs to show all relevant information about the ongoing publishings. The publishmanager works on an array of PublishOptions, these will all have the same root, but targets and languages will be different (if there's more than 1 item in the array). That's why everytime I work on the options, I end up with a select-uniq

Import-Function -Name Get-PublishJobs
Get-PublishJobs | Show-ListView -Title 'Current publish processes' `
    -Modal `
    -Width 1300 `
    -Property `
    @{ Name="State"; Expression={ $_.Status.State } },
    @{ Name="Processed"; Expression={ $_.Options.Parameters[1].Processed } },
    @{ Name="Added to queue"; Expression={$_.Options.Parameters[0].PublishDate |  select -uniq } },
    @{ Name="Root"; Expression={$_.Options.Parameters[0].RootItem.Paths.FullPath |  select -uniq } },
    @{ Name="Language(s)"; Expression={$_.Options.Parameters[0].Language.Name |  select -uniq } },
    @{ Name="Target(s)"; Expression={$_.Options.Parameters[0].TargetDatabase.Name |  select -uniq } },
    @{ Name="Publish Mode"; Expression={$_.Options.Parameters[0].Mode |  select -uniq } },
    @{ Name="Publish Children"; Expression={$_.Options.Parameters[0].Deep |  select -uniq } },
    @{ Name="Publish Related"; Expression={$_.Options.Parameters[0].PublishRelatedItems |  select -uniq } },
    @{ Name="User"; Expression={$_.Options.Parameters[0].UserName |  select -uniq } }
Close-Window

Refresh-PublishStatus

Seeing that publish jobs isn't a constant (they finish and new ones get scheduled) I want a refresh button. This function takes care of the refresh functionality. If there are no more jobs it doesn't rerender properly - instead we close the list.

Import-Function -Name Get-PublishJobs
function Refresh-PublishStatus() 
{
  $jobs = Get-PublishJobs
  if ($jobs.Length -eq 0)
  {
    Show-Alert('No Active publish jobs')
    Close-Window
  }
  else
  {
    $jobs | Update-ListView
  }
}

Invoke-JobManager

In order to cancel a scheduled job we need to use reflection. Fortunately this is just as easy as everything else in SPE. We need to call 2 private methods on Sitecore's DefaultJobManager. I made a method that I can call twice, once for each method.

function Invoke-JobManager($methodname, $job) 
{
    $params = @($job)
    $instancefield = [Sitecore.Jobs.JobManager].GetField('Instance', [Reflection.BindingFlags]::NonPublic -bor [Reflection.BindingFlags]::Static)
    $instance = $instancefield.GetValue($null)
    $method = [Sitecore.Jobs.DefaultJobManager].GetMethod($methodname, [Reflection.BindingFlags]::NonPublic -bor [Reflection.BindingFlags]::Instance)
    $method.Invoke($instance.Value, $params)
}

Adding buttons to the list view

To add my refresh and cancel buttons to the List View I just have to add the scripts to the module on the path Internal/List View/Ribbon/Job they are automatically added to any List Views that iterate on items of the type "Job". Their implementations are simple

Refresh

Refresh simply calls the Refresh-PublishStatus script shown previously.

Import-Function -Name Refresh-PublishStatus
Refresh-PublishStatus

Cancel

Cancel is a bit more complicated. To cancel one that's still scheduled we use two private methods (FinishJob and RemoveJob) on the JobManager, via the before mentioned reflection. There's no proper way of shutting down an already running publish nicely. One potential option could be following the same path as Sitecore's package installation and touching the web.config file in order to restart the web application - I wouldn't recommend this though, the shown implementation just shows a warning that it's not possible to cancel the running jobs.

Import-Function -Name Invoke-JobManager
Import-Function -Name Refresh-PublishStatus

foreach($job in $selectedData)
{
  if ($job.Status.State -eq 'Running')
  {
     Show-Alert 'Cannot cancel already running jobs.';
  }
  else
  {
    Invoke-JobManager 'FinishJob' $job
    Invoke-JobManager 'RemoveJob' $job
  }
}
Refresh-PublishStatus
To avoid the mayhem of users cancelling eachothers jobs, I've decided that this should be admin only. This is simply done by adding a Show Rule to the script - "where the current user is an administrator". This could of course be done by checking roles instead.

Bringing it all together

Having all the individual components, I just have to make a button and test it out. I want my button in the Content Editor ribbon next to the publish button - all I need to do is create a script on the path Content Editor/Ribbon/Publish/Publish and call my Show-PublishStatus, along with a nifty icon.
Content Editor view of the entire module
Import-Function -Name Show-PublishStatus
Show-PublishStatus


And after going to the PowerShell ISE window rebuilding the integration points, my button shows and it's all ready to run:

The publish viewer in use

Want it?

First of all, it's tested on Sitecore 8.2 and 9.0 with SPE 4.7 and 5.0 (the title is gone in SPE 4.7, but that's a known SPE bug). It's not compatible with the Sitecore Publishing Service module - but that has it's own publish viewing capabilities.

I tried adding it to the Sitecore Marketplace, but that doesn't seem to be working very well right now. I will keep trying, but as of right now, your best option is just to type it all in yourself, or catch me on the Sitecore Slack community @morten.engel, and I'll send you a package.