Wednesday, November 20, 2024

Sitecore CDP Certification Practice Exams: A Free Study Companion

Earlier this year, I introduced the Sitecore XM Cloud Developer Certification Practice Exams app, a free resource that seemed to resonate well with the Sitecore developer community. The community's feedback online and offline during the 2024 Sitecore Symposium has been inspiring, and I’m excited to build on that momentum.

Today, I’m proud to unveil the Sitecore CDP Developer Certification Practice Exams app — another cost-free resource to help you confidently prepare for certification and excel as a Sitecore professional.


Expanding Access to Certification Prep

The Sitecore CDP Developer Certification Practice Exams app shares the same goal as its XM Cloud counterpart: providing high-quality, no-cost tools to prepare developers for their certification journeys. By simulating the exam environment and focusing on critical knowledge areas, these tools remove the financial and logistical barriers that often come with traditional study materials.

Features That Mirror the XM Cloud App Success

Authentic Exam Experience: Just like the XM Cloud app, this new tool features randomized 30-question exams and a 60-minute timer to simulate real CDP exam testing conditions.

Competency-Focused Questions: Drawn directly from Sitecore’s documentation, the questions hone your understanding of CDP-specific concepts like architecture, real-time decisioning, data ingestion, and privacy compliance.

Time Management Training: With a timer to mimic the real exam, you’ll develop the pacing skills necessary for success.

Whether you’re familiar with the XM Cloud app or just discovering these resources, you’ll find the CDP version equally intuitive and impactful.

If you're already familiar with the XM Cloud Practice Exams app, you'll find familiarity in the Sitecore CDP Practice Exams app with CDP-specific questions to level up your knowledge in preparation for the exam. 

Built for the Community, Designed for Accessibility

The XM Cloud practice exam app was born from a personal need and a vision to empower developers to pass their exams and earn Sitecore certification badges. With over a decade in the Sitecore ecosystem, I’ve seen how access to high-quality learning tools can unlock opportunities for developers at every level. This app continues that mission, focusing on the expanding world of Sitecore CDP.

Certification should be about your skills and commitment, not your wallet. That’s why this tool, like its predecessor, is 100% free.

Ready to Tackle the Sitecore CDP Exam?

Dive into the Sitecore CDP Developer Certification Practice Exams app and take the next step in your Sitecore journey. With no cost, no risk, and plenty of learning, it’s the perfect added companion for your exam preparation.

👉 Start Practicing Today!

Got feedback? Suggestions? Success stories? 

Drop me a line on LinkedIn or X - or share the app with your network.
I’m always down to hear how these tools are helping developers like you.

Happy learning, and good luck on your certification journey!


Thursday, August 1, 2024

Sitecore MCP 2.0 Roles and Responsibilities Matrix Decoder

TL;DR

I built an app to help me understand the differences in roles and responsibilities between MCP 1.0 and MCP 2.0, and I think it may be helpful for others.  I wanted an easy way to search or browse the different roles and responsibilities and get a clear answer. 

Check it out here:

Sitecore MCP 2.0 Roles and Responsibilities Matrix Decoder App



Navigating the complexities of the Sitecore Managed Cloud PaaS 2.0 roles and responsibilities matrix isn't particularly exciting, especially for those new to the platform or customers moving from MCP 1.0. While I discovered that the roles and responsibilities in MCP 1.0 and MCP 2.0 are fundamentally similar, MCP 2.0 introduces more detailed and specific tasks related to modern cloud infrastructure management and security enhancements.

This week, I built a learning/reference app to help Sitecore customers, developers, architects, operations administrator/support teams, and account/project managers (and myself 😏) easily reference the different roles and responsibilities defined in MCP 2.0. 

The tool sets out to simplify the process of familiarizing oneself with new tasks and responsibilities, making searching and browsing through the matrix far more digestible. Whether transitioning from MCP 1.0 or diving into MCP 2.0 for the first time,

First, let's highlight the key differences between MCP 1.0 and MCP 2.0 roles and responsibilities I noted during my research. 

The RACI Model

