Setting the Default Audio Device on Windows 10

18/09/2025
Windows audio scripting PowerShell Claude code GitHub Copilot artificial intelligence

I recently started using a new wireless headset for my personal laptop (actually an old one that I hadn’t used for years). I generally use it in the evening when my son has gone to bed. Unfortunately, when I restart my laptop the next morning, it defaults to using the headset, despite this being turned off and back in the drawer. So, after manually switching back to the laptop’s default speakers a few times, it was time to automate this.

I already had a PowerShell script to set the audio volume (based on the SendKeys() method using the Windows Script Host Shell) so I assumed this would be another quick script that wouldn’t take much effort. Right?

function Set-SpeakerVolume($volume) {
    # Note each sendKey increases or decreases the volume by 2%
    $wshShell = new-object -com wscript.shell;

    foreach($i in 1..50) {
        $wshShell.SendKeys([char]174)
        Start-Sleep -Milliseconds 10
    }

    $increments = ($volume / 2)
    foreach($i in 1..$increments) {
        $wshShell.SendKeys([char]175)
        Start-Sleep -Milliseconds 10
    }
}

How wrong I was. To control the audio device being used on Windows requires making COM calls to interact with Windows Core Audio APIs. Most people (wisely) avoid interacting directly with these APIs by using the AudioDeviceCmdlets, but I decided rather than using cmdlets that required administrator permissions, I thought I would try and put together a single PowerShell script with no dependencies.

For those looking for a simple solution to this problem - save yourself some time and just download the NirSoft SoundVolumeCommandLine tool.

CALL svcl.exe /SetDefault "RealTek(R) AudioDevice\Speakers\Render" all
CALL svcl.exe /SetVolume "RealTek(R) Audio\Device\Speakers\Render" 40

Below is the final (working) script I came up after wasting a lot of time with Claude.ai and GitHub Copilot. None of the scripts that were generated by Claude or Copilot worked, so I put this together based on 2 existing scripts I found. I plan on trying again with Claude Code or Copilot to generate a working PowerShell script that matches the functionality of the script below. While I was unable to get one working before, it was very useful experience with AI coding assistants, and figuring out what works and what doesn’t. For example, GitHub Copilot was excellent at describing what the AudioDeviceCmdlets source code was doing (which matches my wife’s experience of working with Copilot over the past year).

#requires -Version 3.0 # <Give version number required by script>
 
<#
.SYNOPSIS
Script to set the default audio device and volume.
.DESCRIPTION
Script to set the default audio device and volume.
.PARAMETER DeviceName
The friendly device name of the audio device to set as default.
.PARAMETER Volume
The volume level to set the default audio device to (0-100).
.PARAMETER List
List all available audio devices.
.EXAMPLE

C:\PS> .\Set-DefaultAudioDevice.ps1 -DeviceName "Speakers" -Volume 50
This example sets the default audio device to "Speakers" and sets the volume to 50%.
.EXAMPLE    
C:\PS> .\Set-DefaultAudioDevice.ps1 -List
This example lists the available audio devices
.NOTES
This script is based on the following scripts:
- https://github.com/kaliiiiiiiiii/PublicDuckyChallenger/blob/28d98c66840f20c41d6ca4f6df86dc6172cf7876/rickroll.ps1
- https://github.com/SomeProgrammerGuy/Powershell-Default-Audio-Device-Changer/blob/243d8a7860126de31887066f4714d496898aec0d/Set-DefaultAudioDevice.ps1
#>

 
# =============================================================================
# Parameters
# =============================================================================
Param(
    [Parameter(Position = 0)]
    [string]$DeviceName,
    [Parameter(Position = 1)]
    [ValidateRange(0,100)]
    [int]$Volume,
    [switch]$List
)

# =============================================================================
# Constants
# =============================================================================
Set-Variable ScriptName -option Constant -value "Set-DefaultAudioDevice"


# =============================================================================
# C# Code to interact with Windows Audio APIs
# =============================================================================

Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;

public enum ERole : uint
{
    eConsole         = 0,
    eMultimedia      = 1,
    eCommunications  = 2,
    ERole_enum_count = 3
}

