Friday, January 6, 2017

Remove orphaned timer job instances in SharePoint

After removing a server from a SharePoint farm, and putting it back, if you go to Central Admin > Monitoring > Check job status, sometimes, you can find a few jobs running without associated servers, and they usually stuck between 0% to 100%.  
If so, these timer jobs were running on the server which got removed from the farm and for some reason these jobs became orphaned.
If you try to search for solutions on internet, you would end up solutions like:
  1) Stop the timer Services on all servers
  2) Clean up SharePoint configuration cache
  3) Start the timer services on all servers
  4) Recycle the Admin Services on all servers
  5) IIS reset on all servers
  6) Reboot all servers
  7) This post suggests to run following script. 
      $job = Get-SPTimerJob  | where { $_.Name  -eq  "job-delete-job-history" }
      $job.DaysToKeepHistory = 0
      $job.RunNow()
But none of them works for us. After some struggling, and with some help from my colleague Rob Braun, I figured out a way to do it. The following script can be run on a sever of the farm with orphaned timer job instances, and it will clean up the orphaned ones. In my case, the job definition of the orphaned job instance was "Site Lookup Refresh" with internal name "job-sitelookup-refresh".
Add-PSSnapin "Microsoft.SharePoint.PowerShell"
$asm = [Reflection.Assembly]::LoadFile("C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.dll")
$asmType = $asm.GetType("Microsoft.SharePoint.Administration.SPTimerStore")
# Only need one instance for static mehtod
$instance = $asmType::Default
$jobDefinition = get-sptimerjob | where-object {$_.Name -eq "job-sitelookup-refresh"}
$types = @()
$jobDefinitionType = $jobDefinition.GetType()
$types += $jobDefinitionType
$deleteRunningJobs = $asmType.GetMethod("DeleteRunningJobs", [System.Reflection.BindingFlags]::Static -bor [System.Reflection.BindingFlags]::NonPublic, $Null, $types, $Null)
$deleteRunningJobs.Invoke($instance, $jobDefinition)
As you can see, .Net reflector can invoke internal methods to address problems which simply can't be resolved through SP PowerShell commandlets. Be sure reviewing the code thoroughly through ILSPY or other decompilers, since reflector can be a double-edge sword if the side effects are not well understood.