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.


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:


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 ("") 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.


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:


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


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 to obtain the files from addedfiles, addedfolders, addeditems, and changedfiles folders. 

Alternatively, to 7zip, you can rename the .update file to 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


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#) (

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: 

👨‍💻 Code Breakdown


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.


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:


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.


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.


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.

Sitecore released new versions of the module that address this issue on February 01, 2023:

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

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
  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:
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 and installation instructions:

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! 🤖

Monday, September 26, 2022

Copy Sitecore User Roles from One User to Another User using Sitecore PowerShell

I recently had received a request to add several new Content Author accounts to the CMS and apply the exact user roles from a specific user to the new users.  I ended up scripting this with Sitecore PowerShell Extensions using hardcoded variables (usernames and roles), but because I expect similar requests in the future, I wanted a quick way to copy roles from one user account to another to use in the future.  Since I couldn't find a script readily available to do this, I developed my own. 

The script utilizes the 'user' editor type (which doesn't happen to be listed on - yet still works) within an interactive 'Show-ModalDialog.' It allows you to select a "source user" and a "target user" from the list of all available user accounts.  After choosing both, the script iterates through each source user's roles and applies the target user to each user role. 

The script loads the modal and requires the user to select a Source User and Target User.

Clicking the ellipses button opens a separate dialog that lists all users. 

After selections are made, click OK to initiate the copying of roles. 

The script can be run in Sitecore PowerShell ISE or configured as a module that can be run from the Start Menu.

Happy scripting! 🦸‍♂️

Monday, August 1, 2022

Sitecore Symposium 2022 in Chicago - Tips from an Illinois Native

I'm so excited for Sitecore Symposium 2022 to be hosted in Chicago, the city I grew up and work in! I can't wait to connect with the Sitecore community (in-person 😀), and share some of the city's many offerings with those visiting from out of town. Breaking from my typical technical-centric posts, I wanted this one to contain some of my own tips and recommendations to help prepare and enhance your trip to Chicago this autumn.

Credit: Chait Goli

🚖 Getting Around

Which airport you land in may shape your experience and the cost of transportation to make it into the city. Chicago has two major international airports:

  • O'Hare International Airport
  • Midway International Airport

O'Hare, the larger (and arguably the 'nicer' airport) of the two (~20.5 miles from McCormick Place), is between 26 - 45 minutes (and up to 90 minutes during peak rush hour) to and from Loop, depending on the time of day.  

Midway is closer to the Loop (~10 miles from McCormick Place), coming in at about a 14 - 28 minute drive to/from McCormick Place. 

While you could reach your hotel and McCormick Place through a series of trains and busses, I highly recommend taking an Uber/Lyft or taxi for convenience and speed. Taxis and rideshares are also the way to go if you decide to venture away from the McCormick Place grounds.

🏢 McCormick Place

Located in the "South Loop", McCormick Place is the largest convention center in North America. 

It's absolutely massive and regularly used for annual conventions like the Chicago Auto Show, International Manufacturing Technology Show, Chicago Comic & Entertainment Expo (C2E2), and International Home & Housewares Show. 

From their website:
With over 2.6 million square feet of exhibit space, McCormick Place is the largest and most flexible use convention center in North America. Located on Chicago’s lakefront, just minutes from downtown and facing one of the most iconic skylines in the world, the McCormick Place campus provides world-class facilities that can accommodate just about any event.
You can take a virtual tour ahead of Symposium to help familiarize yourself here: McCormick Place 360° Virtual Tour | Choose Chicago

🍂 Chicago October Weather

Sitecore Symposium 2022 will run in Chicago during mid/late October. While Symposium itself will take place in a temperature-controlled McCormick Place where comfort will be a lesser concern, if and when you venture out into the city you don't want to get caught off-guard by the weather. It could be cloudy and chilly, sunny and warm, windy (particularly closer to the lake), possibly rainy, or a slim chance of snow flurries - all in the same week!

I'm personally hoping for unseasonably high temperatures, but freezing temperatures are not uncommon by mid/late October, so don't forget to pack appropriately! 😇

Here's what you can expect in terms of weather on average during the third week of October: 
  • Sunrise: ~7 AM 🌅
  • Sunset: ~6 PM 🌆
  • High Temp: 62° F (17° C) 🌞
  • Low Temp: 45° F (7° C) 🌚
  • Average Temp: 53° F (12° C) 👍
  • Daily wind average: 12.4 mph (19.9 km/h) 💨 

🌆 Essential Chicago Loop Spots to Visit 

Chicago is a vibrant city with much to offer, and (contrary to the above paragraph) October is a great time to visit.  Cooler temparetures and shorter days mean you can explore without getting too hot or exhausted. At the same time, it's still usually warm enough to enjoy outdoor activities and see the sights if you want to explore beyond McCormick Place. Here are my top picks for first-time visitors.

Travel times are approximate from McCormick Place *

Millennium Park 

Credit: Bhargava Marripati

Millennium Park is located on Michigan Avenue between Washington Street and Columbus Drive. 

This park is home to some of Chicago's most iconic art installations, including Cloud Gate (a.k.a. the "Bean") and Crown Fountain, an interactive work of art and video sculpture. 

  • 💵 Price: Free 
  • 🚕 Uber/Lyft/Taxi: ~ 8 to 10 min ($10 - $25)
  • 🚶‍♀️ Walk: 45 min 

Buckingham Fountain

Credit: Petr Kratochvil
Buckingham Fountain (or The Clarence Buckingham Memorial Fountain as it is formally known) is located in the center of Grant Park between Queen's Landing and Ida B. Wells Drive, just an 8-10 minute walk southeast of Millenium Park.

Built in 1927, Buckingham Fountain is one of the largest fountains in the world and remains one of the most popular spots in Chicago for tourists and locals alike due to its stunning beauty and incredible views of Lake Michigan and the city skyline.

The fountain is typically operated from early May through mid-October, depending on the weather, so there is a chance that the water may not be flowing. That's OK! The views of the city and lake from the fountain grounds are spectacular and worth the visit. 

  • 💵 Price: Free 
  • 🚕 Uber/Lyft/Taxi: ~8 to 10 min ($10 - $25)
  • 🚶‍♀️ Walk: 45 min 

The Skydeck at Willis Tower 

Credit: Jared VanderMeer
Credit: Amit Thakral

Once holding the title of the largest building in the world, the Willis Tower (formally Sears Tower - which natives still often refer to it as) is one of the most iconic sights in Chicago. It's located on the western edge of Chicago's Loop.

The SkyDeck / The Ledge , after a quick 90-second elevator ride (that's 16 feet per second!), presents a breath-taking 360-degree view of The Windy City from 1,353 feet up in the air.

Given that this is one of Chicago's most popular tourist attractions, it's highly recommended that you purchase tickets in advance. They offer a couple of pricing options that range from $35 (Skydeck General Admission) to $64 (Skydeck Expedited Entry).

  • 💵 Price: $35 - $65 
  • 🚕 Uber/Lyft/Taxi: ~8 to 10 min  ($11 - $25)
  • 🚶‍♀️ Walk: 1 hour

Navy Pier 

Credit: Pixabay

A bit farther from McCormick (but well worth the 20-minute rideshare or taxi ride), Navy Pier is one of Chicago's top attractions, for good reasons. From its many restaurants and bars (including the iconic Giordanos and their deep-dish pizza) to the Centennial Wheel in Pier Park that offers a year-round bird's eye view of Lake Michigan and the skyline, Navy Pier has a little something for everyone.

  • 💵 Price: Free
  • 🚕 Uber/Lyft/Taxi: 15 to 20 min ($15 - $30)
  • 🚶‍♀️ Walk: 🚷

Guided Tours 

If you plan to spend a few extra days here before or after Symposium and have some proper downtime to explore the city, consider a guided tour to soak in the sights. You can opt for walking, biking, Segway, double-decker bus, or boat tours. Whether you're into ghosts (it is 'Spooky Season,' after all), food, art, architecture, or sports, there's sure to be a tour for you. You can find more information about tour options here: 

Walking Tours

Segway Tours

Bus Tours


Final Thoughts

There is certainly no shortage of famous sights and hidden gems to explore throughout Chicago, so plan ahead to ensure you don't miss out on all this beautiful city has to offer. From the great (freeeee!) public spaces to the many incredible museums, restaurants, art installations, and historical sites, you're bound to find something that fits your taste, budget, and schedule. 

For more advice while in town, feel free to reach out. 

See you there!

Thursday, July 7, 2022

Sitecore Icon Search 2022 Web App and Extensions Updates + Microsoft Edge Addon v1.0.0 Release

It's been 671 days since the last update to the Sitecore Icon Search web app and 1,278 days since my last update to the Chrome and Firefox extensions. I've made updates to squash new bugs across the Sitecore Icon Search ecosystem that have surfaced since the previous release while mitigating the extensions' delisting at the beginning of 2023. 

WebApp: dependency migration. 

I recently visited only to find that all icon images failed to load. 😢

For context, by default, the app has always used to source the icon images themselves since happens to be a Sitecore site. Given how lazy loading is implemented in the data table, I always expected the strain on to have remained minimal.  

It turns out Sitecore has recently implemented DDOS security protection via Cloudflare. You might notice that if you go to, you'll land on a screen that looks like this before you are redirected to the site:

Due to this change, it left imageless.   The latest updates to the Web app resolves this by hosting the icon images within the application - cutting out the dependency on completely. The browser extensions have also been updated to source icon images from

Chrome Extension: Critical Manifest Updates to avoid looming January 2023 Deadline

The Sitecore Icon Search Chrome extension is actively used by ~500 active users. When I originally built the extension, the latest 'manifest' version (a JSON file containing information that defines the extension) was v2.  

Google has provided two critical dates as manifest v2 is deprecated and replaced entirely with v3.  

January 17, 2022: New Manifest V2 extensions will no longer be accepted by the Chrome Web Store. Developers may still push updates to existing Manifest V2 extensions, but no new Manifest V2 items may be submitted.

January 2023: The Chrome browser will no longer run Manifest V2 extensions. Developers may no longer push updates to existing Manifest V2 extensions.

This means that the Sitecore Icon Search Chome extension will no longer function after January 2023 and, in its current state, cannot be updated until upgraded. The latest release brings the extension's manifest version up to version 3 and provides code fixes for several deprecated APIs that surfaced with the upgrade. 

By default, Chrome should update to the latest version automatically. 

Firefox Add-on: Revival

Back in December 2021, I received an email from the Mozilla team regarding a policy update that I, unfortunately, hadn't found time to respond to.   This results in version 1.0.0 of the Firefox add-on being removed 😢

Version 2.0.0 of the Firefox addon contains all the latest updates made for the WebApp and the Chrome extension and resolves the policy issues. I'm actively awaiting the add-on approval process to conclude and will update this portion of this blog post once the extension is available.   

UI and UX enhancements 

While I was making some updates, I at least decided to have a little fun. I've updated some fonts and scattered emojis across the app. I've also included some phrases catered to Gen-Z Sitecore developers. 😁

Microsoft Edge Addon v1.0.0 Release

While Chrome Extensions can already be added to Microsoft Edge by toggling on a feature, however, to avoid requiring Edge users to jump through hoops, I've decided to port and release the Microsoft Edge version of the browser extension. Version 1.0.0 can be downloaded on the Microsoft Add-ons library.

Monday, May 9, 2022

Latest Azure PaaS Sitecore Logs using a single line of PowerShell

If you’re anything like me, you probably don’t have a passion for manually digging through the series of hundreds of randomly dated folders that look like this in search of the latest Sitecore logs:

Hello darkness, my old friend

Although several tools and approaches are available (including this nifty tool credited to fellow Sitecore MVP Kiran Patil - as well as some of my own previous posts from 2018 and 2019 covering this topic), I've recently adopted a different strategy that's proved to be successful across several Sitecore PaaS clients for quickly obtaining the latest physical Sitecore log for a given server. 

The post-worthy kicker?  It's one line of PowerShell:

$kuduHost = ""; Write-Output "`n[ LATEST SITECORE LOGS ]`n"; $array = @(); Get-ChildItem "C:\home\site\wwwroot\app_data\logs" -File -Recurse | Where-Object { $_.FullName -match "azure.*.txt" -and $_.LastWriteTime -gt (Get-Date).AddHours(-12) } | ForEach-Object { $path = $_.FullName.replace("C:\home\site\wwwroot\app_data\logs\", "$kuduHost/api/vfs/site/wwwroot/App_Data/logs/"); $array += "`n[$($_.LastWriteTime)]`n$path`n"}; $array | Sort-Object $_.LastWriteTime | Select-Object -Last 3


Okay, it's...kind of a long one-liner...but one line nevertheless 

The above example outputs direct links to the latest three physical Sitecore log files, which match the pattern 'azure.*.

In practice, the desired file can be highlighted from the console and at which point you can copy the URL or open it in a new tab.

Let's break it down

The first line defines a variable for the KUDU host you're using:

$kuduHost = ""

The second line outputs a (wholly arbitrary and unnecessary) title:

Write-Output "`n`[ LATEST SITECORE LOGS ]`n"

The third line represents an array variable aptly named `$array` (because I'm clever):

$array = @();

This is where it gets exciting. This Get-ChildItem cmdlet gets all files recursively under the site's `\App_Data\logs` location:

Get-ChildItem "C:\home\site\wwwroot\app_data\logs" -File -Recurse

We can pipe in a Where-Object cmdlet to filter only file names that match 'azure.*.txt' (or if you want all log types - Publishing, Crawling, Dianoga, SPE, etc. - *.txt) and provide a 12-hour threshold against the `LastWriteTime` property:

Get-ChildItem "C:\home\site\wwwroot\app_data\logs" -File -Recurse |
Where-Object {$_.FullName -match "azure.*.txt" -and $_.LastWriteTime -gt (Get-Date).AddHours(-12)}

We can then pipe in a ForEach-Object cmdlet to iterate through each file:

Get-ChildItem "C:\home\site\wwwroot\app_data\logs" -File -Recurse | Where-Object { $_.FullName -match "azure.*.txt" -and $_.LastWriteTime -gt (Get-Date).AddHours(-12) } | ForEach-Object { $path = $_.FullName.replace("C:\home\site\wwwroot\app_data\logs\", "$kuduHost/api/vfs/site/wwwroot/App_Data/logs/"); $array += "[$($_.LastWriteTime)]`n$path`n"}

Notice that in the ForEach-Object cmdlet, we create a variable called `$path` and set it to a string that takes the file's FullName and replaces the 'system path' portion, and replace it with our `$kuduHost` variable concatenated to `/api/vfs/site/wwwroot/App_Data/logs/`.

$path = $_.FullName.replace("C:\home\site\wwwroot\app_data\logs\", "$kuduHost/api/vfs/site/wwwroot/App_Data/logs/")

Without this string replacement, we'd only get the system path for the files in the dataset, which would still require navigating to the file manually:

Also, within the ForEach-Object cmdlet, a formatted string containing the LastWriteTime and the `$path` variable is added to the `$array` variable:

$array += "[$($_.LastWriteTime)]`n$path`n"

The `n used above allows for line breaks.

After the files have been processed, the `$array` variable is called and sorted by LastWriteTime.

A Select-Object cmdlet is piped in to limit the number of results to 3:

$array | Sort-Object $_.LastWriteTime | Select-Object -Last 3

By combining these together, eliminating spaces, and adding semi-colons to separate commands, we've got our one-liner! 🕺

Bonus: IIS HTTP Request Logs

Using the same approach with a few modifications, the application's raw IIS HTTP Request Logs can also be obtained (differences bolded below):

$kuduHost = ""; Write-Output "`n[ LATEST IIS LOGS ]`n"; $array = @(); Get-ChildItem "C:\home\LogFiles\http\RawLogs" -Recurse | Where-Object { $_.FullName -match ".log" -and $_.LastWriteTime -gt (Get-Date).AddHours(-12) } | Sort-Object $_.LastWriteTime | ForEach-Object { $path = $_.FullName.replace("C:\home\LogFiles\http\RawLogs\", "$kuduHost/api/vfs/LogFiles/http/RawLogs/"); $array += "[$($_.LastWriteTime)]`n$path`n"}; $array | Sort-Object $_.LastWriteTime | Select-Object -Last 3

Final Thoughts

You can generate variations of this one-liner by changing the various variables, which can be shared with the rest of your development/troubleshooting team and readily ready to copy from an internal Wiki:

Feel free to use and modify the script as you see fit. 🚀