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! ๐Ÿค–

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 for 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 https://doc.sitecorepowershell.com/interfaces/interactive-dialogs - 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

Boat


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: Sitecore.com dependency migration. 


I recently visited sitecoreicons.com only to find that all icon images failed to load. ๐Ÿ˜ข

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

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



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


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 = "https://yourazuresitename-xp2-cd.scm.azurewebsites.net"; 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 = "https://yourazuresitename-xp2-cd.scm.azurewebsites.net"

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

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 = "https://yourazuresitename-xp2-cm.scm.azurewebsites.net"; 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. ๐Ÿš€

Friday, April 1, 2022

'Tracker.Current.Session.Interaction should not be null' CountryCondition

Uh oh. 

The following errors are heavily present on my client's content delivery servers:

ERROR Evaluation of condition failed. Rule item ID:
Unknown, condition item ID: {9A4BEB4B-4B0F-4392-A798-124CEC8AADA4}

Exception: Sitecore.Framework.Conditions.PostconditionException

Message: Postcondition 'Tracker.Current.Session.Interaction should not be null' failed.

Source: Sitecore.Framework.Conditions
at Sitecore.Framework.Conditions.EnsuresValidator`1.ThrowExceptionCore(String condition, String additionalMessage, ConstraintViolationType type)
at Sitecore.Framework.Conditions.Throw.ValueShouldNotBeNull[T](ConditionValidator`1 validator, String conditionDescription)
at Sitecore.Framework.Conditions.ValidatorExtensions.IsNotNull[T](ConditionValidator`1 validator)
at Sitecore.ContentTesting.Rules.Conditions.CountryCondition`1.Execute(T ruleContext)
at Sitecore.Rules.Conditions.WhenCondition`1.Evaluate(T ruleContext, RuleStack stack)
at Sitecore.Rules.Conditions.OrCondition`1.Evaluate(T ruleContext, RuleStack stack)
at Sitecore.Rules.Conditions.WhenRule`1.Execute(T ruleContext)
at Sitecore.Rules.Conditions.WhenCondition`1.Evaluate(T ruleContext, RuleStack stack)
at Sitecore.Rules.RuleList`1.Run(T ruleContext, Boolean stopOnFirstMatching, Int32& executedRulesCount)

We're talking thousands upon thousands of these errors. 

The error references a GUID we can use to determine the context of this:
{9A4BEB4B-4B0F-4392-A798-124CEC8AADA4}
Moreover, the error's message hints are what ultimately causes the error to trigger: 
Tracker.Current.Session.Interaction should not be null
For context, the GUID refers to the following out-of-the-box Sitecore item:
/sitecore/system/Settings/Rules/Definitions/Elements/Predefined Rules/Predefined Rule
Nothing to see here...


After digging in, I found that the client had configured personalization on a Controller Rendering for their site's homepage component rendering so that depending on the user’s country, unique content is presented. They've implemented the following Predefined rules on the rendering:

- visitor is located in the Asia Pacific region
- visitor is located in the UK
    - or where visitor is equal to the United Kingdom
- where visitor is located in Continental Europe

These are custom rules they've added that categorize a list of countries into a region they want to target.

For example, the `visitor is located in the Asia Pacific region` looks like this:
where the country is equal to Australia
or where the country is equal to Brunei Darussalam
or where the country is equal to China
or where the country is equal to Hong Kong
or where the country is equal to Singapore
or where the country is equal to Taiwan
or where the country is equal to Thailand
or where the country is equal to New Zealand
or where the country is equal to Qatar
or where the country is equal to Turkey
or where the country is equal to Malaysia
or where the country is equal to Russian Federation
or where the country is equal to India
or where the country is equal to Indonesia
or where the country is equal to United Arab Emirates
or where the country is equal to Philippines
or where the country is equal to Israel
or where the country is equal to Pakistan
or where the country is equal to Saudi Arabia
or where the country is equal to Vietnam
or where the country is equal to Japan

Each country has been defined using the out-of-the-box `GeoIP` > `where the country compares to specific countries` condition template.

This line in the error denotes that the `CountryCondition` is the culprit in regards to the recurring error:
at Sitecore.ContentTesting.Rules.Conditions.CountryCondition`1.Execute(T ruleContext)
According to a reflection of the `Sitecore.ContentTesting.Rules.Conditions` namespace in Sitecore.ContentTesting.dll for v10.0.0, this series of checks in the first few lines of the `CountryCondition<T> : VisitCondition<T> where T : RuleContext` class's Execute() method:

[GIST_START]
[GIST_END]

My hunch was that the last check was causing the failure, and instead of failing silently, it throws an error - specifically often in cases where bot-like traffic is involved. 
Condition.Ensures<CurrentInteraction>(Tracker.Current.Session.Interaction, "Tracker.Current.Session.Interaction").IsNotNull<CurrentInteraction>();

To quell the flood of errors initially, I applied a new stand-alone rule preceding each existing visitor is rule located in {REGION_NAME} region called `visitor is human` (also out-of-the-box) to attempt to deflect bots and spam where a country code or identifiable contact/interaction is unlikely to yield a country (which would result in an error):

After applying these changes and publishing them, we see those errors disappear entirely from the error logs.  However, the introduction of the `visitor is human` rule proceeding the rest had an unintended effect: first-time visitors always failed this first condition, causing them to only see the personalized rendering after reloading the page. Removing the rule enabled users to receive the personalized rendering on the first load but reintroduced the flood of errors.

It seemed that instances where `Tracker.Current.Session.Interaction` is null may be caused by bot traffic according to: https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB0960279

Given that this is version 10.0.0, this particular bug should have been resolved in all versions above 8.1.

Upon decompiling and comparing the GetTracker : CreateTrackerProcessor > Process() override in Sitecore.Support.424667.81.dll for v8.1+ to the 10.0.0 Sitecore.Analytics.dll's GetTracker : CreateTrackerProcessor > Process method, there's one notable difference:

v8.1+


v10.0.0

It was unclear if we need a similar patch to implement `args.set_Tracker(new DefaultTracker());` in the Process() method.

I raised the question with Sitecore Support to explore one of two resolutions:

1) A patch for suppressing errors in Sitecore.ContentTesting.Rules.Conditions.CountryCondition's Execute() method, when the `Condition.Ensures<CurrentInteraction>(Tracker.Current.Session.Interaction, "Tracker.Current.Session.Interaction").IsNotNull<CurrentInteraction>();` is not satisfied (perhaps by returning false).