The MCP roles and responsibilities matrix is structured using the RACI model, a framework that defines various stakeholders' involvement in completing project tasks or deliverables. Understanding this model is important for effectively navigating the roles and responsibilities within Sitecore Managed Cloud PaaS 2.0.

What is the RACI Model?

RACI stands for Responsible, Accountable, Consulted, and Informed. Each role in the matrix is assigned one or more of these labels to specify their level of involvement in a given activity. 

  • Responsible (R): The person or people who do the work to complete the task. They are responsible for action/implementation. Responsibility can be shared.

  • Accountable (A): The person who is ultimately answerable for the correct and thorough completion of the task. This role is often called "the one who signs off on the work," and only one person can be assigned to this role for each task.

  • Consulted (C): The people who provide information for the project and with whom there is two-way communication. These are typically subject matter experts.

  • Informed (I): The people who are kept up-to-date on progress, often only on completion of the task or deliverable, and with whom there is just one-way communication.

Applying the RACI Model in Sitecore Managed Cloud PaaS

Sitecore applies the RACI model to delineate roles and responsibilities among Sitecore, customers, and partners within the MCP 2.0 environment to ensure all stakeholders understand their part of the bargain.

Here are some examples of how the RACI model is applied:

  • Activation and Termination: For activating the Sitecore Managed Cloud, Sitecore is Accountable (A) and Responsible (R), while the customer/partner is Informed (I).

  • Provisioning of Environments: Sitecore is Responsible (R) and Accountable (A) for creating new environments while the customer/partner is Consulted (C).

  • Application Design and Implementation: The customer/partner is Responsible (R) and Accountable (A) for planning Sitecore software upgrades, while Sitecore is Informed (I).

  • Infrastructure and Server Management: Sitecore handles the initial provisioning checks as Responsible (R) and Accountable (A), with the customer/partner being Informed (I).

Key Differences Between MCP 1.0 and MCP 2.0 Roles and Responsibilities

While both MCP 1.0 and MCP 2.0 maintain a similar structure and overall responsibilities, MCP 2.0 introduces several new and detailed tasks that reflect advancements in cloud infrastructure management and security. Here are the main differences:

Provisioning of Sitecore Environments

MCP 1.0:
  • Create new environment, installation, and initial set up: Sitecore (R, A)
  • Delete existing environment: Sitecore (R, A)
  • Reset existing environment: Sitecore (R, A)
MCP 2.0:
  • Provision Sitecore Managed Cloud production Hub-spoke environment(s): Sitecore (R, A)
  • Provision Sitecore Managed Cloud non-production environment(s): Sitecore (R, A)
  • Provision Sitecore Managed Cloud disaster recovery Hub-spoke environments(s): Sitecore (R, A)
  • Provision Sitecore Managed Cloud “custom customer-owned” Spoke environment: Sitecore (R, A)
  • Delete existing environment: Sitecore (R, A)
  • Reset existing environment: Sitecore (R, A)

Infrastructure and Server Management

MCP 1.0:
  • Perform initial provisioning check: Sitecore (R, A)
  • Scale infrastructure services (Web App, Solr, Azure SQL, Redis cache, and so on): Sitecore (R)
  • Initial Web Application Firewall - deployment and configuration: Sitecore (R)
  • Set up initial security (Azure SQL firewall): Sitecore (R, A)
  • Set up network firewalls and post-deployment security: Customer/Partner (R, A)
  • Set up third-party services (DevOps tools, CDN, databases, and so on): Customer/Partner (R, A)
  • Custom domain setup: Customer/Partner (R, A)
  • Initial setup and configuration of backup services (blobs, database): Sitecore (R, A)
  • Customization of backup schedules and services: Customer/Partner (R)
  • Consolidation of billing: Sitecore (R, A)
  • Infrastructure performance optimization: Customer/Partner (R, A)
MCP 2.0:
  • Initial Azure Front Door with Web Application Firewall - deployment and configuration: Sitecore (R)
  • Network security groups and initial security setup (Azure SQL firewall): Sitecore (R, A)
  • Network firewalls and post-deployment security setup (Azure SQL firewall): Customer/Partner (R, A)
  • Set up third-party services (DevOps tools, CDN, databases, and so on): Customer/Partner (R, A)
  • Custom domain setup: Customer/Partner (R, A)
  • Initial setup and configuration of backup services (blobs, database): Sitecore (R, A)
  • Customization of backup schedules and services: Customer/Partner (R)
  • Consolidation of billing: Sitecore (R, A)
  • Infrastructure performance optimization: Customer/Partner (R, A)

