Private/Send-sqmWtsMessage.ps1
|
<#
.SYNOPSIS Sendet eine Netzwerk-Meldung an aktive interaktive Sitzungen auf einem Windows-Host. .DESCRIPTION Ruft WTSSendMessage aus wtsapi32.dll direkt per P/Invoke auf, statt msg.exe als externes Programm aufzurufen. Grund: msg.exe (Terminal-Services-Kommandozeilentool) ist Teil der Remote-Desktop-Services-Admintools und auf Windows-Client-SKUs (getestet: Windows 11 Home) haeufig NICHT vorhanden - wtsapi32.dll dagegen ist Bestandteil jeder Windows-Installation (Server wie Client) und wird von msg.exe selbst nur duenn umschlossen. Verifiziert per Livetest DEV03 (lokal, Session 'Console') und DEV03 -> DEV01 (cross-machine, Workgroup ohne Domaene) - beide WTSSendMessage-Aufrufe lieferten TRUE. Sendet an alle Sitzungen mit WTS-Status "Active" (0) auf dem Zielhost. Sitzungen ohne Benutzer (z.B. Session 0 "Services") oder nur "Connected" ohne aktiven Desktop werden nicht angeschrieben. .PARAMETER ComputerName Zielhost, an den die Meldung gesendet werden soll. .PARAMETER Title Titel des Meldungsfensters. .PARAMETER Message Nachrichtentext. .PARAMETER TimeoutSeconds Anzeigedauer beim Empfaenger, bevor das Fenster automatisch verschwindet. 0 = kein Timeout. .NOTES Erfordert auf dem Zielhost Zugriffsrechte fuer Remote-Terminaldienste-Abfragen (i.d.R. lokale Administratorrechte dort) sowie Netzwerkerreichbarkeit (RPC). Gibt KEINE Exception bei Erreichbarkeits-/Rechteproblemen - der Aufrufer wertet WasDelivered/Error aus. #> function Send-sqmWtsMessage { [CmdletBinding()] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true)] [string]$ComputerName, [Parameter(Mandatory = $false)] [string]$Title = 'sqmSQLTool', [Parameter(Mandatory = $true)] [string]$Message, [Parameter(Mandatory = $false)] [int]$TimeoutSeconds = 15 ) if (-not ('sqmSQLTool.Native.Wts' -as [type])) { Add-Type -Namespace sqmSQLTool.Native -Name Wts -MemberDefinition @' [DllImport("wtsapi32.dll", SetLastError = true)] public static extern IntPtr WTSOpenServer(string pServerName); [DllImport("wtsapi32.dll")] public static extern void WTSCloseServer(IntPtr hServer); [DllImport("wtsapi32.dll", SetLastError = true)] public static extern bool WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, out IntPtr ppSessionInfo, out int pCount); [DllImport("wtsapi32.dll")] public static extern void WTSFreeMemory(IntPtr pMemory); [DllImport("wtsapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool WTSSendMessage(IntPtr hServer, int SessionId, string pTitle, int TitleLength, string pMessage, int MessageLength, int Style, int Timeout, out int pResponse, bool bWait); '@ } # WTS_SESSION_INFO: { int SessionId; IntPtr pWinStationName; int State } - 24 Byte auf x64 # (4 Byte SessionId + 4 Byte Padding + 8 Byte Pointer + 4 Byte State + 4 Byte Padding). $sessionInfoSize = 24 $wtsActive = 0 $hServer = [IntPtr]::Zero $pSessionInfo = [IntPtr]::Zero $sentToSessions = [System.Collections.Generic.List[int]]::new() $errorMessage = $null try { $hServer = [sqmSQLTool.Native.Wts]::WTSOpenServer($ComputerName) if ($hServer -eq [IntPtr]::Zero) { $errorMessage = "WTSOpenServer('$ComputerName') lieferte ein Null-Handle." } else { [int]$sessionCount = 0 $enumOk = [sqmSQLTool.Native.Wts]::WTSEnumerateSessions($hServer, 0, 1, [ref]$pSessionInfo, [ref]$sessionCount) if (-not $enumOk) { $errorMessage = "WTSEnumerateSessions auf '$ComputerName' fehlgeschlagen (Win32Error $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error()))." } else { for ($i = 0; $i -lt $sessionCount; $i++) { $entryAddr = [IntPtr]::Add($pSessionInfo, $i * $sessionInfoSize) $sessionId = [System.Runtime.InteropServices.Marshal]::ReadInt32($entryAddr, 0) $state = [System.Runtime.InteropServices.Marshal]::ReadInt32($entryAddr, 16) if ($state -ne $wtsActive) { continue } [int]$response = 0 $sendOk = [sqmSQLTool.Native.Wts]::WTSSendMessage($hServer, $sessionId, $Title, $Title.Length, $Message, $Message.Length, 0, $TimeoutSeconds, [ref]$response, $false) if ($sendOk) { $sentToSessions.Add($sessionId) } } if ($sentToSessions.Count -eq 0) { $errorMessage = "Keine aktive interaktive Sitzung auf '$ComputerName' gefunden." } } } } catch { $errorMessage = "Ausnahme beim Senden an '$ComputerName': $($_.Exception.Message)" } finally { if ($pSessionInfo -ne [IntPtr]::Zero) { [sqmSQLTool.Native.Wts]::WTSFreeMemory($pSessionInfo) } if ($hServer -ne [IntPtr]::Zero) { [sqmSQLTool.Native.Wts]::WTSCloseServer($hServer) } } [PSCustomObject]@{ ComputerName = $ComputerName WasDelivered = ($sentToSessions.Count -gt 0) SessionIds = $sentToSessions Error = $errorMessage } } |