Delete unused media items older than 30 days.
Friday, October 24, 2014
Wednesday, October 22, 2014
Sitecore PowerShell Extended with Pipelines
Every once in a while I have what I think is a cool idea. Then I have to tell someone.
@adamnaj @mike_i_reynolds Extended #Sitecore to run scripts during loggingin loggedin, logout. On logout the context user is anonymous. Why?
— Michael West (@MichaelWest101) October 19, 2014
@MichaelWest101 @mike_i_reynolds This sounds like an absolutely awesome idea!
— Adam Najmanowicz (@adamnaj) October 19, 2014
Thank you Adam for the encouragement. This took me a few days to finally write it all down. Hopefully those reading this will learn something, decide to share it, and point out areas of improvement. Feel free to comment or make suggestions. I expect to add this to a future release of Sitecore PowerShell Extensions (SPE).User Story:
As a spe user, I can create scripts to run during user logging in, successful login, and logout so that I can automate tasks that are tedious.
Acceptance Criteria:
- The scripts must fit into one of the available pipelines provided by Sitecore.
- loggingin
- loggedin
- logout
- The example scripts used must be stolen.
@MichaelWest101 @adamnaj @sitecorejohn I stole from john. You stole from me. John will steal from you. ;)
— Mike Reynolds (@mike_i_reynolds) October 19, 2014
Some concepts you will see in this article:- Config include files
- Pipelines
- Configuration Factory with hint attribute and raw: prefix
First we begin with creating a new library project in Visual Studio. When we are complete with the example, we'll have a project that looks like this:
Second we need to reference Sitecore.Kernel and Cognifide.PowerShell libraries. You'll find the Cognifide.PowerShell library in the bin directory after installing SPE.
Third we will create our new pipeline processor which will execute our PowerShell scripts. Below is the skeleton of the class.
using Sitecore.Pipelines;
namespace Sitecore.SharedSource.Pipelines
{
public abstract class PipelineProcessor<TPipelineArgs> where TPipelineArgs : PipelineArgs
{
protected void Process(TPipelineArgs args)
{
}
}
}
We can go ahead and create our three pipelines to extend the PipelineProcessor. Below is the complete implementation of each pipeline.
using Sitecore.Pipelines.LoggedIn;
namespace Sitecore.SharedSource.Pipelines.LoggedIn
{
public class LoggedInScript : PipelineProcessor<LoggedInArgs> { }
}
using Sitecore.Pipelines.LoggingIn;
namespace Sitecore.SharedSource.Pipelines.LoggingIn
{
public class LoggingInScript : PipelineProcessor<LoggingInArgs> { }
}
using Sitecore.Pipelines.Logout;
namespace Sitecore.SharedSource.Pipelines.Logout
{
public class LogoutScript : PipelineProcessor<LogoutArgs> { }
}
Let's go ahead and setup our script libraries in Sitecore before we create the include config and finish out the implementation of running the scripts.
Now that we have the three pipeline libraries, we can create the include config to map to those. I prefer this solution so I don't have to hard code the GUID for each in compiled code.
Now that we have the three pipeline libraries, we can create the include config to map to those. I prefer this solution so I don't have to hard code the GUID for each in compiled code.
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<processors>
<loggingin argsType="Sitecore.Pipelines.LoggingIn.LoggingInArgs">
<!-- Pipeline to run scripts while the user is logging in. -->
<processor patch:after="processor[position()=last()]" mode="on" type="Sitecore.Sharedsource.Pipelines.LoggingIn.LoggingInScript, Sitecore.SharedSource.PowerShell">
<config hint="raw:Config">
<!-- /sitecore/system/Modules/PowerShell/Script Library/Pipelines/LoggingIn -->
<libraryId>{83C826B6-C478-43D9-92BD-E5589F50DA27}</libraryId>
</config>
</processor>
</loggingin>
<loggedin argsType="Sitecore.Pipelines.LoggedIn.LoggedInArgs">
<!-- Pipeline to run scripts after the user is logged in. -->
<processor patch:after="processor[position()=last()]" mode="on" type="Sitecore.Sharedsource.Pipelines.LoggedIn.LoggedInScript, Sitecore.SharedSource.PowerShell">
<config hint="raw:Config">
<!-- /sitecore/system/Modules/PowerShell/Script Library/Pipelines/LoggedIn -->
<libraryId>{D0226A69-F15D-4CBF-812C-BFE3F14936C5}</libraryId>
</config>
</processor>
</loggedin>
<logout argsType="Sitecore.Pipelines.Logout.LogoutArgs">
<!-- Pipeline to run scripts when the user logs out. -->
<processor patch:after="*[@type='Sitecore.Pipelines.Logout.CheckModified, Sitecore.Kernel']" mode="on" type="Sitecore.Sharedsource.Pipelines.Logout.LogoutScript, Sitecore.SharedSource.PowerShell">
<config hint="raw:Config">
<!-- /sitecore/system/Modules/PowerShell/Script Library/Pipelines/Logout -->
<libraryId>{EE098609-4CA4-4FEE-8A86-3AB410AB9C38}</libraryId>
</config>
</processor>
</logout>
</processors>
</sitecore>
</configuration>
The pipeline is pretty standard. I did have to place the LogoutScript pipeline to be placed right after CheckModified, otherwise the username will be anonymous.
Notice the config section inside the processor. I found an example here by Partech which helped to setup the parameters in the config. John West has a nice article explaining the different options.
With that said, I'll show the remaining implementation of the PipelineProcessor. The code follows a few steps:
- Read the libraryId configured for the specified pipeline. A static collection would create a problem in this example, so be sure to leave it as it is below.
- If the library item contains any scripts, then continue.
- For each script defined in the pipeline library create a new session and execute the script. The args parameter is passed as a session variable for use in the scripts.
public abstract class PipelineProcessor<TPipelineArgs> where TPipelineArgs : PipelineArgs
{
protected PipelineProcessor()
{
Configuration = new Dictionary<string, string>();
}
protected void Process(TPipelineArgs args)
{
Assert.ArgumentNotNull(args, "args");
Assert.IsNotNullOrEmpty(Configuration["libraryId"], "The configuration setting 'libraryId' must exist.");
var libraryId = new ID(Configuration["libraryId"]);
var db = Factory.GetDatabase("master");
var libraryItem = db.GetItem(libraryId);
if (!libraryItem.HasChildren) return;
foreach (var scriptItem in libraryItem.Children.ToList())
{
using (var session = new ScriptSession(ApplicationNames.Default))
{
var script = (scriptItem.Fields[ScriptItemFieldNames.Script] != null)
? scriptItem.Fields[ScriptItemFieldNames.Script].Value
: String.Empty;
session.SetVariable("args", args);
try
{
session.ExecuteScriptPart(script, false);
}
catch (Exception ex)
{
Log.Error(ex.Message, this);
}
}
}
}
protected Dictionary<string, string> Configuration { get; private set; }
public void Config(XmlNode node)
{
Configuration.Add(node.Name, node.InnerText);
}
}
Finally, create your scripts in the libraries and watch it in action. Each of my example scripts are adapted from other articles and are linked at the end of this post.
That's pretty much it. If you really want to mess with your colleagues, write a script to send them a phony email every time they login and logout.
References:
- http://www.sitecore.net/Learn/Blogs/Technical-Blogs/John-West-Sitecore-Blog/Posts/2011/02/The-Sitecore-ASPNET-CMS-Configuration-Factory.aspx
- http://www.partechit.nl/en/blog/2014/09/configurable-pipeline-processors-and-event-handlers
- http://www.matthewkenny.com/2014/10/custom-sitecore-pipelines/
- Some scripts you can use. The random desktop background is really useful.
- http://www.sitecore.net/Learn/Blogs/Technical-Blogs/John-West-Sitecore-Blog/Posts/2012/12/Automatically-Show-the-Quick-Info-Section-in-the-Content-Editor-of-the-Sitecore-ASPNET-CMS.aspx
- http://www.sitecore.net/Learn/Blogs/Technical-Blogs/John-West-Sitecore-Blog/Posts/2010/07/Randomize-Sitecore-Desktop-Background-Image.aspx
- http://sitecorejunkie.com/2013/06/08/enforce-password-expiration-in-the-sitecore-cms/
- http://sitecorejunkie.com/2013/09/24/unlock-sitecore-users-items-during-logout/
Wednesday, October 8, 2014
Sitecore PowerShell Run Task On Demand
Recently I needed to help a colleague run a scheduled task on-demand. Immediately I thought to myself, Sitecore PowerShell Extensions can do it! I was certain other developers had created a solution, but I needed something quick.
Adam Najmanowicz @adamnaj helped by providing one out-of-the-box way to accomplish the task. The short answer is to use the Task Manager. Have a look under Sitecore -> PowerShell Toolbox -> Task Manager
You are then presented with a delightful screen which provides you with options like Execute Now and Edit Schedule.
Michael West @michaelwest101 also had a good suggestion. Oh wait, that was me :)
This solution creates a context menu item that can be made visible when selecting an item with the Schedule template.
What we'll do here is create a new PowerShell Script item called Run Task, set a rule on it to only appear for the Schedule template, and write some simple code to run the task:
$item = Get-Item -Path .
if($item) {
$schedule = Get-TaskSchedule -Item $item
Start-TaskSchedule -Schedule $schedule
}
You can also confirm that the scheduled task completed successfully by checking the Log Viewer.
John West @sitecorejohn (coincidentally with the same last name) did provide a detailed article on an approach that does not the module.
Paul Martin @sitecorepm also provided details to a module that provides an alternative experience to the schedule editor.
I hope this helps someone.
// Michael
Sunday, October 5, 2014
Sitecore Code Editor 1.5 Preview
Recently I've spent some time on the Sitecore Code Editor Module to add Markdown support. Back in May I saw a nice article by Dan Cruickshank where he integrated the MarkdownDeep library into Sitecore module. I thought to myself, "If Dan can do it for his module, and I can it for my module!" So here you have it. An implementation of Markdown in the Code Editor module.
There are a few aspects that must be considered when creating a custom field. First, how do you render text so that html doesn't break the content editor. Second, how do you render text so that html doesn't break the page editor.
The magic for the page editor begins here in the GetCodeTextFieldValue pipeline:
The ReplaceNewLines method simply searches for all new line characters and converts to html breaks:
In the event that you are not viewing in page editor mode, the code skips and runs the MarkdownRenderer:
The content editor mode is handled very similar to page editor in the CodeText class (inherits from Sitecore.Web.UI.HtmlControls.Memo):
Have a look out on Github for a more detailed look into the code that makes this module tick.
// Michael
There are a few aspects that must be considered when creating a custom field. First, how do you render text so that html doesn't break the content editor. Second, how do you render text so that html doesn't break the page editor.
The magic for the page editor begins here in the GetCodeTextFieldValue pipeline:
if (Context.PageMode.IsPageEditorEditing)
{
// Encode so the page editor will render the html.
// Replace with line breaks so the spacing is correct.
args.Result.FirstPart = HtmlUtil.ReplaceNewLines(HttpUtility.HtmlEncode(args.Result.FirstPart));
args.Result.LastPart = HtmlUtil.ReplaceNewLines(HttpUtility.HtmlEncode(args.Result.LastPart));
return;
}
The ReplaceNewLines method simply searches for all new line characters and converts to html breaks:
public static string ReplaceNewLines(string input)
{
if (String.IsNullOrEmpty(input)) return input;
return Regex.Replace(input, @"(\r\n|\n)", "<br />", RegexOptions.Compiled);
}
In the event that you are not viewing in page editor mode, the code skips and runs the MarkdownRenderer:
var parameters = args.GetField().Source.ToDictionary(args.Parameters);
if (!parameters.ContainsKey("mode") || !parameters["mode"].Is("markdown")) return;
// Decode the html and then convert new lines to html breaks.
args.Result.FirstPart = MarkdownRenderer.Render(args.Result.FirstPart, parameters);
args.Result.LastPart = MarkdownRenderer.Render(args.Result.LastPart, parameters);
The content editor mode is handled very similar to page editor in the CodeText class (inherits from Sitecore.Web.UI.HtmlControls.Memo):
protected string RenderPreview()
{
// Renders the html for the field preview in the content editor.
return String.Format("<div style='height: 100%; overflow: hidden;'>{0}</div>",
HtmlUtil.ReplaceNewLines(HttpUtility.HtmlEncode(Value)));
}
protected override void DoRender(HtmlTextWriter output)
{
SetWidthAndHeightStyle();
output.Write("<div {0}>{1}</div>", ControlAttributes, RenderPreview());
}
As you can see by overriding the DoRender you have access to how the control is rendered. Below is a short video demoing the new functionality.
// Michael
Friday, September 26, 2014
Sitecore Custom Login Details With Version
It sure has been a while since I last posted anything interesting...or anything at all! I recently spoke with +Michael Reynolds about blogging but rather than blog I decided to read all the tweets about the Sitecore Symposium :(
Now it's time to pick it back up. What you will see in here isn't all that new, but it is however very useful. Also, at the bottom I have listed a few blogs I know of that cover the same topic. Two of which are far more interesting than mine so be sure to have a look.
Recently at work an issue came up where the QA team did not know if the test environment had the correct build applied (TDS package). I decided to use some old code from a version page I created for the same issue with an Asp.net applications.
The following is a list of files that we will create to make the magic happen.
The final results are clean and simple.
The following is a list of other posts I found helpful.
Now it's time to pick it back up. What you will see in here isn't all that new, but it is however very useful. Also, at the bottom I have listed a few blogs I know of that cover the same topic. Two of which are far more interesting than mine so be sure to have a look.
Recently at work an issue came up where the QA team did not know if the test environment had the correct build applied (TDS package). I decided to use some old code from a version page I created for the same issue with an Asp.net applications.
The following is a list of files that we will create to make the magic happen.
- ReflectionUtil.cs : Generic reflection code to extract version details from the loaded assembly.
- ApplicationDetails.cs : Pipeline to extract version information.
- Sitecore.SharedSource.Version.config : Pipeline configuration
The final results are clean and simple.
Saturday, June 14, 2014
PowerShell Revisited - Running Commands
Running PowerShell Commands
The command Get-Verb is an example of a cmdlet (pronounced "command-let"). Cmdlets are structured verb-noun, where the noun is singular.
Below we will get a list of approved verbs in PowerShell, then format the results to fill the screen from left to right:
PS C:\> Get-Verb | Format-Wide -AutoSize Add Clear Close Copy Enter Exit Find Format Get Hide Join Lock Move New Open Optimize Pop Push Redo Remove Rename Reset Resize Search Select Set Show Skip Split Step Switch Undo Unlock Watch Backup Checkpoint Compare Compress Convert ConvertFrom ConvertTo Dismount Edit Expand Export Group Import Initialize Limit Merge Mount Out Publish Restore Save Sync Unpublish Update Approve Assert Complete Confirm Deny Disable Enable Install Invoke Register Request Restart Resume Start Stop Submit Suspend Uninstall Unregister Wait Debug Measure Ping Repair Resolve Test Trace Connect Disconnect Read Receive Send Write Block Grant Protect Revoke Unblock Unprotect Use
Microsoft has provided a nice page on the verb naming rules which can be found here.
The commands you use in cmd.exe can also be used within PowerShell since they are either aliased, new functions, or the external executable is called.
- dir is an alias for Get-ChildItem
- md is an alias for mkdir which is a function for New-Item
- rmdir is an alias for Remove-Item
- Get-Process
- Get-Service
- Get-Date
- Get-HotFix
- Get-History
- Start-Transcript, Stop-Transcript
- Get-ChildItem
PS C:\> Get-PSDrive Name Used (GB) Free (GB) Provider Root CurrentLocation ---- --------- --------- -------- ---- --------------- Alias Alias C 81.37 151.42 FileSystem C:\ Cert Certificate \ E FileSystem E:\ Env Environment Function Function HKCU Registry HKEY_CURRENT_USER HKLM Registry HKEY_LOCAL_MACHINE I 1112.16 305.87 FileSystem \\chs-fs01_users\Users\Michael.West Variable Variable WSMan WSMan Y 8.88 3.12 FileSystem \\chs\data
PowerShell uses a variety of providers such as those for connecting to the file system and the registry. This design allows one to use for example, the Remove-Item command to delete a file or delete a registry key. Sweet!
PS C:\> $item = New-Object -TypeName PSObject -Property @{Name="Michael";Height=73;}
PS C:\> $item | Format-List
Height : 73
Name : Michael
Here I created a new PSObject to contains some details about my name and height. Let's see the member information.
PS C:\> $item | Get-Member TypeName: System.Management.Automation.PSCustomObject Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() Height NoteProperty System.Int32 Height=73 Name NoteProperty System.String Name=Michael
As you can see, the TypeName is "System.Management.Automation.PSCustomObject" and the new properties "Name" and "Height" are listed. The object type will be relevant to whatever object is piped in to Get-Member.
Get-PSDrive
Gets the Windows PowerShell drives in the current session.
Example:
# Displays details about the local disk C. Get-PSDrive C
Random fact:Try this: (Get-PSDrive)[0].Name <# Returns the name of the first object. #>Try this: (Get-PSDrive)[0].GetType() <# Returns the type of the first object. #>Set-LocationSets the current working location to a specified location.Example:# Changes the location to the registry hive HKEY_LOCAL_MACHINE. The colon is required. Set-Location HKLM:PS C:\> Set-Location HKLM: PS HKLM:\> Set-Location C: PS C:\> Set-Location Alias: PS Alias:\> dir | more CommandType Name ModuleName ----------- ---- ---------- Alias % -> ForEach-Object Alias ? -> Where-Object Alias ac -> Add-Content Alias asnp -> Add-PSSnapin Alias cat -> Get-Content Alias cd -> Set-Location Alias chdir -> Set-Location Alias clc -> Clear-Content Alias clear -> Clear-Host Alias clhy -> Clear-History Alias cli -> Clear-ItemAs you can see in the image above, I changed to the registry, then the C drive, then to Alias. In all of these locations, you can run the standard commands such as dir, ls, cd and so on. In the image you can also see that cd is an alias for Set-Location.Below are some command aliases common in other environments.
Command Aliases Copy-Item cpi, cp, copy ForEach-Object foreach, % Get-ChildItem dir, ls Get-Command gcm Get-Content gc, type, cat Get-Help help Get-Location gl, pwd Move-Item mi, mv, move Rename-Item rni, ren Set-Location cd, chdir Where-Object where, ? Remove-Item ri, rd, del, rm, rmdir Random fact:To count the number of items return, use Measure-Object. Use additional switch parameters such as -Sum and -Average.Try this: Get-Process | Measure-Object <# Returns a table with the results of Measure-Object. #>Try this: (Get-Process | Measure-Object).Count <# Returns the count value. #>Get-ContentGets the content of the item at the specified location.Example:# Reads the entire contents of the file. Get-Content -Path C:\log.txtAdd-ContentAdds content to the specified items, such as adding words to a file.Example:# Appends the text to the file log.txt. Add-Content -Path C:\log.txt -Value "The quick brown fox jumps over the lazy dog."Out-FileSends output to a file.Example:# The first command takes a snapshot of all the running processes. # The list passed through pipeline to Out-File which is then written to log.txt. Get-Process | Out-File -FilePath C:\log.txtWrite-HostWrites customized output to a host. The information is not kept in the pipeline.Example:# The text is written to the console window. Write-Host "This text is written to the host, such as the console window."Write-OutputSends the specified objects to the next command in the pipeline. If the command is the last command in the pipeline, the objects are displayed in the console.Example:# The snapshot of running processes is saved into the variable p. The variable p is then written to the console window. $p = Get-Process; Write-Output -InputObject $pWrite-VerboseThe text is written to the verbose message stream. This is commonly used in PowerShell scripts.New-ItemExample:# The text is written to the verbose stream. If the switch -Verbose is used, the text will be written to the console window. Write-Verbose -Message "The quick brown fox jumps over the lazy dog."
Creates a new item. The item can be a new registry key, file, directory, etc. The type of item created depends on which provider you are connected to.Example:# A new file is created with the specified text. New-Item -Path C:\ -Name log.txt -ItemType "file" New-Item -Path C:\log.txt -ItemType "file" -Value "The quick brown fox jumps over the lazy dog."
Monday, June 2, 2014
Sitecore PowerShell Extensions Manual
This document is a work in progress. Hopefully as you continue to refer to this you'll see the updates.
Subscribe to:
Posts (Atom)









