Showing posts with label powershell. Show all posts
Showing posts with label powershell. Show all posts

Tuesday, September 22, 2026

Building a Custom Sitecore MCP Server in .NET — Tailored for Real Project Work

Hello Sitecorian Community! 👋

This is the blog I have been building toward across the whole series. Blogs 1–3 covered the foundations — understanding MCP, building a minimal .NET server, and setting up Anton Tishchenko’s community server against a live XP instance. All of that was preparation for this: building something genuinely mine, shaped around the actual work I do every day on a Sitecore project.

The Problem I Wanted to Solve

When I started thinking about what to build, the answer came quickly — because the pain was already obvious.

On every Sitecore project I have worked on, there is a category of work that is critical but relentlessly repetitive: managing Content Security Domain (CSD) role access. CSD is Sitecore’s standard security model for controlling which roles can read, write, rename, create, delete, or administer content nodes in the content tree.

On the surface it sounds simple — give a role access to a path. In practice, on an active project with multiple content authors, developers, and client stakeholders all requesting access at different points, it turns into a constant stream of small but time-consuming tasks.

The Before: A Manual, Repetitive Grind

Here is what the daily reality looked like before this server existed.

Every request followed the same path through the Sitecore Security Editor:

  1. Open the Security Editor in the Sitecore Content Editor
  2. Search for the role by name — sounds trivial, but role names across multiple domains (sitecore\, cog\, client-specific) are easy to get wrong, especially for roles you don't work with daily
  3. Navigate to each content node — sometimes a single path, often multiple paths (content root + media library root for the same site)
  4. Tick the correct permission type — CSD access (Read + Write) or CSD Admin (6 rights), with inheritance enabled so child items pick it up
  5. Repeat for every path in the request — a single request often covers 2–4 paths
  6. Repeat the whole thing for every role — some requests cover multiple roles at once
  7. Verify the rules were applied correctly and propagated as expected

The hidden cost: this work required a developer. It was not something a BA or project manager could safely do through the Sitecore UI. Every request became a ticket, a context switch, an interruption to actual development work — adding zero feature value, carrying real risk of human error.

Figure 1 — The same CSD access request: a 5-step manual process taking 15–20 minutes vs a single natural language prompt taking under 1 minute
Figure 2 — Developer hours spent on CSD access management. The onboarding scenario (30–40 requests) sees the greatest saving: 5+ hours to ~45 minutes

The After: Natural Language + MCP

With the Sitecore MCP Server connected to VSCode, the same work now looks like this:

I need to set up CSD access for the "cgdev" team on the DemoSite.

1. Search for Sitecore roles matching "cgdev" to find the exact role names.
2. Grant CSD access (Read and Write) for the found roles on:
- /sitecore/content/DemoSite
- /sitecore/media library/DemoSite
3. Also grant CSD Admin access to any role matching "cgadmin" on the same paths.

GitHub Copilot in agent mode chains the tool calls in sequence — search, then grant, then confirm — and summarises the result. The whole thing takes under a minute. No Security Editor. No navigation. No repeated clicking across paths and roles.

The CSD Tools — Five Tools That Changed My Daily Workflow

The server exposes five tools specifically for CSD role access management. Here is each one in detail.

Figure 3 — Grant flow (top) and revoke flow (bottom). Both start with SearchCsdRole to confirm the exact identity. The no-Deny constraint in the revoke tools is enforced in code — not a prompt instruction

SearchCsdRole — Find the exact role identity first

Before granting or revoking access, you need the exact role identity string — including the domain prefix. On a real project with multiple domains (sitecore\, cog\, client-specific), guessing is error-prone.

This tool searches across all Sitecore domains by partial name match and returns the exact identities.

Search for Sitecore roles matching the name "cgdev".

Returns results like sitecore\cgdev, cog\cgdeveloper — whichever match. You pass those exact strings to the grant or revoke tools. This one step alone eliminates a common class of access mistakes.

GrantCsdAccess — Read + Write with inheritance

Grants CSD access (Read and Write) to one or more roles across one or more content paths. All rules apply with PropagationType Any, meaning they inherit to all child items automatically.

Grant CSD access for the role "sitecore\cgdev" on the following paths:
- /sitecore/content/DemoSite
- /sitecore/media library/DemoSite

You can also pass multiple roles at once. What used to be 4 separate Security Editor operations — 2 roles × 2 paths — is now one prompt.

Safety behaviour: paths that do not exist in Sitecore are reported as PathNotFound and skipped. The tool never silently fails and never applies partial access without telling you.

GrantCsdAdminAccess — Full admin rights with inheritance

Grants full CSD Admin access — Read, Write, Rename, Create, Delete, and Administer — with inheritance. Used for roles that need full editorial control over a site section, not just content authoring. Previously this required manually ticking six checkboxes per role per path in the Security Editor — easy to miss one under time pressure.

Grant CSD Admin access to the role "sitecore\cgadmin" on /sitecore/content/DemoSite 
and /sitecore/media library/DemoSite.

RevokeCsdAccess — Remove Read + Write safely

Removes CSD Read and Write Allow rules from roles on specified content paths. Designed for offboarding team members or restructuring access when a content section is reorganised.

Revoke CSD access for "sitecore\cgdev" on /sitecore/content/DemoSite 
and /sitecore/media library/DemoSite.

The most important design decision in this tool: it reads the item’s explicit ACL, filters out only the target Allow entries for the specified role, and resets the ACL with those entries removed. No Deny rules are ever written. A mistake during a revoke operation should never lock someone out of Sitecore content unexpectedly. This constraint is enforced in code — it cannot be overridden by a prompt.

If a role had no rules on an item, the tool reports RulesRevoked: 0 for that path and continues. It never fails silently.

RevokeCsdAdminAccess — Remove all 6 admin rights safely

Same safe revoke behaviour, but targets all six CSD Admin rights. Used when a role is being downgraded from admin to standard CSD access, or fully removed from a section.

Revoke all CSD Admin permissions for "sitecore\cgadmin" on /sitecore/content/DemoSite.

CSD Access Rights Reference

Figure 4 — CSD grants Read + Write. CSD Admin grants all 6 rights. Both propagate to all descendants automatically

How the CSD Tools Work Under the Hood

