Windows Services perform a variety of tasks in the background, supporting our day-to-day desktop operations. Sometimes it may be necessary for the service to interact with the user for information or interface, which is no problem in the XP era, but since Vista began you will find that this approach does not seem to work.
Session 0 Isolation Experiment
Here's a service called Alertservice, which is to send a prompt dialog to the user, and we'll see what happens to the service in Windows 7.
Using System.ServiceProcess;
Using System.Windows.Forms;
Namespace Alertservice
{public
partial class Service1:servicebase
{public
Service1 ()
{ InitializeComponent ();
}
protected override void OnStart (string[] args)
{
MessageBox.Show ("A message from Alertservice.");
protected override void OnStop ()
{
}
}
}
After the program is compiled, it is loaded into the system service by InstallUtil:
In the service properties, check "Allow service to interact with desktop", which enables Alertservice to interact with desktop users.
The Alertservice service is "started" in Service Manager, and an icon flashes in the taskbar:
Clicking the icon displays the following window, prompting a program (Alertservice) to display the information and whether it needs to be browsed:
Try clicking "View the Message", will display the following image interface (in fact, this interface I can not from the current desktop operation screenshot, is through virtual PC screenshot, the reason to continue reading). Note that the desktop background of the following figure is no longer the default desktop background for Windows 7, stating that Alertservice is not the same as the desktop system session, which is the result of Session 0 isolation.
Session 0 Isolation Principle
In the era of Windows XP, Windows Server 2003, or earlier Windows systems, when the first user logged on to the system, the service and application ran in the same session. This is session 0 as shown in the following figure:
This operation, however, raises the risk of system security because services are run by elevated user rights, and applications are often run by ordinary users who do not have administrator status, and the danger is obvious.
From Vista start session 0, which includes only system services, other applications run with separate sessions to increase the security of the system by isolating the service from the application. As shown in the following illustration:
This allows session 0 to interact with other sessions and not to pop-up information windows, UI windows, etc. from the service to the desktop user. That's why I just said that the diagram can't be shot through the current desktop.
Session Check
In the actual development process, you can use process Explorer to check which session the service or program is in, and will not encounter session 0 isolation problem. We found the Alertservice service that was previously loaded in services, and the right-key property looks at its session state.
You can see that Alertservice is in session 0:
Then look at the Outlook application:
It is clear that services and applications in Windows 7 are in different sessions and that a protective wall is added between them, and in the next article we'll explain how to get the service to interact with desktop users through this wall of protection.
If you do need services to interact with desktop users during the development process, you can bypass session 0 isolation through the Remote Desktop Services API to complete the interaction.
For simple interactions, a service can display a message window on a user session through the wtssendmessage function. For some complex UI interactions, you must call CreateProcessAsUser or other methods (WCF,. NET remoting, and so on, to create an application interface on a desktop user.
Wtssendmessage function
If the service simply sends a message window to the desktop user session, it can be implemented using the Wtssendmessage function. First, add a Interop.cs class to the previous downloaded code and add the following code to the class:
public static void ShowMessageBox (String message, string title)
{
int resp = 0;
Wtssendmessage (
wts_current_server_handle,
Wtsgetactiveconsolesessionid (),
title, title. Length, message
, message. Length,
0, 0, out resp, false);
[DllImport ("kernel32.dll", SetLastError = True)]
public static extern int Wtsgetactiveconsolesessionid ();
[DllImport ("Wtsapi32.dll", SetLastError = True)]
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);
The wtssendmessage is invoked in the ShowMessageBox function to send an information window so that we can use it in the service's OnStart function, open the Service1.cs and add the following code:
protected override void OnStart (string[] args)
{
Interop.showmessagebox ("This a message from Alertservice.",
"Alertservice message");
}
After compiling the program, restart the Alertservice service in Service Manager, and from the following figure you can see that the message window is displayed on the current user's desktop, not session 0.
CreateProcessAsUser function
If you want to create a complex UI program interface through the service to the desktop user session, you need to use the CreateProcessAsUser function to create a new process for the user to run the program. Open the Interop class to continue adding the following code:
public static void CreateProcess (String app, string path) {bool result; IntPtr Htoken = WindowsIdentity.GetCurrent ().
Token;
IntPtr Hdupedtoken = IntPtr.Zero;
process_information pi = new process_information ();
Security_attributes sa = new security_attributes (); Sa.
Length = marshal.sizeof (SA);
Startupinfo si = new Startupinfo ();
SI.CB = marshal.sizeof (SI);
int dwsessionid = Wtsgetactiveconsolesessionid ();
result = Wtsqueryusertoken (Dwsessionid, out htoken);
if (!result) {ShowMessageBox ("Wtsqueryusertoken failed", "alertservice message"); result = Duplicatetokenex (Htoken, generic_all_access, ref SA, (int) security_impersonation_level. Securityidentification, (int) token_type.
Tokenprimary, ref Hdupedtoken);
if (!result) {ShowMessageBox ("Duplicatetokenex failed", "alertservice message");
} IntPtr lpenvironment = IntPtr.Zero;
result = Createenvironmentblock (out lpenvironment, Hdupedtoken, false); if (!result) {ShowMessageBox ("CreAteenvironmentblock failed "," alertservice message "); result = CreateProcessAsUser (Hdupedtoken, app, String.Empty, ref SA, ref SA, False
, 0, IntPtr.Zero, path, ref si, ref pi);
if (!result) {int error = Marshal.GetLastWin32Error ();
String message = String.Format ("CreateProcessAsUser error: {0}", error);
ShowMessageBox (Message, "Alertservice message");
} if (pi.hprocess!= IntPtr.Zero) CloseHandle (pi.hprocess);
if (pi.hthread!= IntPtr.Zero) CloseHandle (Pi.hthread);
if (Hdupedtoken!= IntPtr.Zero) CloseHandle (Hdupedtoken);
[StructLayout (layoutkind.sequential)] public struct STARTUPINFO {public Int32 cb;
public string lpreserved;
public string lpdesktop;
public string Lptitle;
Public Int32 DwX;
Public Int32 Dwy;
Public Int32 dwxsize;
Public Int32 Dwxcountchars;
Public Int32 Dwycountchars;
Public Int32 Dwfillattribute;
Public Int32 dwflags;
Public Int16 Wshowwindow;
Public Int16 CbReserved2; Public INTPTR LpReserved2;
Public IntPtr hStdInput;
Public IntPtr Hstdoutput;
Public IntPtr Hstderror;
[StructLayout (layoutkind.sequential)] public struct Process_information {public IntPtr hprocess;
Public IntPtr Hthread;
Public Int32 Dwprocessid;
Public Int32 dwThreadID;
[StructLayout (layoutkind.sequential)] public struct Security_attributes {public Int32 Length;
Public IntPtr Lpsecuritydescriptor;
public bool bInheritHandle; public enum Security_impersonation_level {securityanonymous, securityidentification, Securityimpersonation, Securi Tydelegation} public enum Token_type {tokenprimary = 1, tokenimpersonation} public const int generic_all_access = 0
x10000000;
[DllImport ("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, callingconvention = Callingconvention.stdcall)]
public static extern bool CloseHandle (INTPTR handle);
[DllImport ("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi, callingconvention = Callingconvention.stdcall)] Public static extern bool CreateProcessAsUser (IntPtr htoken, String lpapplicationname, String lpcommandline, ref Security_att Ributes lpprocessattributes, ref security_attributes lpthreadattributes, bool bInheritHandle, Int32 dwCreationFlags, I Ntptr lpenvrionment, String lpcurrentdirectory, ref startupinfo Lpstartupinfo, ref process_information LpProcessInforma
tion); [DllImport ("advapi32.dll", SetLastError = true)] public static extern bool Duplicatetokenex (IntPtr Hexistingtoken, Int3 2 dwdesiredaccess, ref security_attributes lpthreadattributes, Int32 impersonationlevel, Int32 dwtokentype, ref INTPTR
Phnewtoken); [DllImport ("Wtsapi32.dll", setlasterror=true)] public static extern bool Wtsqueryusertoken (Int32 sessionId, out INTPTR
Token); [DllImport ("Userenv.dll", SetLastError = True)] static extern bool Createenvironmentblock (out IntPtr lpenvironment, Tptr Htoken, bool binherit);
The use of Duplicatetokenex, Wtsqueryusertoken, and Createenvironmentblock functions is also involved in the CreateProcess function, and interested friends can learn from MSDN. Once the CreateProcess function is created, you can actually invoke the application through it, and go back to Service1.cs to modify the OnStart let's open a CMD window. The following code:
Copy Code code as follows:
protected override void OnStart (string[) args)
{
Interop.createprocess ("cmd.exe", @ "C:\Windows\System32\");
}
Recompile the program and start the Alertservice service to see the following image interface. So far, we can solve the session 0 isolation problem with some simple methods. You can also use WCF and other technologies to complete a number of more complex communication methods across the session, to achieve in Windows 7 and Vista system services and desktop users of the interactive operation.