QuickPost – auto-mute volume on Citrix Virtual Desktops

I had a requirement recently that covered the deployment of Citrix Virtual Desktops instances into open-access areas. One of pre-requisites of this deployment was to automatically mute the volume on these Windows 10 workers (accessed from thin clients) so people couldn’t disturb their neighbours. It was a library setting, so I could definitely see the point involved.

Naturally the first suggestion was to simply disable or stop the Windows Audio Service, but we wanted users to be able to use headphones and the like, not prevent them from having sound altogether. The idea was that the default setting would always be muted – users could then turn the mute setting off manually when they plugged in their headphones or headsets.

We could have set the volume to muted in the default profile on the images also, but given that we were using the same image elsewhere, so it was also not an option. We could have probably used FSLogix to redirect different default profiles for different user groups, but this wasn’t an FSLogix customer (yes, believe it or not, I do work for customers who don’t use FSLogix!)

You can see here the default setting with the volume fully up:-

We opted to solve this with a Scheduled Task to run at logon, but you could do this in many ways – Citrix WEM, Ivanti UWM, logon script, etc. The Scheduled Task is deployed via GPP to sessions where the Registry value in HKLM\Software\Citrix\Ica\Session\ClientName matches those from a pre-determined list, which was a bit sketchy, but worked OK for the purposes of this situation where we needed to run the script only when connecting to desktops from particular clients. If you couldn’t skin this satisfactorily, you could simply opt to have all your virtual desktops running in muted mode.

As to how to achieve the manipulation of the volume level programmatically, well, of course a bit of PowerShell would do. This is what I used – lines may wrap:

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)); }

[Audio]::Mute = $true

I saved this as a .ps1 file in an accessible share, and then configured my Scheduled Task to point at the script and run it at user logon

Once this was set up and running, when a user launched the Citrix desktop from the thin client device the volume was automatically muted, as seen below:-

On the flip side, if you wanted to set the volume to unmuted and 100% (there may be certain classes of device where you need to get max volume, or you could preset any percentage you wanted), then replace the last line above with these

[Audio]::Volume = 1.0

[Audio]::Mute = $false

Obviously if you wanted 50% instead of 100%, change the 1.0 to 0.5 or whatever you need for the required percentage – just basic maths involved 🙂

More stuff in the pipeline coming soon – hopefully will be a lot of posts on the way until the government finally decide to reopen the pubs and football stadia 😉


Leave a Reply

Your email address will not be published. Required fields are marked *