The CSD tools build inline SPE (Sitecore PowerShell Extensions) scripts at call time and execute them through the same PowerShell remoting client used by the RunSitecorePowerShellScript tool.

  • Role searches use Get-Role -Filter with the partial name you provide
  • Access grants use New-ItemAcl and Add-ItemAcl with PropagationType Any
  • Access revokes read the item’s full explicit ACL, filter out the target Allow entries for the specified role, and apply the cleaned ACL back with Set-ItemAcl — no Deny rules are ever constructed or written

The Time Saving — In Real Numbers

Across a normal working week, that is roughly 8–10 hours of developer time moving from access management back to actual development work.

The Other Tools in the Server

Figure 5 — All 8 tools across 4 areas. CSD tools are the most project-specific; GraphQL, Item Service, and PowerShell cover broader daily Sitecore interactions

QuerySitecoreGraphQl

Run any GraphQL query against a Sitecore schema (edge, master, core). Handles the HTTP POST with the sc_apikey header. Supports variables and schema introspection.

Query the Sitecore Home item via GraphQL using the path /sitecore/content/Home in English.

Run a Sitecore GraphQL introspection query to list all available types on the edge schema.

GetSitecoreItemByPath

Retrieve a Sitecore item and all its fields by content tree path using the Item Service API (SSC). Supports any database and language.

Get the Sitecore item /sitecore/content/Home from the web database and tell me 
if it has been published — check the __Updated field.

RunSitecorePowerShellScript

Execute any script via SPE remoting — the most open-ended tool in the set. Copilot constructs the script from your intent, or you write it yourself.

Run a Sitecore PowerShell script to get all direct children of /sitecore/content/Home 
and return their Name, ID, and TemplateName as JSON.

Run a Sitecore PowerShell script to clear all Sitecore caches on the CM server.

Program.cs — How It All Wires Up

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = Host.CreateApplicationBuilder(args);
// All logs to stderr - stdout is reserved for MCP protocol messages
builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithTools<GraphQlTools>()
.WithTools<ItemServiceTools>()
.WithTools<PowerShellTools>()
.WithTools<CsdAccessTools>();
await builder.Build().RunAsync();

Each tool class registered independently with .WithTools<T>(). Adding a new tool category is one line.

Prerequisites

  • .NET 8 SDK
  • Sitecore XP or XM CM instance with GraphQL endpoint enabled, Item Service (SSC) enabled, and SPE installed with remoting enabled

Getting Started

git clone https://github.com/gaurarun777/sitecore-mcp.git
cd sitecore-mcp

Open in VSCode — the .vscode/mcp.json prompts for all credentials interactively when the server starts. Nothing sensitive is hardcoded.

⭐ github.com/gaurarun777/sitecore-mcp

Key Lessons from Building This

Design safety into the tool, not into the prompt

The no-Deny-rules constraint in the CSD revoke tools is enforced in code. It cannot be overridden by any prompt. On tools that touch access control, safety behaviour belongs in the implementation — not in hoping the caller remembers the right instruction.

Search before act

The SearchCsdRole tool exists for a reason. Role names across multiple Sitecore domains are easy to misremember. Searching first and confirming the exact identity before granting or revoking is the difference between a clean operation and an access mistake that takes time to diagnose and fix.

Repetitive work is the right first target

The CSD tools did not replace complex, high-judgment Sitecore development work. They replaced work that was clearly repetitive, time-consuming, and low-judgment — the kind where the value is accuracy and speed, not deep thinking. That is always the right first target for any automation.

One prompt can chain multiple tool calls

Copilot in agent mode chains tool calls automatically. You do not need to build orchestration into the server. Design tools to be focused and single-purpose — let the AI chain them together in response to a compound prompt.

The Full Journey

When I sat in that SUGCON India 2026 session, I wasn’t thinking about access management scripts. But when I looked at where developer time was actually going on the project, the answer was obvious. The MCP server did not solve an interesting engineering problem — it solved a real, daily, expensive one.

That is probably the most useful thing you can build.

Till then, Happy Sitecoring! 😊

References:
github.com/gaurarun777/sitecore-mcp
Microsoft Learn — Build MCP Server with C#
Flux Digital — Sitecore MCP Server with XP + VSCode & CoPilot
Anton Tishchenko — mcp-sitecore-server (GitHub)
SUGCON India 2026 — sitecoreknowledgeshare.blogspot.com

Saturday, April 4, 2026

Cleaning Up Sitecore Versions at Scale: A Practical PowerShell Approach

 

Hello Sitecorian Community,

If you’ve worked on a large Sitecore implementation, you’ve probably seen this happen:

Items with 10, 20, sometimes even 50+ versions sitting in the system.

At first, it doesn’t seem like a big deal. But over time:

  • Content trees become heavier
  • Database size increases
  • Performance can degrade
  • Content management becomes harder

And eventually, the question comes up:

How do we safely clean up old versions without breaking anything?

The Real Problem

In enterprise setups (like ours managing hundreds of websites), version sprawl is very common.

Typical scenarios:

  • Frequent content updates create multiple versions
  • Publishing pipelines don’t remove old versions
  • Editors rarely clean up older versions manually
  • Multiple languages multiply the problem

Manually deleting versions is not scalable and also risky.

What We Needed

We wanted a solution that:

  • Works recursively across content trees
  • Supports all languages
  • Keeps only the relevant versions
  • Deletes only safe-to-remove versions
  • Can run in dry-run mode before actual cleanup

Two Cleanup Strategies We Implemented

We ended up building two PowerShell scripts using Sitecore PowerShell Extensions (SPE) depending on the use case.

1️⃣ Basic Cleanup: Keep Latest 3 Versions

This is the simplest and most commonly used approach.

Behavior

For an item with versions:

10, 9, 8, 7, 6

The script will:

  • Keep → 10, 9, 8
  • Delete → 7, 6

If an item has 3 or fewer versions, it is skipped.

When to Use

  • General cleanup
  • Non-workflow-driven environments
  • Quick version reduction across large trees

Core Logic

$allVersions = Get-Item -Path $itemPath -Language $language -Version * |
Sort-Object { [int]$_.Version.Number } -Descending
$versionsToKeep = $allVersions | Select-Object -First 3
$versionsToDelete = $allVersions | Select-Object -Skip 3
foreach ($oldVersion in $versionsToDelete) {
$oldVersion.Versions.RemoveVersion()
}

2️⃣ Smart Cleanup: Keep Latest 3 Approved Versions

This is a workflow-aware cleanup, which is much safer for content-heavy environments.

Behavior

Example:

