Display Content Management Server Information in the Sitecore CMS
The other day I cogitated over potential uses for the getAboutInformation pipeline. Found at /configuration/sitecore/pipelines/getAboutInformation in the Web.config, it can be leveraged to display information on the Sitecore login page, and inside of the About dialog — a dialog that can be launched from the Content Editor.
One thing that came to mind was displaying some information for the Content Management (CM) server where the Sitecore instance lives. Having this information readily available might aid in troubleshooting issues that arise, or seeing the name of the server might stop you from making content changes on the wrong CM server (I am guilty as charged for committing such a blunder in the past).
This post shows how I translated that idea into code.
The first thing we need is a way to get server information. I defined the following interface to describe information we might be interested in for a server:
namespace Sitecore.Sandbox.Utilities.Server.Base { public interface IServer { string Name { get; } string Cpu { get; } string OperatingSystem { get; } } }
We now need a class to implement the above interface. I stumbled upon a page whose author shared how one can acquire server information using classes defined in the System.Management namespace in .NET.
Using information from that page coupled with some experimentation, I came up with the following class:
using System.Collections.Generic; using System.Linq; using System.Management; using Sitecore.Diagnostics; using Sitecore.Sandbox.Utilities.Server.Base; namespace Sitecore.Sandbox.Utilities.Server { public class Server : IServer { private string _Name; public string Name { get { if (string.IsNullOrEmpty(_Name)) { _Name = GetServerName(); } return _Name; } } private string _Cpu; public string Cpu { get { if (string.IsNullOrEmpty(_Cpu)) { _Cpu = GetCpuInformation(); } return _Cpu; } } private string _OperatingSystem; public string OperatingSystem { get { if (string.IsNullOrEmpty(_OperatingSystem)) { _OperatingSystem = GetOperatingSystemName(); } return _OperatingSystem; } } private Server() { } private static string GetServerName() { return GetFirstManagementBaseObjectPropertyFirstInnerProperty("Win32_ComputerSystem", "name"); } private static string GetCpuInformation() { return GetFirstManagementBaseObjectPropertyFirstInnerProperty("Win32_Processor", "name"); } private static string GetOperatingSystemName() { return GetFirstManagementBaseObjectPropertyFirstInnerProperty("Win32_OperatingSystem", "name"); } private static string GetFirstManagementBaseObjectPropertyFirstInnerProperty(string key, string propertyName) { return GetFirstManagementBaseObjectPropertyInnerProperties(key, propertyName).FirstOrDefault(); } private static IEnumerable<string> GetFirstManagementBaseObjectPropertyInnerProperties(string key, string propertyName) { return GetFirstManagementBaseObjectProperty(key, propertyName).Split('|'); } private static string GetFirstManagementBaseObjectProperty(string key, string propertyName) { return GetFirstManagementBaseObject(key)[propertyName].ToString(); } private static ManagementBaseObject GetFirstManagementBaseObject(string key) { Assert.ArgumentNotNullOrEmpty(key, "key"); WqlObjectQuery query = new WqlObjectQuery(string.Format("select * from {0}", key)); ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); return searcher.Get().Cast<ManagementBaseObject>().FirstOrDefault(); } public static IServer CreateNewServer() { return new Server(); } } }
The class above grabs server information via three separate ManagementObjectSearcher queries, one for each property defined in our IServer interface.
In order to use classes defined in the System.Management namespace, I had to reference System.Management in my project in Visual Studio:
Next, I created a class that contains methods that will serve as our getAboutInformation pipeline processors:
using System.Collections.Generic; using Sitecore.Diagnostics; using Sitecore.Pipelines.GetAboutInformation; using Sitecore.Sandbox.Utilities.Server.Base; using Sitecore.Sandbox.Utilities.Server; namespace Sitecore.Sandbox.Pipelines.GetAboutInformation { public class GetContentManagementServerInformation { private static readonly string CurrentServerInformationHtml = GetCurrentServerInformationHtml(); public void SetLoginPageText(GetAboutInformationArgs args) { args.LoginPageText = CurrentServerInformationHtml; } public void SetAboutText(GetAboutInformationArgs args) { args.AboutText = CurrentServerInformationHtml; } private static string GetCurrentServerInformationHtml() { return GetServerInformationHtml(Server.CreateNewServer()); } private static string GetServerInformationHtml(IServer server) { Assert.ArgumentNotNull(server, "server"); IList<string> information = new List<string>(); if (!string.IsNullOrEmpty(server.Name)) { information.Add(string.Format("<strong>Server Name</strong>: {0}", server.Name)); } if (!string.IsNullOrEmpty(server.Cpu)) { information.Add(string.Format("<strong>CPU</strong>: {0}", server.Cpu)); } if (!string.IsNullOrEmpty(server.OperatingSystem)) { information.Add(string.Format("<strong>OS</strong>: {0}", server.OperatingSystem)); } return string.Join("<br />", information); } } }
Both methods set properties on the GetAboutInformationArgs instance using the same HTML generated by the GetServerInformationHtml method. This method is given an instance of the Server class defined above by the GetCurrentServerInformationHtml method.
I then connected all of the above into Sitecore via a configuration include file:
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <getAboutInformation> <processor type="Sitecore.Sandbox.Pipelines.GetAboutInformation.GetContentManagementServerInformation, Sitecore.Sandbox" method="SetLoginPageText" /> <processor type="Sitecore.Sandbox.Pipelines.GetAboutInformation.GetContentManagementServerInformation, Sitecore.Sandbox" method="SetAboutText" /> </getAboutInformation> </pipelines> </sitecore> </configuration>
Let’s see this in action.
When hitting the Sitecore login page in my browser, I saw server information in the right sidebar, under the Sitecore version and revision numbers:
Next, I logged into Sitecore, opened the Content Editor, and launched the About dialog:
As you can see, my CM server information is also displayed here.
You might be questioning why I didn’t include more server information on both the login page and About dialog. One reason why I omitted displaying other properties is due to discovering that the login page area for showing the LoginPageText string does not grow vertically — I saw this when I did include a few more properties in addition to the three shown above.
Sadly, I did not see what would happen when including these additional properties in the the About dialog. Ascertaining whether it is possible to include more information in the About dialog is warranted, though I will leave that exercise for another day.
If you have any other thoughts or ideas for utilizing getAboutInformation pipeline processors, or other areas in Sitecore where server information might be useful, please drop a comment.
Suppress Most Sitecore Wizard Cancel Confirmation Prompts
By default, clicking the ‘Cancel’ button on most wizard forms in Sitecore yields the following confirmation dialog:
Have you ever said to yourself “Yes, I’m sure I’m sure” after seeing this, and wondered if there were a setting you could toggle to turn it off?
Earlier today, while surfing through my Web.config, the closeWizard client pipeline — located at /sitecore/processors/closeWizard in the Web.config — had caught my eye, and I was taken aback over how I had not noticed it before. I was immediately curious over what gems I might find within its only processor — /sitecore/processors/closeWizard/processor[@type=’Sitecore.Web.UI.Pages.WizardForm, Sitecore.Kernel’ and @method=’Confirmation’] — and whether there would be any utility in overriding/extending it.
At first, I thought having a closeWizard client pipeline processor to completely suppress the “Are you sure you want to close the wizard?” confirmation prompt would be ideal, but then imagined how irate someone might be after clicking the ‘Cancel’ button by accident, which would result in the loss of his/her work.
As a happy medium between always prompting users whether they are certain they want to close their wizards and not prompting at all, I came up with the following closeWizard client pipeline processor:
using System; using Sitecore.Configuration; using Sitecore.Diagnostics; using Sitecore.Web.UI.HtmlControls; using Sitecore.Web.UI.Pages; using Sitecore.Web.UI.Sheer; namespace Sitecore.Sandbox.Web.UI.Pages { public class SuppressConfirmationWizardForm : WizardForm { private const string SuppressConfirmationRegistryKey = "/Current_User/Content Editor/Suppress Close Wizard Confirmation"; private static string YesNoCancelDialogPrompt { get; set; } private static int YesNoCancelDialogWidth { get; set; } private static int YesNoCancelDialogHeight { get; set; } static SuppressConfirmationWizardForm() { YesNoCancelDialogPrompt = Settings.GetSetting("SuppressConfirmationYesNoCancelDialog.Prompt"); YesNoCancelDialogWidth = Settings.GetIntSetting("SuppressConfirmationYesNoCancelDialog.Width", 100); YesNoCancelDialogHeight = Settings.GetIntSetting("SuppressConfirmationYesNoCancelDialog.Height", 100); } public void CloseWizard(ClientPipelineArgs args) { if (IsCancel(args)) { args.AbortPipeline(); return; } if (ShouldSaveShouldSuppressConfirmationSetting(args)) { SaveShouldSuppressConfirmationSetting(args); } if (ShouldCloseWizard(args)) { EndWizard(); } else { SheerResponse.YesNoCancel(YesNoCancelDialogPrompt, YesNoCancelDialogWidth.ToString(), YesNoCancelDialogHeight.ToString()); args.WaitForPostBack(); } } private static bool ShouldCloseWizard(ClientPipelineArgs args) { Assert.ArgumentNotNull(args, "args"); return args.HasResult || ShouldSuppressConfirmationSetting(); } private static bool ShouldSuppressConfirmationSetting() { return Registry.GetBool(SuppressConfirmationRegistryKey); } private static bool ShouldSaveShouldSuppressConfirmationSetting(ClientPipelineArgs args) { Assert.ArgumentNotNull(args, "args"); return args.HasResult && !IsCancel(args); } private static void SaveShouldSuppressConfirmationSetting(ClientPipelineArgs args) { Assert.ArgumentNotNull(args, "args"); Registry.SetBool(SuppressConfirmationRegistryKey, AreEqualIgnoreCase(args.Result, "yes")); } private static bool IsCancel(ClientPipelineArgs args) { Assert.ArgumentNotNull(args, "args"); return AreEqualIgnoreCase(args.Result, "cancel"); } private static bool AreEqualIgnoreCase(string one, string two) { return string.Equals(one, two, StringComparison.CurrentCultureIgnoreCase); } } }
The pipeline processor above will let users decide whether they want to continue seeing the “Are you sure?”‘ confirmation prompt — albeit I had to change the messaging to something more fitting giving the new functionality (see the patch include configuration file or testing screenshot below for the new messaging).
If a user clicks ‘Yes’, s/he will never be prompted with this dialog again — this preference is saved in a Sitecore registry setting for the user.
Plus, suppressing this dialog in one place will suppress it everywhere it would display
Clicking ‘No’ will ensure the message is displayed again in the future.
Clicking ‘Cancel’ will just close the confirmation dialog, and return the user back to the wizard.
You might be wondering why I subclassed Sitecore.Web.UI.Pages.WizardForm. I had to do this in order to get access to its EndWizard() method which is a protected method. This method closes the wizard form.
I plugged it all in via a patch include configuration file:
<?xml version="1.0" encoding="utf-8" ?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <processors> <closeWizard> <processor mode="on" patch:instead="processor[@type='Sitecore.Web.UI.Pages.WizardForm, Sitecore.Kernel' and @method='Confirmation']" type="Sitecore.Sandbox.Web.UI.Pages.SuppressConfirmationWizardForm, Sitecore.Sandbox" method="CloseWizard"/> </closeWizard> </processors> <settings> <setting name="SuppressConfirmationYesNoCancelDialog.Prompt" value="You are about to close the wizard. Would you also like to avoid this message in the future?" /> <setting name="SuppressConfirmationYesNoCancelDialog.Width" value="100" /> <setting name="SuppressConfirmationYesNoCancelDialog.Height" value="100" /> </settings> </sitecore> </configuration>
I tested the above pipeline processor on the wizard for creating a new data template:
I decided to omit screenshots after clicking ‘Yes’, ‘No’ and ‘Cancel’ — there really isn’t much to show since all close the confirmation dialog, with the ‘Yes’ and ‘No’ buttons also closing the wizard.
I also did a little research to see what wizard forms in Sitecore might be impacted by the above, and compiled the following list of wizard form classes — this list contains classes from both Sitecore.Kernel.dll and Sitecore.Client.dll:
- Sitecore.Shell.Applications.Analytics.Lookups.RunLookupForm
- Sitecore.Shell.Applications.Analytics.Reports.Summary.UpdateForm
- Sitecore.Shell.Applications.Analytics.SynchronizeDatabase.SynchronizeDatabaseForm
- Sitecore.Shell.Applications.Analytics.VisitorIdentifications.RunVisitorIdentificationsForm
- Sitecore.Shell.Applications.Databases.CleanUp.CleanUpForm
- Sitecore.Shell.Applications.Dialogs.ArchiveDate.ArchiveDateForm
- Sitecore.Shell.Applications.Dialogs.FixLinks.FixLinksForm
- Sitecore.Shell.Applications.Dialogs.Publish.PublishForm
- Sitecore.Shell.Applications.Dialogs.RebuildLinkDatabase.RebuildLinkDatabaseForm
- Sitecore.Shell.Applications.Dialogs.Reminder.ReminderForm
- Sitecore.Shell.Applications.Dialogs.TransferToDatabase.TransferToDatabaseForm
- Sitecore.Shell.Applications.Dialogs.Upload.UploadForm
- Sitecore.Shell.Applications.Globalization.AddLanguage.AddLanguageForm
- Sitecore.Shell.Applications.Globalization.DeleteLanguage.DeleteLanguageForm
- Sitecore.Shell.Applications.Globalization.ExportLanguage.ExportLanguageForm
- Sitecore.Shell.Applications.Globalization.ImportLanguage.ImportLanguageForm
- Sitecore.Shell.Applications.Globalization.UntranslatedFields.UntranslatedFieldsForm
- Sitecore.Shell.Applications.Install.Dialogs.AddFileSourceForm
- Sitecore.Shell.Applications.Install.Dialogs.AddItemSourceForm
- Sitecore.Shell.Applications.Install.Dialogs.AddStaticFileSourceDialog
- Sitecore.Shell.Applications.Install.Dialogs.AddStaticItemSourceDialog
- Sitecore.Shell.Applications.Install.Dialogs.BuildPackage
- Sitecore.Shell.Applications.Install.Dialogs.InstallPackage.InstallPackageForm
- Sitecore.Shell.Applications.Install.Dialogs.UploadPackageForm
- Sitecore.Shell.Applications.Layouts.IDE.Wizards.NewFileWizard.IDENewFileWizardForm
- Sitecore.Shell.Applications.Layouts.IDE.Wizards.NewMethodRenderingWizard.IDENewMethodRenderingWizardForm
- Sitecore.Shell.Applications.Layouts.IDE.Wizards.NewUrlRenderingWizard.IDENewUrlRenderingWizardForm
- Sitecore.Shell.Applications.Layouts.IDE.Wizards.NewWebControlWizard.IDENewWebControlWizardForm
- Sitecore.Shell.Applications.Layouts.Layouter.Wizards.NewLayout.NewLayoutForm
- Sitecore.Shell.Applications.Layouts.Layouter.Wizards.NewSublayout.NewSublayoutForm
- Sitecore.Shell.Applications.Layouts.Layouter.Wizards.NewXMLLayout.NewXMLLayoutForm
- Sitecore.Shell.Applications.Layouts.Layouter.Wizards.NewXSL.NewXSLForm
- Sitecore.Shell.Applications.MarketingAutomation.Dialogs.ForceTriggerForm
- Sitecore.Shell.Applications.MarketingAutomation.Dialogs.ImportVisitorsForm
- Sitecore.Shell.Applications.ScheduledTasks.NewSchedule.NewScheduleForm
- Sitecore.Shell.Applications.ScheduledTasks.NewScheduleCommand.NewScheduleCommandForm
- Sitecore.Shell.Applications.Search.RebuildSearchIndex.RebuildSearchIndexForm
- Sitecore.Shell.Applications.Templates.ChangeTemplate.ChangeTemplateForm
- Sitecore.Shell.Applications.Templates.CreateTemplate.CreateTemplateForm
If you can think of any other ways of customizing this client pipeline, please drop a comment.