Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

SCRIPT LIBRARY · POWERSHELL

Too Many Sync Icons? Taming Explorer's 15 Overlay Handler Limit

List every icon overlay handler in the order Explorer loads them, see which ones fall past the 15-slot limit, and switch off the ones you don't need, with a registry backup first and an easy way back.

AT A GLANCEDisable-ShellOverlayHandler.ps1
What it does
Reads the ShellIconOverlayIdentifiers key, sorts the handlers the way Explorer does, and shows each one's position, CLSID, name and DLL, flagging any past the limit. -Disable backs up the key to a .reg file and moves the chosen handlers to a holding key Explorer ignores; -Enable moves them back. It can restart Explorer so the change shows.
Requires
  • Windows PowerShell 5.1 or PowerShell 7+
  • No modules
Permissions
Listing works as any user. -Disable and -Enable write to HKLM, so they need an elevated PowerShell.
Runs on
Windows 10/11
Tested
Parse-checked in PowerShell 7.4, and the sort order was checked against sample handler names with leading spaces, tabs and underscores. The registry reads, moves and the Explorer restart are Windows-only and weren't run here

Part 11 of the thread Spring cleaning for Windows PCs

Icon overlays are the little badges on file icons: the green tick for synced, the blue arrows for syncing, the padlock, the cloud. Each one comes from a shell extension listed under one registry key, and Explorer only loads the first 15 of them. The rest are ignored without a word.

Fifteen used to be plenty. Then Dropbox registered ten of its own, OneDrive added a handful, Google Drive a few more, a download manager and an archiver wanted one each, and suddenly your cloud of choice shows no icons at all because it lost the alphabetical race. Vendors fight back by putting spaces in front of their key names so they sort first, which is exactly as silly as it sounds. And a buggy handler can make Explorer hang every time it draws a folder.

Mine started as a folder of .reg files, one of which deleted the entire key "to see if that helps". This script is the tidier version. It shows you the list in load order, marks what's past the cut-off, and lets you park the handlers you don't care about, with a backup taken first and a one-line way to put them back.

Disable-ShellOverlayHandler.ps1Download
<#
.SYNOPSIS
    Lists Explorer's icon overlay handlers, shows which ones are past the 15-slot limit, and disables or re-enables the ones you pick.
.DESCRIPTION
    Explorer only loads the first 15 icon overlay handlers under
    HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers, in the order
    the key names sort. Sync apps register several each and pad their names with leading spaces to
    win the race, so the ones you care about can silently lose their icons, and a misbehaving handler
    can make Explorer hang or crash.

    With no parameters it lists every handler in load order with its CLSID, name and DLL, marks the
    ones past -Limit, and includes any you've disabled with this script.

    -Disable exports the whole key to a .reg backup, then moves the chosen handlers to
    HKLM\SOFTWARE\DisabledShellIconOverlays, where Explorer doesn't look. -Enable moves them back.
    Nothing is deleted. Changes take effect when Explorer restarts (-RestartExplorer, or sign out).
.PARAMETER Disable
    Handler names to disable. Wildcards work, and leading spaces are ignored (DropboxExt0* matches '   DropboxExt01').
.PARAMETER Enable
    Previously disabled handler names to put back.
.PARAMETER Limit
    How many handlers Explorer loads. Default: 15.
.PARAMETER BackupFolder
    Where the .reg backups go. Default: OverlayHandlerBackups in your Documents folder.
.PARAMETER RestartExplorer
    Restart Explorer afterwards so the change shows up. Open Explorer windows will close.
.EXAMPLE
    .\Disable-ShellOverlayHandler.ps1 | Format-Table Position, Name, Handler, State
.EXAMPLE
    .\Disable-ShellOverlayHandler.ps1 -Disable 'DropboxExt1*', 'IDM Shell Extension' -RestartExplorer -WhatIf
