Home » Scheduled Tasks

Category Archives: Scheduled Tasks

Execute PowerShell Scripts in Scheduled Tasks using Sitecore PowerShell Extensions

At the Sitecore User Group Conference 2014, I demonstrated how to invoke PowerShell scripts in a Sitecore Scheduled Task using Sitecore PowerShell Extensions, and felt I should pen what I had shown in a blog post — yes, you guessed it: this is that blog post. 😉

In my presentation, I shared the following PowerShell script with the audience:

ForEach($site in [Sitecore.Configuration.Factory]::GetSiteNames()) {
    $siteInfo = [Sitecore.Configuration.Factory]::GetSiteInfo($site)
    if($siteInfo -ne $null) {
         $siteInfo.HtmlCache.Clear()   
         $logEntry = [string]::Format("HtmlCache.Clear() invoked for {0}", $siteInfo.Name)
         Write-Log $logEntry
    }
}

The script above iterates over all sites in your Sitecore instance, clears the Html Cache for each, and creates log entries expressing the Html Cache was cleared for all sites processed.

You would probably never have to use a script like the one above. I only wrote it for demonstration purposes since I couldn’t think of a more practical example to show. If you can think of any practical examples, or feel the script above has some practicality, please share in a comment.

I wrote, tested, and saved the above script in the PowerShell ISE:

powershell-ise-task

The PowerShell script was saved to a new Item created by the dialog above:

task-location-script-library

I then created a Schedule Item to invoke the script housed in the Item above (to learn more about Sitecore Scheduled Tasks, please see John West‘s post discussing them):

create-schedule-item-spe

I saved my Item, waited a bit, and opened up my latest Sitecore log file:

html-cache-clear-spe

As you can see, the Html Cache was cleared for each site in my Sitecore instance.

If you have any questions/comments/thoughts on this, please drop a comment.

Advertisement

Periodically Unlock Items of Idle Users in Sitecore

In my last post I showed a way to unlock locked items of a user when he/she logs out of Sitecore.

I wrote that article to help out the poster of this thread in one of the forums on SDN.

In response to my reply in that thread — I had linked to my previous post in that reply — John West, Chief Technology Officer of Sitecore, had asked whether we would also want to unlock items of users whose sessions had expired, and another SDN user had alluded to the fact that my solution would not unlock items for users who had closed their browser sessions instead of explicitly logging out of Sitecore.

Immediate after reading these responses, I began thinking about a supplemental solution to unlock items for “idle” users — users who have not triggered any sort of request in Sitecore after a certain amount of time.

I first began tinkering with the idea of using the last activity date/time of the logged in user — this is available as a DateTime in the user’s MembershipUser instance via the LastActivityDate property.

However — after reading this article — I learned this date and time does not mean what I thought it had meant, and decided to search for another way to ascertain whether a user is idle in Sitecore.

After some digging around, I discovered Sitecore.Web.Authentication.DomainAccessGuard.Sessions in Sitecore.Kernel.dll — this appears to be a collection of sessions in Sitecore — and immediately felt elated as if I had just won the lottery. I decided to put it to use in the following class (code in this class will be invoked via a scheduled task in Sitecore):

using System;
using System.Collections.Generic;
using System.Web.Security;

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Security.Accounts;
using Sitecore.Tasks;
using Sitecore.Web.Authentication;

namespace Sitecore.Sandbox.Tasks
{
    public class UnlockItemsTask
    {
        private static readonly TimeSpan ElapsedTimeWhenIdle = GetElapsedTimeWhenIdle();

        public void UnlockIdleUserItems(Item[] items, CommandItem command, ScheduleItem schedule)
        {
            if (ElapsedTimeWhenIdle == TimeSpan.Zero)
            {
                return;
            }

            IEnumerable<Item> lockedItems = GetLockedItems(schedule.Database);
            foreach (Item lockedItem in lockedItems)
            {
                UnlockIfApplicable(lockedItem);
            }
        }
        
