Friday, September 8, 2023

Building Better Reports: 5 Sitecore PowerShell Extensions Functions for Your Toolbelt


When navigating the Sitecore ecosystem effectively, PowerShell extensions aren't just a helpful tool; they're practically a prerequisite. As someone who has spent a substantial amount of time in this space, I have distilled a set of functions that I've found myself using time and time again. 

Join me as I unpack a curated list of five functions that have become staples in my Sitecore toolkit.

Get-IsPublished

The "Get-IsPublished" function checks if a specific item, identified by its ID, is published on a "web" database. It takes an item as input and queries for it against the "web" database using its ID. If it finds the item in the database, it returns "TRUE," indicating that it is published. If it doesn't find the item (i.e., if the item is null), it returns "FALSE", indicating that it is not published.

This is particularly useful for displaying a column in your SPE report to denote if the item is published to the web database:


Get-ItemSitecoreCELink

The "Get-ItemSitecoreLink" function generates a URL to open a specific item in the Sitecore Content Editor. It takes a Sitecore item as input and uses various properties of that item (like its ID, version number, and language) to construct a URL. This URL, when accessed, will open the Sitecore Content Editor with that specific item loaded, allowing for easy navigation directly to the editing interface for that item. 

Please note that the base of the URL ("https://yourcmurl.com/") in this function is a placeholder that you'd replace with your actual Sitecore Content Management URL.

This is useful if the report is exported to Excel or CSV as it provides the direct link to reach the item without manually traversing the tree or searching for the item by GUID.


Get-LinkFieldUrl

The "Get-LinkFieldUrl" function retrieves the URL from a Sitecore item's link field. It takes a Sitecore item as its input and utilizes a regular expression to extract and return the URL stored in a "My Link Field" field. The regular expression is designed to find and capture the URL stored as a value in a link HTML element's URL attribute.  

If the "My Link Field" is not empty and matches the pattern specified by the regular expression, the URL is retrieved and returned. If the field is empty or doesn't match the pattern, the function returns nothing, essentially returning a null value.

This function couples well for reports if you need to extract the URL out of a Sitecore Link Field value to display in your report:

Assert-HasLayout

The "Assert-HasLayout" function checks if a given Sitecore item has a "final layout" defined. It accepts a Sitecore item as its input and uses the built-in "Get-Layout" function to retrieve the final layout details of the item.  If the item has a final layout (meaning the $layout variable is not null or empty), the function returns a "TRUE" string, indicating that a layout is present.

If no layout is found for the item (meaning the $layout variable is null or empty), it returns a "FALSE" string.

Usage example


Get-FormattedDate

The "Get-FormattedDate" function takes a raw date string as its input and attempts to turn it into a more user-friendly date format.

The raw date string is expected to follow a particular "yyyyMMddTHHmmssZ" pattern ("20230908T123456Z" representing September 8, 2023, 12:34:56 PM in Coordinated Universal Time, for example) - which is precisely how Sitecore typically stored DateTime fields in the database.

The function reads this string and converts it to a date format that is more commonly used, which includes the month, day, and year (like "09/08/2023"). If, for any reason, it can't convert the input into a date (maybe because the input doesn't follow the expected pattern), it will simply return an empty string. This way, even if it receives unexpected inputs, it won't crash and will still produce a result, even if that result is just an empty string.

By passing in the raw DateTime field value into the Get-FormattedDate function, the report will convert it to a readable string value when exporting the report to Excel, CSV, or JSON.




Whether you're a seasoned Sitecore developer or just starting out, these 5 functions can become essential tools in your developer toolkit, helping you navigate the complexities of Sitecore with greater ease and efficiency ✌.

Friday, June 23, 2023

Security Bulletin SC2023-003-587441: SPE OnDoubleClick Error

Sitecore has been busy patching security vulnerabilities left and right lately.  The latest as of this post is Security Bulletin SC2023-003-587441

We applied the hotfix to a lower environment but discovered during regression testing the update had broken a key feature of Sitecore PowerShell Extensions (SPE).

After running an SPE report, double-clicking on any result item does not open a new window to the selected item as expected.  Instead, the following error was being thrown in the logs:

