<# .SYNOPSIS Lists Explorer preview handlers and maps file extensions to them. .DESCRIPTION Preview handlers are COM objects registered under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers. Explorer finds the one for a file type through the shellex key {8895b1c6-b41f-4c1c-a562-0d564250836f} under the extension (or its ProgID). This script shows what's installed, shows which handler an extension uses, and points an extension at a handler you choose, or removes that mapping. Supports -WhatIf. .PARAMETER ListHandlers List every registered preview handler with its CLSID. .PARAMETER Extension One or more extensions, like .log or .ps1. On its own, shows the current mapping. .PARAMETER Handler The handler to use: a CLSID, or part of its name as shown by -ListHandlers. .PARAMETER Remove Delete the mapping for the extension at the chosen scope. .PARAMETER Scope CurrentUser (HKCU, no admin needed) or LocalMachine (HKLM, needs admin). Default CurrentUser. .PARAMETER Force Replace an existing mapping that points at a different handler. .EXAMPLE .\Set-PreviewHandler.ps1 -ListHandlers .EXAMPLE .\Set-PreviewHandler.ps1 -Extension .log, .ps1, .json -Handler '{1531d583-8375-4d3f-b5fb-d23bbd169f22}' -WhatIf #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Show')] param( [Parameter(Mandatory, ParameterSetName = 'List')][switch]$ListHandlers, [Parameter(Mandatory, ParameterSetName = 'Show')] [Parameter(Mandatory, ParameterSetName = 'Set')] [Parameter(Mandatory, ParameterSetName = 'Remove')] [ValidatePattern('^\.[\w\-\.]+$')][string[]]$Extension, [Parameter(Mandatory, ParameterSetName = 'Set')][string]$Handler, [Parameter(Mandatory, ParameterSetName = 'Remove')][switch]$Remove, [Parameter(ParameterSetName = 'Set')] [Parameter(ParameterSetName = 'Remove')] [ValidateSet('CurrentUser', 'LocalMachine')][string]$Scope = 'CurrentUser', [Parameter(ParameterSetName = 'Set')][switch]$Force ) $previewGuid = '{8895b1c6-b41f-4c1c-a562-0d564250836f}' $hkcr = 'Registry::HKEY_CLASSES_ROOT' function Get-DefaultValue([string]$Key) { if (Test-Path -LiteralPath $Key) { (Get-ItemProperty -LiteralPath $Key -ErrorAction SilentlyContinue).'(default)' } } # Every registered handler: value name is the CLSID, value data is the friendly name. $handlers = foreach ($root in 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\PreviewHandlers') { if (-not (Test-Path -LiteralPath $root)) { continue } $props = Get-ItemProperty -LiteralPath $root foreach ($p in $props.PSObject.Properties | Where-Object Name -match '^\{[0-9a-fA-F\-]{36}\}$') { [pscustomobject]@{ Name = $p.Value; Clsid = $p.Name.ToLower() } } } $handlers = @($handlers | Sort-Object Clsid -Unique | Sort-Object Name) if ($ListHandlers) { return $handlers } foreach ($ext in $Extension.ToLower()) { $userKey = "HKCU:\Software\Classes\$ext\shellex\$previewGuid" $key = if ($Scope -eq 'LocalMachine') { "HKLM:\Software\Classes\$ext\shellex\$previewGuid" } else { $userKey } if ($PSCmdlet.ParameterSetName -eq 'Show') { # Look where handlers usually get registered: the extension, its ProgID, and SystemFileAssociations. $progId = Get-DefaultValue "$hkcr\$ext" $found = $null foreach ($where in @("$hkcr\$ext", $(if ($progId) { "$hkcr\$progId" }), "$hkcr\SystemFileAssociations\$ext")) { if (-not $where) { continue } $clsid = Get-DefaultValue "$where\shellex\$previewGuid" if ($clsid) { $found = [pscustomobject]@{ Extension = $ext; Handler = ($handlers | Where-Object Clsid -eq $clsid.ToLower()).Name; Clsid = $clsid; FoundAt = $where -replace '^Registry::', '' }; break } } if (-not $found) { $found = [pscustomobject]@{ Extension = $ext; Handler = '(none)'; Clsid = $null; FoundAt = $null } } $found continue } try { if ($Remove) { if (-not (Test-Path -LiteralPath $key)) { Write-Verbose "$ext has no mapping at $Scope scope."; continue } if ($PSCmdlet.ShouldProcess("$ext ($Scope)", 'Remove preview handler mapping')) { Remove-Item -LiteralPath $key -Recurse -ErrorAction Stop [pscustomobject]@{ Extension = $ext; Scope = $Scope; Status = 'Removed'; Clsid = $null } } continue } $target = if ($Handler -match '^\{?[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}\}?$') { '{' + $Handler.Trim('{}').ToLower() + '}' } else { $hits = @($handlers | Where-Object Name -like "*$Handler*") if ($hits.Count -ne 1) { throw "'$Handler' matches $($hits.Count) handlers. Use -ListHandlers and pass the CLSID." } $hits[0].Clsid } if ($handlers.Clsid -notcontains $target -and -not $Force) { throw "$target isn't a registered preview handler. Install the handler first, or use -Force to map it anyway." } $current = Get-DefaultValue $key if ($current -and $current -ne $target -and -not $Force) { [pscustomobject]@{ Extension = $ext; Scope = $Scope; Status = "SkippedExisting ($current), use -Force"; Clsid = $current } continue } if ($PSCmdlet.ShouldProcess("$ext ($Scope)", "Set preview handler to $target")) { if (-not (Test-Path -LiteralPath $key)) { $null = New-Item -Path $key -Force -ErrorAction Stop } Set-ItemProperty -LiteralPath $key -Name '(default)' -Value $target -ErrorAction Stop [pscustomobject]@{ Extension = $ext; Scope = $Scope; Status = 'Set'; Clsid = $target } } } catch { Write-Warning "${ext}: $($_.Exception.Message)" } }