Security: Azure Platform

MCP 1.0:
  • Configure encryption at rest and in motion: Sitecore (R, A)
  • Configure and perform disaster recovery: Sitecore (R, A)
  • Configure host security - hardened OS: Sitecore (R, A)
  • Operating system (PaaS): Sitecore (R, A)
  • Sitecore Cloud operations change management (via ServiceNow): Sitecore (R, A)
  • Azure DDoS standard initial setup: Sitecore (R, A)
  • Azure DDoS standard post-provisioning: Customer/Partner (R)
  • Define basic Web Application Firewall requirements - rule management: Customer/Partner (R, A)
  • Initial deployment security hardening of Sitecore product: Sitecore (R, A)
  • Ongoing security hardening of Sitecore product: Customer/Partner (R, A)
MCP 2.0:
  • Configure encryption at rest and in motion: Sitecore (R, A)
  • Configure infrastructure security logging via Azure Defender for Cloud: Sitecore (R, A)
  • Configure and perform disaster recovery: Sitecore (R, A)
  • Configure CD App service for Azure Zone Redundancy: Sitecore (R, A)
  • Configure host security - hardened OS: Sitecore (R, A)
  • Configure initial network security – Network security groups: Sitecore (R, A)
  • Configure initial network security – VNET and subnets: Sitecore (R, A)
  • Configure initial network security – private link / private endpoint (App service, SQL, Key Vault): Sitecore (R, A)
  • Configure Azure Bastion service: Sitecore (R, A)
  • Implementation of Azure S2S VPN: Sitecore (R, A)
  • Ongoing S2S VPN configuration and client-side management: Customer/Partner (R, A)
  • Operating system (PaaS): Sitecore (R, A)
  • Sitecore Cloud operations change management (via ServiceNow): Sitecore (R, A)
  • Azure DDoS standard initial setup: Sitecore (R, A)
  • Azure DDoS standard post-provisioning: Customer/Partner (R)
  • Define basic Web Application Firewall requirements - rule management: Customer/Partner (R, A)
  • Implement initial Web Application Firewall configuration and rule management (Front Door): Sitecore (R, A)
  • Initial deployment security hardening of Sitecore product: Sitecore (R, A)
  • Ongoing security hardening of Sitecore application: Customer/Partner (R, A)

Why I Built This App

The roles and responsibilities matrix is a critical component of the Sitecore Managed Cloud PaaS 2.0 offering, providing essential information about the tasks and duties of different team members. However, the sheer volume of information can be overwhelming. 

My goal was to create an intuitive and user-friendly tool that would streamline familiarizing oneself with these roles, ultimately enhancing productivity and understanding.

Key Features of the App

The web app is designed to be straightforward and efficient, offering several key features:

  • Search Functionality: Users can quickly search for specific roles or responsibilities using keywords. This feature significantly reduces the time spent sifting through documents.
  • Browse Capability: For those who prefer to explore, the app allows users to browse through the roles and responsibilities in a structured manner, making it easy to find relevant information.

How to Use the App

Using the app is straightforward. Simply visit Sitecore MCP 2 Roles and Responsibilities Matrix Decoder, where you can start searching or browsing through the various roles and responsibilities.

Conclusion

Whether you are a new user trying to understand your role or an experienced professional looking for specific responsibilities, this tool is designed to help you find the information you need quickly and easily.

For a detailed look at the roles and responsibilities, refer to the official Sitecore documentation provided in the app. Feel free to reach out if you have any feedback or suggestions for improvement.

Monday, July 22, 2024

Sitecore TDS could not load all files for the project: Overcoming Long Path Restrictions in Windows

For some time, we struggled with a pesky issue in one of my client's Sitecore MVC Helix-based solutions: certain Sitecore TDS items would fail to load due to excessively long file paths. The problem varied depending on where the developer's cloned solution was located, leading to a cascade of error pop-ups in Visual Studio. Through experimentation, we landed on an effective solution to mitigate the errors.

Here’s a detailed rundown of what we tried and what ultimately worked.

The Persistent Error

The error surfaced as a series of pop-up messages in Visual Studio whenever a developer loaded the solution.

Close.  Close. Close. Close. Close.

Each pop-up corresponded to a TDS item that failed to load due to excessively long paths.   Once each pop-up was manually closed, Visual Studio would finally load the solution. 

Within the TDS console, various errors like this would be present:

The file 'C:\GITCODE\Client\Client.com\src\Foundation\SharedTemplates\tds\Client.Foundation.SharedTemplates.Master\sitecore\templates\Foundation\Client\SharedTemplates\Sales Tool\Components\Text With Image Video\_textWithImageVideo\Data\Video Thumbnail Image.item' could not be loaded.

12:07:35 PM: Sitecore TDS has finished parsing 707 files for Client.Foundation.SharedTemplates.Master.

Warning: Sitecore TDS could not load all files for the project. The files may be missing or corrupted. Please see the Sitecore TDS pane in the output window for more details.

Attempts to Resolve the Issue

1. Updating the Windows Registry

For Windows 10, we tried edited the registry to enable long paths by adding a LongPathsEnabled key:

>HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem

Setting LongPathsEnabled to 1 had no effect.

2. Git Long Paths Configuration

We tried enabling long-path support in Git:

git config --system core.longpaths true
(Configures Git to support long paths for all users and repositories on the entire system, requiring administrative privileges.)

git config --global core.longpaths true
(Configures Git to support long paths for all repositories the current user uses without requiring administrative privileges.)

This approach appeared to work for Windows 11, but not Windows 10.

3. Using Directory Junctions (Symbolic Links)

Our breakthrough came from using directory junctions, which effectively shortened the path length by creating a symbolic link to the project directory. This can be accomplished using the command prompt or with PowerShell:

Creating a Directory Junction using the Command Prompt
  1. Open Command Prompt as Administrator: Press Win + R, type cmd, and press Enter.

  2. Run the mklink Command:

    • Suppose your project directory is located at C:\Users\YourUserName\GitSolutions\SitecoreSolution.

      You can create a shorter path like this:
      > mklink /J C:\ShortPath C:\Users\YourUserName\GitSolutions\SitecoreSolution

      This command creates a junction at C:\ShortPath pointing to your actual project directory.

  3. Access Your Project:

    • Open your project from the new shorter path (C:\ShortPath) in Visual Studio.
Creating a Directory Junction using PowerShell
  1. Open PowerShell (in Windows Terminal) as Administrator: Press Win + X, and select "Windows PowerShell (Admin)".

  2. Run the New-Item Cmdlet:

    • Suppose your project directory is located at C:\Users\YourUserName\GitSolutions\SitecoreSolution.

      You can create a shorter path like this:
      > New-Item -ItemType Junction -Path "C:\ShortPath" -Target "C:\Users\YourUserName\GitSolutions\SitecoreSolution"

      This command creates a junction at C:\ShortPath pointing to your actual project directory.

  3. Access Your Project:

    • You can navigate to C:\ShortPath and see that it points to your original directory. Open your solution file from the new shorter path (C:\ShortPath) in Visual Studio.

TL;DR

For Windows 10 Users: Using directory junctions proved to be the most effective solution for developers on Windows 10. It’s a non-invasive approach that doesn’t require registry edits or changes to Git configurations.

For Windows 11 Users: Enabling long paths via Git configuration has shown similar results in Windows 11 without creating a junction.
This setting seems to leverage Windows 11's improved support for long paths across more applications, including Visual Studio.

Conclusion

If you’re struggling with similar issues, I highly recommend trying directory junctions or adjusting your Git configurations based on your operating system.

Try these methods and see the difference it makes in your development process. Share your experiences or tips in the comments. 

Happy Sitecore TDS-ing!

Monday, May 20, 2024

Sitecore PowerShell Extensions Text-to-Speech Audio Synthesis Module

