Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 284 additions & 0 deletions samples/cpp/detectmidi-full/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
// Copyright (c) Microsoft Corporation and Contributors.
// Licensed under the MIT License
// ============================================================================
// This is part of the Windows MIDI Services App SDK and should be used
// in your Windows application via an official binary distribution.
// Further information: https://aka.ms/midi
// ============================================================================

// Full Windows MIDI Services detection sample
//
// This sample demonstrates how to check whether Windows MIDI Services is
// fully installed, enabled, and functional on a PC. It performs four checks:
//
// 1. Registry check - is wdmaud2.drv registered? (Controlled Feature Rollout)
// 2. COM check - are the MIDI Service COM interfaces present?
// 3. Service check - is midisrv.exe registered and not disabled?
// 4. SDK check - is the SDK Runtime installed? (optional)
//
// Unlike the simpler detectmidi sample, this handles the case where Windows
// MIDI Services bits are present but disabled via Controlled Feature Rollout.
//
// See also: midicheckservice tool in src/app-sdk/tools/ for the production
// version of this logic, which includes RPC connectivity verification.
//
// Return codes:
// 0 = All checks passed, Windows MIDI Services is available
// -1 = COM initialization failed
// 1 = Feature not enabled in registry (CFR not rolled out to this PC)
// 2 = COM interfaces not registered (bits not installed)
// 3 = Service not available (access denied, disabled, missing, or not startable)

#include "pch.h"


// ============================================================================
// MIDI Service COM interface GUID
// This is the MidiSrvTransport placeholder used to detect if the MIDI Service
// bits are installed on the system. This UUID should not change.
// ============================================================================

struct __declspec(uuid("2BA15E4E-5417-4A66-85B8-2B2260EFBC84")) MidiSrvTransportPlaceholder : ::IUnknown
{ };


// ============================================================================
// MIDI SDK Client Initializer COM interface GUID
// Used to detect if the SDK Runtime and Tools are installed.
// ============================================================================

struct __declspec(uuid("c3263827-c3b0-bdbd-2500-ce63a3f3f2c3")) MidiClientInitializer : ::IUnknown
{ };


// ============================================================================
// Check 1: Registry - Is wdmaud2.drv present?
//
// Windows MIDI Services uses Controlled Feature Rollout (CFR) to gradually
// enable the feature on PCs. Even after a Windows Update installs the bits,
// the feature may not be enabled yet. The presence of wdmaud2.drv in the
// Drivers32 registry key is the indicator that CFR has enabled it.
// ============================================================================

bool CheckWdmaud2Registry()
{
std::wstring keyPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Drivers32";

for (int i = 0; i < 10; i++)
{
std::wstring valueName = (i == 0) ? L"midi" : L"midi" + std::to_wstring(i);

auto val = wil::reg::try_get_value_string(HKEY_LOCAL_MACHINE, keyPath.c_str(), valueName.c_str());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this instruction throw an exception? If yes, we need to wrap it to try/catch I suppose. Also what about the case when a user doesn't have rights to access registry? Will Windows MIDI Services work at all in this case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! wil::reg::try_get_value_string is the nothrow variant — it returns std::optional and doesn't throw on missing keys or access denied. If the key doesn't exist or the user can't read it, it just returns std::nullopt and the check reports "NOT FOUND" gracefully. The Drivers32 key has read access for all users by default, but even in a restricted scenario the code handles it fine — no crash, no exception.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this location must be readable by all users, or else MIDI 1.0 with the WinMM would never have worked. WinMM APIs are always loaded in-process with the consumer (DAW, utility, etc.) which is almost always running as the current user. Those registry entries are read as part of this.

Pete
Microsoft


if (val.has_value() && val.value() == L"wdmaud2.drv")
{
return true;
}
}

return false;
}


// ============================================================================
// Check 2: COM - Are MIDI Service interfaces registered?
//
// This verifies that the MIDI Service COM class is registered. It loads the
// COM server DLL into the process to test activation. Note: this alone is not
// sufficient because CFR may have the bits present but disabled.
// ============================================================================