Versions:
10, 9, 8, 7, 6, 5, 4, 3, 2, 1

Case 1

Approved versions: 10, 9, 8
→ Keep: 10, 9, 8
→ Delete: everything else

Case 2

Approved versions: 10, 9, 7
→ Keep: 10, 9, 7
→ Delete: 8, 6, 5, 4, 3, 2, 1

Case 3

Approved versions: 10, 7
→ Skip (not enough approved versions)

Key Implementation Details

🔁 Recursive Execution

The script runs across:

  • Root item
  • All children
  • All descendants

🌍 Multi-language Support

Each language version is processed independently.

🧪 Dry Run Mode

Always start with:

$dryRun = $true

Then switch to:

$dryRun = $false

GitHub Repository

You can find both scripts here:

It includes:

  • Basic version cleanup script
  • Workflow-based cleanup script
  • Safe dry-run execution

Why This Helped Us

Before this:

  • Version cleanup was manual and inconsistent
  • Content trees became bloated
  • Investigations were harder

After this:

  • Cleanup became automated and safe
  • We reduced unnecessary versions significantly
  • Improved overall content hygiene

When Should You Run This?

  • After large deployments
  • As part of regular maintenance jobs
  • During performance optimization
  • Before database cleanup activities

Reference screenshot:


Final Thoughts

In large Sitecore environments, version management is often overlooked, but it plays a big role in performance and maintainability.

These scripts helped us:

  • Keep content clean
  • Reduce noise in version history
  • Maintain only what actually matters

If you’re dealing with version-heavy content trees, this approach can save a lot of time and effort.

Stay tuned for more Sitecore-related articles, tips, and tricks to enhance your Sitecore experience.

Till then, happy Sitecoring! 😊

Please leave your comments or share this article if it’s useful for you!

Monday, March 16, 2026

Debugging Sitecore Publishing Issues at Scale: A Simple PowerShell Tool That Saved Our Team Hours

Hello Sitecorian Community,

If you’ve worked with Sitecore in a large multi-site environment, you’ve probably faced this situation.

Someone reports:

“The content is updated in CMS but not visible on the website.”

And the investigation begins.

You start checking:

  • Is the item published?
  • Does it exist in the web database?
  • Is the revision updated?
  • Did the child items publish?
  • Is it a language version issue?

Now imagine doing this when you manage 300+ websites and hundreds of items move across environments every day.

That’s the reality our team deals with.

The Real Problem

Publishing issues in Sitecore are rarely obvious.

Sometimes:

  • The item exists in master but not in web
  • The item exists in both but revision IDs don’t match
  • The updated date differs
  • Only the parent item published, but children didn’t
  • Deployment pipelines move items but something silently fails

When troubleshooting these issues manually, developers often:

  1. Open master database
  2. Inspect the item
  3. Switch to web database
  4. Check again
  5. Compare Revision IDs
  6. Repeat for multiple items or entire trees

Doing this repeatedly across dozens (or hundreds) of items quickly becomes slow, frustrating, and error-prone.

Our Daily Reality

Our platform supports 300+ websites, and content changes constantly move between databases and environments.

During deployments or publishing validations, the most common question is:

Did the content actually publish correctly?

Finding that answer quickly is critical for developers, QA teams, and support engineers.

The Tool We Built

To simplify this process, we built a Sitecore PowerShell Extensions (SPE) script that compares items between two databases.

We call it:

Sitecore Publishing Validation Tool

GitHub Repository (script available here):
https://github.com/gaurarun777/SitecorePowerShell/blob/main/2026/sitecore-publishing-validation-tool.ps1

This script allows developers to:

  • Provide specific item paths
  • Provide root paths for recursive validation
  • Compare two databases (for example master vs web)
  • Validate Revision ID
  • Validate Updated Date
  • Detect missing or mismatched items

Instead of manually switching between databases, the script produces a comparison grid instantly.

The PowerShell Script

Below is the core idea behind the script used in our Sitecore environment.

# Simplified comparison logic
$sourceItem = Get-Item "${sourceDatabase}:$path" -Language $language
$targetItem = Get-Item "${targetDatabase}:$path" -Language $language
$sourceRevision = $sourceItem["__Revision"]
$targetRevision = $targetItem["__Revision"]
$sourceUpdated = $sourceItem["__Updated"]
$targetUpdated = $targetItem["__Updated"]
if ($sourceRevision -ne $targetRevision) {
$status = "Revision Mismatch"
}
elseif ($sourceUpdated -ne $targetUpdated) {
$status = "Updated Date Mismatch"
}
else {
$status = "Match"
}

The script loops through item paths and recursively scans content trees to validate publishing results between databases.

Developers can then quickly identify:

  • Items that failed to publish
  • Items with outdated revisions
  • Missing items in the target database
  • Partial publishing issues within item trees

What the Output Looks Like

The script generates a comparison grid showing:

  • Item Path
  • Exists in Source DB
  • Exists in Target DB
  • Source Revision ID
  • Target Revision ID
  • Updated Dates
  • Comparison Status

Example statuses:

  • Match
  • Revision Mismatch
  • Updated Date Mismatch
  • Missing in Target
  • Missing in Source

This makes it extremely easy to spot publishing issues.

Why This Helped Our Team

Before using this tool:

  • Troubleshooting publishing issues could take 30–60 minutes
  • Developers had to manually inspect each item
  • Recursive tree validation was tedious

Now:

  1. Paste item paths
  2. Select databases
  3. Run the script

Within seconds we can see exactly where publishing failed.

Why Sitecore PowerShell Extensions Is So Powerful

One thing I really appreciate about Sitecore PowerShell Extensions is how quickly developers can build practical operational tools.

With a few lines of PowerShell, you can automate tasks that would otherwise take significant manual effort.

SPE is extremely useful for:

  • Content validation
  • Publishing verification
  • Bulk content operations
  • Content audits
  • Operational automation

Final Thoughts

When working with large Sitecore implementations, operational tooling becomes just as important as development.

Sometimes the biggest productivity improvements come from small internal tools that solve daily problems.

This publishing validation script became one of those tools for our team.

If you manage a large Sitecore environment, I highly recommend building small utilities with Sitecore PowerShell Extensions to simplify repetitive tasks.

They might save your team more time than you expect.

Reference Screesnhots:


If you’re interested in trying the script, you can find it here:

GitHub:
https://github.com/gaurarun777/SitecorePowerShell/blob/main/2026/sitecore-publishing-validation-tool.ps1

