This tip provides a Windows Service that launches a UI application. The UI application runs under a specified user. The credentials of the user are saved encrypted in a configuration file.
Introduction
In Windows XP, Windows Server 2003 and earlier Windows versions, a Windows service could start an application with a user interface, and this application would be visible in the session of the currently logged-in user. In Windows Vista and later, Windows services run in Session 0 which does not allow any interaction with the user. A Windows Service may launch a UI application but this application will never be displayed on the currently logged-in user's desktop. This tip shows how to launch a UI application from a Windows Service by using the Windows API.
Background
This problem emerged when migrating a Cluster Resource from Windows Server 2003 to Windows Server 2012. In Windows Server 2003, a Cluster Resource could be a UI application. In Windows Server 2012, when bringing online the same Cluster Resource, the UI application does not become visible. This happens, because the Cluster Service is also a Windows Service, and the Cluster Resources launched by the Cluster Service are run in Session 0, which does not have user interaction. The workaround is to create a Windows Service that launches the UI application, and make this Windows Service a Cluster Resource. Only the Windows Service will be explained here.
Through the Windows API, it is possible to launch a new process in the currently interactive session. Only Windows Services that run under the local system account have the right to do that. However, the new process also runs under the local system account, whereas the requirement is for the new process to run under a specific user. The solution is for the new process to launch a second new process under a specific user. Once the second new process is started, the first new process terminates. At the end, the Windows Service and the second new process are running, the first in session 0 which is non interactive, and the latter in the currently interactive session. The Windows Service finds the process id of the second new process by the process name. When the service is stopped, it kills that process id. The Windows Service monitors if the process id exists, and stops if it no longer exists. The Windows Service can be made a Cluster Resource. When the second new process fails, the Windows Service will also stop and a Fail Over is initiated.
Using the Code
Two Visual Studio projects are provided:
WindowsService1
: Runs under the local system account, and starts WindowsApplication1
in the currently interactive session. Instructs WindowsApplication1
through the command line parameters to start Notepad. WindowsApplication1
: Starts a new process given by the command line arguments. In this case, the new process is Notepad. The new process runs under a specific user, defined in the configuration file. When WindowsApplication1
is started without command line arguments, a form is shown to enter the user credentials and save them to the configuration file.
WindowsService1
Contains the following items:
WindowsApi
: Contains the Windows API signatures of WTSGetActiveConsoleSessionId
, WTSQueryUserToken
, CreateProcessAsUser
. You can get these at www.pinvoke.net. Service1
: Implements the Windows Service.
OnStart
: Calls WTSGetActiveConsoleSessionId
to get the current interactive session and puts it into the variable userTokenHandle
with WTSQueryUserToken
. Launches WindowsApplication1
with CreateProcessAsUserW
. Passes as command line parameter "C:\Windows\System32\notepad.exe a b c
". Calls GetProcId
to get the process id that was created by WindowsApplication1
. Starts the method MonitorProcess
in a new thread. GetProcId
: Gets the process id with Process.GetProcessesByName
, in this case it will be Notepad. If no such process is found, or more than one processes are found, an exception is thrown. MonitorProcess
: Checks with Process.GetProcessById
if the process id still exists. If it does not exist, calls the Stop
method of the service. OnStop
: Uses Process.Kill
to kill the process id, in this case the Notepad process.
ProjectInstaller
: This class is needed for installing the service.
WindowsApplication1
Contains the following items:
- App.config: contains the credentials of the user under which the created process should run.
ModMain
Main
: is the main entrypoint. If command line arguments exist, then ProcessStart
is called, otherwise Form1
is shown. ProcessStart
: A new process is created. The user credentials are read from the configuration file with Configuration.ConfigurationManager.AppSettings
. The password is set by calling SetPassword
. The first of the command line arguments defines the executable to be started, and the rest of the command line arguments, define the command line arguments for the newly created process. The process is started with Process.Start
. SetPassword
: Calls Decrypt
to decrypt the password and fills a SecureString
.
Form1
: Provides textboxes to fill the domain, username and password. Upon pressing the button ok, the password is encrypted by calling Encrypt
and the values are saved to the configuration file. The configuration file is opened with ConfigurationManager.OpenExeConfiguration
and it is saved with Configuration.Save
. ModCrypto
: provides methods for encryption and decryption.
Encrypt
: Uses System.Security.Cryptography.ProtectedData.Protect
to encrypt a string
. The entropy makes guessing the password more difficult. Decrypt
: Uses System.Security.Cryptography.ProtectedData.UnProtect
to decrypt a string
.
Test
This program has been tested on Windows 7 and Windows Server 2012. It should run on Windows versions of Vista and later. To run the program, follow these steps:
- With Visual Studio 2010, open solution CreateAppFromService.sln.
- Build the solution.
- In Windows Explorer, double click on Source/bin/WindowsApplication1.exe. A form to enter the credentials is shown.
- Enter the credentials under which the Notepad launched by
WindowsApplication1
should run. - Press Ok. The credentials are saved to bin/WindowsApplication1.exe.config. You may see that the Password is encrypted.
- In Windows Explorer, double click on Source/InstallService.bat. If the message "The transacted install has completed." is shown in the console, then
Service1
has been installed successfully. Otherwise, try right-click and choose Run as administrator. - Start/Run/services.msc. This starts the Services Management Console. Check that
Service1
is installed. - Right-click on
Service1
and choose Start. The service's Status
should change to Started
.
- A Notepad should open asking: Cannot find the a b c.txt file. Do you want to create a new file?
You may click Yes or No. This is to demonstrate that parameters may be passed to the UI application.
- Start/Run/taskmgr to start the Windows Task Manager. Check that Notepad runs under the User Name entered in step 4. If this is the case, it seems that all worked well, that means, Windows Service "
Service1
" successfully started WindowsApplication1
, which in turn started Notepad under the specified user. - Right-click on
Service1
and choose Stop. The Notepad should close and the service's Status should change to Stopped. - Start again the
Service1
to test the monitoring thread. - Notepad should start again. Close the Notepad window.
- Right-click on
Service1
and choose Refresh. The service's status should change to Stopped, because the monitoring thread found out that Notepad is no more running, and therefore the monitoring thread instructed the service to stop. - If the service did not start, check the Application Log in the Event Viewer. You may start the event viewer with Start/Run/eventvwr.msc.
- You may find for example the following error:
- Source:
WindowsApplication1
- Message: Main Logon failure: unknown user name or bad password.
- In Windows Explorer, double click on Source/UnInstallService.bat. If the message "The uninstall has completed." is shown in the console, then
Service1
has been uninstalled successfully. Otherwise, try right-click and choose Run as administrator. If this does not work either, in a command prompt type: sc delete Service1
.
History
WindowsService1
launching WindowsApplication1
launching Notepad with credentials in the configuration file encrypted by ProtectedData.Protect
.