28912

.NET Windows Services (WTSSendMessage) : Displays message on XP but not Windows 7

Question:

I have a issue when display a message using .NET window service on Windows 7.It worked properly on Windows XP.I know that it doesn't work on Windows 7 as explained in Microsoft site and a few forums including Stackoverflow.I have followed the following example(using WTSSendMessage) from Pinvoke.NET as mentioned in the Stackoverflow.But it didn't work either.that example worked on Windows XP properly.Can some one please help me as this is a really big issue needed to be fixed ASAP since we already migrated to Windows7.

<strong>Signature:</strong>

[DllImport("wtsapi32.dll", SetLastError=true)] 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);

<strong>Variable declaration:</strong>

public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; public static int WTS_CURRENT_SESSION = -1;

<strong>Code:</strong>

bool result = false; String title = "Hello"; int tlen = title.Length; String msg = "Terminal Service!"; int mlen = msg.Length; int resp = 0; result = WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, title, tlen, msg, mlen, 0, 0, out resp, false); int err = Marshal.GetLastWin32Error(); System.Console.WriteLine("result:{0}, errorCode:{1}, response:{2}", result, err, resp);

Answer1:

WTS_CURRENT_SESSION for a service on Windows 7 or later will equate to session 0, which isn't allowed user interaction. You'll have to enumerate all the active sessions and send a message to each (which should just be one, unless the computer is running terminal services). Something like the following:

<strong>signatures:</strong>

[DllImport("wtsapi32.dll", SetLastError = true)] static extern int WTSEnumerateSessions( System.IntPtr hServer, int Reserved, int Version, ref System.IntPtr ppSessionInfo, ref int pCount); public enum WTS_CONNECTSTATE_CLASS { WTSActive, WTSConnected, WTSConnectQuery, WTSShadow, WTSDisconnected, WTSIdle, WTSListen, WTSReset, WTSDown, WTSInit }

<strong>helper function:</strong>

public static List<Int32> GetActiveSessions(System.IntPtr server) { List<Int32> ret = new List<int>(); IntPtr ppSessionInfo = IntPtr.Zero; Int32 count = 0; Int32 retval = WTSEnumerateSessions(server, 0, 1, ref ppSessionInfo, ref count); Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); Int64 current = (int)ppSessionInfo; if (retval != 0) { for (int i = 0; i < count; i++) { WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO)); current += dataSize; if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) ret.Add(si.SessionID); } WTSFreeMemory(ppSessionInfo); } return ret; }

<strong>in your code:</strong>

List<Int32> sessions = GetActiveSessions(WTS_CURRENT_SERVER_HANDLE); if (sessions.Count < 1) { int err = Marshal.GetLastWin32Error(); _SUDSEventLog.WriteEntry("No active sessions found: errorCode:", err); } else { bool result = false; String title = "Hello"; int tlen = title.Length; String msg = "Terminal Service!"; int mlen = msg.Length; int resp = 0; result = WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, sessions[0], title, tlen, msg, mlen, 0, 0, out resp, false); int err = Marshal.GetLastWin32Error(); System.Console.WriteLine("result:{0}, errorCode:{1}, response:{2}", result, err, resp); }

Answer2:

I believe that this maybe part of Windows Messenger that is not present in Windows 7.

This <a href="http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/tsleft.web%20WIL~Extenders/Terminal~Server%20wtsSendMessage~and~Win7.txt" rel="nofollow">link</a> shows why (RPC disabled by default)

and possible solution:

<blockquote>

By default remote RPC is disabled on Windows 7. It can be enabled by setting the 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server' value 'AllowRemoteRPC' to 1. However, this will only get you another error - 0x721 - which I am guessing is caused by Windows 7 not allowing 'nul' user access. There are several possible ways around this problem but I haven't tried any of them. One approach would be to add credentials from remote machines to the credential cache on the sending machine but I don't know how practical that is. There may be a way to allow 'nul' user access but that is probably not a good idea either.

</blockquote>

And this <a href="http://msdn.microsoft.com/en-us/gg465093" rel="nofollow">MSDN Article</a>: Shows a long worked example on doing this on what looks like Windows 7.

Answer3:

You can show a non-blocking Message Box from your Windows Service to the User. For this, firstly, start a Service named "Interactive Service Detection" from "services.msc". Then initiate a Thread from your Windows Service. In the thread method use "MessageBox.Show()" method to display your Message box to user. Instead of manually starting "Interactive Service Detection" service you can start it c# program.

Answer4:

<strong>YES it can be done!</strong>

I know this is a little late, and this suggestion <em>appears</em> a bit dreadful it actually isn't: you can ensure that the <strong>FIRST</strong> currently logged in user is messaged from a service simply by changing the line