Another year, another exciting Sitecore Hackathon!  This round, I flew solo under the moniker "Sitecorepunk 2077" (a play on the critically acclaimed 2020 action role-playing video game "Cyberpunk 2077").


If you're curious how the event unfolded, I documented my progress on X (formerly Twitter) every couple of hours:













Needless to say, I was utterly exhausted and slept for 12 hours straight, following the 32 hours I had been awake.  While I didn't snag a win (congrats, team Cloud Surfers and team 451 Unavailable For Legal Reasons ), I enjoyed the experience, am proud of what I was able to output, and look forward to the next one.


Module Concept and Inspiration

The 2024 Sitecore Hackathon category I chose to work against was "Best Module for XM/XP or XM Cloud" - although the result could also fit the bill for "Best use of AI".  Inspired by the ever-increasing need for accessible content, I decided to develop a module that converts text content into spoken audio files, which are then stored remotely and saved as an MP3 links within the item's context - all from within Sitecore. Ultimately, once I landed on the idea, the goal was to provide an easy-to-use tool for generating audio versions of Sitecore content, thereby enhancing accessibility and improving user engagement for individuals with visual impairments or preferences for audio content.

Features

Here’s a breakdown of what makes the SPE Text-to-Speech Audio Synthesis Module stand out:

Lifelike Speech Synthesis from Microsoft Azure Cognitive AI Speech Services

One of the core features of this module is its ability to convert text content into lifelike speech. By transforming text into life-like speech, the module makes content more accessible to a broader audience, including those with visual impairments and individuals who prefer consuming content through audio.

The module utilizes Microsoft Azure Cognitive Services Speech Service to generate audio from selected text fields dynamically. This integration ensures high-quality, natural-sounding speech output. Whether it's a blog post, news article, or product description, every piece of content can be converted into audio, broadening its reach and enhancing user engagement.

Storage via Azure Blob Storage

To store the generated audio files, the module leverages Azure Blob Storage APIs. Once an audio file is generated and store locally in a temporary directory, it is then uploaded to a dedicated Azure Storage container. The API returns a URL to the audio file, which is then populated in the context page item’s Audio URL field. 


Interface and Custom Ribbon Button

A custom Ribbon Button on the Home tab streamlines the audio-generating process. This button triggers an interactive Sitecore PowerShell Extensions dialog where authors can configure various options, such as voice selection, field selection, and speech rate adjustment, and kick off the speech synthesis generation.


The customizable options ensure the audio output matches the intended tone and speech rate, providing a tailored listening experience.

Multi-Language Support

Recognizing the diverse needs of global users, the module supports multiple languages. For demonstration purposes and within the natural time constraints of the Hackathon, the following languages are supported in the initial implementation:

  • English (en)
  • Japanese (ja-JP)
  • German (de-DE)
  • Danish (da)

Each supported language selection has a series of Neural (lifelike, natural-sounding) voice options from Microsoft Azure Cognitive Services Speech Service (~449 neural voices to choose from). These hand-selected voices are configured to provide the best audio experience for each language. Of course, support can be expanded to include additional languages (there are 136 languages supported by Azure AI Speech Services).  


High-level Technical Breakdown

Initialization and Setup

The script sets up the necessary Azure services and local environment configurations.


User Interaction and Dialog Configuration

The script provides a dynamic interface through a custom Ribbon Button in the Sitecore Content Editor. This button, titled 'Generate Audio' or 'Regenerate Audio' based on the context item’s state, opens a dialog for configuring the audio output.  The fields and options available in the dialog are as follows:

- Field to Convert to Speech
  • Lists all Rich Text Editor (RTE) and multi-line text fields available on the item.
  • Special Case: If the 'Speech Content Override' field is populated, it appears as an additional option.

- Include Title?
  • A standalone radio button to include the item's title in the audio file.

- Voice
  • Dynamic option based on the item's language, the dialog offers preselected AI Neural voices.

- Speech Rate
  • Control the how fast the speech is spoken. 
    • Optional double value, defaulting to 1.0 if left empty.
    • Range: Between 0.5 (slow) and 2.0 (fast).

The dialog properties and user input handling are defined as follows:


Fetching and Sanitizing Text Content