bool CheckMidiServiceComRegistration()
{
wil::com_ptr_nothrow<IUnknown> servicePointer;

HRESULT hr = CoCreateInstance(
__uuidof(MidiSrvTransportPlaceholder),
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&servicePointer)
);

return SUCCEEDED(hr);
}


// ============================================================================
// Check 3: Service - Is midisrv.exe registered and not disabled?
//
// Verify that the MIDI Service exists in the Service Control Manager and is
// not disabled. A disabled service means CFR has not enabled it or it was
// manually disabled. This does NOT verify RPC connectivity — for that, see
// the midicheckservice tool which calls MidiSrvVerifyConnectivity().
// ============================================================================

// Returns:
// 0 = service is available and not disabled
// 1 = access denied opening Service Control Manager
// 2 = service not found (midisrv not registered)
// 3 = access denied opening midisrv service
// 4 = service is disabled
// -1 = other error (config query failed, allocation failed)
int CheckMidiServiceAvailable()
{
SC_HANDLE hSCManager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about access denied case? It affects only this check or entire Windows MIDI Services won't work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SC_MANAGER_CONNECT is the most basic SCM permission — any standard user has it. Access denied here would be a very unusual scenario (restrictive group policy, for example), and in that case the MIDI Service itself probably would not work either. But I agree it would be better to distinguish the cases — I can add a GetLastError() check to print a specific message for access denied instead of just "NOT AVAILABLE". Want me to make that change?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Want me to make that change?

If it's not too difficult, yes. Thank you!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! CheckMidiServiceAvailable now returns distinct error codes so the caller can tell if it was access denied (suggests running as Administrator) or a missing/disabled service (suggests midifixreg). Thanks for the feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

if (!hSCManager)
{
return (GetLastError() == ERROR_ACCESS_DENIED) ? 1 : -1;
}

auto closeSCM = wil::scope_exit([&] { CloseServiceHandle(hSCManager); });

SC_HANDLE hService = OpenServiceW(hSCManager, L"midisrv", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
if (!hService)
{
DWORD err = GetLastError();
if (err == ERROR_ACCESS_DENIED) return 3;
if (err == ERROR_SERVICE_DOES_NOT_EXIST) return 2;
return -1;
}

auto closeSvc = wil::scope_exit([&] { CloseServiceHandle(hService); });

// Query service configuration to check if it is disabled
DWORD bytesNeeded = 0;
QueryServiceConfigW(hService, nullptr, 0, &bytesNeeded);

if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || bytesNeeded == 0)
{
return -1;
}

LPQUERY_SERVICE_CONFIGW config = static_cast<LPQUERY_SERVICE_CONFIGW>(LocalAlloc(LPTR, bytesNeeded));
if (!config)
{
return -1;
}

auto freeConfig = wil::scope_exit([&] { LocalFree(config); });

if (!QueryServiceConfigW(hService, config, bytesNeeded, &bytesNeeded))
{
return -1;
}

// A disabled service cannot be started — this is the key check for CFR
if (config->dwStartType == SERVICE_DISABLED)
{
return 4;
}

return 0;
}


// ============================================================================
// Check 4: SDK Runtime - Is the SDK Runtime installed?
//
// This is optional. The SDK Runtime is only needed if your application uses
// the Windows MIDI Services SDK (the NuGet package). Applications that use
// only WinMM or WinRT MIDI 1.0 APIs do not need the SDK Runtime.
// ============================================================================

bool CheckSdkRuntimeInstalled()
{
wil::com_ptr_nothrow<IUnknown> initPointer;

HRESULT hr = CoCreateInstance(
__uuidof(MidiClientInitializer),
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&initPointer)
);

return SUCCEEDED(hr);
}


// ============================================================================
// Main
// ============================================================================