Would love to hear what internal tools or automation scripts your Sitecore teams are using to improve daily operations.

Stay tuned for more Sitecore-related articles, tips, and tricks to enhance your Sitecore experience.

Till then, happy Sitecoring! 😊

Please leave your comments or share this article if it’s useful for you!

Tuesday, March 10, 2026

Auditing Sitemap Cache Configuration Across 300+ SXA Sites in Sitecore 10.4

Hello Sitecorian Community,

In large SXA implementations, operational issues rarely affect just one site. In our case, we were working on a Sitecore 10.4 SXA solution with 300+ websites, and we encountered a performance concern:

The sitemap refresh job was executing more frequently than expected.

To properly investigate the issue, we first needed visibility.

Specifically, we needed to answer:

  • How many sites actually have a Sitemap item?
  • What values are configured for:
  • Refresh Threshold
  • Cache Type
  • Cache Expiration
  • Are there inconsistencies across tenants and sites?

Manually checking 300+ sites was not realistic. Automation was the only viable approach.

Understanding the SXA Structure

In SXA, the typical structure looks like this:

/sitecore/content/{Tenant}/{Tenant}/{Site}/Settings/Sitemap

The configuration fields we were interested in are stored directly on the Sitemap item under the Settings node.

The required fields:

  • Refresh Threshold
  • Cache Type
  • Cache Expiration

Our goal was to extract:

  • Sitemap item path
  • Configured values of the three fields
  • Total count of sitemap items found

Approach: Automating with Sitecore PowerShell Extensions (SPE)

Instead of writing everything from scratch, I reused an existing PowerShell script that I had previously written for deleting Flashes items in an older SXA setup.

Given that we now have powerful AI-assisted tools available, I provided the reference script to ChatGPT and adapted it to:

  • Traverse all Settings nodes
  • Locate Sitemap items
  • Extract required field values
  • Display results in a Show-ListView

This significantly reduced the time required to build a reliable audit script.

Final Working Script

# -----------------------------------------
# SXA: Read Sitemap cache settings per site
# Path pattern: .../Settings/Sitemap
# -----------------------------------------

# Fields on the Sitemap item
$fieldRefreshThreshold = "Refresh Threshold"
$fieldCacheType = "Cache Type"
$fieldCacheExpiration = "Cache Expiration"

$results = @()

# Find all "Settings" items under /sitecore/content (fast query by name)
$settingsItems = Get-Item -Path master: -Query "fast:/sitecore/content//*[@@name='Settings']"

foreach ($settings in $settingsItems) {

# Get child Sitemap item under Settings
$sitemapPath = "$($settings.Paths.FullPath)/Sitemap"
$sitemapItem = Get-Item -Path ("master:" + $sitemapPath) -ErrorAction SilentlyContinue

if ($null -ne $sitemapItem) {
$results += [pscustomobject]@{
SitemapItemName = $sitemapItem.Name
SitemapTemplate = $sitemapItem.TemplateName
SitemapPath = $sitemapItem.Paths.FullPath
"Refresh Threshold" = $sitemapItem[$fieldRefreshThreshold]
"Cache Type" = $sitemapItem[$fieldCacheType]
"Cache Expiration" = $sitemapItem[$fieldCacheExpiration]
}
}
}

# Show in list view
$results | Show-ListView `
-Title "SXA Sitemap Settings (Refresh Threshold / Cache Type / Cache Expiration)" `

-Property SitemapItemName, SitemapTemplate, SitemapPath, "Refresh Threshold", "Cache Type", "Cache Expiration"

Write-Host ""
Write-Host "Total Sitemap items found under Settings: $($results.Count)" -ForegroundColor Green

OutPut:

Why This Matters in Large SXA Implementations

In enterprise setups with hundreds of sites:

  • Configuration drift is common
  • Some sites may override defaults
  • Cache misconfiguration can lead to:
  • Excessive job executions
  • Increased publishing pressure
  • Performance degradation

Before fixing the problem, you need visibility.

Automation through SPE enables:

  • Rapid environment auditing
  • Cross-tenant configuration comparison
  • Reliable investigation at scale

Key Takeaways

  • In large multi-tenant SXA environments, manual verification does not scale.
  • Structural traversal (Settings/Sitemap) is a predictable way to audit configuration.
  • SPE is extremely powerful for operational investigations.
  • AI-assisted scripting can accelerate development when you already understand the architecture.

Conclusion

Investigating performance issues in a 300+ site SXA environment requires structured visibility and automation.

A small PowerShell audit script can save hours of manual effort and provide precise insights needed to diagnose job behavior in environments like jobs.aspx.

If you’re managing a multi-tenant SXA solution, consider building a small internal audit toolkit using SPE — it pays off quickly.

Stay tuned for more Sitecore-related articles, tips, and tricks to enhance your Sitecore experience.

Till then, happy Sitecoring! 😊

Please leave your comments or share this article if it’s useful for you!


Sunday, March 1, 2026

Understanding Sitecore Cache Behavior: Why Your PowerShell Updates Don't Appear (And How to Fix It Properly)

 Hello Sitecorian Community,

After 11 years architecting Sitecore solutions, I still see this question pop up regularly: “Why doesn’t my PowerShell script update show in the Content Editor until I clear cache?” It’s one of those things that trips up even experienced developers, especially when moving to containerized environments.

Let me walk you through what’s actually happening under the hood, and more importantly, how to handle it properly in enterprise implementations.

The Classic Scenario

You’re probably familiar with this pattern:

 $item = Get-Item "master:/sitecore/content/Home"
$item.Editing.BeginEdit()
$item["Title"] = "Updated via PowerShell"
$item.Editing.EndEdit()

Script executes clean. Database row updates. But Content Editor shows stale data. Refresh the browser — sometimes the new value appears, sometimes it doesn’t. Clear cache, everything’s suddenly correct.

If you’re scratching your head wondering why this happens, you’re thinking about Sitecore wrong. Let me explain.

Sitecore’s Memory-First Architecture

The key thing to understand: Sitecore is designed to avoid database calls at all costs. This isn’t a side effect — it’s the core architectural decision that lets Sitecore scale to millions of items.

Here’s what actually happens when Sitecore needs to retrieve an item:

First, check Item Cache: Sitecore looks for a fully constructed Sitecore.Data.Items.Item object in the Item Cache. If it’s there, return it immediately. Done. No further lookups needed.