The Invoke-AudioStreamFetch function handles the core functionality of fetching the text content from Sitecore, sanitizing it, and preparing it for conversion into speech.

The function checks if the title should be included and concatenates it with the main text content. It then sanitizes the text by removing HTML tags and special characters, ensuring clean input for the TTS service.


Sending Text to Azure AI for Speech Synthesis

As seen above, the sanitized text is then sent to the speech service endpoint for conversion into an audio file. The response, which contains the audio stream, is saved locally.


Uploading the Audio File to Azure Blob Storage

Once the audio file is generated, it is uploaded to Azure Blob Storage by calling the Upload-FileToAzureStorage function. This function handles the Azure Storage REST API authentication and the file upload process.


Updating Sitecore Item with Audio URL

After uploading the audio file to Azure, the script updates the Sitecore item with the URL of the audio file, ensuring that the content authors can easily access and manage the generated audio files.


Utilizing the Audio File on the Front-end

Once an item's Audio URL field has been populated, it can be used on the front-end within an HTML audio tag:




This is the simplest approach for playing the audio file, but further styling customizations are doable.


Video Demo

Part of the Hackathon Entry includes a video demo. You can check it out below:


Final Thoughts

Participating in the Sitecore Hackathon has always been an exhilarating experience for me, given the time crunch and competitiveness of the community. That night, the development of the SPE Text-to-Speech Audio Synthesis Module pushed my organizational and technical boundaries, and I'm proud of what I could accomplish in such a short timeframe. More importantly, I hope the resulting module helps highlight the importance of accessibility in content management and end-user experiences. 

If you're interested in or inspired to build your own Text-to-Speech synthesis module, the full PowerShell script and documentation are available on Github.

Tuesday, April 9, 2024

Sitecore XM Cloud Developer Certification Practice Exams: A Free Study Companion

Certification is a crucial milestone for any developer pursuing excellence and proficiency in Sitecore XM Cloud.  One of my preferred ways to learn and study is via practice exams.  However, with existing spread of Sitecore XM Cloud practice exams available online cost being between $30 and $150, the financial burden of personally preparing can be as daunting as the exam itself. 

That's why I'm excited to introduce the Sitecore XM Cloud Developer Certification Practice Exams app, a completely free resource designed to democratize the preparation process for all Sitecore developers.



Elevating Your Exam Readiness Without the Cost

The XM Cloud Certification demands a deep understanding of numerous Sitecore aspects, from XM Cloud architecture and developer workflow to security and data modeling. This exhaustive list requires serious preparation. The Sitecore XM Cloud Developer Certification Practice Exams app offer a thorough, cost-free study tool that reflects the actual exam's breadth and depth.

Tailored for Comprehensive Preparation

  • Precise Exam Simulation: The practice exams simulate the actual test with 50 questions chosen randomly, testing not just your knowledge but also your ability to perform under exam conditions.

  • Competency-Centric Learning: Dive into crucial competencies on which the exam will test you. Each practice question is sourced from Sitecore's documentation and is an opportunity to fortify your understanding of core Sitecore XM Cloud competencies.

  • Real Exam Experience: Sharpen your time management skills with a 100-minute timer that mirrors the exam's duration.



Commitment to Community and Accessibility

Access to educational resources should be barrier-free in a landscape dotted with expensive prep materials.  The Sitecore XM Cloud Developer Certification Practice Exams app was born from a blend of personal needs and a commitment to the Sitecore community. This practice exam tool is my contribution towards leveling the playing field for all aspiring XM Cloud certified developers.

I'm excited to offer this resource to the community, ensuring that everyone has the chance to study effectively and become certified without the financial strain. Start your free practice runs today and please share this tool with anyone who might benefit.


Happy learning!

Wednesday, February 14, 2024

Sitecore ADM: Resolving Stalled Tasks and Restoring Task Processing

My team is currently in the process of purging millions of historical anonymous xDB contact records and associated data using the ADM module for a client whose xDB shard database sizes have been approaching max storage capacity for the Azure tier.  Because xDB is a crucial portion of the client site's operations, our options for reducing the DB size have been somewhat limiting due to complex custom external integrations with xDB. 