int __cdecl main()
{
HRESULT hrInit = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(hrInit))
{
std::cout << "Failed to initialize COM." << std::endl;
return -1;
}

auto comCleanup = wil::scope_exit([] { CoUninitialize(); });

std::cout << "Windows MIDI Services - Full Detection Sample" << std::endl;
std::cout << "==============================================" << std::endl;
std::cout << std::endl;

// ---- Check 1: Registry (CFR) ----
std::cout << "[1/4] Checking registry for wdmaud2.drv (Controlled Feature Rollout)... ";

bool registryOk = CheckWdmaud2Registry();
std::cout << (registryOk ? "FOUND" : "NOT FOUND") << std::endl;

if (!registryOk)
{
std::cout << std::endl;
std::cout << "RESULT: Windows MIDI Services has not been enabled on this PC." << std::endl;
std::cout << "The feature is rolled out gradually via Windows Update." << std::endl;
return 1;
}

// ---- Check 2: COM registration ----
std::cout << "[2/4] Checking MIDI Service COM registration... ";

bool comOk = CheckMidiServiceComRegistration();
std::cout << (comOk ? "REGISTERED" : "NOT REGISTERED") << std::endl;

if (!comOk)
{
std::cout << std::endl;
std::cout << "RESULT: MIDI Service COM interfaces are not registered." << std::endl;
std::cout << "The MIDI Service bits may not be installed on this version of Windows." << std::endl;
return 2;
}

// ---- Check 3: Service availability ----
std::cout << "[3/4] Checking MIDI Service (midisrv.exe) availability... ";

int serviceResult = CheckMidiServiceAvailable();
std::cout << (serviceResult == 0 ? "AVAILABLE" : "NOT AVAILABLE") << std::endl;

if (serviceResult != 0)
{
std::cout << std::endl;
if (serviceResult == 1 || serviceResult == 3)
{
std::cout << "RESULT: Access denied querying the MIDI Service." << std::endl;
std::cout << "Try running this tool as Administrator." << std::endl;
}
else
{
std::cout << "RESULT: MIDI Service is not available." << std::endl;
std::cout << "The service may be disabled or not yet enabled via Controlled Feature Rollout." << std::endl;
std::cout << "Try running 'midifixreg' from an Administrator command prompt and reboot." << std::endl;
}
return 3;
}

// ---- Check 4: SDK Runtime (optional) ----
std::cout << "[4/4] Checking SDK Runtime installation (optional)... ";

bool sdkOk = CheckSdkRuntimeInstalled();
std::cout << (sdkOk ? "INSTALLED" : "NOT INSTALLED") << std::endl;

std::cout << std::endl;
std::cout << "RESULT: Windows MIDI Services is installed and enabled." << std::endl;

if (!sdkOk)
{
std::cout << "Note: The SDK Runtime is not installed. If your app uses the MIDI SDK," << std::endl;
std::cout << "download it from https://aka.ms/midi" << std::endl;
}

return 0;
}
9 changes: 9 additions & 0 deletions samples/cpp/detectmidi-full/pch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation and Contributors.
// Licensed under the MIT License
// ============================================================================
// This is part of the Windows MIDI Services App SDK and should be used
// in your Windows application via an official binary distribution.
// Further information: https://aka.ms/midi
// ============================================================================

#include "pch.h"
25 changes: 25 additions & 0 deletions samples/cpp/detectmidi-full/pch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation and Contributors.
// Licensed under the MIT License
// ============================================================================
// This is part of the Windows MIDI Services App SDK and should be used
// in your Windows application via an official binary distribution.
// Further information: https://aka.ms/midi
// ============================================================================

#pragma once

#pragma warning (push)
#pragma warning (disable: 4005)

#include <windows.h>
#include <winsvc.h>

#include <iostream>
#include <string>

#include <wil\com.h>
#include <wil\resource.h>
#include <wil\result_macros.h>
#include <wil\registry.h>

#pragma warning (pop)