If not in Item Cache, check Data Cache components: The Data Cache stores the raw building blocks:

  • ItemDefinition (ID, name, template ID, parent ID)
  • FieldList (which fields exist for this item)
  • Individual field values

If these components exist, Sitecore reconstructs the Item object from them, caches it in Item Cache, and returns it.

Finally, query the database: If the Data Cache doesn’t have what’s needed, Sitecore hits the [Items], [SharedFields], [UnversionedFields], and [VersionedFields] tables, loads the data, populates both Data Cache and Item Cache, then returns the item.

When you update an item via PowerShell, you’re writing directly to those database tables. But you’re not touching Item Cache or Data Cache. Your PowerShell script bypasses the entire ItemProvider event pipeline. No item:saved event fires. No cache invalidation events propagate. The EventQueue table doesn’t get new records. From Sitecore’s perspective, nothing changed.

The Multi-Cache Problem

This is where it gets interesting architecturally. Sitecore doesn’t have a single monolithic cache — it has multiple specialized caches that work together:

Item Cache: Stores complete Item objects (includes all fields, versions, language data)

Data Cache: Stores the raw components used to build items:

  • ItemDefinition objects
  • FieldList objects
  • Individual field values

StandardValues Cache: Stores template field default values (consulted when an item doesn’t have its own value for a field)

Path Cache: Maps item paths to GUIDs for fast lookups

AccessResult Cache: Stores security filtering results

Registry Cache: Configuration and settings

Here’s the problem: after a PowerShell update, you might have:

  • Item Cache: Contains old Item object with stale field values
  • Data Cache: Still has old field value entries
  • Path Cache: Correct (maps path to ID, which didn’t change)
  • Database: Has new values

When Sitecore retrieves your item, depending on cache state, you get inconsistent results. Two requests for the same item can return different data based on whether they hit Item Cache or rebuild from Data Cache or query the database.

I’ve debugged this with dotTrace profiler, and watching the cache hit patterns is fascinating. Here’s what actually happens:

// Request 1: Gets served from Item Cache
var item1 = Sitecore.Context.Database.GetItem(itemId);
// Returns cached Item object with old "Title" value
// Request 2: Item Cache entry was evicted, rebuilds from Data Cache
var item2 = Sitecore.Context.Database.GetItem(itemId);
// Rebuilds Item from field data, still sees old cached field values
// Request 3: Data Cache entries also evicted, hits database
var item3 = Sitecore.Context.Database.GetItem(itemId);
// Finally sees new value from database, then caches it

Why Docker Amplifies This

In traditional deployments, you might not notice this much. In containerized environments, it becomes painfully obvious. Here’s why:

Memory pressure: Containers typically run with 2–4GB RAM allocations versus 32GB+ on VMs. Cache eviction happens constantly.

Isolated process spaces: Each container has completely independent memory. CM and CD don’t share anything. In Kubernetes, you might have 3 CD replicas — each with its own cache state showing different versions of your content.

Frequent restarts: During development, containers restart constantly. Every restart = cold cache = more visible inconsistency.

No distributed cache by default: Unless you’ve implemented Redis or another distributed cache, each container is an island.

I’ve architected several Kubernetes-based Sitecore implementations, and this is where developers get bitten hard. They’ll make a PowerShell update on CM, publish it, then hit different CD pods and see different content. It’s not a bug — it’s architecture.

What Actually Happens in the UI

When you edit through Content Editor or Experience Editor, Sitecore triggers the full save pipeline through its event system. The item:saved event fires and propagates through multiple registered handlers that:

  1. Remove the item from Item Cache
  2. Clear Data Cache entries for that item ID
  3. Trigger StandardValues Cache clearing if it’s a template
  4. Add entries to the EventQueue table for remote cache invalidation across CM instances
  5. Update link database and search indexes
  6. Fire any custom event handlers you’ve registered

PowerShell’s BeginEdit()/EndEdit() methods skip all of this. They call directly into Sitecore.Data.DataProviders and write to the database. Fast, efficient, but completely bypasses the event pipeline — which means no automatic cache invalidation.

This is by design. PowerShell gives you low-level data access for performance. The trade-off is you’re responsible for cache management yourself.

Why Auto-Clearing Would Break Everything

Some developers ask: “Why doesn’t Sitecore just clear cache after every script operation?”

Think about the implications. I recently wrote a migration script that updated 50,000 items. If Sitecore cleared cache after each operation:

  • 50,000 cache clear operations
  • 50,000 cache rebuild operations on next access
  • Memory thrashing from constant allocations/deallocations
  • GC pressure from all that object churn
  • Potential OutOfMemoryException on large operations

On a production instance with millions of items, this would tank performance. Bulk operations would become impossibly slow. Memory usage would spike uncontrollably.

Sitecore’s design choice: give architects control. Want aggressive cache clearing? Do it. Want to batch updates and clear once? Do that. Want selective clearing? You got it.

The Pattern I Actually Use Now

After years of getting burned by this, here’s how I write PowerShell scripts now:

# Keep track of what I touched
$affectedItems = @()
# Do the actual updates
$items = Get - ChildItem "master:/sitecore/content/Home" - Recurse
foreach($item in $items) {
if ($item["Title"] - eq "OldValue") {
$item.Editing.BeginEdit()
$item["Title"] = "NewValue"
$item.Editing.EndEdit()
$affectedItems += $item
}
}
# Clear ONLY the items I changed
foreach($item in $affectedItems) {
[Sitecore.Data.Caching.CacheManager]::GetItemCache($item.Database).RemoveItem($item.ID)
}
# Publish to CD
foreach($item in $affectedItems) {
Publish - Item - Item $item - Target "web" - PublishMode Smart - Recurse: $false
}


This is way better than nuking all caches. You’re surgically removing just the stuff you changed. The rest of the cache stays intact, site stays fast, everyone’s happy.

Note: In most cases, clearing ItemCache alone is sufficient. DataCache entries are secondary and typically rebuild automatically. If you need more thorough cache clearing, you can add DataCache key pattern matching, but it’s rarely necessary for typical content updates.

Stuff That’s Saved Me Hours of Debugging

A few tricks I’ve picked up over the years:

Watch the EventQueue table: If you’re running multiple CMs (like in Kubernetes), check your Core database’s EventQueue table. I’ve seen situations where events just stop propagating between instances. Cache invalidation events pile up, never get processed, and suddenly different CMs show different content.