public static int WTS_CURRENT_SESSION = -1;

to

public static int WTS_CURRENT_SESSION = 1;

this is because since Vista (EG: 2008, Vista, 2008 R2, Win7, Win 2012, Win 8, Win 2016, Win Ten etc) the ID is simply the <strong>first logged</strong> in user is always 1, the next 2 etc.

Similarly (.. and, therefore) you can ensure the <strong>LAST</strong> logged in user by doing the reverse, going DOWN the numbers I know it's naff but you simply loop ints down until you get a non error. You need to go down as the WTSSendMessage calls await a result procedurally (i.e. lock/wait the current thread)

Therefore: a solution whereby you message <strong>ALL</strong> logged in users (by setting the timeout to zero) and dispatching to a <em>guesswork of numbers</em> in parallel, via all different threads, and simply ignoring the dead threads.

I use a NuGet package to convert my <em>c#console</em> code to <em>c#service</em> via TopShelf <a href="https://www.c-sharpcorner.com/article/creating-windows-service-in-net-with-topshelf/" rel="nofollow">following this tutorial</a> and using the Thread[STAThread] to make sure this works. It will always call the LAST logged in user which in Windows Ten is only ever one person.

class Service { [DllImport("wtsapi32.dll", SetLastError = true)] 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); public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; public static int WTS_CURRENT_SESSION = 1; public void Start() { for (int user_session = 10; user_session>0; user_session--) { Thread t = new Thread(() => { try { bool result = false; String title = "Alert"; int tlen = title.Length; String msg = "hi"; int mlen = msg.Length; int resp = 7; result = WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, user_session, title, tlen, msg, mlen, 4, 0, out resp, true); int err = Marshal.GetLastWin32Error(); if (err == 0) { if (result) //user responded to box { if (resp == 7) //user clicked no { } else if (resp == 6) //user clicked yes { } Debug.WriteLine("user_session:" + user_session + " err:" + err + " resp:" + resp); } } } catch (Exception ex) { Debug.WriteLine("no such thread exists", ex); } //Application App = new Application(); //App.Run(new MessageForm()); }); t.SetApartmentState(ApartmentState.STA); t.Start(); } } public void Stop() { // write code here that runs when the Windows Service stops. } }

in case you're wondering this does work and the above code is all you need if you're following the boiler plate TopShelf examples

Recommend

  • How to take users session screen shot using C#
  • WTSQuerySessionInformation returning empty strings
  • iOS: How to convert the self-drawn content of an UIView to an image (widespread general solution ret
  • Extending enums
  • SWIFT uiviewcontroller init
  • Should this be giving a compile error?
  • Intermediate and return values in continuation-passing style
  • DELETE FROM … reporting syntax error at or near “.”
  • Saving Files in silverlight and asynchronous call backs
  • Observable.forkJoin() TS2322 error after updating to TypeScript 2.4.1 and Rxjs 5.4.2
  • Tracking screen recorder in windows app
  • Duplicated password validation messages ONLY IF new password is 1 char long
  • SQL Server 2005, Caches and all that jazz
  • Process.PrivateMemorySize64 returning committed memory instead of private
  • Angular Ui-router can't access $stateParams inside my controller
  • ASP.NET MVC - Detect Time Spent on Page
  • How to get google-services.json from Developer console?
  • Deployments not visible in Kubernetes Dashboard
  • How to resolve docker host names (/etc/hosts) in containers
  • Creating My Symmetric Key in C#
  • It is possible use the same sql azure instance from two different cloud service of two different sub
  • Zend Framework bassed projects
  • Are Richfaces and Primefaces compatible with each other?
  • Request response issues in biztalk
  • HttpListener.IsSupported is false on XP SP3
  • How do I retrieve the user information of a user authenticated with Apache's mod_ldap?
  • cordova is not defined - cordova.js has already been loaded :: Ionic
  • Clear fused location provider's location for testing
  • Center align outputs in ipython notebook
  • How to run “Deployd” on port 80 instead of port 5000 in webserver.
  • azure media services - The request body is too large and exceeds the maximum permissible limit
  • SSO with signing and signature validation doesn't work
  • Deserializing XML into class C#
  • ActionScript 2 vs ActionScript 3 performance
  • How can I estimate amount of memory left with calling System.gc()?
  • Apache 2.4 - remove | delete | uninstall
  • How to include full .NET prerequisite for Wix Burn installer
  • How do you join a server to an Active Directory (domain)?
  • costura.fody for a dll that references another dll
  • jQuery Masonry / Isotope and fluid images: Momentary overlap on window resize