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.
- 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.
<#
.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
| Parameter | Type | Default | What it's for |
|---|---|---|---|
-Disable | string[] | — | Handler names to disable. Wildcards work, and the leading spaces vendors add are ignored. |
-Enable | string[] | — | Previously disabled handlers to put back. |
-Limit | int | 15 | How many handlers Explorer loads. Some people use 11 to leave room for overlays Windows draws itself. |
-BackupFolder | string | Documents\OverlayHandlerBackups | Where the .reg exports go before any change. |
-RestartExplorer | switch | — | 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, HandlerWhat'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' -WhatIfDo it for real and restart Explorer.
.\Disable-ShellOverlayHandler.ps1 -Disable 'DropboxExt0[4-9]', 'DropboxExt10' -RestartExplorer -Confirm:$falseChanged your mind.
.\Disable-ShellOverlayHandler.ps1 -Enable 'DropboxExt*' -RestartExplorerWhat you'll see
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
- 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. - 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
-Limitis marked OverLimit. - Look up what each one is. The CLSID leads to
HKCR\CLSID\{...}for a readable name and toInprocServer32for the DLL, which is usually enough to tell which app owns a cryptically named key. - 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 throughShouldProcessand is moved withMove-ItemtoHKLM\SOFTWARE\DisabledShellIconOverlays, where Explorer never looks. Nothing is deleted. - Put back on request.
-Enablemoves 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.exeas 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.