Skip to content

Instantly share code, notes, and snippets.

@jiripolasek
Last active October 16, 2025 10:58
Show Gist options
  • Select an option

  • Save jiripolasek/7452d72d56422bd3ecfeaa1826fc2534 to your computer and use it in GitHub Desktop.

Select an option

Save jiripolasek/7452d72d56422bd3ecfeaa1826fc2534 to your computer and use it in GitHub Desktop.
Mirrors Microsoft.CmdPal.Ext.WebSearch.Helpers.DefaultBrowserInfo detection flow; Prints results of each step to help diagnose mismatches on a user's machine.
<#
Detect-DefaultBrowser.ps1 (PowerShell 5.1+ compatible)
Prints step-by-step diagnostics for default browser detection (http and optional https).
USAGE
powershell -NoProfile -ExecutionPolicy Bypass -File .\Detect-DefaultBrowser.ps1 -Verbose
.\Detect-DefaultBrowser.ps1 -IncludeHttps -TestUrl "https://example.com"
.\Detect-DefaultBrowser.ps1 -TestUrl "https://example.com" -Launch
#>
[CmdletBinding()]
param(
[string]$TestUrl = $null, # e.g. "https://example.com"
[switch]$Launch, # actually launch the browser with TestUrl
[switch]$IncludeHttps # also inspect HTTPS in addition to HTTP
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Write-Step {
param([string]$Title, [string]$Value)
Write-Host ("`n=== {0} ===" -f $Title) -ForegroundColor Cyan
if ($null -eq $Value -or $Value -eq '') { $Value = '<null or empty>' }
Write-Host $Value
}
function Write-Section {
param([string]$Title)
Write-Host "`n####################### $Title #######################" -ForegroundColor Yellow
}
function Dump-RegKey {
param(
[Parameter(Mandatory)][string]$RegPath,
[string[]]$ValueNames = @($null, 'ProgId','ApplicationName','FriendlyTypeName')
)
$out = [ordered]@{}
foreach ($vn in $ValueNames) {
$val = [Microsoft.Win32.Registry]::GetValue($RegPath, $vn, $null)
$k = if ($vn) { $vn } else { '(Default)' }
$out[$k] = $val
}
[pscustomobject]$out
}
# ---------- P/Invoke SHLoadIndirectString ----------
if (-not ([System.Management.Automation.PSTypeName]'ShlwapiNative').Type) {
Add-Type -Language CSharp -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using System.Text;
public static class ShlwapiNative
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = false)]
public static extern int SHLoadIndirectString(
string pszSource,
StringBuilder pszOutBuf,
uint cchOutBuf,
IntPtr ppvReserved
);
}
'@
}
function Resolve-IndirectString {
param([Parameter(Mandatory)][string]$String)
if (-not $String.StartsWith('@')) { return $String }
$sb = New-Object System.Text.StringBuilder 512
$hr = [ShlwapiNative]::SHLoadIndirectString($String, $sb, 512, [IntPtr]::Zero)
if ($hr -eq 0) { return $sb.ToString() }
throw ("SHLoadIndirectString failed (HRESULT=0x{0:X8}) for '{1}'" -f $hr, $String)
}
function Normalize-AppName {
param([string]$Name)
if (-not $Name) { return $null }
$n = $Name -replace '(?i)\bURL\b','' -replace '(?i)\bHTML\b','' -replace '(?i)\bDocument\b','' -replace '(?i)\bWeb\b',''
$n.TrimEnd()
}
function Parse-CommandPattern {
param([string]$Command)
if ([string]::IsNullOrWhiteSpace($Command)) { return $null }
# Firefox WindowsApps quoting hack (Store build sometimes lacks quotes)
if ($Command -match 'firefox\.exe' -and $Command -match '\\WindowsApps\\' -and -not $Command.TrimStart().StartsWith('"')) {
$idx = $Command.IndexOf('firefox.exe', [StringComparison]::Ordinal)
if ($idx -ge 0) {
$pathEnd = $idx + 'firefox.exe'.Length
$Command = '"' + $Command.Insert($pathEnd, '"')
}
}
$trim = $Command.Trim()
$exe = $null; $args = ''
if ($trim.StartsWith('"')) {
$end = $trim.IndexOf('"',1)
if ($end -gt 0) {
$exe = $trim.Substring(1, $end-1)
$args = $trim.Substring($end+1).Trim()
} else {
$exe = $trim.Trim('"')
}
} else {
$space = $trim.IndexOf(' ')
if ($space -gt 0) {
$exe = $trim.Substring(0,$space)
$args = $trim.Substring($space+1).Trim()
} else {
$exe = $trim
}
}
[pscustomobject]@{ Command=$Command; Path=$exe; Arguments=$args }
}
function Validate-PathOrUri {
param([string]$PathOrUri)
$isFile = $false; $isUri = $false; $uriRef = $null
if ($PathOrUri) { $isFile = Test-Path -LiteralPath $PathOrUri -PathType Leaf }
if (-not $isFile -and $PathOrUri) { $isUri = [Uri]::TryCreate($PathOrUri, [UriKind]::Absolute, [ref]$uriRef) }
$nearest = $null
if (-not $isFile -and $PathOrUri) {
try {
$dir = [System.IO.Path]::GetDirectoryName($PathOrUri)
while ($dir -and -not (Test-Path -LiteralPath $dir -PathType Container)) {
$dir = [System.IO.Path]::GetDirectoryName($dir)
}
$nearest = $dir
} catch {}
}
[pscustomobject]@{ ExistsAsFile=$isFile; IsAbsoluteUri=$isUri; NearestExistingFolder=$nearest }
}
function Get-FileDiagnostics {
param([string]$FilePath)
if (-not (Test-Path -LiteralPath $FilePath -PathType Leaf)) { return $null }
$ver = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($FilePath)
$sig = $null
try { $sig = Get-AuthenticodeSignature -LiteralPath $FilePath } catch {}
$sigObj = $null
if ($sig) {
$sigObj = [pscustomobject]@{
Status = $sig.Status
StatusMessage = $sig.StatusMessage
SignerSubject = $sig.SignerCertificate.Subject
Issuer = $sig.SignerCertificate.Issuer
NotBefore = $sig.SignerCertificate.NotBefore
NotAfter = $sig.SignerCertificate.NotAfter
}
}
[pscustomobject]@{
VersionInfo = [pscustomobject]@{
FileVersion = $ver.FileVersion
ProductVersion = $ver.ProductVersion
ProductName = $ver.ProductName
FileDescription = $ver.FileDescription
CompanyName = $ver.CompanyName
OriginalFilename = $ver.OriginalFilename
}
Signature = $sigObj
}
}
# ---------- Edge fallback constants ----------
$MSEdgePath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft\Edge\Application\msedge.exe'
$MSEdgeArgumentsPattern = '--single-argument %1'
$MSEdgeName = 'Microsoft Edge'
# ---------- Main ----------
$protocols = @('http'); if ($IncludeHttps) { $protocols += 'https' }
$aggregate = @()
foreach ($proto in $protocols) {
Write-Section $proto
$r = [ordered]@{
Protocol = $proto
ProgIdSource = $null
ProgId = $null
UserChoiceLatestDump = $null
UserChoiceDump = $null
HKCR_ApplicationNameRaw = $null
HKCR_ApplicationNameResolved = $null
HKCR_ApplicationNameClean = $null
Command_HKCR = $null
Command_HKCU_Classes = $null
Command_HKLM_Classes = $null
Command_HKLM_Wow6432_Classes = $null
CommandChosenSource = $null
CommandPatternFinal = $null
ExtractedPath = $null
ExtractedArguments = $null
PathValidation = $null
FileDiagnostics = $null
FinalBrowserPath = $null
FinalBrowserName = $null
FinalArgumentsPattern = $null
ExceptionMessage = $null
FallbackUsed = $false
}
try {
# 1) ProgId (UserChoiceLatest -> UserChoice)
$ucLatest = "HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\${proto}\UserChoiceLatest"
$ucLegacy = "HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\${proto}\UserChoice"
$r.UserChoiceLatestDump = Dump-RegKey -RegPath $ucLatest
$r.UserChoiceDump = Dump-RegKey -RegPath $ucLegacy
$progId = [Microsoft.Win32.Registry]::GetValue($ucLatest,'ProgId',$null)
$r.ProgIdSource = 'UserChoiceLatest'
if (-not $progId) {
$progId = [Microsoft.Win32.Registry]::GetValue($ucLegacy,'ProgId',$null)
$r.ProgIdSource = 'UserChoice'
}
$r.ProgId = $progId
Write-Step ("{0} ProgId source" -f $proto) $r.ProgIdSource
Write-Step ("{0} ProgId" -f $proto) $r.ProgId
if (-not $progId) { throw ("ProgId not found for {0}." -f $proto) }
# 2) Application Name (HKCR\<ProgId>\Application\ApplicationName OR HKCR\<ProgId>\(Default))
$rawAppName = [Microsoft.Win32.Registry]::GetValue("HKEY_CLASSES_ROOT\${progId}\Application",'ApplicationName',$null)
if (-not $rawAppName) { $rawAppName = [Microsoft.Win32.Registry]::GetValue("HKEY_CLASSES_ROOT\${progId}",$null,$null) }
$r.HKCR_ApplicationNameRaw = $rawAppName
Write-Step ("{0} Raw application name" -f $proto) $r.HKCR_ApplicationNameRaw
$resolvedName = $rawAppName
if ($resolvedName -and $resolvedName.StartsWith('@')) { $resolvedName = Resolve-IndirectString $resolvedName }
$r.HKCR_ApplicationNameResolved = $resolvedName
$r.HKCR_ApplicationNameClean = Normalize-AppName $resolvedName
Write-Step ("{0} Application name after @-resolve" -f $proto) $r.HKCR_ApplicationNameResolved
Write-Step ("{0} Cleaned application name" -f $proto) $r.HKCR_ApplicationNameClean
# 3) Commands from multiple hives
$cmdHKCR = [Microsoft.Win32.Registry]::GetValue("HKEY_CLASSES_ROOT\${progId}\shell\open\command",$null,$null)
$cmdHKCU_Classes = [Microsoft.Win32.Registry]::GetValue("HKEY_CURRENT_USER\Software\Classes\${progId}\shell\open\command",$null,$null)
$cmdHKLM_Classes = [Microsoft.Win32.Registry]::GetValue("HKEY_LOCAL_MACHINE\Software\Classes\${progId}\shell\open\command",$null,$null)
$cmdHKLM_W6432 = [Microsoft.Win32.Registry]::GetValue("HKEY_LOCAL_MACHINE\Software\WOW6432Node\Classes\${progId}\shell\open\command",$null,$null)
$r.Command_HKCR = $cmdHKCR
$r.Command_HKCU_Classes = $cmdHKCU_Classes
$r.Command_HKLM_Classes = $cmdHKLM_Classes
$r.Command_HKLM_Wow6432_Classes = $cmdHKLM_W6432
Write-Step ("{0} Command (HKCR)" -f $proto) $cmdHKCR
Write-Step ("{0} Command (HKCU\Software\Classes)" -f $proto) $cmdHKCU_Classes
Write-Step ("{0} Command (HKLM\Software\Classes)" -f $proto) $cmdHKLM_Classes
Write-Step ("{0} Command (HKLM\WOW6432\Classes)" -f $proto) $cmdHKLM_W6432
# Choose command (prefer HKCR, then HKCU\Classes, then HKLM\Classes, then HKLM\WOW6432)
$chosen = $cmdHKCR
$src = 'HKCR'
if ([string]::IsNullOrWhiteSpace($chosen) -and $cmdHKCU_Classes) { $chosen = $cmdHKCU_Classes; $src='HKCU\Software\Classes' }
if ([string]::IsNullOrWhiteSpace($chosen) -and $cmdHKLM_Classes) { $chosen = $cmdHKLM_Classes; $src='HKLM\Software\Classes' }
if ([string]::IsNullOrWhiteSpace($chosen) -and $cmdHKLM_W6432) { $chosen = $cmdHKLM_W6432; $src='HKLM\WOW6432\Classes' }
if ([string]::IsNullOrWhiteSpace($chosen)) { throw ("No command found under any hive for {0} ({1})." -f $progId, $proto) }
if ($chosen.StartsWith('@')) { $chosen = Resolve-IndirectString $chosen }
$parsed = Parse-CommandPattern -Command $chosen
$r.CommandChosenSource = $src
$r.CommandPatternFinal = $parsed.Command
$r.ExtractedPath = $parsed.Path
$r.ExtractedArguments = $parsed.Arguments
Write-Step ("{0} Command chosen source" -f $proto) $r.CommandChosenSource
Write-Step ("{0} Final command pattern" -f $proto) $r.CommandPatternFinal
Write-Step ("{0} Extracted path" -f $proto) $r.ExtractedPath
Write-Step ("{0} Extracted args" -f $proto) $r.ExtractedArguments
# 4) Validate path/URI
$val = Validate-PathOrUri -PathOrUri $r.ExtractedPath
$r.PathValidation = $val
$nearestText = $val.NearestExistingFolder
if (-not $nearestText) { $nearestText = '<none>' }
$pvText = ("ExistsAsFile={0}; IsAbsoluteUri={1}; NearestExistingFolder={2}" -f $val.ExistsAsFile, $val.IsAbsoluteUri, $nearestText)
Write-Step ("{0} Path validation" -f $proto) $pvText
if ([string]::IsNullOrWhiteSpace($r.ExtractedPath)) {
throw ("Default browser path empty for {0}." -f $proto)
}
if (-not $val.ExistsAsFile -and -not $val.IsAbsoluteUri) {
throw ("Command validation failed for {0}: {1}" -f $proto, $r.CommandPatternFinal)
}
# 5) File diagnostics (if file)
if ($val.ExistsAsFile) {
$diag = Get-FileDiagnostics -FilePath $r.ExtractedPath
$r.FileDiagnostics = $diag
$verText = '<none>'
if ($diag -and $diag.VersionInfo) {
$verText = $diag.VersionInfo | Format-List | Out-String
}
Write-Step ("{0} FileVersionInfo" -f $proto) $verText
$sigText = '<none>'
if ($diag -and $diag.Signature) {
$sigText = $diag.Signature | Format-List | Out-String
}
Write-Step ("{0} Signature" -f $proto) $sigText
}
# 6) Final outputs
$r.FinalBrowserPath = $r.ExtractedPath
if ($r.HKCR_ApplicationNameClean) {
$r.FinalBrowserName = $r.HKCR_ApplicationNameClean
} elseif ($r.HKCR_ApplicationNameResolved) {
$r.FinalBrowserName = $r.HKCR_ApplicationNameResolved
} else {
$r.FinalBrowserName = '<unknown>'
}
$r.FinalArgumentsPattern = $r.ExtractedArguments
Write-Step ("{0} Final Browser Path" -f $proto) $r.FinalBrowserPath
Write-Step ("{0} Final Browser Name" -f $proto) $r.FinalBrowserName
Write-Step ("{0} Final Args Pattern" -f $proto) $r.FinalArgumentsPattern
# Optional: preview / launch with a test URL
if ($TestUrl) {
$argsToUse = $r.FinalArgumentsPattern
if ($argsToUse -match '%1') {
$argsToUse = $argsToUse -replace '%1', ('"{0}"' -f $TestUrl)
} elseif ($argsToUse) {
$argsToUse = $argsToUse + ' ' + ('"{0}"' -f $TestUrl)
} else {
$argsToUse = ('"{0}"' -f $TestUrl)
}
Write-Step ("{0} Launch Preview" -f $proto) ('"{0}" {1}' -f $r.FinalBrowserPath, $argsToUse)
if ($Launch) {
Start-Process -FilePath $r.FinalBrowserPath -ArgumentList $argsToUse
Write-Host "Launched." -ForegroundColor Green
} else {
Write-Host "(Not launching; pass -Launch to actually start)" -ForegroundColor DarkYellow
}
}
}
catch {
$r.ExceptionMessage = $_.Exception.Message
$r.FallbackUsed = $true
Write-Step ("{0} Exception (fallback to Edge)" -f $proto) $r.ExceptionMessage
$r.FinalBrowserPath = $MSEdgePath
$r.FinalBrowserName = $MSEdgeName
$r.FinalArgumentsPattern = $MSEdgeArgumentsPattern
Write-Step ("{0} Final (Fallback) Path" -f $proto) $r.FinalBrowserPath
Write-Step ("{0} Final (Fallback) Name" -f $proto) $r.FinalBrowserName
Write-Step ("{0} Final (Fallback) Args" -f $proto) $r.FinalArgumentsPattern
}
$aggregate += [pscustomobject]$r
}
# Emit structured results (pipe to ConvertTo-Json if needed)
$aggregate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment