How to Set a Default Browser for All Users with PowerShell: A Step-by-Step Guide

Managing the default browser across multiple user profiles on a Windows system can be a daunting task, especially in enterprise environments where consistency and compliance are crucial. Automating this process with a PowerShell script simplifies the task, ensuring that every user on the system has the same browser experience.

This blog post will explore a PowerShell script designed to set the default browser for all users on a Windows machine, providing IT professionals and Managed Service Providers (MSPs) with a powerful tool to maintain control over their environments.

Background

In an IT setting, particularly within enterprises or MSPs, maintaining a consistent user experience is vital. With multiple users on a single machine or across a network, manually setting the default browser for each profile can be labor-intensive and prone to errors. A PowerShell script that automates this process is not only a time-saver but also ensures uniformity, which can be critical for compliance and user support.

This specific script is a modified version of a script by Danysys, designed to change the default browser for all users by updating registry keys. The script is highly adaptable, supporting popular browsers like Mozilla Firefox, Google Chrome, and Microsoft Edge. By utilizing the script, IT administrators can ensure that all users have the correct browser set as default, regardless of the number of profiles or the state of the system.

The Script:

#Requires -Version 5.1

<#
.SYNOPSIS
    Sets the default browser for all users.
.DESCRIPTION
    Sets the default browser for all users.
.EXAMPLE
    -Browser "Mozilla Firefox" -RestartExplorer

    Setting default browser of Mozilla Firefox for Administrator.
    Setting 
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice\Hash changed from 2q7+uVxu0/A= to FKcuHm4FMN4=
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice\ProgId changed from ChromeHTML to FirefoxURL-308046B0AF4A39CB
    Setting 
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice\Hash changed from zR3ANZC6jVI= to clMyDtJdxck=
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice\ProgId changed from ChromeHTML to FirefoxURL-308046B0AF4A39CB
    Setting 
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\htm\UserChoice\Hash changed from IQfza9L6Tfw= to t8+HFkmUAd0=
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\htm\UserChoice\ProgId changed from ChromeHTML to FirefoxHTML-308046B0AF4A39CB
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.htm\UserChoice\Hash changed from IQfza9L6Tfw= to t8+HFkmUAd0=
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.htm\UserChoice\ProgId changed from ChromeHTML to FirefoxHTML-308046B0AF4A39CB
    Setting 
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\html\UserChoice\Hash changed from 7CcRlkLW3ik= to q0Eix6jwLFg=
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\html\UserChoice\ProgId changed from ChromeHTML to FirefoxHTML-308046B0AF4A39CB
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.html\UserChoice\Hash changed from 7CcRlkLW3ik= to q0Eix6jwLFg=
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.html\UserChoice\ProgId changed from ChromeHTML to FirefoxHTML-308046B0AF4A39CB
    Setting 
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\xhtml\UserChoice\Hash changed from IC7TXk1anlM= to y2gIOuiaLb0=
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\xhtml\UserChoice\ProgId changed from ChromeHTML to FirefoxHTML-308046B0AF4A39CB
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.xhtml\UserChoice\Hash changed from IC7TXk1anlM= to y2gIOuiaLb0=
    Registry::HKEY_USERS\S-1-5-21-528047445-1317477324-4168425688-500\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.xhtml\UserChoice\ProgId changed from ChromeHTML to FirefoxHTML-308046B0AF4A39CB
    Restarting Explorer.exe

PARAMETER: -Browser "Mozilla Firefox"
    Set the default browser to either "Mozilla Firefox", "Google Chrome" or "Microsoft Edge".

PARAMETER: -Restart Explorer
    Restarts Explorer.exe so that the desktop icons for .html files refresh immediately.
    
LICENSE:
    Modified version from: https://github.com/DanysysTeam/PS-SFTA/blob/22a32292e576afc976a1167d92b50741ef523066/SFTA.ps1
    This script incorporates the `Get-HexDateTime` and `Get-Hash` functions from Danysys, without which it would not be possible.
    
    LICENSE: https://github.com/DanysysTeam/PS-SFTA/blob/22a32292e576afc976a1167d92b50741ef523066/SFTA.ps1
    MIT License
    
    Copyright (c) 2022 Danysys. <danysys.com>

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.

.OUTPUTS
    None
.NOTES
    Minimum OS Architecture Supported: Windows 10+
    Release Notes:
    Initial Release
By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://ninjastage2.wpengine.com/terms-of-use.
    Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
    Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
    Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
    Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
    Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
    Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
    EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
#>

[CmdletBinding()]
param (
    [Parameter()]
    [String]$Browser,
    [Parameter()]
    [Switch]$RestartExplorer = [System.Convert]::ToBoolean($env:restartExplorer)
)

begin {
    if ($env:browser -and $env:browser -notlike "null") {
        $Browser = $env:browser
    }

    # If no browser is selected, terminate with an error message.
    if (-not $Browser) {
        Write-Host "[Error] Please select at least one browser!"
        Exit 1
    }

    # Test if running as Administrator
    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    # Test if running as System
    function Test-IsSystem {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        return $id.Name -like "NT AUTHORITY*" -or $id.IsSystem
    }

    function Get-HexDateTime {
        # This function was created by DanySys at https://github.com/DanysysTeam/PS-SFTA
        [OutputType([string])]
    
        $now = [DateTime]::Now
        $dateTime = [DateTime]::New($now.Year, $now.Month, $now.Day, $now.Hour, $now.Minute, 0)
        $fileTime = $dateTime.ToFileTime()
        $hi = ($fileTime -shr 32)
        $low = ($fileTime -band 0xFFFFFFFFL)
        ($hi.ToString("X8") + $low.ToString("X8")).ToLower()
    }

    function Get-Hash {
        # This function was created by DanySys at https://github.com/DanysysTeam/PS-SFTA
        [CmdletBinding()]
        param (
            [Parameter( Position = 0, Mandatory = $True )]
            [string]
            $BaseInfo
        )
    
        function local:Get-ShiftRight {
            [CmdletBinding()]
            param (
                [Parameter( Position = 0, Mandatory = $true)]
                [long] $iValue, 
                
                [Parameter( Position = 1, Mandatory = $true)]
                [int] $iCount 
            )
        
            if ($iValue -band 0x80000000) {
                Write-Output (( $iValue -shr $iCount) -bxor 0xFFFF0000)
            }
            else {
                Write-Output ($iValue -shr $iCount)
            }
        }
    
        function local:Get-Long {
            [CmdletBinding()]
            param (
                [Parameter( Position = 0, Mandatory = $true)]
                [byte[]] $Bytes,
        
                [Parameter( Position = 1)]
                [int] $Index = 0
            )
        
            Write-Output ([BitConverter]::ToInt32($Bytes, $Index))
        }
    
        function local:Convert-Int32 {
            param (
                [Parameter( Position = 0, Mandatory = $true)]
                [long] $Value
            )
        
            [byte[]] $bytes = [BitConverter]::GetBytes($Value)
            return [BitConverter]::ToInt32( $bytes, 0) 
        }
    
        [Byte[]] $bytesBaseInfo = [System.Text.Encoding]::Unicode.GetBytes($baseInfo) 
        $bytesBaseInfo += 0x00, 0x00  
        
        $MD5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
        [Byte[]] $bytesMD5 = $MD5.ComputeHash($bytesBaseInfo)
        
        $lengthBase = ($baseInfo.Length * 2) + 2 
        $length = (($lengthBase -band 4) -le 1) + (Get-ShiftRight $lengthBase 2) - 1
        $base64Hash = ""
    
        if ($length -gt 1) {
        
            $map = @{PDATA = 0; CACHE = 0; COUNTER = 0 ; INDEX = 0; MD51 = 0; MD52 = 0; OUTHASH1 = 0; OUTHASH2 = 0;
                R0 = 0; R1 = @(0, 0); R2 = @(0, 0); R3 = 0; R4 = @(0, 0); R5 = @(0, 0); R6 = @(0, 0); R7 = @(0, 0)
            }
        
            $map.CACHE = 0
            $map.OUTHASH1 = 0
            $map.PDATA = 0
            $map.MD51 = (((Get-Long $bytesMD5) -bor 1) + 0x69FB0000L)
            $map.MD52 = ((Get-Long $bytesMD5 4) -bor 1) + 0x13DB0000L
            $map.INDEX = Get-ShiftRight ($length - 2) 1
            $map.COUNTER = $map.INDEX + 1
        
            while ($map.COUNTER) {
                $map.R0 = Convert-Int32 ((Get-Long $bytesBaseInfo $map.PDATA) + [long]$map.OUTHASH1)
                $map.R1[0] = Convert-Int32 (Get-Long $bytesBaseInfo ($map.PDATA + 4))
                $map.PDATA = $map.PDATA + 8
                $map.R2[0] = Convert-Int32 (($map.R0 * ([long]$map.MD51)) - (0x10FA9605L * ((Get-ShiftRight $map.R0 16))))
                $map.R2[1] = Convert-Int32 ((0x79F8A395L * ([long]$map.R2[0])) + (0x689B6B9FL * (Get-ShiftRight $map.R2[0] 16)))
                $map.R3 = Convert-Int32 ((0xEA970001L * $map.R2[1]) - (0x3C101569L * (Get-ShiftRight $map.R2[1] 16) ))
                $map.R4[0] = Convert-Int32 ($map.R3 + $map.R1[0])
                $map.R5[0] = Convert-Int32 ($map.CACHE + $map.R3)
                $map.R6[0] = Convert-Int32 (($map.R4[0] * [long]$map.MD52) - (0x3CE8EC25L * (Get-ShiftRight $map.R4[0] 16)))
                $map.R6[1] = Convert-Int32 ((0x59C3AF2DL * $map.R6[0]) - (0x2232E0F1L * (Get-ShiftRight $map.R6[0] 16)))
                $map.OUTHASH1 = Convert-Int32 ((0x1EC90001L * $map.R6[1]) + (0x35BD1EC9L * (Get-ShiftRight $map.R6[1] 16)))
                $map.OUTHASH2 = Convert-Int32 ([long]$map.R5[0] + [long]$map.OUTHASH1)
                $map.CACHE = ([long]$map.OUTHASH2)
                $map.COUNTER = $map.COUNTER - 1
            }
    
            [Byte[]] $outHash = @(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
            [byte[]] $buffer = [BitConverter]::GetBytes($map.OUTHASH1)
            $buffer.CopyTo($outHash, 0)
            $buffer = [BitConverter]::GetBytes($map.OUTHASH2)
            $buffer.CopyTo($outHash, 4)
        
            $map = @{PDATA = 0; CACHE = 0; COUNTER = 0 ; INDEX = 0; MD51 = 0; MD52 = 0; OUTHASH1 = 0; OUTHASH2 = 0;
                R0 = 0; R1 = @(0, 0); R2 = @(0, 0); R3 = 0; R4 = @(0, 0); R5 = @(0, 0); R6 = @(0, 0); R7 = @(0, 0)
            }
        
            $map.CACHE = 0
            $map.OUTHASH1 = 0
            $map.PDATA = 0
            $map.MD51 = ((Get-Long $bytesMD5) -bor 1)
            $map.MD52 = ((Get-Long $bytesMD5 4) -bor 1)
            $map.INDEX = Get-ShiftRight ($length - 2) 1
            $map.COUNTER = $map.INDEX + 1
    
            while ($map.COUNTER) {
                $map.R0 = Convert-Int32 ((Get-Long $bytesBaseInfo $map.PDATA) + ([long]$map.OUTHASH1))
                $map.PDATA = $map.PDATA + 8
                $map.R1[0] = Convert-Int32 ($map.R0 * [long]$map.MD51)
                $map.R1[1] = Convert-Int32 ((0xB1110000L * $map.R1[0]) - (0x30674EEFL * (Get-ShiftRight $map.R1[0] 16)))
                $map.R2[0] = Convert-Int32 ((0x5B9F0000L * $map.R1[1]) - (0x78F7A461L * (Get-ShiftRight $map.R1[1] 16)))
                $map.R2[1] = Convert-Int32 ((0x12CEB96DL * (Get-ShiftRight $map.R2[0] 16)) - (0x46930000L * $map.R2[0]))
                $map.R3 = Convert-Int32 ((0x1D830000L * $map.R2[1]) + (0x257E1D83L * (Get-ShiftRight $map.R2[1] 16)))
                $map.R4[0] = Convert-Int32 ([long]$map.MD52 * ([long]$map.R3 + (Get-Long $bytesBaseInfo ($map.PDATA - 4))))
                $map.R4[1] = Convert-Int32 ((0x16F50000L * $map.R4[0]) - (0x5D8BE90BL * (Get-ShiftRight $map.R4[0] 16)))
                $map.R5[0] = Convert-Int32 ((0x96FF0000L * $map.R4[1]) - (0x2C7C6901L * (Get-ShiftRight $map.R4[1] 16)))
                $map.R5[1] = Convert-Int32 ((0x2B890000L * $map.R5[0]) + (0x7C932B89L * (Get-ShiftRight $map.R5[0] 16)))
                $map.OUTHASH1 = Convert-Int32 ((0x9F690000L * $map.R5[1]) - (0x405B6097L * (Get-ShiftRight ($map.R5[1]) 16)))
                $map.OUTHASH2 = Convert-Int32 ([long]$map.OUTHASH1 + $map.CACHE + $map.R3) 
                $map.CACHE = ([long]$map.OUTHASH2)
                $map.COUNTER = $map.COUNTER - 1
            }
        
            $buffer = [BitConverter]::GetBytes($map.OUTHASH1)
            $buffer.CopyTo($outHash, 8)
            $buffer = [BitConverter]::GetBytes($map.OUTHASH2)
            $buffer.CopyTo($outHash, 12)
        
            [Byte[]] $outHashBase = @(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
            $hashValue1 = ((Get-Long $outHash 8) -bxor (Get-Long $outHash))
            $hashValue2 = ((Get-Long $outHash 12) -bxor (Get-Long $outHash 4))
        
            $buffer = [BitConverter]::GetBytes($hashValue1)
            $buffer.CopyTo($outHashBase, 0)
            $buffer = [BitConverter]::GetBytes($hashValue2)
            $buffer.CopyTo($outHashBase, 4)
            $base64Hash = [Convert]::ToBase64String($outHashBase) 
        }
    
        $base64Hash
    }

    # Helper function for setting registry keys
    function Set-RegKey {
        param (
            $Path,
            $Name,
            $Value,
            [ValidateSet("DWord", "QWord", "String", "ExpandedString", "Binary", "MultiString", "Unknown")]
            $PropertyType = "DWord"
        )
        if (-not $(Test-Path -Path $Path)) {
            # Check if path does not exist and create the path
            New-Item -Path $Path -Force | Out-Null
        }
        if ((Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue)) {
            # Update property and print out what it was changed from and changed to
            $CurrentValue = (Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name
            try {
                Set-ItemProperty -Path $Path -Name $Name -Value $Value -Force -Confirm:$false -ErrorAction Stop | Out-Null
            }
            catch {
                Write-Host "[Error] Unable to set registry key for $Name at $Path please see below error!"
                Write-Host "[Error] $($_.Exception.Message)"
                exit 1
            }
            Write-Host "$Path\$Name changed from $CurrentValue to $($(Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name)"
        }
        else {
            # Create property with value
            try {
                New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType $PropertyType -Force -Confirm:$false -ErrorAction Stop | Out-Null
            }
            catch {
                Write-Host "[Error] Unable to set registry key for $Name at $Path please see below error!"
                Write-Host "[Error] $($_.Exception.Message)"
                exit 1
            }
            Write-Host "Set $Path\$Name to $($(Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name)"
        }
    }

    # Retrieves all accounts on a system.
    function Get-UserHives {
        param (
            [Parameter()]
            [ValidateSet('AzureAD', 'DomainAndLocal', 'All')]
            [String]$Type = "All",
            [Parameter()]
            [String[]]$ExcludedUsers,
            [Parameter()]
            [switch]$IncludeDefault
        )
    
        # User account SID's follow a particular patter depending on if they're azure AD or a Domain account or a local "workgroup" account.
        $Patterns = switch ($Type) {
            "AzureAD" { "S-1-12-1-(\d+-?){4}$" }
            "DomainAndLocal" { "S-1-5-21-(\d+-?){4}$" }
            "All" { "S-1-12-1-(\d+-?){4}$" ; "S-1-5-21-(\d+-?){4}$" } 
        }
    
        # We'll need the NTuser.dat file to load each users registry hive. So we grab it if their account sid matches the above pattern. 
        $UserProfiles = Foreach ($Pattern in $Patterns) { 
            Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*" |
                Where-Object { $_.PSChildName -match $Pattern } | 
                Select-Object @{Name = "SID"; Expression = { $_.PSChildName } },
                @{Name = "UserName"; Expression = { "$($_.ProfileImagePath | Split-Path -Leaf)" } }, 
                @{Name = "UserHive"; Expression = { "$($_.ProfileImagePath)\NTuser.dat" } }, 
                @{Name = "Path"; Expression = { $_.ProfileImagePath } }
        }
    
        # There are some situations where grabbing the .Default user's info is needed.
        switch ($IncludeDefault) {
            $True {
                $DefaultProfile = "" | Select-Object UserName, SID, UserHive, Path
                $DefaultProfile.UserName = "Default"
                $DefaultProfile.SID = "DefaultProfile"
                $DefaultProfile.Userhive = "$env:SystemDrive\Users\Default\NTUSER.DAT"
                $DefaultProfile.Path = "C:\Users\Default"
    
                $DefaultProfile | Where-Object { $ExcludedUsers -notcontains $_.UserName }
            }
        }
    
        $UserProfiles | Where-Object { $ExcludedUsers -notcontains $_.UserName }
    }

    # This is used to check that the browser is installed.
    function Find-UninstallKey {
        [CmdletBinding()]
        param (
            [Parameter(ValueFromPipeline = $True)]
            [String]$DisplayName,
            [Parameter()]
            [Switch]$UninstallString
        )
        process {
            $UninstallList = New-Object System.Collections.Generic.List[Object]
    
            $Result = Get-ChildItem -Path Registry::HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Get-ItemProperty | Where-Object { $_.DisplayName -like "*$DisplayName*" }
            if ($Result) { $UninstallList.Add($Result) }
    
            $Result = Get-ChildItem -Path Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Get-ItemProperty | Where-Object { $_.DisplayName -like "*$DisplayName*" }
            if ($Result) { $UninstallList.Add($Result) }
    
            # Programs don't always have an uninstall string listed here so to account for that I made this optional.
            if ($UninstallString) {
                $UninstallList | Select-Object -ExpandProperty UninstallString -ErrorAction SilentlyContinue
            }
            else {
                $UninstallList
            }
        }
    }
}
process {
    if (-not (Test-IsElevated)) {
        Write-Host "[Error] Access Denied. Please run with Administrator privileges."
        exit 1
    }

    # Protocols and file associations to set
    $Protocols = "http", "https"
    $Files = "htm", "html", "xhtml"

    # Handlers for each product
    switch ($Browser) {
        "Google Chrome" {
            $DisplayName = "Chrome"
            $urlID = "ChromeHTML"
            $htmlID = "ChromeHTML"
        }
        "Microsoft Edge" {
            $DisplayName = "Edge"
            $urlID = "MSEdgeHTM"
            $htmlID = "MSEdgeHTM"
        }
        "Mozilla Firefox" {
            $DisplayName = "Firefox"
            $urlID = "FirefoxURL-308046B0AF4A39CB"
            $htmlID = "FirefoxHTML-308046B0AF4A39CB"
        }
        default {
            Write-Host "[Error] Only the following browsers can be made the default. 'Google Chrome','Microsoft Edge' or 'Mozilla Firefox'."
            exit 1
        }
    }

    if (-not (Find-UninstallKey -DisplayName "$DisplayName")) {
        Write-Host "[Error] $Browser is not installed. Please ensure it's installed System-Wide prior to running this script."
        exit 1
    }

    $UserProfiles = Get-UserHives -Type "All"
    # Loop through each profile on the machine
    Foreach ($UserProfile in $UserProfiles) {
        # Load User ntuser.dat if it's not already loaded
        If (($ProfileWasLoaded = Test-Path Registry::HKEY_USERS\$($UserProfile.SID)) -eq $false) {
            Start-Process -FilePath "cmd.exe" -ArgumentList "/C reg.exe LOAD HKU\$($UserProfile.SID) `"$($UserProfile.UserHive)`"" -Wait -WindowStyle Hidden
        }

        # The hex date and user experience don't really change
        $userExperience = "User Choice set via Windows User Experience {D18B6DD5-6124-4341-9318-804003BAFA0B}"
        $hexDateTime = Get-HexDateTime


        Write-Host "`nSetting default browser of $Browser for $($UserProfile.UserName)."

        # Set protocol association registry keys
        $Protocols | ForEach-Object {
            Write-Host "Setting "
            $Protocol = $_

            $ToBeHashed = "$Protocol$($UserProfile.SID)$urlID$hexDateTime$userExperience".ToLower()
            $Hash = Get-Hash -BaseInfo $ToBeHashed

            Set-RegKey -Path "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$Protocol\UserChoice" -Name "Hash" -Value $Hash -PropertyType String
            Set-RegKey -Path "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$Protocol\UserChoice" -Name "ProgId" -Value $urlID -PropertyType String
        }

        # Set file association registry keys
        $Files | ForEach-Object {
            Write-Host "Setting "
            $File = $_

            $ToBeHashed = ".$File$($UserProfile.SID)$htmlID$hexDateTime$userExperience".ToLower()
            $Hash = Get-Hash -BaseInfo $ToBeHashed

            Set-RegKey -Path "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$File\UserChoice" -Name "Hash" -Value $Hash -PropertyType String
            Set-RegKey -Path "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$File\UserChoice" -Name "ProgId" -Value $htmlID -PropertyType String
            Set-RegKey -Path "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.$File\UserChoice" -Name "Hash" -Value $Hash -PropertyType String
            Set-RegKey -Path "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.$File\UserChoice" -Name "ProgId" -Value $htmlID -PropertyType String
        }
            
        # Unload NTuser.dat
        If ($ProfileWasLoaded -eq $false) {
            [gc]::Collect()
            Start-Sleep 1
            Start-Process -FilePath "cmd.exe" -ArgumentList "/C reg.exe UNLOAD HKU\$($UserProfile.SID)" -Wait -WindowStyle Hidden | Out-Null
        }
    }

    # Restart explorer if requested
    if ($RestartExplorer) {
        Write-Host "`nRestarting Explorer.exe as requested."

        # Stop all instances of Explorer
        Get-Process explorer | Stop-Process -Force
        
        Start-Sleep -Seconds 1

        # Restart Explorer if not running as System and Explorer is not already running
        if (!(Test-IsSystem) -and !(Get-Process -Name "explorer")) {
            Start-Process explorer.exe
        }
    }
}
end {
    
    
    
}

 

Access over 300+ scripts in the NinjaOne Dojo

Get Access

Detailed Breakdown

The script works by interacting with the Windows registry, specifically targeting the user-specific registry hives that store default application settings. Here’s a step-by-step breakdown of how the script operates:

1. Script Initialization:

  • The script begins by verifying if it is being run with elevated privileges (Administrator access). If not, the script will terminate, ensuring that only authorized personnel can make system-wide changes.

2. Parameter Handling:

  • Two parameters are accepted: the browser to set as the default and a switch to restart the Explorer.exe process. The browser parameter accepts “Mozilla Firefox,” “Google Chrome,” or “Microsoft Edge,” and defaults to whatever is set in the environment variable if not provided directly.

3. Browser Validation:

  • Before proceeding, the script checks whether the specified browser is installed on the system by searching the registry for its uninstall key. If the browser is not installed, the script exits with an error message.

4. Profile Management:

  • The script retrieves a list of all user profiles on the machine by examining the registry for user-specific settings. It identifies each user’s SID (Security Identifier) and loads their registry hive to modify the default browser settings.

5. Setting Default Protocol and File Associations:

  • For each user profile, the script modifies the registry to set the selected browser as the default handler for HTTP, HTTPS, HTM, HTML, and XHTML file types. This involves generating a unique hash for the settings to ensure they are recognized by Windows as valid and secure.

6. Restarting Explorer (Optional):

  • If the -RestartExplorer switch is used, the script stops and restarts the Explorer.exe process. This step is necessary to immediately apply the changes and refresh desktop icons associated with the affected file types.

Potential Use Cases

Imagine an IT administrator managing a large corporate network with hundreds of users. The company has recently standardized on Firefox as the default browser due to its enhanced security features.

Rather than manually setting Firefox as the default browser on each user’s profile, the administrator can run this script across the network, ensuring that every user, regardless of their login frequency or profile state, uses Firefox by default. This not only saves time but also reduces the likelihood of user errors or non-compliance with corporate policies.

Comparisons

While this PowerShell script offers a streamlined approach, there are other methods to achieve the same result, such as using Group Policy or deploying a software management tool. Group Policy provides a more centralized way to enforce browser settings but can be complex to configure and may not cover every scenario, especially with user-specific profiles.

Software management tools, on the other hand, often come with additional costs and require a learning curve. This script strikes a balance by offering a customizable, no-cost solution that integrates directly into the existing Windows environment.

FAQs

Q: Can this script be used on systems running versions of Windows older than Windows 10?

A: The script is designed for Windows 10 and later versions. It leverages specific registry structures and protocols that may not be available on older versions of Windows.

Q: What happens if a user installs a new browser after the script has been run?

A: If a new browser is installed after the script has been executed, it may attempt to set itself as the default. In such cases, re-running the script will re-apply the desired settings.

Q: Can the script be modified to include other browsers not listed in the parameters?

A: Yes, the script can be adapted to support other browsers by adding their respective protocol and file handler identifiers.

Implications

By using this script, IT professionals can enforce browser consistency across all user profiles, which is essential for security and compliance. However, administrators must be aware of the potential implications, such as overriding user preferences and the need to ensure that all necessary browsers are installed system-wide before running the script.

Recommendations

  • Test in a Controlled Environment: Before deploying this script across an entire network, it’s advisable to test it in a controlled environment to ensure it behaves as expected.
  • Ensure All Browsers Are Installed: Verify that the selected default browser is installed on all systems where the script will be run.
  • Consider User Communication: Inform users of the change, particularly if their default browser preferences will be overridden.

Final Thoughts

For IT professionals and MSPs, managing user environments efficiently is critical. This PowerShell script offers a robust solution for standardizing the default browser across all user profiles on a Windows system. While NinjaOne provides comprehensive tools for endpoint management, including software deployment and user profile management, this script can be an excellent complement, offering fine-grained control over browser settings.

Add a Comment

Your email address will not be published. Required fields are marked *

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service deliver tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Categories:

You might also like

×

See NinjaOne in action!

By submitting this form, I accept NinjaOne's privacy policy.

NinjaOne Terms & Conditions

By clicking the “I Accept” button below, you indicate your acceptance of the following legal terms as well as our Terms of Use:

  • Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms.
  • Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party.
  • Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library belonging to or under the control of any other software provider.
  • Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations.
  • Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks.
  • Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script.
  • EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).