The only solution I found was this function: Send-TSMessageBox.
http://pinvoke.net/default.aspx/wtsapi32.WTSSendMessage
This could be run as SYSTEM but shows up on the user's desktop.
One downside: On a virtual machine, the message box shows up in session 0 (which is the Hyper-V "integrated" connection window). If you are connected via RDP (mstc) session you will not see the message box.
But on a Citrix desktop it works. The message box pops up within the user session and not on the Citrix host.
Here's the full function:
Function Send-TSMessageBox {
<#
.SYNOPSIS
Send a message or prompt to the interactive user with the ability to get the results.
.DESCRIPTION
Allows the administrator to send a message / prompt to an interactive user.
.EXAMPLE
"Send a message immediately w/o waiting for a responce."
Send-TSMessageBox -Title "Email Problem" -Message "We are currently having delays and are working on the issue."
"Send a message waiting 60 seconds for a reponse of [Yes / No]."
$Result = Send-TSMessageBox -Title "System Updated" -Message "System requires a reboot. Would you like to the reboot system now?" `
-ButtonSet 4 -Timeout 60 -WaitResponse $true
.ButtonSets
0 = OK
1 = Ok/Cancel
2 = Abort/Retry/Ignore
3 = Yes/No/Cancel
4 = Yes/No
5 = Retry/Cancel
6 = Cancel/Try Again/Continue
.Results
"" = 0
"Ok" = 1
"Cancel" = 2
"Abort" = 3
"Retry" = 4
"Ignore" = 5
"Yes" = 6
"No" = 7
"Try Again" = 10
"Continue" = 11
"Timed out" = 32000
"Not set to wait" = 32001
.NOTES
Author: Raymond H Clark
Twitter: @Rowdybullgaming
.RESOURCES
http://technet.microsoft.com/en-us/query/aa383488
http://technet.microsoft.com/en-us/query/aa383842
http://pinvoke.net/default.aspx/wtsapi32.WTSSendMessage
#>
Param([string]$Title = "Title", [string]$Message = "Message", [int]$ButtonSet = 0, [int]$Timeout = 0, [bool]$WaitResponse = $false)
$Signature = @"
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSSendMessage(
IntPtr hServer,
[MarshalAs(UnmanagedType.I4)] int SessionId,
String pTitle,
[MarshalAs(UnmanagedType.U4)] int TitleLength,
String pMessage,
[MarshalAs(UnmanagedType.U4)] int MessageLength,
[MarshalAs(UnmanagedType.U4)] int Style,
[MarshalAs(UnmanagedType.U4)] int Timeout,
[MarshalAs(UnmanagedType.U4)] out int pResponse,
bool bWait);
[DllImport("kernel32.dll")]
public static extern uint WTSGetActiveConsoleSessionId();
"@
[int]$TitleLength = $Title.Length;
[int]$MessageLength = $Message.Length;
[int]$Response = 0;
$MessageBox = Add-Type -memberDefinition $Signature -name "WTSAPISendMessage" -namespace "WTSAPI" -passThru
$SessionId = $MessageBox::WTSGetActiveConsoleSessionId()
$MessageBox::WTSSendMessage(0, $SessionId, $Title, $TitleLength, $Message, $MessageLength, $ButtonSet, $Timeout, [ref] $Response, $WaitResponse)
$Response
}
Unfortunately the design of the messsage box is very limited. Actually it looks ugly :-)