#>
[CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'List')]
param(
    [Parameter(Mandatory, ParameterSetName = 'Disable')]
    [ValidateNotNullOrEmpty()]
    [string[]]$Disable,

    [Parameter(Mandatory, ParameterSetName = 'Enable')]
    [ValidateNotNullOrEmpty()]
    [string[]]$Enable,

    [ValidateRange(1, 64)]
    [int]$Limit = 15,

    [Parameter(ParameterSetName = 'Disable')]
    [Parameter(ParameterSetName = 'Enable')]
    [string]$BackupFolder = (Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'OverlayHandlerBackups'),

    [Parameter(ParameterSetName = 'Disable')]
    [Parameter(ParameterSetName = 'Enable')]
    [switch]$RestartExplorer
)

$activeKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers'
$parkedKey = 'HKLM:\SOFTWARE\DisabledShellIconOverlays'

function Get-OverlayHandler([string]$KeyPath, [string]$State) {
    if (-not (Test-Path -LiteralPath $KeyPath)) { return }
    $keys = [System.Collections.Generic.List[object]]::new()
    foreach ($k in Get-ChildItem -LiteralPath $KeyPath) { $keys.Add($k) }
    # The registry sorts names ordinally after upper-casing them, which is why '   Name' (spaces) beats 'Name'. Match that exactly.
    $keys.Sort([Comparison[object]] { param($a, $b) [string]::CompareOrdinal($a.PSChildName.ToUpperInvariant(), $b.PSChildName.ToUpperInvariant()) })

    $position = 0
    foreach ($k in $keys) {
        $position++
        $clsid = [string]$k.GetValue('')
        $handler = $null; $dll = $null
        if ($clsid -match '^\{[0-9A-Fa-f-]{36}\}$') {
            $clsidKey = "Registry::HKEY_CLASSES_ROOT\CLSID\$clsid"
            $handler = (Get-ItemProperty -LiteralPath $clsidKey -ErrorAction SilentlyContinue).'(default)'
            $dll = (Get-ItemProperty -LiteralPath "$clsidKey\InprocServer32" -ErrorAction SilentlyContinue).'(default)'
        }
        [pscustomobject]@{
            Position = if ($State -eq 'Active') { $position } else { $null }
            Name     = $k.PSChildName.Trim()
            RawName  = $k.PSChildName
            State    = if ($State -ne 'Active') { 'Disabled' } elseif ($position -le $Limit) { 'Loaded' } else { 'OverLimit' }
            Handler  = $handler
            Clsid    = $clsid
            Dll      = $dll
        }
    }
}

function Select-Handler($List, [string[]]$Pattern) {
    foreach ($p in $Pattern) {
        $hit = @($List | Where-Object { $_.Name -like $p.Trim() -or $_.RawName -eq $p })
        if (-not $hit) { Write-Warning "No handler matches '$p'." }
        $hit
    }
}

if ($PSCmdlet.ParameterSetName -eq 'List') {
    Get-OverlayHandler $activeKey 'Active'
    Get-OverlayHandler $parkedKey 'Disabled'
    return
}

$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
if (-not ([Security.Principal.WindowsPrincipal]$identity).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    throw 'Changing overlay handlers writes to HKLM, so run this from an elevated PowerShell.'
}

if ($PSCmdlet.ParameterSetName -eq 'Disable') {
    $targets = @(Select-Handler (Get-OverlayHandler $activeKey 'Active') $Disable | Sort-Object RawName -Unique)
    $from = $activeKey; $to = $parkedKey; $verb = 'Disable overlay handler'; $done = 'Disabled'
}
else {
    $targets = @(Select-Handler (Get-OverlayHandler $parkedKey 'Disabled') $Enable | Sort-Object RawName -Unique)
    $from = $parkedKey; $to = $activeKey; $verb = 'Re-enable overlay handler'; $done = 'Enabled'
}
if (-not $targets) { return }

