Unable to properly set the InteractiveProcess property on a windows service

L

letibal

Hello,

I have written a windows service and created an installer for it.
The service runs under the system accounts. When started, it launches a
GUI.
By default, the InteractiveProcess property of the service is not set
(this can be checked by right-clicking on the service in the Services
window (Admin tools>Services), choosing Properties, LogOn tab). In
order to enable my service to launch a GUI at startup, I added the
following lines in my installer :

public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
SetInteractWithDesktop();
}


private void SetInteractWithDesktop()
{
RegistryKey fm;
ServiceType type;

fm =
Registry.LocalMachine.OpenSubKey(@"SYSTEM\CURRENTCONTROLSET\SERVICES\myserv",
true);
type = ServiceType.InteractiveProcess |
ServiceType.Win32OwnProcess;
fm.SetValue("Type", (int)type);
fm.Close();

}

This modifies the appropriate Registry key in order to enable the
InteractiveProcess property.

If I install this service and run it : it does not display my GUI.
However, if I check in Admin tools>Services>*myserv*Properties>LogOn
tab, I can see the box ticked for "Allow service to interact with
Desktop", which is the expected behavior.

Now if I untick and tick again this box, press ok, and start my
service, it works fine, displaying the GUI correctly.

I'm wondering whether the registry key Im changing when installing the
service, is the only one to be changed in order to enable interaction
with the desktop.

Any ideas that could help me ?

Cheers,

Tibo
 
D

David Levine

Changing the registry key will only affect how the service control manager
handles the service the next time it reads the registry key. Normally this
occurs the next time the system boots. It takes effect after you manually
change it because the SCM is the one modifying the registry key value. If
you want the changes to take effect immediately you need to use the
unmanaged SCM API calls to create the service using the correct service type
flags. There may be a way to modify it after it has been created - you'd
have to do some research to find out.

The way I install my service so that the service type is set to Interactive
is to override the installer class and to manually invoke the call to
CreateService, specifying the parameters myself. The actual call in the
installer code looks like this...

// set up call....lots of code here...
Win32Interop.CreateService( serviceName,displayName, description,
null,assemblypath,ServiceStartMode.Automatic,
ServiceType.InteractiveProcess | ServiceType.Win32OwnProcess );


where Win32Interop.CreateService is defined as...

public static void CreateService(
string serviceName,string displayName,string description,
string dependsOn,string assemblyFile,
System.ServiceProcess.ServiceStartMode startType,
System.ServiceProcess.ServiceType serviceType
)
{
IntPtr scHandle = IntPtr.Zero;
IntPtr hService = IntPtr.Zero;
try
{
scHandle = OpenSCManager(null,null,SC_MANAGER_ALL_ACCESS);
if ( scHandle != INVALID_HANDLE_VALUE )
{
hService = CreateService(scHandle, serviceName,
displayName, SERVICE_ALL_ACCESS, (uint)serviceType,
(uint)startType,SERVICE_ERROR_NORMAL,
assemblyFile, null, null, dependsOn, null, null);
}
if (hService == IntPtr.Zero) // did it create the service???
// "Error creating windows service."
throw new Win32Exception(
Marshal.GetLastWin32Error(),
LocalizationManager.Resources.GetString("ServiceInstaller_CreateService")
);
}
finally
{
if (hService != IntPtr.Zero)
CloseServiceHandle(hService); // close both handles
if (scHandle != IntPtr.Zero)
CloseServiceHandle(scHandle);
}

} // CreateService



where I defined the signature of the API as
[DllImport("advapi32", SetLastError = true)]
private static extern IntPtr CreateService(IntPtr hSCManager, string
serviceName, string displayName,
uint dwDesiredAccess, uint serviceType, uint startType, uint
errorControl,
string lpBinaryPathName, string lpLoadOrderGroup, string lpdwTagId,
string lpDependencies,
string lpServiceStartName, string lpPassword);



This has the added benefit of allowing me to specify the service
description - for some reason it was not part of the 1.1 API.
 
L

letibal

Cheers for that explanation, you're the man !!

I'm posting here my solution
Note : I also borrowed some code from the useful following resources :

http://www.codeproject.com/csharp/csharpwindowsserviceinst.asp
http://www.codeproject.com/csharp/sercviceinstallerext.asp


************************************************************************************************
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Configuration.Install;
using System.ComponentModel;
using System.ServiceProcess;

public class myServiceInstaller :
System.ServiceProcess.ServiceInstaller
{

#region Misc Win32 Interop Stuff
// Win32 function to unlock the service database.
[DllImport("advapi32.dll")]
public static extern bool
UnlockServiceDatabase(IntPtr hSCManager);

// Win32 function to lock the service database to perform
write operations.
[DllImport("advapi32.dll")]
public static extern IntPtr
LockServiceDatabase(IntPtr hSCManager);

// Win32 function to open the service control manager
[DllImport("advapi32.dll")]
public static extern
IntPtr OpenSCManager(string lpMachineName, string
lpDatabaseName, int dwDesiredAccess);

// Win32 function to open a service instance
[DllImport("advapi32.dll")]
public static extern IntPtr
OpenService(IntPtr hSCManager, string lpServiceName,
int dwDesiredAccess);


// Win32 function to close a service related handle.
[DllImport("advapi32.dll")]
public static extern bool
CloseServiceHandle(IntPtr hSCObject);

[DllImport("advapi32.dll")]
public static extern bool ChangeServiceConfigA(
int hService, ServiceType dwServiceType, int
dwStartType,
int dwErrorControl, string lpBinaryPathName, string
lpLoadOrderGroup,
int lpdwTagId, string lpDependencies, string
lpServiceStartName,
string lpPassword, string lpDisplayName);

private const int SC_MANAGER_ALL_ACCESS = 0xF003F;
private const int SERVICE_ALL_ACCESS = 0xF01FF;
private const int SERVICE_NO_CHANGE = -1;
#endregion

private void LogInstallMessage(string msg)
{
Console.WriteLine(msg);
}

public myServiceInstaller()
: base()
{
base.Committed += new
InstallEventHandler(this.UpdateServiceConfig);
}

private void UpdateServiceConfig(object sender,
InstallEventArgs e)
{
IntPtr scmHndl = IntPtr.Zero;
IntPtr svcHndl = IntPtr.Zero;
IntPtr tmpBuf = IntPtr.Zero;
IntPtr svcLock = IntPtr.Zero;

bool rslt = false;

try
{
// Open the service control manager
scmHndl = OpenSCManager(null, null,
SC_MANAGER_ALL_ACCESS);
if (scmHndl.ToInt32() <= 0)
{
LogInstallMessage("Failed to Open Service
Control Manager");
return;
}
// Lock the Service Database
svcLock = LockServiceDatabase(scmHndl);
if (svcLock.ToInt32() <= 0)
{
LogInstallMessage("Failed to Lock Service
Database for Write");
return;
}
// Open the service
svcHndl = OpenService(scmHndl, base.ServiceName,
SERVICE_ALL_ACCESS);
if (svcHndl.ToInt32() <= 0)
{
LogInstallMessage("Failed to Open Service ");
return;
}

rslt = ChangeServiceConfigA((int)svcHndl,
ServiceType.Win32OwnProcess |
ServiceType.InteractiveProcess,
SERVICE_NO_CHANGE,
SERVICE_NO_CHANGE,
null,
null,
0,
null,
null,
null,
null);
if (!rslt)
LogInstallMessage("Could not set the
service type.");
else
LogInstallMessage("Service type set.");
}
catch (Exception ex)
{
LogInstallMessage(ex.Message);
}
finally
{
if (scmHndl != IntPtr.Zero)
{
// Unlock the service database
if (svcLock != IntPtr.Zero)
{
UnlockServiceDatabase(svcLock);
svcLock = IntPtr.Zero;
}
// Close the service control manager handle
CloseServiceHandle(scmHndl);
scmHndl = IntPtr.Zero;
}
// Close the service handle
if (svcHndl != IntPtr.Zero)
{
CloseServiceHandle(svcHndl);
svcHndl = IntPtr.Zero;
}
// Free the memory
if (tmpBuf != IntPtr.Zero)
{
Marshal.FreeHGlobal(tmpBuf);
tmpBuf = IntPtr.Zero;
}
}
}
}
}
}



David said:
Changing the registry key will only affect how the service control manager
handles the service the next time it reads the registry key. Normally this
occurs the next time the system boots. It takes effect after you manually
change it because the SCM is the one modifying the registry key value. If
you want the changes to take effect immediately you need to use the
unmanaged SCM API calls to create the service using the correct service type
flags. There may be a way to modify it after it has been created - you'd
have to do some research to find out.

The way I install my service so that the service type is set to Interactive
is to override the installer class and to manually invoke the call to
CreateService, specifying the parameters myself. The actual call in the
installer code looks like this...

// set up call....lots of code here...
Win32Interop.CreateService( serviceName,displayName, description,
null,assemblypath,ServiceStartMode.Automatic,
ServiceType.InteractiveProcess | ServiceType.Win32OwnProcess );


where Win32Interop.CreateService is defined as...

public static void CreateService(
string serviceName,string displayName,string description,
string dependsOn,string assemblyFile,
System.ServiceProcess.ServiceStartMode startType,
System.ServiceProcess.ServiceType serviceType
)
{
IntPtr scHandle = IntPtr.Zero;
IntPtr hService = IntPtr.Zero;
try
{
scHandle = OpenSCManager(null,null,SC_MANAGER_ALL_ACCESS);
if ( scHandle != INVALID_HANDLE_VALUE )
{
hService = CreateService(scHandle, serviceName,
displayName, SERVICE_ALL_ACCESS, (uint)serviceType,
(uint)startType,SERVICE_ERROR_NORMAL,
assemblyFile, null, null, dependsOn, null, null);
}
if (hService == IntPtr.Zero) // did it create the service???
// "Error creating windows service."
throw new Win32Exception(
Marshal.GetLastWin32Error(),
LocalizationManager.Resources.GetString("ServiceInstaller_CreateService")
);
}
finally
{
if (hService != IntPtr.Zero)
CloseServiceHandle(hService); // close both handles
if (scHandle != IntPtr.Zero)
CloseServiceHandle(scHandle);
}

} // CreateService



where I defined the signature of the API as
[DllImport("advapi32", SetLastError = true)]
private static extern IntPtr CreateService(IntPtr hSCManager, string
serviceName, string displayName,
uint dwDesiredAccess, uint serviceType, uint startType, uint
errorControl,
string lpBinaryPathName, string lpLoadOrderGroup, string lpdwTagId,
string lpDependencies,
string lpServiceStartName, string lpPassword);



This has the added benefit of allowing me to specify the service
description - for some reason it was not part of the 1.1 API.



Hello,

I have written a windows service and created an installer for it.
The service runs under the system accounts. When started, it launches a
GUI.
By default, the InteractiveProcess property of the service is not set
(this can be checked by right-clicking on the service in the Services
window (Admin tools>Services), choosing Properties, LogOn tab). In
order to enable my service to launch a GUI at startup, I added the
following lines in my installer :

public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
SetInteractWithDesktop();
}


private void SetInteractWithDesktop()
{
RegistryKey fm;
ServiceType type;

fm =
Registry.LocalMachine.OpenSubKey(@"SYSTEM\CURRENTCONTROLSET\SERVICES\myserv",
true);
type = ServiceType.InteractiveProcess |
ServiceType.Win32OwnProcess;
fm.SetValue("Type", (int)type);
fm.Close();

}

This modifies the appropriate Registry key in order to enable the
InteractiveProcess property.

If I install this service and run it : it does not display my GUI.
However, if I check in Admin tools>Services>*myserv*Properties>LogOn
tab, I can see the box ticked for "Allow service to interact with
Desktop", which is the expected behavior.

Now if I untick and tick again this box, press ok, and start my
service, it works fine, displaying the GUI correctly.

I'm wondering whether the registry key Im changing when installing the
service, is the only one to be changed in order to enable interaction
with the desktop.

Any ideas that could help me ?

Cheers,

Tibo
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top