ERROR Application error.
Exception: System.Web.HttpUnhandledException
Message: Exception of type 'System.Web.HttpUnhandledException' was thrown.
Source: System.Web
at System.Web.UI.Page.HandleError(Exception e)
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest()
at System.Web.UI.Page.ProcessRequest(HttpContext context)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.<>c__DisplayClass285_0.<ExecuteStepImpl>b__0()
at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Nested Exception

Exception: Sitecore.Exceptions.AccessDeniedException
Message: Calling Spe.Client.Applications.PowerShellResultViewerList.OnDoubleClick method through reflection is not allowed.
Source: Sitecore.Kernel
at Sitecore.Reflection.MethodFilter.Filter[T](MethodInfo method)
at Sitecore.Shell.Framework.Commands.CommandManager.GetMethodCommand(String command)
at Sitecore.Web.UI.Sheer.ClientPage.Dispatch(String command)
at Sitecore.Web.UI.Sheer.ClientPage.RaiseEvent()
at Sitecore.Web.UI.Sheer.ClientPage.OnPreRender(EventArgs e)
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

The main message here:
"Calling Spe.Client.Applications.PowerShellResultViewerList.OnDoubleClick method through reflection is not allowed."

One of the more profound differences noted when comparing the original files against the hotfix files is the `Sitecore.Reflection.Filtering.config`; where we see several entries removed and many new entries related to SPE (Spe.Client.Applications.PowerShellResultViewerList.OnDoubleClick included) have been added


Upon further investigation, it was discovered that in the `OnDoubleClick` definition, an extra trailing space was present:

Removing the space restored the broken double-click functionality on the instance. 

If you find yourself in a similar situation, check this file first and determine if you have any trailing or unexpected spaces within the `methodName,` and remove them before redeploying the file. 

Hope this helps! ✌

Thursday, April 20, 2023

Sitecore Support Security Bulletin SC2023-002-576660: Hotfix vs Patch


Sitecore recently released a Support Security Bulletin SC2023-002-576660 that addresses a vulnerability in the platform. You can find the details of this bulletin in their knowledge base article KB1002979. Some of my team already had questions regarding the recommended approaches for addressing this vulnerability, specifically the differences between applying a hotfix and a patch. In this post, I'll clarify these approaches and discuss how to apply the hotfix to various Sitecore roles.

Two Approaches: Hotfix vs. Patch

The bulletin outlines two approaches to resolve the vulnerability:

  1. Applying as a hotfix
  2. Applying as a patch

The main difference between these two options is that the patch only fixes the known attack vector (a specific method or pathway that cybercriminals have been observed using to exploit vulnerabilities in software or systems). At the same time, the hotfix addresses the vulnerability more comprehensively - covering scenarios beyond the known attack vector. Due to this critical difference, Sitecore strongly recommends applying hotfixes rather than installing the patch. 

Applying the Hotfix to Different Sitecore Roles

The hotfix comes packaged as a `.update` package and can be easily installed using the Sitecore Update Installation Wizard. This process is straightforward for a Content Management (CM) role. However, applying the hotfix to other functions requires additional steps.

For non-CM roles, installing the hotfix on the CM instance is generally recommended, then syncing the changes made with some other cases using your typical development practices. The idea is to ensure the updated files and configurations installed on the CM instance are copied to Content Delivery (CD) or other Sitecore roles to maintain a consistent codebase and configuration.

To manually extract the contents of the .update package, you can use a tool like 7zip without renaming the file. Right-click the file and choose one of the "Extract" options. You can then extract the package.zip to obtain the files from addedfiles, addedfolders, addeditems, and changedfiles folders. 

Alternatively, to 7zip, you can rename the .update file to .update.zip and extract it using Window's built-in zip manager. Once you've extracted the files, you can plan to drop them into the rest of your non-CM XP roles.

It's worth mentioning that your particular solution may already have a previous cumulative hotfix applied. You may need to check your solution to see if any of the extracted files (such as the Sitecore.Kernel.dll) are already referenced to a static hotfix location and make the necessary updates to the solution to reference.

Compare the DLLs and configs against your solution and merge updates wherever necessary.   Skipping this step risks overwriting the installation during a subsequent deployment.

What's in the latest Hotfix?

Looking at 10.0.0 installation, there are 42 file differences between the current solution and the hotfix files:

  • \sitecore modules\Web\ExperienceForms\scripts\form.conditions.js

  • \sitecore\shell\client\Applications\FormsBuilder\Layouts\Renderings\FormDesignBoard\FormDesignBoard.js
  • \sitecore\shell\client\Applications\FormsBuilder\Layouts\Renderings\Composites\SubmitActionsManager\SubmitActionsManager.js
  • \sitecore\shell\Applications\Page Modes\ChromeTypes\FieldChromeType.js
  • \sitecore\shell\Applications\Page Modes\InlineEditingUtil.js
  • \sitecore\shell\Applications\Content Manager\Content Editor.Search.js
  • \sitecore\shell\Applications\Buckets\scripts\ItemBucket.js

  • \bin\Sitecore.Services.Infrastructure.Sitecore.dll
  • \bin\Sitecore.Services.Infrastructure.dll
  • \bin\Sitecore.Services.Core.dll
  • \bin\Sitecore.Services.Client.dll
  • \bin\Sitecore.Mvc.ExperienceEditor.dll
  • \bin\Sitecore.Mvc.dll
  • \bin\Sitecore.Mvc.DeviceSimulator.dll
  • \bin\Sitecore.Kernel.dll
  • \bin\Sitecore.ExperienceForms.SubmitActions.dll
  • \bin\Sitecore.ExperienceForms.Mvc.dll
  • \bin\Sitecore.ExperienceForms.dll
  • \bin\Sitecore.ExperienceForms.Data.SqlServer.dll
  • \bin\Sitecore.ExperienceForms.Client.dll
  • \bin\Sitecore.ExperienceForms.Analytics.dll
  • \bin\Sitecore.ExperienceExplorer.Web.dll
  • \bin\Sitecore.ExperienceExplorer.dll
  • \bin\Sitecore.ExperienceExplorer.Core.dll
  • \bin\Sitecore.ExperienceExplorer.Analytics.dll
  • \bin\Sitecore.ExperienceEditor.Speak.Ribbon.dll
  • \bin\Sitecore.ExperienceEditor.Speak.dll
  • \bin\Sitecore.ExperienceEditor.dll
  • \bin\Sitecore.ContentSearch.SolrProvider.dll
  • \bin\Sitecore.ContentSearch.SolrNetExtension.dll
  • \bin\Sitecore.ContentSearch.Linq.Solr.dll
  • \bin\Sitecore.ContentSearch.Linq.dll
  • \bin\Sitecore.ContentSearch.dll
  • \bin\Sitecore.ContentSearch.Data.dll
  • \bin\Sitecore.ContentSearch.ContentExtraction.dll
  • \bin\Sitecore.ContentSearch.Client.dll
  • \bin\Sitecore.Content.Services.dll
  • \bin\Sitecore.Client.dll
  • \bin\Sitecore.Buckets.dll
  • \bin\Sitecore.Buckets.Client.dll

  • \App_Config\Sitecore\Services.Client\Sitecore.Services.Client.config
  • \App_Config\Sitecore.config

Conclusion

When addressing the vulnerability outlined in Sitecore Support Security Bulletin SC2023-002-576660 - or others where .update files are provided as part of the solution, applying the hotfix rather than the patch is strongly recommended. The hotfix should be installed on the CM instance and synced with other cases using your regular development practices. This ensures a consistent codebase and configuration across all Sitecore roles.

Hopefully, this helps clarify the recommended approaches for addressing this and future vulnerabilities in Sitecore.

Tuesday, February 28, 2023

Sitecore xDB Contact Lookup Utility

My client's lead flow includes capturing form data into a custom database. Each record contains an xDB Contact ID to correlate the data with the data captured in xDB directly.  We had a scenario where we had hundreds of records in the custom database where the xDB contact ID was missing, likely due to a networking issue between the CD and xConnect.

I needed a way to match users captured in the custom database against the data in xDB to obtain an existing xDB contact ID.  Luckily, Sitecore's documentation provides some sample code to help get me started: xConnect Client API (C#) (sitecore.com)

Thus was born, the...

🧰 xDB Contact Lookup Utility