Turn on cache logging during dev: Just temporarily, because it’s chatty. But seeing exactly what’s getting cached and cleared makes everything make sense.

Use the Sitecore diagnostics tools: There’s a Support Diagnostics module that shows you what’s in cache right now. It’s like X-ray vision for understanding what’s happening.

Check Application Insights: On newer Sitecore versions, you can actually see cache hit/miss ratios. If your hit ratio is low, something’s wrong with your cache strategy.

The Publishing Thing Nobody Mentions

Here’s something that bit me hard when we went headless: clearing CM cache means nothing to your CD instances until you publish.

In the old days, some devs (not me, I swear) would point CD at the master database. Terrible practice, but it meant updates showed up everywhere immediately. In a proper architecture with separate CM/CD? Publishing isn’t optional.

And Publishing Service has its own cache quirks too. I’ve seen situations where the publishing job queue gets backed up, and suddenly your CD is minutes behind CM even though you’re publishing. Fun times.

What I Wish Someone Had Told Me Years Ago

Look, after writing probably hundreds of PowerShell scripts for Sitecore — migrations, bulk updates, automated content fixes — here’s what I’ve learned:

PowerShell changes the database, not the cache. You’re reaching under Sitecore’s hood and modifying data directly. Sitecore doesn’t know you did that unless you tell it.

Cache clearing isn’t overhead, it’s part of the job. Budget for it. Plan for it. Do it.

In real architectures, you HAVE to publish. Don’t rely on CM and CD magically staying in sync. They won’t.

Clear what you actually changed. Don’t be lazy and nuke everything unless you really need to.

Test in realistic environments. If your dev environment has unlimited memory and production doesn’t, you won’t see cache issues until it’s too late.

Once I stopped thinking of Sitecore like a traditional CMS and started understanding it’s really a memory-first system that happens to persist to a database, everything clicked. The cache isn’t misbehaving — it’s doing exactly what it’s supposed to. You just need to work with it.

These days, when a junior dev comes to me saying “my PowerShell script isn’t working,” I already know what they forgot. We all learn this lesson eventually. Hopefully, this helps you learn it a bit faster than I did.

Stay tuned for more Sitecore-related articles, tips, and tricks to enhance your Sitecore experience.

Till then, happy Sitecoring! 😊

Please leave your comments or share this article if it’s useful for you!

Wednesday, November 26, 2025

Bulk User Creation in Sitecore with Predefined Roles Using PowerShell (Inline Data - No CSV Needed)

 Hello Sitecorian Community! 👋

User onboarding is a routine task for any Sitecore administrator — but when the list grows beyond a handful of users, things start becoming slow, repetitive, and error-prone. A few days ago, I faced exactly this challenge while onboarding multiple content authors and admins across different departments.

Creating users manually via User Manager?

✔ Works

❌ Doesn’t scale

❌ Prone to mistakes

❌ Takes too much time

I wanted a clean automation approach — something quick, reliable, and without depending on CSV uploads, especially since many enterprises don’t allow file-based imports for security reasons.

So like always, PowerShell + SPE came to the rescue!

Let’s walk through the challenge and how I solved it.

Why Automate User Creation in Sitecore?

If you’ve ever created multiple users manually, you already know the pain:

  • Typing usernames and emails
  • Setting passwords
  • Assigning multiple roles
  • Making sure profile fields are correct
  • Repeating it for every single user

And just one mistake can lead to:

  • Wrong access levels
  • Incorrect roles
  • Broken workflows
  • Inconsistent naming

Automation helps you:

  • Save time
  • Maintain consistency
  • Avoid human errors
  • Create 10, 20, or 100 users in seconds
  • Ensure each user gets the correct predefined roles

Inline Data — No CSV, No External Dependency

In some organizations, importing CSV files is restricted due to compliance/security.

So I built this script to use inline PowerShell hashtables where user details are defined right inside the script.

It’s:

  • Self-contained
  • Easy to maintain
  • Easy to update
  • Perfect for DevOps teams
  • Ideal for one-time admin runs

Just open SPE → Paste the script → Run it.

PowerShell Script (Inline Users + Predefined Roles)

🔗 Script link placeholder (add your GitHub link):

https://github.com/gaurarun777/SitecorePowerShell/blob/main/Sitecore-BulkUserCreation.ps1

How This Script Helps

1. Faster Onboarding

Create 5, 50, or even 500 users in seconds — no repetitive clicking.

2. Guaranteed Consistency

Each user gets:

  • Correct roles
  • Correct password
  • Correct profile fields

Every time.

3. No CSV Upload Needed

All data sits inside the script.

Perfect for environments with strict security rules.

4. Human Errors Eliminated

No accidental typos.

No missed roles.

No duplicate users.

5. Reusable for Future Teams

Just update the user array and reuse the script anytime.

Conclusion

Bulk user creation doesn’t need to be painful.

With a simple PowerShell script and inline data, you can automate user onboarding in minutes — without relying on CSV files or manual entry.

This approach has already saved us hours of admin effort and ensures accuracy every single time.

I hope you enjoy this blog. Stay tuned for more Sitecore-related articles, tips, and tricks to enhance your Sitecore experience.

Till then, happy Sitecoring! 😊

Please leave your comments or share this article if it’s useful for you!

Automating Access Control in Sitecore: Granting, Revoking, or Denying Permissions on Multiple Nodes

 

Hello Sitecorian Community! 👋

If you’re managing a large Sitecore environment, you know the drill — setting permissions across hundreds (or even thousands) of content items can be incredibly time-consuming and prone to errors. Imagine having to manually update permissions for each item — it’s a tedious, repetitive task that steals valuable time and increases the risk of mistakes. And when something goes wrong, it can have serious security implications.

So, how do you automate this process? How can you ensure consistency, eliminate human error, and manage permissions at scale? Well, I’ve got a solution for you! In this blog post, I’ll walk you through a PowerShell script that makes managing permissions across multiple items a breeze.

The Problem: Manual Permissions Management in Sitecore

Managing user access and permissions is a key part of maintaining a secure Sitecore environment. But as your Sitecore instance grows, it can feel like you’re drowning in a sea of permission settings. Here’s why:

  • Granting Permissions: You need to make sure the right people or roles have access to the right content.
  • Revoking Permissions: When someone leaves a project or role, you must manually remove their access to sensitive content.
  • Denying Permissions: Sometimes, you need to deny access to specific roles, even overriding inheritance rules.

