How to Send a Desktop Toast Notification from a Batch Script
Native Batch scripts are restricted to the console window. If your script is running in the background and completes an important task, like finishing a large download or completing a system audit, the user might not notice until they manually check the window. A "Toast Notification" is a modern Windows popup that slides in from the corner of the screen, just like an email or calendar alert. Using a PowerShell bridge, your Batch script can send these notifications to the user's desktop.
This guide will explain how to trigger Windows 10/11 toast notifications and simple message boxes from a Batch script.
Method 1: Toast Notification via PowerShell and WinRT
Windows 10 and 11 provide a built-in notification API through the Windows Runtime (WinRT). PowerShell can access this API without installing any third-party modules.
Implementation
@echo off
setlocal
set "Title=Backup Complete"
set "Message=Backup finished successfully at %time%."
set "AppId=MyBackupTool"
echo [INFO] Sending toast notification...
powershell -NoProfile -Command ^
"try {" ^
" [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null;" ^
" $template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent(" ^
" [Windows.UI.Notifications.ToastTemplateType]::ToastText02);" ^
" $textNodes = $template.GetElementsByTagName('text');" ^
" $textNodes.Item(0).InnerText = '%Title%';" ^
" $textNodes.Item(1).InnerText = '%Message%';" ^
" $toast = [Windows.UI.Notifications.ToastNotification]::new($template);" ^
" [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('%AppId%').Show($toast);" ^
" exit 0" ^
"} catch {" ^
" Write-Error $_.Exception.Message;" ^
" exit 1" ^
"}"
if errorlevel 1 (
echo [WARNING] Toast notification failed - falling back to console message. >&2
echo === %Title% ===
echo %Message%
)
endlocal
exit /b 0
How this works:
- The first line loads the WinRT
ToastNotificationManagertype into PowerShell. TheContentType = WindowsRuntimeattribute tells PowerShell to load it as a Windows Runtime type rather than a standard .NET assembly. GetTemplateContentretrieves a built-in XML template with two text fields (title and body).- The text nodes are populated with your title and message.
CreateToastNotifierregisters a notification source using your App ID string, andShow()displays the toast.
Important notes:
- Windows 10/11 only: The WinRT notification API does not exist on Windows 7 or 8. The script includes a fallback (console echo) for environments where the toast fails.
- User session required: Toast notifications are displayed on the interactive desktop. If the script runs as a SYSTEM service or a Task Scheduler task with "Run whether user is logged on or not," there is no desktop to display on, and the notification fails silently.
- App ID: The
AppIdstring identifies your script in the Action Center. Use a descriptive name so the user knows which tool sent the notification. This does NOT need to match an installed application.
Method 2: Simple Message Box (Fallback)
If you need a notification that works on older Windows versions or when the WinRT API is unavailable, a standard Windows Forms message box is a reliable fallback. Unlike a toast notification, a message box is modal, it stays on screen until the user clicks OK.
@echo off
setlocal
set "Title=Script Complete"
set "Message=The system audit has finished. Please review the results."
powershell -NoProfile -Command ^
"Add-Type -AssemblyName System.Windows.Forms;" ^
"[System.Windows.Forms.MessageBox]::Show(" ^
" '%Message%'," ^
" '%Title%'," ^
" [System.Windows.Forms.MessageBoxButtons]::OK," ^
" [System.Windows.Forms.MessageBoxIcon]::Information" ^
") | Out-Null"
endlocal
exit /b 0
When to use each method:
| Feature | Toast (Method 1) | MessageBox (Method 2) |
|---|---|---|
| Non-intrusive (doesn't block script) | Yes, dismisses automatically | No, waits for user to click OK |
| Stays in Action Center | Yes | No |
| Works on Windows 7/8 | No | Yes |
| Works from background tasks | No, needs interactive desktop | No, needs interactive desktop |
| Requires user response | No | Yes |
Method 3: A Reusable Notification Subroutine
In practice, you want to call notifications from various points in your script without duplicating the PowerShell snippet. This subroutine tries the modern toast first, then falls back to a message box, then to a console message.
@echo off
setlocal
set "AppId=DeployTool"
:: === Main Script Logic ===
echo [INFO] Starting deployment...
:: ... (deployment steps here) ...
call :Notify "Deployment Complete" "All files deployed successfully at %time%."
echo [INFO] Done.
endlocal
exit /b 0
:Notify
:: Usage: call :Notify "Title" "Message"
:: Tries toast notification first, falls back to message box, then console
:: Try toast notification (Windows 10/11)
powershell -NoProfile -Command ^
"try {" ^
" [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null;" ^
" $t = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent(1);" ^
" $x = $t.GetElementsByTagName('text');" ^
" $x.Item(0).InnerText = '%~1';" ^
" $x.Item(1).InnerText = '%~2';" ^
" [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('%AppId%').Show(" ^
" [Windows.UI.Notifications.ToastNotification]::new($t));" ^
" exit 0" ^
"} catch { exit 1 }" >nul 2>&1
if not errorlevel 1 exit /b 0
:: Fallback: message box (works on older Windows)
powershell -NoProfile -Command ^
"Add-Type -AssemblyName System.Windows.Forms;" ^
"[System.Windows.Forms.MessageBox]::Show('%~2','%~1'," ^
" [System.Windows.Forms.MessageBoxButtons]::OK," ^
" [System.Windows.Forms.MessageBoxIcon]::Information) | Out-Null" >nul 2>&1
if not errorlevel 1 exit /b 0
:: Final fallback: console output
echo [NOTIFICATION] %~1: %~2
exit /b 0
Why a cascading fallback:
- Toast works: Notification appears in the corner and in Action Center. Ideal.
- Toast fails, MessageBox works: A dialog box appears. Less elegant but still visible.
- Both fail (e.g., no interactive session, PowerShell unavailable): The message is printed to the console/log, so it is never lost entirely.
How to Avoid Common Errors
Problem: Special Characters in Messages
If your message contains ' (single quotes), &, |, %, or >, the Batch-to-PowerShell parameter passing can break because these characters have special meaning in one or both languages.
Solution: Keep notification messages simple. Use alphanumeric text and basic punctuation. Avoid embedding file paths or error messages that might contain special characters directly in the notification text. If you must include a path, replace backslashes with forward slashes or double them.
Problem: Notifications Fail Silently in Background Tasks
Task Scheduler tasks configured with "Run whether user is logged on or not" execute in a non-interactive session (Session 0). There is no desktop to display notifications on, so both toast notifications and message boxes fail silently.
Solution: Only send desktop notifications when the script is running in an interactive user session. Use session detection (see our guide on detecting parent processes) to determine the context:
:: Only notify if running in an interactive session
if defined SESSIONNAME (
call :Notify "Title" "Message"
) else (
echo [INFO] Non-interactive session - skipping desktop notification. >&2
)
Problem: Toast Notifications Not Appearing
If the notification API call succeeds (no error) but no toast appears, common causes include:
- Focus Assist / Do Not Disturb is enabled, suppressing notifications.
- Notification permissions for the app ID are disabled in Windows Settings > Notifications.
- The Action Center service is not running.
Solution: These are user/system configuration issues, not script bugs. Your script should not depend on the notification being seen: always log the same information to a file as a reliable record.
Wrong Way: Using msg * for Notifications
The msg * command creates a legacy popup that looks outdated, requires Terminal Services, may not work on all Windows editions, and cannot be dismissed by the system: it blocks until clicked.
Correct Way: Use toast notifications (Method 1) for non-intrusive modern alerts, or Windows Forms message boxes (Method 2) when you need to confirm the user has acknowledged the message.
Best Practices and Rules
1. Notify Only for Significant Events
Too many notifications are annoying and will be ignored. Reserve notifications for task completion, critical failures, or events that require user action. Do not notify for every file processed or every routine checkpoint.
2. Include Timestamps
Since toast notifications persist in the Action Center for hours, always include the time in the message text so the user knows when the event occurred: "Backup completed at 14:32:05".
3. Always Log in Addition to Notifying
A notification is transient: the user might miss it, dismiss it, or not be at their desk. Always write the same information to a log file or the Event Log so there is a permanent record regardless of whether the notification was seen.
4. Use a Descriptive App ID
The AppId string appears in the Action Center grouped with your other notifications. Use a meaningful name like BackupTool or DeployScript rather than a generic BatchScript.
5. Test on Your Target Windows Version
The WinRT notification API behaves differently across Windows 10 builds and Windows 11. Test on your target platforms to verify that notifications appear as expected.
Conclusions
Sending desktop toast notifications bridges the gap between legacy console scripts and the modern Windows user experience. By using the WinRT API through PowerShell, with cascading fallbacks for older systems and non-interactive sessions, you provide professional, non-intrusive alerts that integrate with the Windows Action Center. This visibility is essential for background automation where the user needs to know when a task completes or fails.