The utility is your standard .aspx file that can be dropped into the/sitecore/admin folder or at the root of CM instance. It combines a series of xConnect API calls to search for contact information in the xDB.  Applicable parameters include email address, first name, last name, or xDB contact ID.

It can be used in two ways:

First, through the UI (as shown above), you have the ability to populate text boxes to obtain parameters and view the result list on the page.

Secondly as a simple rest endpoint, with the ability to pass parameters into the URL and return a JSON response containing a matching xDB contact ID.

For example: 
/xDB-Contact-Lookup.aspx?email=test@test.com&firstName=test&lastName=test




👨‍💻 Code Breakdown

Page_Load

The Page_Load event checks if a query parameter named "email," "lastName," or "firstName" are present in the URL. 

If at least the "email" parameter is present, the method calls the SearchForContactIdByEmail .If the lastName and firstName parameters accompany the email parameter, the SearchForContactIdByName method is also used to search for a contact. The search results are returned as a JSON response which contains an xDB contact ID of the first match or "NoMatch" if no match was found. The JSON string is written to the HTTP response, ending the response. If the "email" query parameter does not exist, the method does nothing and loads the UI.

btnLookup_Click

This button click handler retrieves user input from four text boxes for known contact ID, email, last name, and first name. If the user input is for a known contact ID, it calls the SearchForContactById method to retrieve the contact's information and displays it in an HTML table. 

If there is no matching contact, it displays a message saying that no contact was found. 

If the user input is for email, last name, and/or first name, it calls SearchForContactIdByEmail and/or SearchForContactIdByName methods to retrieve contact information and displays it in an HTML table. 

If there are no matching contacts, it displays a message saying that no contact was found.

There are three methods implemented for using the xConnect API:

SearchForContactById

This method searches for a Contact object in xDB using a known contact ID. The method SearchForContactById takes a string parameter knownContactId and returns a Contact object. The method first converts the knownContactId string to a Guid, creates an instance of Sitecore.XConnect.Client.XConnectClient, and uses the Get method of the client object to retrieve a Contact object using a Sitecore.XConnect.ContactReference object created from the Guid

It also uses a Sitecore.XConnect.ContactExpandOptions object to specify which facets of the Contact object to retrieve. 

If the Get method throws an XdbExecutionException, the method catches the exception and returns null.

SearchForContactIdByName

This method searches for a list of Contact objects by their LastName and FirstName facets of PersonalInformation

SearchForContactIdByName takes two string parameters, lastName and firstName, and returns a list of Contact objects. 

Inside the Sitecore.XConnect.Client.XConnectClient using block, the method creates an IAsyncQueryable object named queryableLastName that represents a xConnect API query to xDB. The query retrieves all contacts where the LastName and FirstName facets of PersonalInformation match the values of the lastName and firstName parameters. 

The query then sorts the results in descending order of LastModified. The WithExpandOptions method is used to specify which facets of the Contact object to retrieve. The method then retrieves the query results using a batch enumerator, which is added to a List<Contact> object named idsList. 

If the query throws an XdbExecutionException, the method catches the exception and returns null.

SearchForContactIdByEmail

This method searches for a list of Contact objects by their EmailAddress and optionally their LastName and FirstName facets of PersonalInformation

SearchForContactIdByEmail takes three string parameters, email, lastName, and firstName, and returns a list of Contact objects. 

Inside the using block, the method creates an IAsyncQueryable object named queryable that represents a xConnect API query to the xDB. The query retrieves all contacts where the PreferredEmail facet of EmailAddressList matches the email parameter value. 

The query then sorts the results in descending order of LastModified. The WithExpandOptions method is used to specify which facets of the Contact object to retrieve. The method then retrieves the query results using a batch enumerator, which is added to a List<Contact> object named idsList

If the firstName and lastName parameters are not empty, the method checks if the FirstName facet of PersonalInformation and LastName facet of PersonalInformation match the values of the firstName and lastName parameters respectively for each Contact object retrieved. 

If both match, the Contact object is added to idsList. If the firstName and lastName parameters are empty, all Contact objects retrieved are added to the idsList

If the query throws an XdbExecutionException, the method catches the exception and returns null.

Final Result

The full code can be copied here. Add the content to a .aspx file and place it where you need it.


As always, feel free to modify or build on top of this to satisfy your own requirements.

🚀