
Kod: Tümünü seç
# ================================================
# WindowsVolumeManager.ps1 - Core Audio API based precise volume manager
# Usage: .\WindowsVolumeManager.ps1 [-Increase] [-Decrease] [-Mute] [-Unmute]
# [-ShowDevice] [-ListDevices] [-Menu] [-SetVolume <double>] [-Step <double>]
# Example: .\WindowsVolumeManager.ps1 -SetVolume 55.67
# ================================================
param(
[switch]$Increase,
[switch]$Decrease,
[switch]$Mute,
[switch]$Unmute,
[switch]$ShowDevice,
[switch]$ListDevices,
[switch]$Menu,
[string]$SetVolume,
[string]$Step = "5"
)
$ErrorActionPreference = "Stop"
function ConvertTo-InvariantDouble {
param([string]$Value)
$result = 0.0
$ok = [double]::TryParse(
$Value,
[System.Globalization.NumberStyles]::Float,
[System.Globalization.CultureInfo]::InvariantCulture,
[ref]$result
)
if (-not $ok) {
throw "Invalid numeric value: '$Value'. Use a dot as decimal separator, e.g. 55.67"
}
return $result
}
# Parse Step once up front using invariant culture (avoids locale-dependent
# comma/dot decimal separator issues, e.g. on Turkish (tr-TR) system locale).
$StepValueParsed = ConvertTo-InvariantDouble -Value $Step
try {
# ---------- Core Audio API COM Interop Definitions ----------
$AudioInteropCode = @"
using System;
using System.Runtime.InteropServices;
[ComImport, Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAudioEndpointVolume
{
int RegisterControlChangeNotify(IntPtr pNotify);
int UnregisterControlChangeNotify(IntPtr pNotify);
int GetChannelCount(out int pnChannelCount);
int SetMasterVolumeLevel(float fLevelDB, Guid pguidEventContext);
int SetMasterVolumeLevelScalar(float fLevel, Guid pguidEventContext);
int GetMasterVolumeLevel(out float pfLevelDB);
int GetMasterVolumeLevelScalar(out float pfLevel);
int SetChannelVolumeLevel(uint nChannel, float fLevelDB, Guid pguidEventContext);
int SetChannelVolumeLevelScalar(uint nChannel, float fLevel, Guid pguidEventContext);
int GetChannelVolumeLevel(uint nChannel, out float pfLevelDB);
int GetChannelVolumeLevelScalar(uint nChannel, out float pfLevel);
int SetMute(bool bMute, Guid pguidEventContext);
int GetMute(out bool pbMute);
int GetVolumeStepInfo(out uint pnStep, out uint pnStepCount);
int VolumeStepUp(Guid pguidEventContext);
int VolumeStepDown(Guid pguidEventContext);
int QueryHardwareSupport(out uint pdwHardwareSupportMask);
int GetVolumeRange(out float pflVolumeMindB, out float pflVolumeMaxdB, out float pflVolumeIncrementdB);
}
[ComImport, Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMMDevice
{
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, out IAudioEndpointVolume ppInterface);
int OpenPropertyStore(int stgmAccess, out IntPtr ppProperties);
int GetId(out IntPtr ppstrId);
int GetState(out int pdwState);
}
[ComImport, Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMMDeviceEnumerator
{
int EnumAudioEndpoints(int dataFlow, int dwStateMask, out IntPtr ppDevices);
int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice ppEndpoint);
int GetDevice(string pwstrId, out IMMDevice ppDevice);
int RegisterEndpointNotificationCallback(IntPtr pClient);
int UnregisterEndpointNotificationCallback(IntPtr pClient);
}
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
public class MMDeviceEnumeratorComObject
{
}
public static class AudioEndpointHelper
{
// Internal only: raw COM interface objects must never be returned to PowerShell.
// PowerShell resolves COM object members through IDispatch, and this interface
// is a pure vtable (IUnknown-based) interface with no IDispatch support, so any
// method call attempted directly from PowerShell script code fails with
// "does not contain a method named ...". All COM calls must stay inside
// compiled C# static methods below, exposing only plain .NET types (float, bool).
private static IAudioEndpointVolume GetEndpointVolume()
{
var enumerator = (IMMDeviceEnumerator)(new MMDeviceEnumeratorComObject());
IMMDevice device;
// dataFlow: eRender = 0 (playback devices)
// role: eMultimedia = 1
enumerator.GetDefaultAudioEndpoint(0, 1, out device);
Guid iidAudioEndpointVolume = typeof(IAudioEndpointVolume).GUID;
IAudioEndpointVolume endpointVolume;
// CLSCTX_ALL = 23
device.Activate(ref iidAudioEndpointVolume, 23, IntPtr.Zero, out endpointVolume);
return endpointVolume;
}
public static float GetVolumeScalar()
{
var endpointVolume = GetEndpointVolume();
float level;
endpointVolume.GetMasterVolumeLevelScalar(out level);
return level;
}
public static void SetVolumeScalar(float level)
{
var endpointVolume = GetEndpointVolume();
endpointVolume.SetMasterVolumeLevelScalar(level, Guid.Empty);
}
public static bool GetMuteState()
{
var endpointVolume = GetEndpointVolume();
bool muted;
endpointVolume.GetMute(out muted);
return muted;
}
public static void SetMuteFlag(bool mute)
{
var endpointVolume = GetEndpointVolume();
endpointVolume.SetMute(mute, Guid.Empty);
}
}
"@
Add-Type -TypeDefinition $AudioInteropCode -Language CSharp -ErrorAction Stop
# ---------- Functions ----------
function Get-VolumeLevel {
$level = [AudioEndpointHelper]::GetVolumeScalar()
return [Math]::Round($level * 100, 2)
}
function Set-VolumeLevel {
param([double]$Percent)
if ($Percent -lt 0) { $Percent = 0 }
if ($Percent -gt 100) { $Percent = 100 }
$scalarValue = [float]($Percent / 100.0)
[AudioEndpointHelper]::SetVolumeScalar($scalarValue)
Write-Host "Volume set to -> $(Get-VolumeLevel)%"
}
function Add-VolumeStep {
param([double]$StepValue)
$currentLevel = Get-VolumeLevel
$targetLevel = $currentLevel + $StepValue
Set-VolumeLevel -Percent $targetLevel
}
function Remove-VolumeStep {
param([double]$StepValue)
$currentLevel = Get-VolumeLevel
$targetLevel = $currentLevel - $StepValue
Set-VolumeLevel -Percent $targetLevel
}
function Set-MuteState {
param([bool]$MuteState)
[AudioEndpointHelper]::SetMuteFlag($MuteState)
if ($MuteState) {
Write-Host "Audio muted."
} else {
Write-Host "Audio unmuted."
}
}
function Show-DefaultDevice {
Write-Host "=== Active Playback Device ==="
try {
$devices = Get-CimInstance -ClassName Win32_SoundDevice -ErrorAction Stop | Where-Object { $_.Status -eq "OK" }
foreach ($device in $devices) {
Write-Host " Name: $($device.Name)"
Write-Host " Manufacturer: $($device.Manufacturer)"
}
} catch {
Write-Host " Unable to enumerate devices via CIM: $($_.Exception.Message)"
}
}
function Show-DeviceList {
Write-Host "=== Playback Device List ==="
try {
Get-CimInstance -ClassName Win32_SoundDevice -ErrorAction Stop | ForEach-Object {
Write-Host " - $($_.Name)"
}
} catch {
Write-Host " Unable to enumerate devices via CIM: $($_.Exception.Message)"
}
}
function Show-MainMenu {
while ($true) {
Clear-Host
Write-Host "==============================="
Write-Host " WINDOWS VOLUME MANAGER"
Write-Host " Level: $(Get-VolumeLevel)%"
Write-Host "==============================="
Write-Host " 1) Increase Volume (+$StepValueParsed%)"
Write-Host " 2) Decrease Volume (-$StepValueParsed%)"
Write-Host " 3) Mute"
Write-Host " 4) Unmute"
Write-Host " 5) Show Active Device"
Write-Host " 6) Device List"
Write-Host " 7) Set Precise Volume (e.g. 55.67)"
Write-Host " 0) Exit"
Write-Host "==============================="
$userChoice = Read-Host "Choice"
switch ($userChoice) {
"1" { Add-VolumeStep -StepValue $StepValueParsed }
"2" { Remove-VolumeStep -StepValue $StepValueParsed }
"3" { Set-MuteState -MuteState $true }
"4" { Set-MuteState -MuteState $false }
"5" { Show-DefaultDevice }
"6" { Show-DeviceList }
"7" {
$inputValue = Read-Host "Enter precise volume percent (0-100, e.g. 55.67)"
try {
$parsedValue = ConvertTo-InvariantDouble -Value $inputValue
Set-VolumeLevel -Percent $parsedValue
} catch {
Write-Host "Invalid value. Use a dot as decimal separator, e.g. 55.67"
}
}
"0" { Write-Host "Exiting..."; break }
default { Write-Host "Invalid choice." }
}
Read-Host "Press Enter to continue"
}
}
# ---------- Argument Dispatcher ----------
if ($Increase) {
Add-VolumeStep -StepValue $StepValueParsed
}
elseif ($Decrease) {
Remove-VolumeStep -StepValue $StepValueParsed
}
elseif ($Mute) {
Set-MuteState -MuteState $true
}
elseif ($Unmute) {
Set-MuteState -MuteState $false
}
elseif ($ShowDevice) {
Show-DefaultDevice
}
elseif ($ListDevices) {
Show-DeviceList
}
elseif ($PSBoundParameters.ContainsKey('SetVolume')) {
$parsedSetVolume = ConvertTo-InvariantDouble -Value $SetVolume
Set-VolumeLevel -Percent $parsedSetVolume
}
elseif ($Menu) {
Show-MainMenu
}
else {
Show-MainMenu
}
}
catch {
Write-Host ""
Write-Host "=== ERROR ==="
Write-Host $_.Exception.Message
if ($_.Exception.InnerException) {
Write-Host "Inner exception: $($_.Exception.InnerException.Message)"
}
Write-Host ""
Write-Host "Possible causes:"
Write-Host " 1. Execution Policy is blocking script execution."
Write-Host " Fix: run PowerShell as your normal user and execute:"
Write-Host " powershell -ExecutionPolicy Bypass -File .\WindowsVolumeManager.ps1"
Write-Host " 2. No default playback device is currently set in Windows Sound settings."
Write-Host " 3. Add-Type compilation failed (check .NET Framework / C# compiler availability)."
}
finally {
Read-Host "`nPress ENTER to exit."
}
Gerçekten sorun çıkarabilecek yerler:
Execution Policy : Bu sistemde muhtemelen Bypass ile çalıştırdım. Başka bir makinede (özellikle kurumsal/domain'e bağlı bir bilgisayarda) daha katı bir politika (Restricted veya AllSigned) olabilir; o zaman yine -ExecutionPolicy Bypass gerekir.
İndirilen dosya engeli : Betiği internetten indirip kopyaladıysanız (mail, USB, tarayıcı), Windows dosyaya "Zone.Identifier" damgası koyar ve "bu betik dijital olarak imzalanmamış" uyarısı çıkabilir. Gerekirse: Unblock-File .\WindowsVolumeManager.ps1
komutu ile çalıştırın
Antivirüs/EDR yazılımları : Bazı kurumsal antivirüs çözümleri, çalışma zamanında C# derleme (Add-Type) ve COM interop kalıbını şüpheli davranış olarak işaretleyebilir (malware'ler de benzer teknik kullanabildiği için). Ev bilgisayarında sorun olmaz ama şirket bilgisayarında karantinaya alınabilir.
Varsayılan oynatma cihazı yoksa : Sistemde hiç varsayılan ses çıkış cihazı ayarlanmamışsa (nadir ama mümkün),GetDefaultAudioEndpoint yordamı hata verebilir; şbundan dolayı try/catch ile hatayı yakalayıp anlamlı bir hata mesajı gösterdim (hata mesajını kullanıcı görebiliyor), yani script çökmüyor.
PowerShell 7 (pwsh) üzerinde çalıştırılırsa : Windows'ta sorun olmaz (COM interop hâlâ destekleniyor), çünkü IAudioEndpointVolume Windows'a özgü bir API.
Güle güle kullanın.Başka biryerde böyle bir otomasyon bulamazsınız.


