後ほどミュートを解除しておく PowerShell スクリプト

Pocket

本記事は シェルスクリプト&PowerShell Advent Calendar 2023 の1日目の記事だ。
如何にハードルを下げるかを考え、しょーもない内容となることを意識している…と言い訳しておこう。

何故ミュートを解除したいのか

さて、一時的に PC をミュートにしておきたい事がある。

コロナ禍を経て、 Cisco や Polycom といったテレビ会議システムに替わって、 Zoom などのWeb会議ソフトが普及した。
しかし、近年出社を強要されるのが一般化してくると、結局以前のように会議室に集まって、離れた拠点の相手とビデオ会議を行うことになる。
このとき、全員各々のPCでビデオ会議には参加するものの、音声は卓上マイク&スピーカー使う事があり、ハウリングを避ける為に卓上マイクを使う人以外はスピーカー・マイクをミュートにするだろう。

ところが、会議終了後ミュートしっぱなしにしてしまい、通知等に気づきにくくなることがあるので、一定時間後にミュートを簡単に解除できるようにしておきたい。

それだけのために、スクリプト経由で MMDevice API 呼ぶのも仰々しいなぁと思っていた所、よく考えたらキーボードにあるミュート状態をトグルキーを仮想的に押せばいいじゃないかと気づいた。

ということで、早速 PowerShell でキーボードのマルチメディアキーを押すスクリプトを書いてみる。

早速コード例

#Requires -Version 5
$addTypeOptions = if ($PSVersionTable.PSVersion -lt [version]'6.0') { @{ Language = 'CSharpVersion3' }; } else { @{}; };
$typeKeyboard = Add-Type -PassThru @addTypeOptions -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
namespace D8B35E79FC964E69854B61A2C9FB3C08 {
    public static class Keyboard {
        [StructLayout(LayoutKind.Sequential)]
        struct MOUSEINPUT {
            public Int32 dx; public Int32 dy; public UInt32 mouseData; public UInt32 dwFlags; public UInt32 time; public IntPtr dwExtraInfo;
        }
        [StructLayout(LayoutKind.Sequential)]
        struct KEYBDINPUT {
            public UInt16 wVk; public UInt16 wScan; public UInt32 dwFlags; public UInt32 time; public IntPtr dwExtraInfo;
        }
        // Define the unused MOUSE structure due to the UNION_INPUT structure size determination.
        [StructLayout(LayoutKind.Explicit)]
        struct UNION_INPUT {
            [FieldOffset(0)] public MOUSEINPUT mi;
            [FieldOffset(0)] public KEYBDINPUT ki;
        }
        [StructLayout(LayoutKind.Sequential)]
        struct INPUT {
            public int type;
            public UNION_INPUT ui;
        }

        [DllImport("user32.dll", SetLastError = true)]
        extern static uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);

        public static void TypeKey(UInt16 vk) {
            var inputs = new INPUT[2];
            inputs[1].type = inputs[0].type = 1; // INPUT_KEYBOARD
            inputs[1].ui.ki.wVk = inputs[0].ui.ki.wVk = vk;
            inputs[0].ui.ki.dwFlags = 0x0000; // KEYEVENTF_KEYDOWN
            inputs[1].ui.ki.dwFlags = 0x0002; // KEYEVENTF_KEYUP
            var uSent = SendInput(2, inputs, Marshal.SizeOf(inputs[0]));
            if (uSent != 2) {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            }
        }
    }
}
'@ | Where-Object Name -EQ Keyboard;
$typeKeyboard::TypeKey(<# VK_VOLUME_MUTE #> 0xAD);

これを Push-MuteKey.ps1 等の名前で保存しておいて、

Start-Sleep -Seconds ([datetime]'11:01' - [datetime]::Now).TotalSeconds; .\Push-MuteKey.ps1;

などと指定して実行すれば、指定時間にミュートキーが押されてミュートが解除される。

Win32 API C# 経由で叩く場合、 32bit か 64bit どちらのプロセスでも問題なく動くよう気をつける必要があったり、 PowerShell のバージョン (5 か 7以降か) でコンパイル時の挙動が異なるので、そこらへんも気をつけて実装した。
うーん、 Win32 API 叩くために色々構造体を定義する必要もあって、結局仰々しいなぁ。。。

おわりに

2日目の明日は @ko1nksm 氏の記事の予定だ。

シェルスクリプト&PowerShell Advent Calendar 2023 は、執筆者募集中です!
こんな割としょうも無い内容で構わないので、久々になんか書いてみませんか!?

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください