.NET version
.NET 10.0 (observed in an app targeting net10.0-windows10.0.17763.0)
Did it work in .NET Framework?
Yes
Did it work in any of the earlier releases of .NET Core or .NET 5+?
No. TaskDialog was introduced in .NET Core 3.1 / .NET 5 (it does not exist in .NET Framework).
Issue description
TaskDialog.ShowDialog throws an unhandled IndexOutOfRangeException from TaskDialogPage.GetBoundButtonByID when the native TaskDialogIndirect API returns a button ID that falls in the custom-button range (≥ 100) but the page has no custom buttons configured.
Stack trace
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Windows.Forms.TaskDialogPage.GetBoundButtonByID(Int32 buttonID)
at System.Windows.Forms.TaskDialog.ShowDialogInternal(IntPtr hwndOwner, TaskDialogPage page, TaskDialogStartupLocation startupLocation)
at System.Windows.Forms.TaskDialog.ShowDialog(IntPtr hwndOwner, TaskDialogPage page, TaskDialogStartupLocation startupLocation)
Root cause
In TaskDialogPage.GetBoundButtonByID (source: src/System.Windows.Forms/System/Windows/Forms/Dialogs/TaskDialog/TaskDialogPage.cs):
internal TaskDialogButton? GetBoundButtonByID(int buttonID)
{
if (BoundDialog is null)
{
throw new InvalidOperationException();
}
if (buttonID == 0)
{
return null;
}
// Check if the button is part of the custom buttons.
if (buttonID >= CustomButtonStartID) // CustomButtonStartID = 100
{
return _boundCustomButtons![buttonID - CustomButtonStartID]; // ← No bounds check!
}
else
{
_boundStandardButtonsByID!.TryGetValue(buttonID, out TaskDialogButton? button);
return button;
}
}
When buttonID >= 100 but _boundCustomButtons is empty (because no custom buttons were configured), the array access _boundCustomButtons[buttonID - 100] throws IndexOutOfRangeException.
The caller in TaskDialog.ShowDialogInternal already anticipates this method possibly returning null and handles it:
return _boundPage.GetBoundButtonByID(resultButtonID) ??
CreatePlaceholderButton((TaskDialogResult)resultButtonID);
But the exception prevents the null-coalescing fallback from ever executing.
Why might TaskDialogIndirect return an unexpected ID?
The .NET source already acknowledges that TaskDialogIndirect can return unexpected button IDs in abnormal close scenarios:
"in some cases when the dialog is closed abnormally (e.g. when closing the main window while a modeless task dialog is displayed), the dialog returns IDCANCEL (2) without priorly raising the TDN_BUTTON_CLICKED notification."
In our case, the returned ID was ≥ 100 (custom-button range) rather than a standard button ID, and it was returned for a dialog that only had a standard OK button configured. We encountered this while showing a TaskDialog to warn the user about a third-party injected DLL (AudioDevProps2.dll from Nahimic / A-Volute) detected in the process. We cannot confirm whether the injected DLL is the direct cause of the unexpected return value, but regardless of the trigger, GetBoundButtonByID should not throw on an out-of-range ID — it should return null, which the caller already handles.
Same bug in GetBoundRadioButtonByID
GetBoundRadioButtonByID in the same file has an identical unguarded access:
internal TaskDialogRadioButton? GetBoundRadioButtonByID(int buttonID)
{
if (BoundDialog is null)
{
throw new InvalidOperationException();
}
return buttonID == 0 ? null : _radioButtons[buttonID - RadioButtonStartID]; // ← No bounds check
}
Steps to reproduce
Note: I could not reproduce this on a clean machine. The crash was observed in a real-world scenario where the dialog was closed abnormally (see below). However, the underlying bug — an unguarded array access — can be verified directly from the source code.
Our scenario
We encountered this in an open-source WPF app (MaaAssistantArknights). Our app detects third-party DLLs that get injected into the process (e.g. audio OSD software like Nahimic), because these injected DLLs are known to break WPF rendering and cause crashes. To warn the user about this, we deliberately chose to use TaskDialog (WinForms) instead of a WPF dialog — precisely because the injected DLLs we are warning about already break WPF rendering, so we cannot rely on WPF to display the warning itself.
The crash is intermittent — sometimes the dialog returns normally, sometimes it crashes. When it does crash, the exception propagates up and terminates the app.
Our calling code
var page = new TaskDialogPage
{
Caption = "MAA",
Heading = "Warning Heading",
Text = warningText,
Icon = TaskDialogIcon.Warning,
Buttons = { TaskDialogButton.OK }, // Only a standard button (ID = 1), no custom buttons
SizeToContent = true,
Verification = new()
{
Text = "Don't show again",
Checked = false,
},
};
var result = TaskDialog.ShowDialog(owner, page);
The dialog only has a standard OK button (ID = 1), so normally TaskDialogIndirect should return 1. In our case, it returned an ID ≥ 100 (custom-button range) instead, and GetBoundButtonByID tried to index into the empty _boundCustomButtons array and threw.
The injected DLL in question was AudioDevProps2.dll from Nahimic / A-Volute (C:\ProgramData\A-Volute\A-Volute.Nahimic\Modules\Scheduled\x64\AudioDevProps2.dll). We cannot confirm whether the injected DLL is the direct cause of the unexpected return value — the crash may also be triggered by other abnormal close paths.
Log excerpt
We received reports from two different users. Both had the same injected DLL (AudioDevProps2.dll from Nahimic) detected in their processes.
In the first user's log, the app crashed once at startup, then on the next launch the dialog was shown and dismissed normally, and our code successfully logged the detected module:
[2026-07-31 12:08:22.071][FTL][Bootstrapper] <2> Unhandled exception occurred
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Windows.Forms.TaskDialogPage.GetBoundButtonByID(Int32 buttonID)
at System.Windows.Forms.TaskDialog.ShowDialogInternal(IntPtr hwndOwner, TaskDialogPage page, TaskDialogStartupLocation startupLocation)
at System.Windows.Forms.TaskDialog.ShowDialog(IntPtr hwndOwner, TaskDialogPage page, TaskDialogStartupLocation startupLocation)
at MaaWpfGui.Utilities.BadModules.CheckAndWarnBadInjectedModules()
at MaaWpfGui.Main.Bootstrapper.OnLaunch()
...
[2026-07-31 12:31:53.053][WRN][BadModules] <2> Detected bad injected modules:
C:\ProgramData\A-Volute\A-Volute.Nahimic\Modules\Scheduled\x64\AudioDevProps2.dll
In the second user's log, the crash occurred on every launch (4 times), and the app never started successfully:
[2026-07-31 13:34:24.533][FTL][Bootstrapper] <2> Unhandled exception occurred
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Windows.Forms.TaskDialogPage.GetBoundButtonByID(Int32 buttonID)
...
[2026-07-31 13:37:08.930][FTL][Bootstrapper] <2> Unhandled exception occurred
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Windows.Forms.TaskDialogPage.GetBoundButtonByID(Int32 buttonID)
...
[2026-07-31 13:37:34.020][FTL][Bootstrapper] <2> Unhandled exception occurred
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Windows.Forms.TaskDialogPage.GetBoundButtonByID(Int32 buttonID)
...
[2026-07-31 13:44:18.728][FTL][Bootstrapper] <2> Unhandled exception occurred
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Windows.Forms.TaskDialogPage.GetBoundButtonByID(Int32 buttonID)
...
The crash is intermittent — sometimes the dialog returns normally, sometimes it crashes. We could not determine what makes the difference.
Verification without reproduction
The bug can be confirmed by inspecting TaskDialogPage.GetBoundButtonByID: when _boundCustomButtons is empty and buttonID >= 100, the expression _boundCustomButtons[buttonID - CustomButtonStartID] will always throw IndexOutOfRangeException. There is no bounds check or fallback to null, even though the caller (ShowDialogInternal) already handles null via ?? CreatePlaceholderButton(...).
.NET version
.NET 10.0 (observed in an app targeting net10.0-windows10.0.17763.0)
Did it work in .NET Framework?
Yes
Did it work in any of the earlier releases of .NET Core or .NET 5+?
No. TaskDialog was introduced in .NET Core 3.1 / .NET 5 (it does not exist in .NET Framework).
Issue description
TaskDialog.ShowDialogthrows an unhandledIndexOutOfRangeExceptionfromTaskDialogPage.GetBoundButtonByIDwhen the nativeTaskDialogIndirectAPI returns a button ID that falls in the custom-button range (≥ 100) but the page has no custom buttons configured.Stack trace
Root cause
In
TaskDialogPage.GetBoundButtonByID(source:src/System.Windows.Forms/System/Windows/Forms/Dialogs/TaskDialog/TaskDialogPage.cs):When
buttonID >= 100but_boundCustomButtonsis empty (because no custom buttons were configured), the array access_boundCustomButtons[buttonID - 100]throwsIndexOutOfRangeException.The caller in
TaskDialog.ShowDialogInternalalready anticipates this method possibly returningnulland handles it:But the exception prevents the null-coalescing fallback from ever executing.
Why might
TaskDialogIndirectreturn an unexpected ID?The .NET source already acknowledges that
TaskDialogIndirectcan return unexpected button IDs in abnormal close scenarios:In our case, the returned ID was ≥ 100 (custom-button range) rather than a standard button ID, and it was returned for a dialog that only had a standard
OKbutton configured. We encountered this while showing aTaskDialogto warn the user about a third-party injected DLL (AudioDevProps2.dllfrom Nahimic / A-Volute) detected in the process. We cannot confirm whether the injected DLL is the direct cause of the unexpected return value, but regardless of the trigger,GetBoundButtonByIDshould not throw on an out-of-range ID — it should returnnull, which the caller already handles.Same bug in GetBoundRadioButtonByID
GetBoundRadioButtonByIDin the same file has an identical unguarded access:Steps to reproduce
Note: I could not reproduce this on a clean machine. The crash was observed in a real-world scenario where the dialog was closed abnormally (see below). However, the underlying bug — an unguarded array access — can be verified directly from the source code.
Our scenario
We encountered this in an open-source WPF app (MaaAssistantArknights). Our app detects third-party DLLs that get injected into the process (e.g. audio OSD software like Nahimic), because these injected DLLs are known to break WPF rendering and cause crashes. To warn the user about this, we deliberately chose to use
TaskDialog(WinForms) instead of a WPF dialog — precisely because the injected DLLs we are warning about already break WPF rendering, so we cannot rely on WPF to display the warning itself.The crash is intermittent — sometimes the dialog returns normally, sometimes it crashes. When it does crash, the exception propagates up and terminates the app.
Our calling code
The dialog only has a standard
OKbutton (ID = 1), so normallyTaskDialogIndirectshould return1. In our case, it returned an ID ≥ 100 (custom-button range) instead, andGetBoundButtonByIDtried to index into the empty_boundCustomButtonsarray and threw.The injected DLL in question was
AudioDevProps2.dllfrom Nahimic / A-Volute (C:\ProgramData\A-Volute\A-Volute.Nahimic\Modules\Scheduled\x64\AudioDevProps2.dll). We cannot confirm whether the injected DLL is the direct cause of the unexpected return value — the crash may also be triggered by other abnormal close paths.Log excerpt
We received reports from two different users. Both had the same injected DLL (
AudioDevProps2.dllfrom Nahimic) detected in their processes.In the first user's log, the app crashed once at startup, then on the next launch the dialog was shown and dismissed normally, and our code successfully logged the detected module:
In the second user's log, the crash occurred on every launch (4 times), and the app never started successfully:
The crash is intermittent — sometimes the dialog returns normally, sometimes it crashes. We could not determine what makes the difference.
Verification without reproduction
The bug can be confirmed by inspecting
TaskDialogPage.GetBoundButtonByID: when_boundCustomButtonsis empty andbuttonID >= 100, the expression_boundCustomButtons[buttonID - CustomButtonStartID]will always throwIndexOutOfRangeException. There is no bounds check or fallback tonull, even though the caller (ShowDialogInternal) already handlesnullvia?? CreatePlaceholderButton(...).