From 30331dfa1bd5c9abeebbe3bbdf844297c9a86957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saulo=20Ver=C3=ADssimo?= Date: Tue, 10 Mar 2026 21:53:51 -0300 Subject: [PATCH 1/2] Add full MIDI setup detection sample Addresses #862 and #793. The existing detectmidi sample only checks COM activation, which gives false positives with Controlled Feature Rollout (CFR). This new sample performs registry, COM, service, and SDK Runtime checks based on the midicheckservice tool logic. --- samples/cpp/detectmidi-full/main.cpp | 266 +++++++++++++++++++++++++++ samples/cpp/detectmidi-full/pch.cpp | 9 + samples/cpp/detectmidi-full/pch.h | 25 +++ 3 files changed, 300 insertions(+) create mode 100644 samples/cpp/detectmidi-full/main.cpp create mode 100644 samples/cpp/detectmidi-full/pch.cpp create mode 100644 samples/cpp/detectmidi-full/pch.h diff --git a/samples/cpp/detectmidi-full/main.cpp b/samples/cpp/detectmidi-full/main.cpp new file mode 100644 index 000000000..70fe9688e --- /dev/null +++ b/samples/cpp/detectmidi-full/main.cpp @@ -0,0 +1,266 @@ +// 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 (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()); + + 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 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(). +// ============================================================================ + +bool CheckMidiServiceAvailable() +{ + SC_HANDLE hSCManager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT); + if (!hSCManager) + { + return false; + } + + auto closeSCM = wil::scope_exit([&] { CloseServiceHandle(hSCManager); }); + + SC_HANDLE hService = OpenServiceW(hSCManager, L"midisrv", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); + if (!hService) + { + return false; + } + + 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 false; + } + + LPQUERY_SERVICE_CONFIGW config = static_cast(LocalAlloc(LPTR, bytesNeeded)); + if (!config) + { + return false; + } + + auto freeConfig = wil::scope_exit([&] { LocalFree(config); }); + + if (!QueryServiceConfigW(hService, config, bytesNeeded, &bytesNeeded)) + { + return false; + } + + // A disabled service cannot be started — this is the key check for CFR + if (config->dwStartType == SERVICE_DISABLED) + { + return false; + } + + return true; +} + + +// ============================================================================ +// 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 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... "; + + bool serviceOk = CheckMidiServiceAvailable(); + std::cout << (serviceOk ? "AVAILABLE" : "NOT AVAILABLE") << std::endl; + + if (!serviceOk) + { + std::cout << std::endl; + std::cout << "RESULT: MIDI Service is not available." << std::endl; + std::cout << "The service may be disabled or the installation may be broken." << 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; +} diff --git a/samples/cpp/detectmidi-full/pch.cpp b/samples/cpp/detectmidi-full/pch.cpp new file mode 100644 index 000000000..80aa2a1a4 --- /dev/null +++ b/samples/cpp/detectmidi-full/pch.cpp @@ -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" diff --git a/samples/cpp/detectmidi-full/pch.h b/samples/cpp/detectmidi-full/pch.h new file mode 100644 index 000000000..b88eb3f48 --- /dev/null +++ b/samples/cpp/detectmidi-full/pch.h @@ -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 +#include + +#include +#include + +#include +#include +#include +#include + +#pragma warning (pop) From 988486704fecb4ea4fed18eee6718f8b5ba491ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saulo=20Ver=C3=ADssimo?= Date: Mon, 16 Mar 2026 09:13:51 -0300 Subject: [PATCH 2/2] Handle access denied in service availability check Address review feedback: CheckMidiServiceAvailable now returns distinct error codes so the caller can tell the user whether the failure is an access denied (suggest running as Administrator) or a missing/disabled service (suggest midifixreg). --- samples/cpp/detectmidi-full/main.cpp | 48 +++++++++++++++++++--------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/samples/cpp/detectmidi-full/main.cpp b/samples/cpp/detectmidi-full/main.cpp index 70fe9688e..f6b20627c 100644 --- a/samples/cpp/detectmidi-full/main.cpp +++ b/samples/cpp/detectmidi-full/main.cpp @@ -27,7 +27,7 @@ // -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 (disabled, missing, or not startable) +// 3 = Service not available (access denied, disabled, missing, or not startable) #include "pch.h" @@ -112,12 +112,19 @@ bool CheckMidiServiceComRegistration() // the midicheckservice tool which calls MidiSrvVerifyConnectivity(). // ============================================================================ -bool CheckMidiServiceAvailable() +// 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); if (!hSCManager) { - return false; + return (GetLastError() == ERROR_ACCESS_DENIED) ? 1 : -1; } auto closeSCM = wil::scope_exit([&] { CloseServiceHandle(hSCManager); }); @@ -125,7 +132,10 @@ bool CheckMidiServiceAvailable() SC_HANDLE hService = OpenServiceW(hSCManager, L"midisrv", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); if (!hService) { - return false; + 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); }); @@ -136,29 +146,29 @@ bool CheckMidiServiceAvailable() if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || bytesNeeded == 0) { - return false; + return -1; } LPQUERY_SERVICE_CONFIGW config = static_cast(LocalAlloc(LPTR, bytesNeeded)); if (!config) { - return false; + return -1; } auto freeConfig = wil::scope_exit([&] { LocalFree(config); }); if (!QueryServiceConfigW(hService, config, bytesNeeded, &bytesNeeded)) { - return false; + return -1; } // A disabled service cannot be started — this is the key check for CFR if (config->dwStartType == SERVICE_DISABLED) { - return false; + return 4; } - return true; + return 0; } @@ -235,15 +245,23 @@ int __cdecl main() // ---- Check 3: Service availability ---- std::cout << "[3/4] Checking MIDI Service (midisrv.exe) availability... "; - bool serviceOk = CheckMidiServiceAvailable(); - std::cout << (serviceOk ? "AVAILABLE" : "NOT AVAILABLE") << std::endl; + int serviceResult = CheckMidiServiceAvailable(); + std::cout << (serviceResult == 0 ? "AVAILABLE" : "NOT AVAILABLE") << std::endl; - if (!serviceOk) + if (serviceResult != 0) { std::cout << std::endl; - std::cout << "RESULT: MIDI Service is not available." << std::endl; - std::cout << "The service may be disabled or the installation may be broken." << std::endl; - std::cout << "Try running 'midifixreg' from an Administrator command prompt and reboot." << 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; }