In our approach, we opted to use ADM to purge historical anonymous contacts in batches. We prepare ~300k contact records per shard for each batch, which are manually retrieved via SQL query. Once we've created the temporary table in the shard DB, we prepare the data by generating a comma-delimited list of contacts and then kick off the purge process via ADM. 

When ADM populates its Tasks table, each queued record is subsequently processed by ADM and removed from the Tasks table as it completes processing that record.  The ADM task execution is a generally slow process (1 contact processed every 2-3 seconds); we closely monitor the progress with a SQL query:



With this approach (in addition to SHRINK and REINDEX operations between batches), we have seen the necessary disk size reduction of both xDB shard DBs after running a cadence of several batches.  

However, we ran into a snag in a recent batch, which resulted in the entire ADM task processing halting entirely.  The issue appeared to directly correlate with general Azure Maintenance operations, which had occurred over the weekend while the batch was mid-process.  Azure Maintenance updates typically happen without any advanced notice or warning.  Usually, Azure Maintenance operations have minimal adverse effects, but this round seemed to have caused much of the infrastructure to spiral.  We observed that the ADM tasks were no longer processing when all was said and done.  

Attempts to re-start the job via ADM kept resulting in the same error:

"[ADM] Response from xConnect did not indicate success. Status code: BadRequest, Message: {\"Message\":\"The remove task can't be started while another one is running.\"}"

Upon initial analysis, we noted that the ADM tasks table was still populated with IDs that had yet to be processed when the operation was cut off. I began dissecting the ADM binary files for clues - specifically in search of the message "The remove task can't be started while another one is running".  

I learned that the StartContactsDataRemoving method queries an IsRunning method to determine if any other tasks are in progress. If there are, it throws a BadRequest
response and returns the "The remove task can't be started while another one is running.
message" message. 



Digging deeper led me to this ClearRemoveDataSettings method - called in the StopRunningTasksAndClearStorage method.  Deeper in, there are references to a PropertiesRepository class and an object name of "RemoveDataSettings" used to store task information:


This, in turn, finally led me to a PropertyValueQuery method in a PropertiesRepositoryQueries class, which contained a SQL command used as part of the process:




We reviewed the current state of the ADM Properties table within the ADM DB and found three entries, including RemoveDataSettings:


The RemoveDataSettings record's value appeared to be a JSON representation of ADM's last ADM removal task run.  However, the JSON representation was cut off after a few hundred characters.  With this state of the present value, ADM was convinced that the task was not completed.  

Following the approach used in the code (mimicking what should occur when an ADM removal task is completed), we ran the following command:

We also entirely cleared the remaining IDs and Tasks table and re-initialized the process.  With these steps, our ADM tasks were back to processing as expected.

I hope this one helps anyone in a similar situation!


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.

🚀


Tuesday, December 13, 2022

Sitecore Analytics Database Manager (ADM) Contact Outliers - No Contact IDs Displaying

I recently installed the Analytics Database Manager (ADM) v10.0.2 module on a Sitecore 10.0.0 site. After completing the setup, I noticed that the xDB Contact Outliers report failed to render the Contact ID column in the table.


When I checked the browser's console, I saw that the data was indeed present in the dataset. The challenge was that the front-end code was closed-source and compiled in Angular, making it difficult to modify.

Since the Analytics Database Manager is distributed "as is" and not supported in the scope of the Sitecore Support Program, I had to try to resolve this myself. 

I searched for the `.NumberOfInteractions` property in the compiled ADM code since it was a unique enough property related to the data set that should point me in the right direction.

Luckily, I found a hit at `\sitecore\shell\client\Applications\ADM\main.577d3292f13f80d19c29.bundle.js`.

Next to it, I found what appeared to be the definition for the Contact ID column. However, the property `.id` did not match the data object's `Id` as seen in the console (note the capitalization). 