or

2) An override patch for the tracker creation similar to the https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB0960279 hotfix.

Sitecore Support was able to reproduce the reported behavior in a clean local instance of Sitecore 10.0.0 (Initial Release) without involving any of our specific customizations. They registered the issue as a bug in their bug tracking system (future reference number 456739).

Sitecore's official statement regarding the bug: 
The reported behavior occurs because Sitecore.Rules.Conditions.WhenRule class inherits CanEvaluate() method from Sitecore.Rules.Conditions.RuleCondition class which always returns true. That is why the rule is not skipped so the evaluation continues and leads to the exception. 
In order to workaround the issue, please try to avoid the usage of Conditional renderings rules.
Please assign conditions to your renderings directly without wrapping them into conditional renderings rule.
 
In this case, Sitecore will trigger CanEvaluate method related to the specific condition, so the condition will be skipped.

I ended up pursuing the following approach myself:

  1. Decompiled the `Sitecore.ContentTesting.dll` and copied the Sitecore.ContentTesting.Rules.Conditions.CountryCondition class into our solution called `CountryConditionOverride`.  I used Telerik JustDecompile for this. 

  2. Commented the offending line (48) and replaced it with a simple null check that returns false.

  3. Enabled the custom override class by updating the `Type` field on the following default Sitecore item and published it:
/sitecore/system/Settings/Rules/Definitions/Elements/GeoIP/Country
{52E42C59-7210-43E5-94A6-3EA6B98835B8}
Field: Script (section) > Type 
Old value:  `Sitecore.ContentTesting.Rules.Conditions.CountryCondition,Sitecore.ContentTesting`
New value: `Foundation.Overrides.Rules.Conditions.CountryConditionOverride, Foundation.Overrides`

The final override class:


This resolves the issue with erroneous error log entries while retaining the expected Country condition functionality when the `Tracker.Current.Session.Interaction` happens to not be null.

I've shared my resolution with Sitecore Support and will keep an eye on an official hotfix (reference 456739), which I will share once it's available. 

Hopefully, in the meantime, this helps someone out there in a similar scenario!

Thursday, September 23, 2021

Sitecore Cache Tuning: LAYOUT_DELTA_CACHE

While tuning caches for a production level Sitecore 10.0.0 site, I came across a cache name I was unfamiliar with using a cache tunerLAYOUT_DELTA_CACHE


There were also log entries specific to this cache:

4484 12:07:57 INFO Cache created: 'LAYOUT_DELTA_CACHE' (max size: 50MB, running total: 6580MB)

Oddly enough, at the time of this post, no Google Search results mentioned this cache name.


