PowerShell support for MIDI is currently experimental and in-development. Expect minor changes in the cmdlets in the future.
These cmdlets require a minimum of PowerShell 7.6. We recommend using the latest official version. Earlier 7.x releases run on an earlier .NET, which cannot load the module, so Import-Module refuses rather than failing later.
PowerShell itself is not installed by the SDK Runtime and Tools installer. The .NET desktop runtime the cmdlets need is, currently .NET 10.
The version of PowerShell which usually ships with Windows is currently the older Windows PowerShell. These cmdlets support the new cross-platform version of PowerShell. Please see the link above for how to install the latest 7.x version of PowerShell
PowerShell is the primary command-line scripting language and environment in Windows, and the current version is also available on Linux and macOS. It is often used by system administrators to automate tasks to set up PCs, by developers to automate deployment or testing, and increasingly by technical Windows users to automate other common tasks. To learn more about PowerShell, see the documentation.
The PowerShell cmdlets for MIDI were created to enable advanced users on Windows to script MIDI. Here are some ideas of things which can be done:
The cmdlets are reasonably fast, but we wouldn’t expect someone to, for example, use them to create a high-performance MIDI sequencer, or other app which is real-time sensitive.
The MIDI Console tool can also do the things the PowerShell cmdlets can do. The primary difference is the console tool opens (and closes) a new connection each time you do something like send a MIDI message. That is inefficient if you need to have a script which does many things with the same connection. If you only need to send a single message, using
midi endpoint send-message 0x25971234is simple and fast.
The default startup mode of Windows MIDI Services is to demand-start. That means it will not start up and begin enumerating endpoints, connecting to resources, etc. until it has been called. This is required so we don’t slow down Windows startup for non-MIDI users. You can change this in the Services snap-in in Windows, or via command-line (including PowerShell) service management.
This confirms Windows MIDI Services is available, starting the service if it is not already running. Every other cmdlet in the module performs the same check, so calling this first is optional. It is a useful first line in a script because it fails immediately, with a clear message, on a PC where Windows MIDI Services is not installed.
#Requires -Version 7.6
import-module WindowsMidiServices
Start-Midi
There is no matching shutdown cmdlet. Sessions and connections are released when you stop them, or when the PowerShell process ends.
One of the first things a MIDI tool or application typically does is list out all the available connections so that the software or its user can decide which endpoint(s) to create a connection to.
With no parameters, returns all UMP MIDI Endpoints. With an endpoint device id, returns just that one. Does not require an active MIDI Session.
#Requires -Version 7.6
import-module WindowsMidiServices
# List all available endpoints. Enumeration functions do not require an active session.
Write-Host "Available MIDI Endpoints" -ForegroundColor Cyan
(Get-MidiEndpointDeviceInfo) | Sort-Object -Property Name | Format-Table -AutoSize
# I'm using the default loopback that is created when you set up MIDI through the MIDI Settings app
$endpointDeviceId = "\\?\swd#midisrv#midiu_loop_a_default#{e7cce071-3c03-423f-88d3-f1045d02552b}"
# show some info about the device we're interested in
Write-Host "Endpoint we intend to connect to" -ForegroundColor Cyan
(Get-MidiEndpointDeviceInfo $endpointDeviceId) | Format-List
Returns the MIDI Groups an endpoint uses, taken from its declared function blocks when it has them, and from its group terminal blocks when it does not. Inactive function blocks are left out unless -IncludeInactive is supplied.
Get-MidiEndpointGroup -EndpointDeviceId $endpointDeviceId | Format-Table -AutoSize
Returns the MIDI 1.0 ports which the older Windows MIDI APIs (WinMM and WinRT MIDI 1.0) see. These are created by Windows MIDI Services alongside the UMP endpoints they belong to, so this is how you map an endpoint to the port numbers an older application will show.
The ports can be listed in full, or narrowed by direction, by owning endpoint, by name, by container, or fetched individually by port device id.
# every MIDI 1.0 input port on the PC
Get-MidiLegacyPort -Flow MidiMessageSource | Format-Table -AutoSize
# only the ports belonging to one endpoint
Get-MidiLegacyPort -EndpointDeviceId $endpointDeviceId | Format-Table -AutoSize
# by the name an older application displays
Get-MidiLegacyPort -Name "MIDISPORT 2x2 In A"
Enumerates all the active MIDI Sessions in the service, from every application, not just this one.
# list all the active sessions
Write-Host "All active MIDI sessions" -ForegroundColor Cyan
(Get-MidiSession) | Sort-Object -Property Name | Format-Table -AutoSize
To send and receive messages with Windows MIDI Services, you must have an active session. The session is tracked in the service so that a MIDI users has visibility into the processes using MIDI on their PC. Most processes only need one MIDI Session, but they may open more than one if they need to group and manage connection usage by project, page, or similar.
Once you have an active session, you can open one or more connections to endpoints. Each active connection allocate resources on the client and in the service, so you only want to open connections you need, and ideally, only one connection per endpoint device id.
Given the session name as a parameter, creates and activates a new MIDI Session. The returned object is required for calls which use a session, such as sending and receiving messages.
# create a new session so we can send and receive messages
$session = Start-MidiSession "Powershell Demo Session"
Ends the MIDI session
Stop-MidiSession $session
Given the session object and an endpoint device id, opens a connection to a MIDI UMP endpoint. The returned connection object is required for cmdlets which send and receive messages.
# open a connection to the endpoint
$connection = Open-MidiEndpointConnection $session $endpointDeviceId
Given session and connection objects, closes an open MIDI Endpoint Connection within the specified session.
Close-MidiEndpointConnection $session $connection
Given a connection object and an array of valid UMP message words (formatted as 32 bit integer values as complete UMPs), sends the single message. Do not include more than one valid UMP in the array of words.
A Timestamp value of 0 means to send the message immediately. Otherwise, a valid 64 bit integer derived from the MIDI Clock should be provided for scheduling a message in the near future.
# each sub-array is a complete MIDI UMP
$messages = (0x40905252, 0x02001111), (0x40805252, 0x02000000), 0x25971234
foreach ($message in $messages)
{
Write-Host "Sending MIDI message" -ForegroundColor Cyan
Send-MidiMessage $connection $message -Timestamp 0
}
To receive MIDI messages, use PowerShell’s Register-ObjectEvent and background job support to handle the incoming messages. The monitor-messages sample includes the code for this.
The event handler args themselves are simplified from what direct WinRT clients receive. In this case, the data is supplied as a Timestamp field and an array of MIDI words as the Words field
$eventHandlerAction = {
#Write-Host "Message Received"
#Write-Host $EventArgs.Timestamp
Get-MidiMessageInfo $EventArgs.Words
}
$job = Register-ObjectEvent -SourceIdentifier "OnMessageReceivedHandler" -InputObject $connection -EventName "MessageReceived" -Action $eventHandlerAction
# just spin until a key is pressed
do
{
Receive-Job -Job $job
} until ([System.Console]::KeyAvailable)
# we don't do anything with the key here, but you could
$keyPressed = [System.Console]::ReadKey($true)
Write-Host
Write-Host "Key pressed. Shutting down ... "
Unregister-Event -SourceIdentifier "OnMessageReceivedHandler"
Stop-Job $job
Remove-Job $job
MIDI 1.0 bytestream System Exclusive data, of the kind held in a .syx file, is carried over UMP as SysEx7 messages. These two cmdlets do the conversion and the flow control for you.
Sends a .syx file, or a byte array, to an open connection. Progress is reported through PowerShell’s normal progress bar, and Ctrl+C cancels the transfer.
-MessagesPerTransfer and -DelayBetweenTransfersMilliseconds pace the data. Some devices, particularly older ones, need a slower pace to keep up.
Send-MidiSystemExclusive -Connection $connection -Path .\patches.syx -GroupIndex 0
Captures System Exclusive data arriving on a group. Without -Path it writes one object per block of received bytes; with -Path it writes a .syx file instead, as the data arrives, so the file is complete even if the capture is interrupted.
Receiving is open ended, so it runs until -MessageCount messages have arrived, -TimeoutSeconds elapses, or Ctrl+C is pressed. Supply at least one of those unless you intend to stop it by hand.
# capture one complete message, or give up after 30 seconds
Receive-MidiSystemExclusive -Connection $connection -Path .\dump.syx -MessageCount 1 -TimeoutSeconds 30
Loopback endpoints are a pair of endpoints wired together, so what an application sends to one, another application receives from the other. Basic loopbacks are the single-endpoint MIDI 1.0 flavor, where an endpoint simply receives what it sends.
An endpoint created by these cmdlets is transient: it disappears when the service restarts. Supply -SaveToConfiguration to also write it to the Windows MIDI Services configuration file so it comes back.
# the base name gets " (A)" and " (B)" appended, matching the other MIDI tools
New-MidiLoopback -BaseName "My Loopback" -SaveToConfiguration
# or name each side yourself
New-MidiLoopback -NameA "Sequencer Out" -NameB "Synth In"
New-MidiBasicLoopback -Name "My Basic Loopback"
Lists the loopbacks the service currently has, including any created by other applications.
Get-MidiLoopback | Format-Table -AutoSize
Mute is a property rather than an action, so it is set rather than toggled. Mute is not an approved PowerShell verb.
Set-MidiLoopbackMute -AssociationId $loopback.AssociationId -Muted $true
Removes the endpoint from the running service. An entry saved in the configuration file is not affected, so a saved loopback returns when the service restarts.
Get-MidiLoopback | Where-Object { $_.EndpointA.Name -like 'My Loopback*' } | Remove-MidiLoopback
Lists the remote hosts this PC can currently see advertised on the network.
Get-MidiNetworkAdvertisedHost | Format-Table -AutoSize
Connects to a remote host, either one which was discovered, or one at a fixed address and port. A discovered host is matched on its advertisement, so the connection survives it moving to a new address; a direct address cannot do that, and is not retried automatically if it stops answering.
# connect to something which was discovered
Get-MidiNetworkAdvertisedHost |
Where-Object { $_.DeviceName -eq 'BomeBox' } |
Connect-MidiNetworkHost -SaveToConfiguration
# connect to a fixed address
Connect-MidiNetworkHost -HostNameOrAddress 192.168.1.243 -Port 33327 -SaveToConfiguration
Disconnects by client identifier, by the device id of the host it was matched to, or by address and port.
Disconnect-MidiNetworkHost -ClientId $response.ClientId
Disconnect-MidiNetworkHost -HostNameOrAddress 192.168.1.243 -Port 33327
Hosts are what this PC advertises for remote devices to connect to. Clients are the connections this PC makes out to remote hosts. A client is reported even when it is not connected, so EntryState is what says whether it is usable.
Get-MidiNetworkConfiguredHost | Format-Table -AutoSize
Get-MidiNetworkConfiguredClient | Format-Table -AutoSize
In addition to enumeration, session management, and sending messages, there are some simple utility cmdlets
Given a valid MIDI UMP message, this returns use-friendly information from decoding the supplied MIDI message
# each sub-array is a complete MIDI UMP
$messages = (0x40905252, 0x02001111), (0x40805252, 0x02000000), 0x25971234
foreach ($message in $messages)
{
# this gets / displays information about the MIDI message we're sending
Get-MidiMessageInfo $message | Format-List
}
PowerShell samples in the repo on GitHub.