When you’re dealing with hundreds of content items, making these adjustments manually can become a nightmare.

Not to mention, a single mistake can lead to unauthorized access, potential data leaks, or just plain frustration. So, how can we fix this?

The Solution: Automating Access Control with PowerShell

Here’s where automation shines! By using PowerShell, we can automate the process of granting, revoking, and denying permissions for multiple items at once. This saves you a lot of time, reduces the risk of human error, and ensures consistent permissions across your Sitecore environment.

In this post, I’ll walk you through how the script works and how you can use it in your own Sitecore instance.

How the PowerShell Script Solves the Problem

The script works by applying permissions to multiple content items in a single operation. Whether you’re granting, revoking, or denying permissions, the script automates all of it with minimal input. Here’s how it addresses each of the challenges:

  1. Granting Permissions:
  • You can quickly assign read, write, delete, and other rights to a specific role for multiple nodes.
  • This can be done for all items in a folder, or even across the entire Sitecore tree.
  1. Revoking Permissions:
  • The script removes any explicit “Allow” or “Deny” permissions from items, making sure that old permissions don’t stick around longer than needed.
  1. Denying Permissions:
  • You can explicitly deny access, even overriding inheritance (which would normally propagate permissions from parent items).
  1. Database Flexibility:
  • The script allows you to select the Sitecore database (master, web, core) you want to work with, making it adaptable for different environments (e.g., live, staging).
  1. Inheritance Management:
  • You can control whether child items inherit permissions from their parent items or whether you want to break that inheritance and set custom permissions.

Key Features of the Script:

  • Grant, revoke, or deny permissions on multiple items.
  • Select the database (master, web, core) for changes.
  • Control inheritance for child items.
  • An interactive dialog for setting parameters (no need to modify the script every time).

The PowerShell Script: Let’s Take a Look

https://github.com/gaurarun777/SitecorePowerShell/blob/main/Sitecore-AccessControl-Automation.ps1

How to Use the Script

  1. Customize the Parameters:
  • Choose the database (master, web, core) where you want the changes to be applied.
  • Enter the role for which you want to set permissions.
  • Specify the item paths (one per line).
  • Select the permissions (Read, Write, Delete, etc.) and choose whether you want to grant, revoke, or deny them.
  1. Run the Script:
  • Execute the script in a PowerShell environment connected to your Sitecore instance. You’ll need administrative privileges to apply changes.
  1. Verify the Changes:
  • After running the script, verify that the permissions were updated as expected by checking the Access Control tab in Sitecore for the affected items.


Conclusion

Managing permissions across a large Sitecore instance no longer has to be a nightmare. With this PowerShell script, you can grant, revoke, or deny permissions in bulk — saving time, reducing the risk of errors, and maintaining consistency across your Sitecore environment.

Stay tuned for more Sitecore-related articles, tips, and tricks to enhance your Sitecore experience.

Till then, happy Sitecoring! 😊

Please leave your comments or share this article if it’s useful for you!


Tuesday, July 29, 2025

🔧 Supercharge Your Sitecore Admin Workflow: PowerShell Scripts for Efficient User Management

Hi All,

Managing users in a large Sitecore environment can quickly become overwhelming — especially when dealing with inactive accounts, security audits, or regular maintenance. Whether you’re a Sitecore administrator or a dev lead, having a clean, secure user base is crucial.

In this blog, I’ll walk you through 4 essential PowerShell scripts that have helped my team streamline Sitecore user management and perform periodic cleanup with ease. 🧹⚙️

🚀 Why PowerShell for Sitecore?

Sitecore PowerShell Extensions (SPE) provide a powerful scripting environment to interact with Sitecore APIs directly — enabling automation, reporting, and even UI integration.

These scripts are simple, reusable, and can be scheduled or triggered on demand — helping you keep your environment healthy and compliant.

🧑‍💼 Use Case #1: List All Disabled Users from Sitecore

Inactive, disabled accounts can pile up over time. Use this script to identify all users who were disabled within the past— handy for audits or cleanup reports.

# Get all users (from all domains)
$allUsers = Get-User -Filter "*"
 
# Filter disabled users (IsEnabled = $false)
$disabledUsers = $allUsers | Where-Object { $_.IsEnabled -eq $false }
 
# Add parsed domain info for each user
$disabledUsersWithDomain = $disabledUsers | ForEach-Object {
    $splitName = $_.Name -split '\\'
    [PSCustomObject]@{
        Name      = $_.Name
        Domain    = if ($splitName.Length -eq 2) { $splitName[0] } else { "unknown" }
        UserName  = if ($splitName.Length -eq 2) { $splitName[1] } else { $_.Name }
        IsEnabled = $_.IsEnabled
    }
}
 
# Show in interactive list view
$disabledUsersWithDomain | Show-ListView -Title "Disabled Users" -Property Name, Domain, UserName, IsEnabled

✅ Benefits:

  • Audit ready
  • Easily exportable

🕵️ Use Case #2: Find Users Who Haven’t Logged In for 6+ Months

This one’s gold for spring cleaning your user base. It checks the LastLogin date and lists users who haven’t logged in since the specified threshold.

Script 1:

Add-Type -AssemblyName "System.Web"
 
# Set the cutoff date (6 months ago)
$cutoffDate = (Get-Date).AddMonths(-6)
 
# Get all Sitecore users
$allUsers = Get-User -Filter *
 
# Create a list of inactive users
$inactiveUsers = @()
 
foreach ($sitecoreUser in $allUsers) {
    $userName = $sitecoreUser.Name
 
    # Get Membership user for accurate LastLoginDate
    $membershipUser = [System.Web.Security.Membership]::GetUser($userName, $false)
 
    # Skip if membership user doesn't exist
    if ($membershipUser -eq $null) {
        continue
    }
 
    $lastLogin = $membershipUser.LastLoginDate
 
    if ($lastLogin -eq $null -or $lastLogin -lt $cutoffDate) {
        $inactiveUsers += [PSCustomObject]@{
            Username   = $sitecoreUser.Name
            FullName   = $sitecoreUser.Profile.FullName
            Email      = $sitecoreUser.Profile.Email
            LastLogin  = if ($lastLogin) { $lastLogin } else { "Never Logged In" }
        }
    }
}
 
# Show the list
$inactiveUsers | Sort-Object LastLogin | Show-ListView -Title "Users Not Logged In in Last 6 Months (Accurate)" -Property Username, FullName, Email, LastLogin

