Responding to USB Devices in PowerShell

Tue, Mar 2, 2010 3-minute read

I recently got a cool USB MIDI keyboard (M-Audio Axiom 49) and software (Propellerhead Reason) combo that lets me play keyboard with all kinds of cool sounds effects and general fun. Despite how cool the system is, it’s not quite as spontaneous as a plain ol’ electronic keyboard. Just to piddle around for a bit, you have to turn on the keyboard, launch Reason, create a new song, add a virtual instrument, and then start playing.

How can PowerShell of all things help the anguish of a struggling keyboard hack?

The answer comes in two parts – creating a template song, and then having PowerShell launch it when you turn on the keyboard.

It turns out that you can resolve a lot of the issues in Reason by just creating a good starter song. Add in the mixer, a synthesizer, and save it to disk – I called it “Default.rns.” When you double-click on this file, Reason starts up with all of the presets you had configured.

The second part is more tricky, unless of course you happen to have Real Ultimate Power.

The core of this solution comes in three parts:

  1. WMI lets you enumerate USB devices on the system.
  2. The Register-WmiEvent cmdlet lets you respond to WMI events.
  3. WMI has a default __InstanceCreationEvent class that lets you respond to ANY new instance being created (Services, Processes, etc.)

The answer to #1 has come up a few times on the Team Blog:

http://blogs.msdn.com/powershell/archive/2007/02/24/displaying-usb-devices-using-wmi.aspx
http://blogs.msdn.com/powershell/archive/2009/01/10/get-usb-using-wmi-association-classes-in-powershell.aspx

I had no idea what the Axiom keyboard was being recognized as, so I did the easy thing. Ran the query once, turned off the keyboard, and then ran it again. Compare-Object to the rescue:

$before = gwmi Win32_USBControllerDevice |% { [wmi] $_.Dependent }
$after = gwmi Win32_USBControllerDevice |% { [wmi] $_.Dependent }
Compare-Object $before $after -PassThru

That gives an object like:

Availability                :
Caption                     : USB Axiom 49
ClassGuid                   : {4d36e96c-e325-11ce-bfc1-08002be10318}
CompatibleID                : {USB\Class_01&SubClass_01&Prot_00, USB\Class_01&SubClass_01, USB\Class_01}
ConfigManagerErrorCode      : 0
ConfigManagerUserConfig     : False
CreationClassName           : Win32_PnPEntity
Description                 : USB Audio Device
DeviceID                    : USB\VID_0763&PID_0199&MI_00\6&2F841C5F&0&0000
ErrorCleared                :
ErrorDescription            :
HardwareID                  : {USB\VID_0763&PID_0199&REV_0105&MI_00, USB\VID_0763&PID_0199&MI_00}
InstallDate                 :
LastErrorCode               :
Manufacturer                : (Generic USB Audio)
Name                        : USB Axiom 49
PNPDeviceID                 : USB\VID_0763&PID_0199&MI_00\6&2F841C5F&0&0000

There’s the ticket. It’s actually a Win32_PnpEntity. While the previous WMI query helped us discover USB entities, now that we know how the keyboard is identified to the system, we can drop the complicated WMI dependency traversal, and turn this specific goal into a simpler query:

Get-WmiObject Win32_PnPEntity –Filter "Name='USB Axiom 49'"

Now that we know which WMI class represents the keyboard, and the query that selects it specifically, we can use WMI’s __InstanceCreationEvent to monitor for that instance as it comes and goes. The Register-WmiEvent cmdlet makes this a snap:

function Register-KeyboardEvent
{
    $query = "SELECT * FROM __InstanceCreationEvent " +
         "WITHIN 5 " +
         "WHERE TargetInstance ISA 'Win32_PnPEntity' " +
         "AND TargetInstance.Name = 'USB Axiom 49' "
    $null = Register-WmiEvent -Query $query -SourceIdentifier KeyboardMonitor -Action { E:\lee\reason\Default.rns }
}

After calling this function, PowerShell automatically launches Reason with your favourite presets already loaded whenever you turn on the USB keyboard.