[Guid("f8679f50-850a-41cf-9c72-430f290290c8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IPolicyConfig
{
    // HRESULT GetMixFormat(PCWSTR, WAVEFORMATEX **);
    [PreserveSig]
    int GetMixFormat();
    
    // HRESULT STDMETHODCALLTYPE GetDeviceFormat(PCWSTR, INT, WAVEFORMATEX **);
    [PreserveSig]
 int GetDeviceFormat();
 
    // HRESULT STDMETHODCALLTYPE ResetDeviceFormat(PCWSTR);
    [PreserveSig]
    int ResetDeviceFormat();
    
    // HRESULT STDMETHODCALLTYPE SetDeviceFormat(PCWSTR, WAVEFORMATEX *, WAVEFORMATEX *);
    [PreserveSig]
    int SetDeviceFormat();
 
    // HRESULT STDMETHODCALLTYPE GetProcessingPeriod(PCWSTR, INT, PINT64, PINT64);
    [PreserveSig]
    int GetProcessingPeriod();
 
    // HRESULT STDMETHODCALLTYPE SetProcessingPeriod(PCWSTR, PINT64);
    [PreserveSig]
    int SetProcessingPeriod();
 
    // HRESULT STDMETHODCALLTYPE GetShareMode(PCWSTR, struct DeviceShareMode *);
    [PreserveSig]
    int GetShareMode();
 
    // HRESULT STDMETHODCALLTYPE SetShareMode(PCWSTR, struct DeviceShareMode *);
    [PreserveSig]
    int SetShareMode();
  
    // HRESULT STDMETHODCALLTYPE GetPropertyValue(PCWSTR, const PROPERTYKEY &, PROPVARIANT *);
    [PreserveSig]
    int GetPropertyValue();
 
    // HRESULT STDMETHODCALLTYPE SetPropertyValue(PCWSTR, const PROPERTYKEY &, PROPVARIANT *);
    [PreserveSig]
    int SetPropertyValue();
 
    // HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(__in PCWSTR wszDeviceId, __in ERole role);
    [PreserveSig]
    int SetDefaultEndpoint(
        [In] [MarshalAs(UnmanagedType.LPWStr)] string wszDeviceId, 
        [In] [MarshalAs(UnmanagedType.U4)] ERole role);
 
    // HRESULT STDMETHODCALLTYPE SetEndpointVisibility(PCWSTR, INT);
    [PreserveSig]
 int SetEndpointVisibility();
}

[ComImport, Guid("870af99c-171d-4f9e-af0d-e63df40c2bc9")]
internal class _CPolicyConfigClient
{
}

public class PolicyConfigClient
{
    public static int SetDefaultDevice(string deviceID)
    {
        IPolicyConfig _policyConfigClient = (new _CPolicyConfigClient() as IPolicyConfig);

 try
        {
            Marshal.ThrowExceptionForHR(_policyConfigClient.SetDefaultEndpoint(deviceID, ERole.eConsole));
      Marshal.ThrowExceptionForHR(_policyConfigClient.SetDefaultEndpoint(deviceID, ERole.eMultimedia));
      Marshal.ThrowExceptionForHR(_policyConfigClient.SetDefaultEndpoint(deviceID, ERole.eCommunications));
      return 0;
        }
        catch
        {
            return 1;
        }
    }
}
"@

Add-Type -TypeDefinition @"
using System.Runtime.InteropServices;
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IAudioEndpointVolume
{
    // f(), g(), ... are unused COM method slots. Define these if you care
    int f(); int g(); int h(); int i();
    int SetMasterVolumeLevelScalar(float fLevel, System.Guid pguidEventContext);
    int j();
    int GetMasterVolumeLevelScalar(out float pfLevel);
    int k(); int l(); int m(); int n();
    int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, System.Guid pguidEventContext);
    int GetMute(out bool pbMute);
}
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDevice
{
    int Activate(ref System.Guid id, int clsCtx, int activationParams, out IAudioEndpointVolume aev);
}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDeviceEnumerator
{
    int f(); // Unused
    int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
}
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }
public class Audio
{
    static IAudioEndpointVolume Vol()
    {
        var enumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
        IMMDevice dev = null;
        Marshal.ThrowExceptionForHR(enumerator.GetDefaultAudioEndpoint(/*eRender*/ 0, /*eMultimedia*/ 1, out dev));
        IAudioEndpointVolume epv = null;
        var epvid = typeof(IAudioEndpointVolume).GUID;
        Marshal.ThrowExceptionForHR(dev.Activate(ref epvid, /*CLSCTX_ALL*/ 23, 0, out epv));
        return epv;
    }
    public static float Volume
    {
        get { float v = -1; Marshal.ThrowExceptionForHR(Vol().GetMasterVolumeLevelScalar(out v)); return v; }
        set { Marshal.ThrowExceptionForHR(Vol().SetMasterVolumeLevelScalar(value, System.Guid.Empty)); }
    }
    public static bool Mute
    {
        get { bool mute; Marshal.ThrowExceptionForHR(Vol().GetMute(out mute)); return mute; }
        set { Marshal.ThrowExceptionForHR(Vol().SetMute(value, System.Guid.Empty)); }
    }
}
"@