Sitecore's documentation also had no mention of the setting either. 

Reaching out to Sitecore Support helped clarify things, and I wanted to share in case anyone else happens to come across this same cache that needs tuning. 

"The cache seems to be utilized when applying layout deltas when Sitecore is retrieving the layout field. The default size of this cache is 50MB, however, you can modify it with the "Caching.LayoutFieldDeltaCacheSize" setting."

Check out the following example configuration:


Hopefully, this helps with your Sitecore cache tuning activities. Happy tuning! ๐Ÿ˜Š

Monday, July 26, 2021

Docker Compose Failing; 'unknown flag: --d'

My Windows Docker installation was recently updated to version 3.5.2 (66501), after which commands like docker-compose up --d failed with the following errors:

level=warning msg="network default: network.external.name is deprecated in favor of network.name" not implemented


unknown flag: --d


As it turns out, this is due to an experimental feature that seems to be enabled by default in Docker's Experimental Features settings:


The solution, for now, was to simply disabled the experimental feature by unchecking the `Use Docker Compose V2` checkbox in the settings; or run `docker-compose disable-v2`.
More information about the Compose V2 beta CLI can be found here: 

Compose V2 beta | Docker Documentation

Wednesday, May 26, 2021

Resolving Docker Container Networking Issues while connected to VPN using Cisco AnyConnect

After prepping and polishing a custom legacy Sitecore 8.2 Docker environment for our developers, we ran into a significant blocker that had us questioning whether we needed to backtrack and use locally installed Sitecore instances instead. 

The blocker stemmed from this particular client's VPN: Cisco AnyConnect Mobility Client. ๐Ÿคฎ

At a high level, any time we connect to the VPN using Cisco AnyConnect, the running containers would begin to misbehave - and symptoms disappeared after VPN was de-activated.  

Symptoms included:

  1. Inability to use custom hostnames to access the site (pings from the host to the CM and Solr hostnames failed with an `Unreachable` or `Request Timeout` code).
  2. Inability to use the localhost:portnumber hostname to access the site.
  3. Complete loss of internet access from the running containers.

Because a VPN connection is required for several API-based components, it was essential to solve this.  


Some of the troubleshooting attempts included:

  • Switched ISOLATION mode from isolation to hyperv.
  • Checked and uncheck various options in the Cisco AnyConnect settings (including `Allow local (LAN) access when using VPN`).
  • Checked and unchecked various Docker settings under the General tab in Docker Desktop settings (`Expose daemon on tcp://localhost:2375 without TLS`).
  • Asserted local firewall settings.
  • Applied DNS overrides to the Docker Engine daemon.json file that matched the active DNS configuration for the VPN endpoint.
  • Applied various parameters to the docker-compose.yml file (dns, extra_hosts, etc.)
  • Fiddled with various `Advanced TCP/IP Settings` under Control Panel\Network and Internet\Network Connections in Windows.
  • Created custom Hyper-V and Docker bridge/transparent networks to try to restore internet connectivity. 
The number of tabs I had opened in my browser was unfathomable without much to show for it.  There were undoubtedly many similar issues reported across the web related to Cisco AnyConnect and Docker, but no suggestions remedied the problem.

After hours of troubleshooting, I tried to replicate the behavior with other VPN connections not using the Cisco AnyConnect client and found that none of the symptoms were present.   I couldn't find any evidence that this is an issue with Docker itself but instead caused by how Cisco AnyConnect handled connections and IP routing.  

I then came across a comment in a thread related to drive sharing with Docker when using AnyConnect: https://github.com/docker/for-win/issues/360#issuecomment-442586618 

I ♥ you, jrbercart

Since we don't have any pull over the client's VPN setup and configuration, I decided to try OpenConnect as a substitution for Cisco AnyConnect, which evidently uses the same protocol to establish a VPN connection.  

I connected to the client's VPN endpoint using OpenConnect, and all of the networking issues with the running Docker containers disappeared!  

If you happen to find yourself in a similar situation, go ahead and drop Cisco AnyConnect and give OpenConnect a try to save yourself some troubleshooting hours! ☺

Thursday, April 8, 2021

Sentiment Analysis and Keyword Extraction using Sitecore PowerShell and Microsoft Cognitive Text Analytics

Sitecore Hackathon 2021

Well...wow, it actually happened...

I managed to snag a category win for the 2021 Sitecore Hackathon! ๐Ÿ˜…


This year, I unexpectedly flew solo as my team members could not attend (both due to completely understandable reasons).  Luckily for me, one of this year's categories, in particular, made me feel like I stood a chance: "Best use of Sitecore PowerShell Extensions to help Content Authors and Marketers."

YES. YES YES 1000x YES. 

Knowing that I needed to land on something fairly quickly to complete all submission requirements (a completed module with clean code, reliable installation instructions, a well-documented README.md, and a video) my evening began with a brainstorming session listing all possible routes I could take for the next 24 hours.  

I actually landed on a similar concept I posted about a couple of years back; interacting with Microsoft's Cognitive Services using PowerShell, then focusing on content translation. I knew Microsoft had continued to update their API offerings since that post, so I started digging into what was new.  I stumbled upon the Sentiment Analytics API, which seemed like an excellent use case that could satisfy the 'help Content Authors and marketers' category requirement.  

By providing the right combination of SPE user interactivity (modal dialogs, accessibility of the utility in the Ribbon, etc.), I could build a utility that analyzes content from a given field and provide a sentence-by-sentence breakdown of the content's sentiment score using AI.

After playing around with the example APIs in the browser, I decided to create my Text Analytics Cognitive Service in Azure, grab my API keys, and fiddle around with the API further in PostMan.  At that point, I felt pretty confident that I could integrate this with SPE. ๐Ÿคž

The Sentiment Analyzer would

  • Analyze the sentiment of field content directly in Sitecore.

  • Give Content Authors the ability to run an analysis of a given field's content, which returns an overall sentiment score and a sentence-by-sentence breakdown of each sentence's sentiment score and corresponding confidence scores.

  • The results are displayed using a Show-Result modal and rendered in an easy-to-digest format.

I built the user dialog, wrote code that generated the appropriate POST data to be passed to the sentiment API endpoint, built the functions to render the data (using emojis, of course ๐Ÿ‘ฉ‍๐Ÿš€), configured a new Sitecore template and the corresponding item for API key storage then tied it all together into an SPE module that exposed the tool from the right-clicked Context Menu, and from the Ribbon.

As midnight approached, I felt that I was in decent enough shape with the Sentiment Analysis script, I could begin exploring using another API in the same Text Analytics product group. I moved forward with a second tool utilizing the API's keyphrase extraction feature without a tremendous amount of overhead; mostly endpoint changes, JSON parsing, and data rendering differences. 

The Keyword Analyzer would:

  • Analyze a field's content to extract critical keywords/phrases.

  • Give Content Authors the ability to analyze a given field's content which returns a list of extracted keywords that can then be used to manually populate a meta keywords field.

  • The results are displayed using a Show-Result modal and rendered in an easy-to-copy format. 


I got started, but a couple hours later...


Then a few hours later...

I spent most of the day (alongside juggling sick-kids priorities) polishing the scripts I had so far; resolving logic issues, error prevention, adding code comments, and overall meticulous code clean-up.

Eventually, I had a functional set of utilities. 

Buttons in the Ribbon configured in the SPE module.


Dialog when clicking either utility against an item
with a Single-Line, Multi-Line, or Rich Text field. 

Sample output of sentiment analysis

Sample output of keyword analysis

I made sure to stop by for a late morning Coffee Break. ☕


I built the final structure of the SPE module using the Module Wizard ๐Ÿง™‍♂️ to configure my integration points.  The module also stores the API Settings item, so swapping in an API key would be seamless for anyone who installs the module.  


⚡ The module looked like this in the tree:




I spent the final hours of the event packaging the module/testing the installation steps before working on multiple documentation phases (using Markdown for absolutely everything in 2020 was really coming in handy).

It wasn't long before a mid-afternoon Twitter update:



The video production was probably one of the most challenging parts of this experience.  After writing a short-handed verbal script, I tried to record the entire demo in a single recording.  I used OBS Studio to record and the built-in Video Editor in Windows for post-production.  I even squeezed some personal music snippets I composed some time ago without risking Copywrite strikes on YouTube. ๐Ÿ˜‚

The video submission can be viewed here:

By around 5 PM, I was done and had submitted my entry ๐Ÿš€

The full Github submission can be found here, including the full source code for both scripts, the module ZIP for installation, and installation steps. 

Take it for a spin if you care to! ๐Ÿคน‍♂️

I'm really humbled and proud to have been a part of the winner's circle this year.  Another big shout-out to the folks who run and judge the event, as well as a big congratulations to the other category winners!

Check out the complete 2021 Sitecore Hackathon winners announcement here: https://www.youtube.com/watch?v=YEOy7lIDZUU

I'm already looking forward to next year. ๐Ÿ“†