[Sample of Feb 28th] Interactive Windows Service

 

Homepage image
Sample of the Day RSS Feed

Sample download: https://code.msdn.microsoft.com/CSCreateProcessAsUserFromSe-b682134e

Today’s code sample demonstrates a very frequently asked programming scenario for Windows developers: how to create/launch a process interactively in the session of the logged-on user from a Windows Service application.

The sample was written by Microsoft Support Engineer Ashish Thakur.

imageYou can find more code samples that demonstrate the most typical programming scenarios by using Microsoft All-In-One Code Framework Sample Browser or Sample Browser Visual Studio extension. They give you the flexibility to search samples, download samples on demand, manage the downloaded samples in a centralized place, and automatically be notified about sample updates. If it is the first time that you hear about Microsoft All-In-One Code Framework, please watch the introduction video on Microsoft Showcase, or read the introduction on our homepage https://1code.codeplex.com/.

 

Introduction

The sample demonstrates how to create/launch a process interactively in the session of the logged-on user from a service application written in C#.Net.

Just for the demonstration purpose, the path/name of the child process (Notepad.exe in this sample) has been hardcoded in the parent service application, but this sample can be referenced for launching any other interactive child applications. Main implementation of the sample is in the class named, “CSCreateProcessAsUserFromService” which P/Invokes to some native Windows APIs to launch the process.

 

Running the sample

1) Open the “CSCreateProcessAsUserFromService.sln” file in Visual Studio 2010.

2) Build the project and get the service application exe, such as - CSCreateProcessAsUserFromService.exe.

3) Once the project is built, use the command line service controller utility “sc” to create/start/stop/delete the windows service.

Syntax of the “sc” command (make sure to run as administrator) –

sc <server> [command] [service name] <option1> <option2>...

For example –

Creating a windows service with name “My Sample Service” –
sc create "My Sample Service" binpath= <Full Path of CSCreateProcessAsUserFromService.exe>

Starting the service “My Sample Service” will launch the child process –
sc start "My Sample Service"

Stopping the service “My Sample Service” –
sc stop "My Sample Service"

Deleting the windows service named “My Sample Service” –
sc delete "My Sample Service"

Using the code

1) Launching of the child process has been implemented in the class, “CSCreateProcessAsUserFromService”.

2) In the “OnStart” of the service, we have created a thread to launch/create the child process. Creating child process in a separate thread is always good as it will not block the service main thread. Blocking of service main thread might result in timeout error by Service Control Manager (SCM).

 protected override void OnStart(string[] args) 
{ 
    // As creating a child process might be a time consuming operation, 
    // its better to do that in a separate thread than blocking the main thread. 
    System.Threading.Thread ProcessCreationThread = new System.Threading.Thread(MyThreadFunc); 
    ProcessCreationThread.Start(); 
}

3) The thread procedure passes into the name of the application to be launched. In the sample, we have tried launching Notepad.exe. But any other application name can be specified.

 // This thread function would launch a child process 
// in the interactive session of the logged-on user. 
public static void MyThreadFunc() 
{ 
    CreateProcessAsUserWrapper.LaunchChildProcess("C:\\Windows\\notepad.exe"); 
}

4) The “CSCreateProcessAsUserFromService” class first enumerates all the sessions running/available by calling WTSEnumerateSessions.

5) After getting all the sessions, we tried to check which session is an active session i.e. the session of the logged-on user.

6) Then we obtain the primary access token of the logged-on user specified by the session ID by calling WTSQueryUserToken.

7) The user token received from WTSQueryUserToken, along with the child application’s name is then passed into the function CreateProcessAsUser to launch the child process interactively in the session of the logged-on user.

 public static void LaunchChildProcess(string ChildProcName) 
{ 
    IntPtr ppSessionInfo = IntPtr.Zero; 
    UInt32 SessionCount = 0; 
  
    if (WTSEnumerateSessions( 
        (IntPtr)WTS_CURRENT_SERVER_HANDLE,  // Current RD Session Host Server handle would be zero. 
        0,                                  // This reserved parameter must be zero. 
        1,                                  // The version of the enumeration request must be 1. 
        ref ppSessionInfo,                  // This would point to an array of session info. 
        ref SessionCount                    // This would indicate the length of the above array. 
        )) 
    { 
        for (int nCount = 0; nCount < SessionCount; nCount++) 
        { 
            // Extract each session info and check if it is the 
            // "Active Session" of the current logged-on user. 
            WTS_SESSION_INFO tSessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure( 
                ppSessionInfo + nCount * Marshal.SizeOf(typeof(WTS_SESSION_INFO)), 
                typeof(WTS_SESSION_INFO) 
                ); 
  
            if (WTS_CONNECTSTATE_CLASS.WTSActive == tSessionInfo.State) 
            { 
                IntPtr hToken = IntPtr.Zero; 
                if (WTSQueryUserToken(tSessionInfo.SessionID, out hToken)) 
                { 
                    // Launch the child process interactively 
                    // with the token of the logged-on user. 
                    PROCESS_INFORMATION tProcessInfo; 
                    STARTUPINFO tStartUpInfo = new STARTUPINFO(); 
                    tStartUpInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); 
  
                    bool ChildProcStarted = CreateProcessAsUser( 
                        hToken,             // Token of the logged-on user. 
                        ChildProcName,      // Name of the process to be started. 
                        null,               // Any command line arguments to be passed. 
                        IntPtr.Zero,        // Default Process' attributes. 
                        IntPtr.Zero,        // Default Thread's attributes. 
                        false,              // Does NOT inherit parent's handles. 
                        0,                  // No any specific creation flag. 
                        null,               // Default environment path. 
                        null,               // Default current directory. 
                        ref tStartUpInfo,   // Process Startup Info. 
                        out tProcessInfo    // Process information to be returned. 
                        ); 
  
                    if (ChildProcStarted) 
                    { 
                        // The child process creation is successful! 
  
                        // If the child process is created, it can be controlled via the out 
                        // param "tProcessInfo". For now, as we don't want to do any thing 
                        // with the child process, closing the child process' handles 
                        // to prevent the handle leak. 
                        CloseHandle(tProcessInfo.hThread); 
                        CloseHandle(tProcessInfo.hProcess); 
                    } 
                    else 
                    { 
                        // CreateProcessAsUser failed! 
                    } 
  
                    // Whether child process was created or not, close the token handle 
                    // and break the loop as processing for current active user has been done. 
                    CloseHandle(hToken); 
                    break; 
                } 
                else 
                { 
                    // WTSQueryUserToken failed! 
                } 
            } 
            else 
            { 
                // This Session is not active! 
            } 
        } 
  
        // Free the memory allocated for the session info array. 
        WTSFreeMemory(ppSessionInfo); 
    } 
    else 
    { 
        // WTSEnumerateSessions failed! 
    } 
} 

 

More Information

WTSEnumerateSessions

WTSQueryUserToken

CreateProcessAsUser

Interactive Services