Search code examples
powershellaudio-device

How to identify the default audio device in Powershell?


I am looking for a solution to get the default audio device via Powershell. In best case, it could work via embedded C#-code to directly use IMMDeviceEnumerator::GetDefaultAudioEndpoint (see here IMMDeviceEnumertor).

But if it is easier to get this via RegKeys, then this is also OK. I have seen a couple of code-snippets reading the keys from HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render or \Capture, but I still struggle to identify the DEFAULT device.

It seems, when I do modify the order of devices, then I can simply search for active devices (DeviceState=1) and then sort by the values "Level:0", "Level:1" and "Level:2", but the level-values are not available on a system, where the user has not modied the order manually. What is the sort-criteria in such case?

This is the code-snippet to solve it via RegKeys, but as mentioned - not working for all situations:

$regAudio =  "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio"
$nameId = "{b3f8fa53-0004-438e-9003-51a46e139bfc},6"
$classId = "{a45c254e-df1c-4efd-8020-67d146a850e0},2"
$driverDetails = "{83da6326-97a6-4088-9453-a1923f573b29},3"

function get-DefaultDevice($type) {
    $activeDevices = foreach($key in  Get-ChildItem "$regAudio\$type\") {
        foreach($item in Get-ItemProperty $key.PsPath) { 
            if ($item.DeviceState -eq $activeState) {$item}
        }
    }
    $defaultDevice = $activeDevices | Sort-Object -Property "Level:0","Level:1","Level:2" | select -last 1
    $details = Get-ItemProperty "$($defaultDevice.PSPath)\Properties"
    $name = "$($details.$classId) ($($details.$nameId))"
    return @{
        name   = $name
        driver = $details.$driverDetails
    }
}

$OsRender = get-DefaultDevice "Render"
$OsCapture = get-DefaultDevice "Capture"

Is there any way to get this info "in a smart way" (without any external DLLs, of course)?


Solution

  • Finally I figured it out and I am happy to share the working code-snippet:

    cls
    Add-Type @'
    [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IMMDevice {
        int a(); int o();
        int GetId([MarshalAs(UnmanagedType.LPWStr)] out string id);
    }
    [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IMMDeviceEnumerator {
        int f();
        int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
    }
    [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }
    
    public static string GetDefault (int direction) {
        var enumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
        IMMDevice dev = null;
        Marshal.ThrowExceptionForHR(enumerator.GetDefaultAudioEndpoint(direction, 1, out dev));
        string id = null;
        Marshal.ThrowExceptionForHR(dev.GetId(out id));
        return id;
    }
    '@ -name audio -Namespace system
    
    function getFriendlyName($id) {
        $reg = "HKLM:\SYSTEM\CurrentControlSet\Enum\SWD\MMDEVAPI\$id"
        return (get-ItemProperty $reg).FriendlyName
    }
    
    $id0 = [audio]::GetDefault(0)
    $id1 = [audio]::GetDefault(1)
    write-host "Default Speaker: $(getFriendlyName $id0)" 
    write-host "Default Micro  : $(getFriendlyName $id1)"
    

    and in case you need a language-neutral international name of each device MMDEVICE-id and (optional) its driver then use this function:

    # https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/shared/devpkey.h
    # https://github.com/EddieRingle/portaudio/blob/master/src/hostapi/wasapi/mingw-include/mmdeviceapi.h
    $regId         = "{b3f8fa53-0004-438e-9003-51a46e139bfc},2"
    $regName       = "{b3f8fa53-0004-438e-9003-51a46e139bfc},6"
    $regFormFactor = "{1da5d803-d492-4edd-8c23-e0c0ffee7f0e},0"
    
    # https://learn.microsoft.com/en-us/windows/win32/api/mmdeviceapi/ne-mmdeviceapi-endpointformfactor
    $formFactor = @(
        "RemoteNetworkDevice",
        "Speakers",
        "LineLevel",
        "Headphones",
        "Microphone",
        "Headset",
        "Handset",
        "UnknownDigitalPassthrough",
        "SPDIF",
        "DigitalAudioDisplayDevice",
        "UnknownFormFactor"
    )
    
    function getInternationalNameAndDriver($id) {
        $guid = $id.Substring(17)
        $subKey = @("Render","Capture")[[int]::Parse($id[5])]
        $reg = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\$subKey\$guid\Properties"
        $details = get-ItemProperty $reg -ea 0
        if ($details) {
            $id   = $details.$regId.subString(4)
            $name = $details.$regName
            $form = $formFactor[$details.$regFormFactor]
    
            $hardware = get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Enum\$id"
            $regDrv   = $hardware.Driver
    
            $driver   = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Class\$regDrv"
            $drvName    = $driver.DriverDesc
            $drvVersion = $driver.DriverVersion
        }
        return "$form ($name), driver: $drvName $drvVersion"
    }
    

    this gives you an output like this:

    Default Speaker: Speakers (Realtek Audio), driver: Realtek Audio 6.0.1.6127
    Default Micro  : Microphone (Logitech BRIO), driver: USB Audio Device 10.0.22000.653