The table populated as expected by capitalizing the `i' in `Id`.



I hope this helps anyone who may encounter the same issue.



UPDATE
Sitecore released new versions of the module that address this issue on February 01, 2023:
https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB0232559




Tuesday, December 6, 2022

Friday, November 4, 2022

DALL-E OpenAI Image Generator for Sitecore

On January 5, 2021, OpenAI revealed DALL-E, an artificial intelligence program that creates images from textual descriptions, on January 5, 2021. It uses a 12-billion parameter training version of the GPT-3 transformer model to interpret the natural language inputs and generate corresponding images. 

Since the initial private beta release, access to DALL-E was limited to those on a waitlist. However, about a month back, on September 28, 2022, DALL-E 2 opened to the public, and the waitlist requirement was lifted. Over the past month, millions of users have explored DALL-E's capabilities using https://labs.openai.com/.

Yesterday, on November 3, 2022, OpenAI announced the DALL-E API, which developers could start immediately consuming. 🤩


DALL·E joins GPT-3, Embeddings, and Codex in our API platform, adding a new building block that developers can use to create novel experiences and applications. All API customers can use the DALL·E API today.
As soon as I heard the news, I couldn't help but immediately explore the API myself. I was already familiar with OpenAI's API playground for text-based AI  prompts and had an existing API key ready. 

My first instinct to consume the DALL-E API was to use plain old Windows PowerShell 🤓

"A photorealistic close-up of a variety of river rocks"

"A super cute puppy"

So dope 😎 - and wildly simple!

DALL-E OpenAI Image Generator for Sitecore

AI-generated images were featured prominently in Paige O'Neill's keynote at the 2022 Sitecore Symposium in Chicago. 

There's real value in this technology. A reality where designers, marketers, and developers who need a patterned backdrop can unleash their creativity to quickly and easily generate an image without needing to license stock images is nearly here. Even in its current state, the ease and simplicity of the DALL-E API make this the perfect candidate for consuming in a CMS.

With the fresh release of the DALL-E API, I couldn't think of a better time to give it a whirl and integrate it into Sitecore. 

🥁 Introducing...the ✨ DALL-E OpenAI Image Generator for Sitecore ✨ :




The Details

Naturally, Sitecore PowerShell Extensions was the way to go. The 'Context Menu' and 'Insert Item' integration points allow users to open the prompt while targetting any 'Media Folder' - either by right-clicking, expanding Insert, and selecting 'DALL-E OpenAI Image Generator' or selecting 'DALL-E OpenAI Image Generator' from the Folder tab of the active Media Folder.
Both integration points are configured out-of-the-box to only show on items where the Template is a 'Media Folder.'
Once the prompt is toggled, the user can configure their call to the DALL-E API using the following inputs:
  1. A prompt for the DALL-E to interpret.
  2. Image sizing options - restricted to these three sizes at this time: '256x256', '512x512', 'and 1024x1024' (see https://beta.openai.com/docs/api-reference/images/create#images/create-size)
  3. The number of images to generate (DALL-E API allows an integer from 0 to 10. The module limits generation to up to 5)
  4. The base name for new Media Items created in the Media Library after images are generated from DALL-E.



The module also comes bundled with a Settings item located in the following location:
  • /sitecore/system/Modules/PowerShell/Script Library/DALL-E OpenAI Image Generator/OpenAI API Settings
  • {F102AE0D-6A5B-499B-9500-505D0E6F686F}
This item allows users to change or override some default settings - the most important being the 'OpenAI API Key' field, where users must populate their unique API key. after installing the module, You can generate an OpenAI API Key by logging in and navigating to the API Keys settings for your account: https://beta.openai.com/account/api-keys
The Setting item's Template is located in the following location:
  • /sitecore/templates/Modules/DALL-E OpenAI Image Generator
  • {169EE16E-5D44-4CC1-B475-A2DFC8B08A0B}

The Code

The complete source code is available on GitHub, along with the Sitecore package (see https://github.com/strezag/DALL-E-OpenAI-Image-Generator-for-Sitecore/releases) and installation instructions: 
https://github.com/strezag/DALL-E-OpenAI-Image-Generator-for-Sitecore

The module is totally open to any community contributions or general suggestions. Beyond that, feel free to fork a version of your own a build on top of it.

Final Thoughts

I'm really excited about this space and where I believe it's headed. As this technology continues to grow, we should expect an expansion of the DALL-E API (and others) that will almost certainly redefine how we work with digital visual assets.

Enjoy! 🤖