# =============================================================================
# Functions
# =============================================================================

function Set-DefaultAudioDevice {
    Param (
        [parameter(Mandatory=$true)]
        [string[]]
        $deviceId
    )

    if ([PolicyConfigClient]::SetDefaultDevice("{0.0.0.00000000}.$deviceId") -eq 0) {
        Write-Host "SUCCESS: The default audio device has been set."
    }
    else {
        Write-Host "ERROR: There has been a problem setting the default audio device."
    }
}

function Get-SpeakerDeviceId {
    Param (
        [parameter(Mandatory=$true)]
        [string[]]
        $speakerName
    )

    $deviceTypes = @('Headset Earphone', 'Speakers', 'Headphones')
    Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render\*\Properties\" |
    Where {($deviceTypes.Contains($_."{a45c254e-df1c-4efd-8020-67d146a850e0},2")) -and ($_."{b3f8fa53-0004-438e-9003-51a46e139bfc},6" -eq $speakerName)} |
    ForEach {$_.PSParentPath.substring($_.PSParentPath.length - 38, 38)}
}

function Get-AudioOutputDevices {   
    $audioDevices = @()

    $devices = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render\*\Properties\"
    
    foreach ($device in $devices) {        
       $audioDevice =  New-Object PsObject -property @{
        'ID' = $($device.PSParentPath.substring($device.PSParentPath.length - 38, 38))
        'Type' = $($device.'{a45c254e-df1c-4efd-8020-67d146a850e0},2')
        'Name' = $($device.'{b3f8fa53-0004-438e-9003-51a46e139bfc},6')
       }
       
       $audioDevices += $audioDevice
    }
        
    return $audioDevices
}

function Set-DefaultAudioDeviceVolume { 
    Param (
        [parameter(Mandatory=$true)]
        [ValidateRange(0, 100)]
        [int]$volume
    )

    # Convert volume percentage to a value between 0.0 and 1.0
    $volumeValue = $volume / 100.0
    [Audio]::Volume = $volumeValue
}   

function Main {

    if ($List) {
        try {
            $names = Get-AudioOutputDevices | Select-Object -ExpandProperty Name | Select-Object -Unique | Sort-Object
            if ($names.Count -eq 0) {
                Write-Host "No audio output devices found."
            } else {
                Write-Host "Available audio output devices:"
                $names | ForEach-Object { Write-Host " - $_" }
            }
        } catch {
            Write-Error "Failed to enumerate audio devices: $_"
        }

        exit 0
    }

    if ($DeviceName) {
        try {
            Set-DefaultAudioDevice (Get-SpeakerDeviceId -speakerName $DeviceName | Select-Object -first 1)
            Write-Host "Set default audio device to '$DeviceName'."
        } catch {
            Write-Error "Failed to set default audio device: $_"
            exit 1
        }
    } else {
        Write-Error "DeviceName parameter is required when not listing devices."
        exit 1
    }

    if ($Volume -ne $null) {
        try {
            Set-DefaultAudioDeviceVolume -volume $Volume
            Write-Host "Set default audio device volume to $Volume%."
        } catch {
            Write-Error "Failed to set default audio device volume: $_"
            exit 1
        }
    }    
}
 
# =============================================================================
# Start of Script Body
# =============================================================================

$timeStamp= (Get-Date).ToString('HH:mm dd/MM/yyyy')
Write-Host "Starting $ScriptName at $timeStamp"
$scriptTimer = [System.Diagnostics.Stopwatch]::StartNew()

Main

$elapsedTimeInSeconds = $scriptTimer.ElapsedMilliseconds / 1000
$message = [string]::Format("Script total execution time: {0} seconds", $elapsedTimeInSeconds)
Write-Host $message

# =============================================================================
# End of Script Body
# =============================================================================

Serverless Days Belfast

21/05/2025
serverless artificial intelligence conference Belfast developers