# Back up both keys before touching anything. No backup, no changes.
$backupFile = $null
if (-not $WhatIfPreference) {
    try {
        $null = New-Item -ItemType Directory -Path $BackupFolder -Force -ErrorAction Stop
        $stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
        foreach ($pair in @(@($activeKey, 'active'), @($parkedKey, 'disabled'))) {
            if (-not (Test-Path -LiteralPath $pair[0])) { continue }
            $file = Join-Path $BackupFolder "ShellIconOverlayIdentifiers-$($pair[1])-$stamp.reg"
            $regPath = $pair[0] -replace '^HKLM:', 'HKEY_LOCAL_MACHINE'
            $null = & "$env:SystemRoot\System32\reg.exe" export $regPath $file /y 2>&1
            if ($LASTEXITCODE -ne 0) { throw "reg.exe export of $regPath failed with exit code $LASTEXITCODE" }
            if ($pair[1] -eq 'active') { $backupFile = $file }
        }
    }
    catch {
        throw "Backup failed, so nothing was changed: $($_.Exception.Message)"
    }
}

foreach ($t in $targets) {
    if (-not $PSCmdlet.ShouldProcess("'$($t.Name)' ($($t.Handler))", $verb)) { continue }
    $result = [pscustomobject]@{ Name = $t.Name; Handler = $t.Handler; Action = $null; Backup = $backupFile }
    try {
        if (-not (Test-Path -LiteralPath $to)) { $null = New-Item -Path $to -Force -ErrorAction Stop }
        $existing = Join-Path $to $t.RawName
        if (Test-Path -LiteralPath $existing) {
            if ($done -eq 'Enabled') { throw 'The app has already re-created this handler, so it is active again. Nothing to move.' }
            Remove-Item -LiteralPath $existing -Recurse -Force -ErrorAction Stop   # an older parked copy
        }
        Move-Item -LiteralPath (Join-Path $from $t.RawName) -Destination $to -ErrorAction Stop
        $result.Action = $done
    }
    catch {
        $result.Action = 'Failed'
        Write-Warning "$($t.Name): $($_.Exception.Message)"
    }
    $result
}

if ($RestartExplorer -and $PSCmdlet.ShouldProcess('explorer.exe', 'Restart Explorer')) {
    Get-Process -Name explorer -ErrorAction SilentlyContinue | Stop-Process -Force
    # Windows restarts the shell by itself, as the signed-in user. Starting it from here would run it elevated.
    Start-Sleep -Seconds 4
    if (-not (Get-Process -Name explorer -ErrorAction SilentlyContinue)) {
        Write-Warning 'Explorer did not come back on its own. Press Ctrl+Shift+Esc, choose Run new task, and type explorer.'
    }
}
elseif (-not $RestartExplorer -and -not $WhatIfPreference) {
    Write-Warning 'Restart Explorer (or sign out and back in) for the change to show.'
}

Parameters

ParameterTypeDefaultWhat it's for
-Disablestring[]—Handler names to disable. Wildcards work, and the leading spaces vendors add are ignored.
-Enablestring[]—Previously disabled handlers to put back.
-Limitint15How many handlers Explorer loads. Some people use 11 to leave room for overlays Windows draws itself.
-BackupFolderstringDocuments\OverlayHandlerBackupsWhere the .reg exports go before any change.
-RestartExplorerswitch—Restart Explorer afterwards. The taskbar blinks and open Explorer windows close.

Run it

The list, in load order.

.\Disable-ShellOverlayHandler.ps1 | Format-Table Position, Name, State, Handler

What's being ignored right now?

.\Disable-ShellOverlayHandler.ps1 | Where-Object State -eq 'OverLimit'

Park Dropbox's extra overlays and a download manager's, and see what would happen first.

.\Disable-ShellOverlayHandler.ps1 -Disable 'DropboxExt0[4-9]', 'DropboxExt10', 'IDM Shell Extension' -WhatIf

Do it for real and restart Explorer.

.\Disable-ShellOverlayHandler.ps1 -Disable 'DropboxExt0[4-9]', 'DropboxExt10' -RestartExplorer -Confirm:$false

