3

In a Powershell script, Mute is performed with:

(New-Object -ComObject WScript.Shell).SendKeys([char]173)

That works.

Windows reference lists constants for each key. Mute constant is VK_VOLUME_MUTE.

Is there some way to use VK_VOLUME_MUTE in the above Powershell command, instead of 173?

I tried key(VK_VOLUME_MUTE) and VkKeyScan(VK_VOLUME_MUTE), but I couldn't get them to work.

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscana

This question appears to be a different question, and there's no applicable answer.

asked Oct 24 at 23:24
5
  • 2
    Constants like VK_VOLUME_MUTE are pre-defined only for C/C++ code, not for scripts. Have a look at this question for using constants in Powershell (hint: it's not simple!). Commented Oct 24 at 23:37
  • 1
    @RemyLebeau Thx. If i'm understanding your linked page, it's creating a new constant and hardcoding the value of that constant into the script. Which doesn't benefit grom the native constant as desired. Oh well! Commented Oct 24 at 23:52
  • 2
    You absolutely don't want to follow the linked question, it's just better to define your static class with the char constants in C# and then Add-Type it.... an easy way to start: public static class VirtualKeyCodes { public const char VK_VOLUME_MUTE = (char)0xAD; ... } then from pwsh you can $shell.SendKeys([VirtualKeyCodes]::VK_VOLUME_MUTE) for example. The powershell concept of constants is just nonsense garbage. Commented Oct 25 at 0:18
  • 1
    @SantiagoSquarzon Thx, but still creating a new constant, which doesn't benefit from the native constant. Commented Oct 25 at 0:26
  • 1
    Indeed, it's just better to use the values from your reference table. The static class is worth if you're gonna constantly use it and don't want to keep looking at that reference Commented Oct 25 at 0:34

2 Answers 2

5

If you want to get volume mute, this returns it. However, I don't see a reason you cannot just declare the constant.

# Check if System.Windows.Forms is already loaded
if (-not ("System.Windows.Forms" -in [AppDomain]::CurrentDomain.GetAssemblies().FullName)) {
 Add-Type -AssemblyName System.Windows.Forms
}
# Get VK_VOLUME_MUTE value
$volumeMute = [int][System.Windows.Forms.Keys]::VolumeMute
echo $volumeMute
answered Oct 25 at 1:24
Sign up to request clarification or add additional context in comments.

6 Comments

I want to keep my code minimal, while making it more understandable. In my particular use-case, declaring a constant is a bit too much code. Your code works! But loading Forms adds unnecessary overhead. I can just insert a comment -- simpler. It's a one-time call.
Nicely done, but note that in order to pass a virtual-key code to (New-Object -ComObject WScript.Shell).SendKeys(), you need a [char] rather than an [int] cast, as shown in the question (in Windows PowerShell, the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and final version is 5.1, you need [string] [char]). Also, it's simpler to just use Add-Type -AssemblyName System.Windows.Forms unconditionally: if the assembly is already loaded, the statement is a fast and quiet no-op.
Can you apply your edits to the answer, or post a new answer? Fast and quiet?
Your wish is my command; please see my answer.
[string][char] appears to be working with Powershell 7+, is there any reason to not use it to maximise backward compatibility?
No, it's perfectly fine to use [string] [char] for cross-edition compatibility.
1

The VK_* (virtual-key code) constants are defined in the C/C++ Winuser.h preprocessor file and are therefore not directly available in PowerShell.

However, they are available via the WinForms .NET API, specifically via the [System.Windows.Forms.Keys] enum-derived type, whose symbolic names correspond to the VK_* constants as follows:[1]

  • Pascal-casing the VK_* constants without their VK_ prefix yields the corresponding [System.Windows.Forms.Keys] enumeration value;
    e.g., VK_VOLUME_MUTE corresponds to [System.Windows.Forms.Keys]::VolumeMute.

That said, the type of interest requires first loading the WinForms assembly, System.Windows.Forms; given that this assembly is not strictly necessary to use the COM-based (New-Object -ComObject WScript.Shell).SendKeys() API,[2] this added overhead may or may not be acceptable; in terms of runtime cost (additional execution time), it is negligible, however.


Here's a complete example that works in both PowerShell editions (in both Windows PowerShell - the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and final version is 5.1 - as well as in PowerShell (Core) 7, the modern, cross-platform, install-on-demand edition):

# Allow accessing the WinForm types by type (class) name only.
# Note: Syntactically, `using` statements must come first in a script.
using namespace System.Windows.Forms
# Make sure the WinForms assembly is loaded.
# Note: If the assembly is already loaded, this is a fast and quiet no-op.
Add-Type -AssemblyName System.Windows.Forms
# Note: In *PowerShell 7* you may omit [string]
(New-Object -ComObject WScript.Shell).SendKeys([string] [char] [Keys]::VolumeMute)

Note the use of Add-Type -AssemblyName to load the well-known WinForms assembly.
While alternative use of a using assembly statement (using assembly System.Windows.Forms) should work (and does in Windows PowerShell), sadly it doesn't in PowerShell 7, up to at least v7.5.x, due to a well-known bug - see GitHub issue #11856.


[1] Seemingly, not all VK_* constants have WinForms counterparts, such as the VK_GAMEPAD_* ones.

[2] Note that the WinForms API too supports sending keystrokes in principle, via the [System.Windows.Forms.SendKeys] class, but sending virtual-key codes this way is not supported.

answered 2 days ago

6 Comments

Bravo! Bravo! Bravo! (i had to repeat myself due to minimum character-count)
👍😁...........
How would it look if you only wanted to support v7?
Simply remove the [string] cast.
"added overhead may not be acceptable; runtime cost is negligible" -- By "overhead" do you mean RAM?
Yes. The extra RAM consumed may well be negligible too - I haven't looked into it.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.