        private static IEnumerable<Item> GetLockedItems(Database database)
        {
            Assert.ArgumentNotNull(database, "database");
            return database.SelectItems("fast://*[@__lock='%owner=%']");
        }

        private void UnlockIfApplicable(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            if (!ShouldUnlockItem(item))
            {
                return;
            }
            
            Unlock(item);
        }

        private static bool ShouldUnlockItem(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            if(!item.Locking.IsLocked())
            {
                return false;
            }

            string owner = item.Locking.GetOwner();
            return !IsUserAdmin(owner) && IsUserIdle(owner);
        }

        private static bool IsUserAdmin(string username)
        {
            Assert.ArgumentNotNullOrEmpty(username, "username");
            User user = User.FromName(username, false);
            Assert.IsNotNull(user, "User must be null due to a wrinkle in the interwebs :-/");
            return user.IsAdministrator;
        }

        private static bool IsUserIdle(string username)
        {
            Assert.ArgumentNotNullOrEmpty(username, "username");
            DomainAccessGuard.Session userSession = DomainAccessGuard.Sessions.Find(session => session.UserName == username);
            if(userSession == null)
            {
                return true;
            }

            return userSession.LastRequest.Add(ElapsedTimeWhenIdle) <= DateTime.Now;
        }

        private void Unlock(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            try
            {
                string owner = item.Locking.GetOwner();
                item.Editing.BeginEdit();
                item.Locking.Unlock();
                item.Editing.EndEdit();
                Log.Info(string.Format("Unlocked {0} - was locked by {1}", item.Paths.Path, owner), this);
            }
            catch (Exception ex)
            {
                Log.Error(this.ToString(), ex, this);
            }
        }

        private static TimeSpan GetElapsedTimeWhenIdle()
        {
            TimeSpan elapsedTimeWhenIdle;
            if (TimeSpan.TryParse(Settings.GetSetting("UnlockItems.ElapsedTimeWhenIdle"), out elapsedTimeWhenIdle))
            {
                return elapsedTimeWhenIdle;
            }
            
            return TimeSpan.Zero;
        }
    }
}

Methods in the class above grab all locked items in Sitecore via a fast query, and unlock them if the users of each are not administrators, and are idle — I determine this from an idle threshold value that is stored in a custom setting (see the patch configuration file below) and the last time the user had made any sort of request in Sitecore via his/her DomainAccessGuard.Session instance from Sitecore.Web.Authentication.DomainAccessGuard.Sessions.

If a DomainAccessGuard.Session instance does not exist for the user — it’s null — this means the user’s session had expired, so we should also unlock the item.

I’ve also included code to log which items have been unlocked by the Unlock method — for auditing purposes — and of course log exceptions if any are encountered — we must do all we can to support our support teams by capturing information in log files :).

I then created a patch configuration file to store our idle threshold value — I’ve used one minute here for testing (I can’t sit around all day waiting for items to unlock ;)):

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="UnlockItems.ElapsedTimeWhenIdle" value="00:00:01:00" />
    </settings>
  </sitecore>
</configuration> 

I then created a task command for the class above in Sitecore:

unlock-items-task-command

I then mapped the task command to a scheduled task (to learn about scheduled tasks, see John West’s blog post where he discusses them):

unlock-items-scheduled-task

Let’s light the fuse on this, and see what it does.

I logged into Sitecore using one of my test accounts, and locked some items:

locked-some-items

I then logged into Sitecore using a different account in a different browser session, and navigated to one of the locked items:

mike-locked-items

I then walked away, made a cup of coffee, returned, and saw this:

items-unlocked

I opened up my latest Sitecore log, and saw the following:

unlocked-log

I do want to caution you from running off with this code, and putting it into your Sitecore instance(s) — it is an all or nothing solution (it will unlock items for all non-adminstrators which might invoke some anger in users, and also defeat the purpose of locking items in the first place), so it’s quite important that a business decision is made before using this solution, or one that is similar in nature.

If you have any thoughts on this, please leave a comment.