Changed your mind.

.\Disable-ShellOverlayHandler.ps1 -Enable 'DropboxExt*' -RestartExplorer

What you'll see

Example outputvalues are illustrative
Position Name                  State     Handler
-------- ----                  -----     -------
       1 DropboxExt01          Loaded    DropboxExt
       2 DropboxExt02          Loaded    DropboxExt
       3 DropboxExt03          Loaded    DropboxExt
         ...
      10 DropboxExt10          Loaded    DropboxExt
      11 OneDrive1             Loaded    Microsoft OneDrive Overlay (ErrorConflict)
      12 OneDrive2             Loaded    Microsoft OneDrive Overlay (SyncInProgress)
      13 OneDrive3             Loaded    Microsoft OneDrive Overlay (InSync)
      14 OneDrive4             Loaded    Microsoft OneDrive Overlay (Shared)
      15 OneDrive5             Loaded    Microsoft OneDrive Overlay (ReadOnly)
      16 OneDrive6             OverLimit Microsoft OneDrive Overlay (Locked)
      17 GoogleDriveSynced     OverLimit Google Drive Synced
      18 EnhancedStorageShell  OverLimit Enhanced Storage Icon Overlay Handler
      19 SharingPrivate        OverLimit Sharing Overlay Handler

How it works

  1. Read the key. Everything lives under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers. Each subkey is one handler, and its default value is the CLSID of the shell extension.
  2. Sort it like Explorer does. The names are compared ordinally after upper-casing, the way the registry stores them, so leading tabs and spaces come first. That gives each handler its real load position, and anything past -Limit is marked OverLimit.
  3. Look up what each one is. The CLSID leads to HKCR\CLSID\{...} for a readable name and to InprocServer32 for the DLL, which is usually enough to tell which app owns a cryptically named key.
  4. Back up, then park. Before changing anything it exports the active key (and the holding key, if it exists) with reg.exe export. If the backup fails, nothing is changed. Each chosen handler then goes through ShouldProcess and is moved with Move-Item to HKLM\SOFTWARE\DisabledShellIconOverlays, where Explorer never looks. Nothing is deleted.
  5. Put back on request. -Enable moves handlers from the holding key back to the real one. Double-click the .reg backup if you'd rather restore the whole key the old way.

Overlays aren't the only shell extensions that slow Explorer down. Preview handlers are the other usual suspect when selecting a file makes the window freeze.

Take it further

  • Hunt down a crashing extension. If Explorer keeps crashing, look in the Application log for event 1000 with explorer.exe as the faulting application. The faulting module name often points straight at the DLL this script lists.
  • Check context menu handlers too. The same idea works for HKCR\*\shellex\ContextMenuHandlers, which is where slow right-click menus come from. ShellExView from NirSoft shows them all with a disable checkbox.
  • Keep it in shape. Run the list from a scheduled task after big app updates and warn yourself when your preferred sync app's handlers slip past position 15.

Things that'll trip you up

  • Updates put them back. Dropbox, OneDrive and friends re-register their overlays when they update, so a handler you parked can reappear in the active list. Run the list again after a big app update. -Enable won't overwrite a handler the app has already re-created.
  • It's alphabetical, spaces first. Explorer loads keys in the order the registry sorts their names, which is why a name starting with three spaces beats one starting with two. Renaming keys to reorder them works until the next app update undoes it, so disabling the ones you don't need is the more durable fix.
  • Nothing changes until Explorer restarts. Overlays load when Explorer starts. Use -RestartExplorer, sign out and back in, or restart Explorer from Task Manager. The script lets Windows bring Explorer back itself rather than starting it from an elevated prompt, which would leave you with an elevated shell.
  • 32-bit apps have their own list. There's a second copy of the key under WOW6432Node that only 32-bit programs' file dialogs use. Explorer on 64-bit Windows doesn't read it, so the script leaves it alone.