Script 2:

Add-Type -AssemblyName "System.Web"
 
# Get the membership provider (adjust provider name if custom)
$provider = [System.Web.Security.Membership]::Provider
 
# Set cutoff date to 6 months ago
$cutoffDate = (Get-Date).AddMonths(-6)
 
# Prepare list for inactive users
$inactiveUsers = @()
 
# Paging parameters
$pageSize = 1000
$pageIndex = 0
$totalRecords = 0
 
do {
    # Retrieve a page of users
    $usersPage = $provider.GetAllUsers($pageIndex, $pageSize, [ref]$totalRecords)
 
    foreach ($user in $usersPage) {
        # Get LastLoginDate from membership user
        $lastLoginDate = $user.LastLoginDate
 
        if ($lastLoginDate -eq $null -or $lastLoginDate -lt $cutoffDate) {
            # Try to get Sitecore user for profile info
            $sitecoreUser = Get-User -Identity $user.UserName -ErrorAction SilentlyContinue
 
            $inactiveUsers += [PSCustomObject]@{
                "Username"  = if ($sitecoreUser) { $sitecoreUser.Name } else { $user.UserName }
                "FullName"  = if ($sitecoreUser) { $sitecoreUser.Profile.FullName } else { "" }
                "Email"     = if ($sitecoreUser) { $sitecoreUser.Profile.Email } else { "" }
                "LastLogin" = if ($lastLoginDate) { $lastLoginDate } else { "Never Logged In" }
            }
        }
    }
 
    $pageIndex++
} while ($pageIndex * $pageSize -lt $totalRecords)
 
# Output the inactive users sorted by last login date
$inactiveUsers | Sort-Object LastLogin | Show-ListView -Title "Users Not Logged In Last 6 Months" -Property Username, FullName, Email, LastLogin
💡 Pro Tip: You can combine this with your organizational offboarding process to auto-disable accounts.

❌ Use Case #3: Disable List of Users in Bulk

Need to quickly disable multiple users? Paste a list of usernames and run this batch disable script.

# Prompt for comma-separated list (e.g., amgen\pia,sitecore\admin)
$userList = Read-Host "Enter comma-separated list of usernames or fully qualified usernames to disable"
 
# Split and clean the input
$userNames = $userList -split ',' | ForEach-Object { $_.Trim() }
 
foreach ($userName in $userNames) {
    if ([string]::IsNullOrWhiteSpace($userName)) {
        Write-Host "⚠️ Skipped empty username entry." -ForegroundColor DarkYellow
        continue
    }
 
    # Check if user exists
    $user = Get-User -Identity $userName -ErrorAction SilentlyContinue
 
    if ($user -ne $null) {
        if ($user.IsEnabled) {
            try {
                Disable-User -Identity $userName
                Write-Host "✅ Disabled: $userName" -ForegroundColor Green
            } catch {
                Write-Host "❌ Failed to disable $userName — $($_.Exception.Message)" -ForegroundColor Red
            }
        } else {
            Write-Host "ℹ️ Already disabled: $userName" -ForegroundColor Yellow
        }
    } else {
        Write-Host "❌ User not found: $userName" -ForegroundColor Red
    }
}

🧠 Use Cases:

  • Security lockdowns
  • Role changes
  • Temporary suspension

✅ Use Case #4: Enable List of Users in Bulk

Just like disabling, enabling a list of users is just as straightforward.

# Prompt for comma-separated list (e.g., amgen\pia,sitecore\admin)
$userList = Read-Host "Enter comma-separated list of usernames or fully qualified usernames to enable"
 
# Split and clean the input
$userNames = $userList -split ',' | ForEach-Object { $_.Trim() }
 
foreach ($userName in $userNames) {
    if ([string]::IsNullOrWhiteSpace($userName)) {
        Write-Host "⚠️ Skipped empty username entry." -ForegroundColor DarkYellow
        continue
    }
 
    # Check if user exists
    $user = Get-User -Identity $userName -ErrorAction SilentlyContinue
 
    if ($user -ne $null) {
        if (-not $user.IsEnabled) {
            try {
                Enable-User -Identity $userName
                Write-Host "✅ Enabled: $userName" -ForegroundColor Green
            } catch {
                Write-Host "❌ Failed to enable $userName — $($_.Exception.Message)" -ForegroundColor Red
            }
        } else {
            Write-Host "ℹ️ Already enabled: $userName" -ForegroundColor Yellow
        }
    } else {
        Write-Host "❌ User not found: $userName" -ForegroundColor Red
    }
}

🚀 Ideal for:

  • Reinstating users post-project
  • Bulk onboarding
  • Re-enabling after audits

🗂️ Optional: Exporting to CSV

You can export results of any of the above scripts for record-keeping:

$inactiveUsers | Export-Csv -Path "C:\SitecoreReports\InactiveUsers.csv" -NoTypeInformation

🔄 Bonus Tip: Automate It!

You can schedule these scripts via Task Scheduler or integrate into a custom Sitecore SPE Job for automation. This ensures your environment stays tidy without manual intervention.

🧭 Wrapping Up

Sitecore user management doesn’t have to be tedious. With these PowerShell scripts:

✅ You save time
 ✅ Reduce risk
 ✅ Improve governance
 ✅ Keep your environment secure

🔐 Whether you’re prepping for an audit, onboarding a team, or cleaning up dormant accounts — PowerShell is your best friend.

📌 Have your own PowerShell tips or scripts for Sitecore? Drop them in the comments or connect with me on LinkedIn. Let’s make Sitecore management smarter — together!

References:

https://github.com/gaurarun777/SitecorePowerShell/blob/main/SitecoreEnableListOfUsers.ps1

https://github.com/gaurarun777/SitecorePowerShell/blob/main/SitecoreDisableListOfUsers.ps1

https://github.com/gaurarun777/SitecorePowerShell/blob/main/SitecoreInactiveUsersfrom6months.ps1

https://github.com/gaurarun777/SitecorePowerShell/blob/main/SitecoreInactiveUsersfrom6months_1.ps1

https://github.com/gaurarun777/SitecorePowerShell/blob/main/SitecoreListAlldisabledusers.ps1

I hope you enjoy this Sitecore blog. Stay tuned for more Sitecore related articles.

Till that happy Sitecoring :)

Please leave your comments or share this article if it’s useful for you.