Skip to content

Instantly share code, notes, and snippets.

@jiripolasek
Created October 16, 2025 13:24
Show Gist options
  • Select an option

  • Save jiripolasek/79ea55c8c80651e15586cef24b77405f to your computer and use it in GitHub Desktop.

Select an option

Save jiripolasek/79ea55c8c80651e15586cef24b77405f to your computer and use it in GitHub Desktop.
Detects effective default browser using Windows API
<#
Get-ProtocolAssoc.ps1
Queries Windows for the effective handler of http/https using AssocQueryString.
Compatible with Windows PowerShell 5.1 and PowerShell 7+.
OUTPUT
- Prints each field for http and https
- Returns a PSCustomObject you can pipe to ConvertTo-Json
USAGE
powershell -NoProfile -ExecutionPolicy Bypass -File .\Get-ProtocolAssoc.ps1
#>
[CmdletBinding()]
param(
[string[]]$Protocols = @('http','https'),
[string]$Verb = 'open'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Write-Section {
param([string]$Title)
Write-Host "`n####################### $Title #######################" -ForegroundColor Yellow
}
function Write-Field {
param([string]$Name, [string]$Value)
if ([string]::IsNullOrEmpty($Value)) { $Value = '<null or empty>' }
Write-Host ("{0}: {1}" -f $Name, $Value)
}
# ---- P/Invoke wrapper for AssocQueryStringW ----
if (-not ([System.Management.Automation.PSTypeName]'AssocNative').Type) {
Add-Type -Language CSharp -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using System.Text;
public static class AssocNative
{
[Flags]
public enum AssocF : uint
{
None = 0x00000000,
Init_NoRemapCLSID = 0x00000001,
Verify = 0x00000040,
NoUserSettings = 0x00000010,
IgnoreBaseClass = 0x00000020
}
public enum AssocStr : uint
{
Command = 1,
Executable = 2,
FriendlyDocName = 3,
FriendlyAppName = 4,
NoOpen = 5,
ShellNewValue = 6,
DDECommand = 7,
DDEApplication = 8,
DDEIfExec = 9,
DDETopic = 10,
InfoTip = 11,
QuickTip = 12,
TileInfo = 13,
ContentType = 14,
DefaultIcon = 15,
ShellExtension = 16,
DropTarget = 17,
DelegateExecute = 18,
SupportedURISchemes = 19,
ProgID = 20,
AppID = 21, // Some SDKs list AppID around here; value can vary by SDK. We handle failure gracefully.
// Note: Different Windows SDKs have additional values; querying unknown ones may fail, which we catch.
}
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true, EntryPoint = "AssocQueryStringW")]
public static extern int AssocQueryString(
AssocF flags,
AssocStr str,
string pszAssoc,
string pszExtra,
StringBuilder pszOut,
ref uint pcchOut
);
}
'@
}
function Invoke-AssocQuery {
param(
[AssocNative+AssocF]$Flags,
[AssocNative+AssocStr]$What,
[Parameter(Mandatory)][string]$Assoc, # e.g., "http"
[string]$Verb = 'open'
)
# First call to get length
[uint32]$len = 0
[void][AssocNative]::AssocQueryString($Flags, $What, $Assoc, $Verb, $null, [ref]$len)
if ($len -eq 0) { return $null } # Not available
$sb = New-Object System.Text.StringBuilder ([int]$len)
$hr = [AssocNative]::AssocQueryString($Flags, $What, $Assoc, $Verb, $sb, [ref]$len)
if ($hr -ne 0) { return $null } # Failure => treat as not available
# Some APIs include an extra null; trim it.
$val = $sb.ToString().TrimEnd([char]0)
if ([string]::IsNullOrWhiteSpace($val)) { return $null }
$val
}
# Combine a couple of useful flags:
# - Verify: ensure returned data maps to something real when possible
# - Init_NoRemapCLSID: avoid remapping through class IDs
$AssocFlags = [AssocNative+AssocF]::Verify -bor [AssocNative+AssocF]::Init_NoRemapCLSID
function Get-ProtocolAssoc {
param([Parameter(Mandatory)][string]$Protocol, [string]$Verb = 'open')
$obj = [ordered]@{
Protocol = $Protocol
Verb = $Verb
Command = $null
Executable = $null
FriendlyAppName = $null
AppID = $null
DefaultIcon = $null
}
$obj.Command = Invoke-AssocQuery -Flags $AssocFlags -What ([AssocNative+AssocStr]::Command) -Assoc $Protocol -Verb $Verb
$obj.Executable = Invoke-AssocQuery -Flags $AssocFlags -What ([AssocNative+AssocStr]::Executable) -Assoc $Protocol -Verb $Verb
$obj.FriendlyAppName = Invoke-AssocQuery -Flags $AssocFlags -What ([AssocNative+AssocStr]::FriendlyAppName)-Assoc $Protocol -Verb $Verb
# APPID is not guaranteed on all systems/SDKs; if query fails, we'll just leave it null.
try { $obj.AppID = Invoke-AssocQuery -Flags $AssocFlags -What ([AssocNative+AssocStr]::AppID) -Assoc $Protocol -Verb $Verb } catch {}
$obj.DefaultIcon = Invoke-AssocQuery -Flags $AssocFlags -What ([AssocNative+AssocStr]::DefaultIcon) -Assoc $Protocol -Verb $Verb
[pscustomobject]$obj
}
$results = @()
foreach ($p in $Protocols) {
Write-Section $p
$info = Get-ProtocolAssoc -Protocol $p -Verb $Verb
Write-Field 'Command' $info.Command
Write-Field 'Executable' $info.Executable
Write-Field 'FriendlyAppName' $info.FriendlyAppName
Write-Field 'AppID' $info.AppID
Write-Field 'DefaultIcon' $info.DefaultIcon
$results += $info
}
# Emit structured results (pipe to ConvertTo-Json if desired)
$results
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment