Skip to content

Instantly share code, notes, and snippets.

@FriedrichWeinmann
Created March 1, 2023 11:21
Show Gist options
  • Save FriedrichWeinmann/3f0c86089e3b70273962ae04d06f89d4 to your computer and use it in GitHub Desktop.
Save FriedrichWeinmann/3f0c86089e3b70273962ae04d06f89d4 to your computer and use it in GitHub Desktop.
Tools to update speaker settings on windows
#
function Disable-Privilege {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[ValidateSet('SeAssignPrimaryTokenPrivilege','SeAuditPrivilege','SeBackupPrivilege','SeChangeNotifyPrivilege','SeCreateGlobalPrivilege','SeCreatePagefilePrivilege','SeCreatePermanentPrivilege','SeCreateSymbolicLinkPrivilege','SeCreateTokenPrivilege','SeDebugPrivilege','SeDelegateSessionUserImpersonatePrivilege','SeEnableDelegationPrivilege','SeImpersonatePrivilege','SeIncreaseBasePriorityPrivilege','SeIncreaseQuotaPrivilege','SeIncreaseWorkingSetPrivilege','SeLoadDriverPrivilege','SeLockMemoryPrivilege','SeMachineAccountPrivilege','SeManageVolumePrivilege','SeProfileSingleProcessPrivilege','SeRelabelPrivilege','SeRemoteShutdownPrivilege','SeRestorePrivilege','SeSecurityPrivilege','SeShutdownPrivilege','SeSyncAgentPrivilege','SeSystemEnvironmentPrivilege','SeSystemProfilePrivilege','SeSystemtimePrivilege','SeTakeOwnershipPrivilege','SeTcbPrivilege','SeTimeZonePrivilege','SeTrustedCredManAccessPrivilege','SeUndockPrivilege','SeUnsolicitedInputPrivilege')]
[string]
$Privilege
)
begin {
#region Source Code
$source = @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
public static class Advapi32
{
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool OpenProcessToken(
IntPtr ProcessHandle,
int DesiredAccess,
ref IntPtr TokenHandle);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
ref long lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
bool DisableAllPrivileges,
ref TokPriv1Luid NewState,
int BufferLength,
IntPtr PreviousState,
IntPtr ReturnLength);
}
public static class Kernel32
{
[DllImport("kernel32.dll")]
public static extern uint GetLastError();
}
[Flags()]
public enum TokenAccess
{
AssignPrimary = 0x0001,
Duplicate = 0x0002,
Impersonate = 0x0004,
Query = 0x0008,
QuerySource = 0x0010,
AdjustPrivileges = 0x0020,
AdjustGroups = 0x0040,
AdjustDefault = 0x0080,
AdjustSessionID = 0x0100,
StandardRightsRead = 0x00020000,
StandardRightsRequired = 0x000F0000,
Read = StandardRightsRead | Query,
ModifyRights = Query | AdjustPrivileges,
FullControl = AssignPrimary | Duplicate | Impersonate | Query | QuerySource | AdjustPrivileges | AdjustGroups | AdjustDefault | AdjustSessionID | StandardRightsRead | StandardRightsRequired
}
"@
Add-Type -TypeDefinition $source -ErrorAction Ignore
#endregion Source Code
}
process {
$ProcHandle = (Get-Process -Id $pid).Handle
$hTokenHandle = [IntPtr]::Zero
$null = [Advapi32]::OpenProcessToken($ProcHandle, [TokenAccess]::ModifyRights, [ref]$hTokenHandle)
$TokPriv1Luid = [TokPriv1Luid]::new()
$TokPriv1Luid.Count = 1
$TokPriv1Luid.Attr = 0x00000000 # SE_PRIVILEGE_DISABLED
$LuidVal = $Null
$null = [Advapi32]::LookupPrivilegeValue($null, $Privilege, [ref]$LuidVal)
$TokPriv1Luid.Luid = $LuidVal
$null = [Advapi32]::AdjustTokenPrivileges($hTokenHandle, $False, [ref]$TokPriv1Luid, 0, [IntPtr]::Zero, [IntPtr]::Zero)
}
}
function Enable-Privilege {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[ValidateSet('SeAssignPrimaryTokenPrivilege','SeAuditPrivilege','SeBackupPrivilege','SeChangeNotifyPrivilege','SeCreateGlobalPrivilege','SeCreatePagefilePrivilege','SeCreatePermanentPrivilege','SeCreateSymbolicLinkPrivilege','SeCreateTokenPrivilege','SeDebugPrivilege','SeDelegateSessionUserImpersonatePrivilege','SeEnableDelegationPrivilege','SeImpersonatePrivilege','SeIncreaseBasePriorityPrivilege','SeIncreaseQuotaPrivilege','SeIncreaseWorkingSetPrivilege','SeLoadDriverPrivilege','SeLockMemoryPrivilege','SeMachineAccountPrivilege','SeManageVolumePrivilege','SeProfileSingleProcessPrivilege','SeRelabelPrivilege','SeRemoteShutdownPrivilege','SeRestorePrivilege','SeSecurityPrivilege','SeShutdownPrivilege','SeSyncAgentPrivilege','SeSystemEnvironmentPrivilege','SeSystemProfilePrivilege','SeSystemtimePrivilege','SeTakeOwnershipPrivilege','SeTcbPrivilege','SeTimeZonePrivilege','SeTrustedCredManAccessPrivilege','SeUndockPrivilege','SeUnsolicitedInputPrivilege')]
[string]
$Privilege
)
begin {
#region Source Code
$source = @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
public static class Advapi32
{
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool OpenProcessToken(
IntPtr ProcessHandle,
int DesiredAccess,
ref IntPtr TokenHandle);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
ref long lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
bool DisableAllPrivileges,
ref TokPriv1Luid NewState,
int BufferLength,
IntPtr PreviousState,
IntPtr ReturnLength);
}
public static class Kernel32
{
[DllImport("kernel32.dll")]
public static extern uint GetLastError();
}
[Flags()]
public enum TokenAccess
{
AssignPrimary = 0x0001,
Duplicate = 0x0002,
Impersonate = 0x0004,
Query = 0x0008,
QuerySource = 0x0010,
AdjustPrivileges = 0x0020,
AdjustGroups = 0x0040,
AdjustDefault = 0x0080,
AdjustSessionID = 0x0100,
StandardRightsRead = 0x00020000,
StandardRightsRequired = 0x000F0000,
Read = StandardRightsRead | Query,
ModifyRights = Query | AdjustPrivileges,
FullControl = AssignPrimary | Duplicate | Impersonate | Query | QuerySource | AdjustPrivileges | AdjustGroups | AdjustDefault | AdjustSessionID | StandardRightsRead | StandardRightsRequired
}
"@
Add-Type -TypeDefinition $source -ErrorAction Ignore
#endregion Source Code
}
process {
$ProcHandle = (Get-Process -Id $pid).Handle
$hTokenHandle = [IntPtr]::Zero
$null = [Advapi32]::OpenProcessToken($ProcHandle, [TokenAccess]::ModifyRights, [ref]$hTokenHandle)
$TokPriv1Luid = [TokPriv1Luid]::new()
$TokPriv1Luid.Count = 1
$TokPriv1Luid.Attr = 0x00000002 # SE_PRIVILEGE_ENABLED
$LuidVal = $Null
$null = [Advapi32]::LookupPrivilegeValue($null, $Privilege, [ref]$LuidVal)
$TokPriv1Luid.Luid = $LuidVal
$null = [Advapi32]::AdjustTokenPrivileges($hTokenHandle, $False, [ref]$TokPriv1Luid, 0, [IntPtr]::Zero, [IntPtr]::Zero)
}
}
function Enable-RegistryAccess {
[CmdletBinding()]
param (
[string]
$Path
)
$rootPath = $Path -replace '^HKLM:\\'
$changeData = @{
Owner = $null
NewRule = $null
}
Enable-Privilege -Privilege SeBackupPrivilege
Enable-Privilege -Privilege SeRestorePrivilege
Enable-Privilege -Privilege SeTakeOwnershipPrivilege
if (-not $script:_registryAccess) {
$script:_registryAccess = @{ }
}
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
# Step 1: Take Ownership
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($rootPath, 'ReadWriteSubTree', 'QueryValues')
$acl = $key.GetAccessControl()
$changeData.Owner = $acl.GetOwner([System.Security.Principal.SecurityIdentifier])
$acl.SetOwner($currentUser)
$key.SetAccessControl($acl)
$key.Close()
# Step 2: Set Access Rights
$rule = [System.Security.AccessControl.RegistryAccessRule]::new(
$currentUser,
[System.Security.AccessControl.RegistryRights]::FullControl,
'Allow'
)
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($rootPath, 'ReadWriteSubTree', 'QueryValues')
$acl = $key.GetAccessControl()
$acl.AddAccessRule($rule)
$key.SetAccessControl($acl)
$key.Close()
$changeData.NewRule = $rule
$script:_registryAccess[$rootPath] = $changeData
Disable-Privilege -Privilege SeBackupPrivilege
Disable-Privilege -Privilege SeRestorePrivilege
Disable-Privilege -Privilege SeTakeOwnershipPrivilege
}
function Disable-RegistryAccess {
[CmdletBinding()]
param (
[string]
$Path
)
$rootPath = $Path -replace '^HKLM:\\'
Enable-Privilege -Privilege SeBackupPrivilege
Enable-Privilege -Privilege SeRestorePrivilege
Enable-Privilege -Privilege SeTakeOwnershipPrivilege
if (-not $script:_registryAccess) {
$script:_registryAccess = @{ }
}
$config = $script:_registryAccess[$rootPath]
# Step 1: Remove new rule
if ($config.NewRule) {
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($rootPath, 'ReadWriteSubTree', 'QueryValues')
$acl = $key.GetAccessControl()
$acl.RemoveAccessRuleSpecific($config.NewRule)
$key.SetAccessControl($acl)
$key.Close()
}
# Step 2: Restore Ownership
if ($config.Owner) {
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($rootPath, 'ReadWriteSubTree', 'QueryValues')
$acl = $key.GetAccessControl()
$acl.SetOwner($config.Owner)
$key.SetAccessControl($acl)
$key.Close()
}
$script:_registryAccess.Remove($rootPath)
Disable-Privilege -Privilege SeBackupPrivilege
Disable-Privilege -Privilege SeRestorePrivilege
Disable-Privilege -Privilege SeTakeOwnershipPrivilege
}
function Get-Speaker {
[CmdletBinding()]
param (
[string[]]
$ComputerName,
[PSCredential]
$Credential,
[string]
$Name = '*',
[ValidateSet('Enabled', 'Removed', 'Disconnected', 'All')]
[string[]]
$Type = @('Enabled', 'Disconnected')
)
begin {
#region Code
$scriptblock = {
param (
$Data
)
$Name = $Data.Name
$Type = $Data.Type
$rootKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render"
:main foreach ($speakerRoot in Get-ChildItem -Path $rootKey) {
$rootProperties = Get-ItemProperty -LiteralPath $speakerRoot.PSPath
$properties = Get-ItemProperty -LiteralPath "$($speakerRoot.PSPath)\Properties"
if ($properties.'{a45c254e-df1c-4efd-8020-67d146a850e0},2' -notlike $Name) { continue }
if ($Type -notcontains 'All') {
switch ($rootProperties.DeviceState) {
1 { if ($Type -notcontains 'Enabled') { continue main } }
4 { if ($Type -notcontains 'Removed') { continue main } }
8 { if ($Type -notcontains 'Disconnected') { continue main } }
default { continue main }
}
}
$state = switch ($rootProperties.DeviceState) {
1 { 'Enabled' }
4 { 'Removed' }
8 { 'Disconnected' }
default { "Unknown ($_)" }
}
[PSCustomObject]@{
ID = $speakerRoot.PSChildName
State = $state
# Resolved Properties
Name = $properties.'{b3f8fa53-0004-438e-9003-51a46e139bfc},6'
DisplayName = $properties.'{a45c254e-df1c-4efd-8020-67d146a850e0},2'
Description = $properties.'{b3f8fa53-0004-438e-9003-51a46e139bfc},26'
Driver = $properties.'{a8b865dd-2e3d-4094-ad97-e593a70c75d6},5'
DriverDetails = $properties.'{83da6326-97a6-4088-9453-a1923f573b29},3'
# All Data
Properties = $properties
ComputerName = $env:COMPUTERNAME
}
}
}
#endregion Code
}
process {
$param = @{
ArgumentList = @{
Name = $Name
Type = $Type
}
}
if ($ComputerName) { $param.ComputerName = $ComputerName }
if ($Credential) { $param.Credential = $Credential }
$results = Invoke-Command @param -ScriptBlock $scriptblock
$results
}
}
function Set-Speaker {
[CmdletBinding()]
param (
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string]
$ComputerName,
[PSCredential]
$Credential,
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string]
$ID,
[string]
$DisplayName,
[string]
$Description
)
begin {
#region Scriptblock
$scriptblock = {
param (
$Data
)
foreach ($command in $Data.Commands) {
Set-Item -Path "function:\$($command.Name)" -Value $command.Definition
}
$ID = $Data.ID
$DisplayName = $Data.DisplayName
$Description = $Data.Description
# Enable-Privilege -Privilege SeRestorePrivilege
# Enable-Privilege -Privilege SeBackupPrivilege
$rootKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render"
$speakerRoot = Join-Path -Path $rootKey -ChildPath $ID
if (-not (Test-Path -Path $speakerRoot)) {
Write-Error "Speaker not found: $ID"
return
}
$propertyRoot = Join-Path -Path $speakerRoot -ChildPath Properties
Enable-RegistryAccess -Path $propertyRoot
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey(($propertyRoot -replace '^HKLM:\\'), $true)
try {
if ($DisplayName) {
$key.SetValue('{a45c254e-df1c-4efd-8020-67d146a850e0},2', $DisplayName)
}
if ($Description) {
$key.SetValue('{b3f8fa53-0004-438e-9003-51a46e139bfc},26', $Description)
}
}
finally {
$key.Close()
Disable-RegistryAccess -Path $propertyRoot
}
}
#endregion Scriptblock
}
process {
$param = @{
ArgumentList = @{
ID = $ID
DisplayName = $DisplayName
Description = $Description
Commands = @(
Get-Command Enable-Privilege
Get-Command Disable-Privilege
Get-Command Enable-RegistryAccess
Get-Command Disable-RegistryAccess
)
}
}
if ($ComputerName) { $param.ComputerName = $ComputerName }
if ($Credential) { $param.Credential = $Credential }
Invoke-Command @param -ScriptBlock $scriptblock
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment