Will .NET 2.0 Windows Services run under Windows 2000?

J

Jeff Dege

I posted, a week or so ago, about problems I was having with a Windows
Service, written in C# and .NET 2.0, that would install and run fine, on
some machines, and would install, but would hang on startup, on other
machines.

On exploring it further, it seems that the machines that this service
would work on were running Windows XP or Windows 2003 Server, and that
the machines that it failed on were all running Windows 2000.

The Windows 2000 machines had .NET 2.0 installed, and Service Pack 4.

Has anyone else seen this problem? We still have customers running
Windows 2000, so this is a problem.

I put together a moderately short sample service, that does nothing but
install itself and write to the event log. and it showed the same problem.


In Visual Studio 2005, create a console app, add references to
System.Configuration.Install and System.ServiceProcess, then paste this
in program.cs, and compile.

Run from the command-line without arguments, it runs as a regular console
app. If the first argument is "install", it installs itself itself as a
service. If the first argument is anything else, it uninstalls.

It can also be installed or uninstalled using installutil.exe - that uses
the no-parameter constructor of the TestInstaller class.

When running, it runs a loop writes a message to the Event Log every ten
seconds (and to the console, if it's running as a console app.) Pressing
any key while it's running from the console breaks the loop, clicking on
"Stop" in the Service Control Manager does the same, when it's running as
a service.

It has AutoLog turned on, so when things work correctly, the Service
Control Manager will write to the Event Log as it sends start and stop
events to the service.

The bit with WNetGetConnectionA() is to get around a problem with
mapped drive letters. If the .EXE is sitting on a mapped drive,
the install method writes the mapped drive into the service's
ImagePath registry key, and the Service Control Manager doesn't
seem to understand them.

If anyone can try to compile this, and try it on any Windows 2000
machines they have lying around, I'd appreciate it.

Thanks.


==== BEGIN SOURCE ====

using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Text;
using System.Threading;


namespace TestServiceNS
{
class TestService : ServiceBase
{
static void Main(string[] args)
{
TestService testService = new TestService(args);
testService.begin();
}

public TestService(string[] args)
{
Log.log("TestService constructed");

if (Environment.UserInteractive)
runningAsService = false;
else
runningAsService = true;

Log.log((runningAsService ? "" : "not ") +
"running as service");

CanHandlePowerEvent = true;
CanHandleSessionChangeEvent = true;
CanPauseAndContinue = true;
CanShutdown = true;
CanStop = true;

AutoLog = true;

if (args.Length > 0)
serviceInstall(args[0]);
}

private bool runningAsService;
private bool runningInstall = false;

private void serviceInstall(string action)
{
Log.log("serviceInstall(" + action + ")");
runningInstall = true;

try
{
string location =
toUnc(Assembly.GetEntryAssembly().Location);

String path =
String.Format("/assemblypath={0}", location);
String[] cmdLine = { path };

InstallContext ctx =
new InstallContext("install.Log.log", cmdLine);

TransactedInstaller ti = new TransactedInstaller();
ti.Context = ctx;

TestInstaller testInstaller =
new TestInstaller("Test", "TestService",
ServiceAccount.LocalSystem,
ServiceStartMode.Automatic, null, null);

ti.Installers.Add(testInstaller);

if (action == "install")
{
Log.log("installing service");
ti.Install(new Hashtable());
}
else
{
Log.log("uninstalling service");
ti.Uninstall(null);
}
}
catch (Exception ex)
{
Log.log("Exception: " + ex);
}

Log.log("serviceInstall(" + action + ") finished");
}

public void begin()
{
Log.log("TestService started");

if (Environment.UserInteractive)
{
if (runningInstall)
Log.log("Running install");
else
work();
}
else
ServiceBase.Run(this);

if (!runningAsService)
{
Console.WriteLine("Press any key to exit");
Console.ReadKey(true);
}

Log.log("TestService finished");
}


[DllImport("mpr.dll")]
static extern uint WNetGetConnectionA(string lpLocalName,
StringBuilder lpRemoteName, ref int lpnLength);

static string toUnc(string path)
{
if (path.Contains(":"))
{
string[] tmp = path.Split(':');
string driveLetter = tmp[0] + ":";
string remainingPath = tmp[1];

int length = 256;
StringBuilder networkShare = new StringBuilder(length);

uint status =
WNetGetConnectionA(driveLetter, networkShare, ref length);
if (status == 0)
return networkShare.ToString() + remainingPath;
}

return path;
}

public bool checkExit()
{
if (runningAsService)
return shutdownRequested;

if (!Console.KeyAvailable)
return false;

Console.ReadKey(true);

return true;
}

public void work()
{
try
{
for (int i=0; ; i++)
{
if (checkExit())
break;

if (pauseRequested)
continue;

if ((i%100) == 0)
Log.log("in work loop" +
(runningAsService ? "" :
", press any key to break") + " ...");

Thread.Sleep(100);
}
}
catch (Exception ex)
{
Log.log("Exception: " + ex);
}
}

private Thread workerThread;

private bool shutdownRequested = false;
private bool pauseRequested = false;

public void requestShutDown()
{
Log.log("Shutdown requested");
shutdownRequested = true;
}

public void pause()
{
Log.log("Pause requested");
pauseRequested = true;
}

public void resume()
{
Log.log("Resume requested");
pauseRequested = false;
}

protected override void Dispose(bool disposing)
{
Log.log("Dispose()");
base.Dispose(disposing);
}

protected override void OnStart(string[] args)
{
Log.log("OnStart()");
workerThread = new Thread(new ThreadStart(work));
workerThread.Start();
base.OnStart(args);
}

protected override void OnStop()
{
Log.log("OnStop()");
requestShutDown();
workerThread.Join(2000);
base.OnStop();
}

protected override void OnPause()
{
Log.log("OnPause()");
pause();
base.OnPause();
}

protected override void OnContinue()
{
Log.log("OnContinue()");
resume();
base.OnContinue();
}

protected override void OnShutdown()
{
Log.log("OnShutdown()");
base.OnShutdown();
}

protected override void OnCustomCommand(int command)
{
Log.log("OnCustomCommand()");
base.OnCustomCommand(command);
}

protected override bool OnPowerEvent(
PowerBroadcastStatus powerStatus)
{
Log.log("OnPowerEvent()");
return base.OnPowerEvent(powerStatus);
}

protected override void OnSessionChange(
SessionChangeDescription changeDescription)
{
Log.log("OnSessionChange()");
base.OnSessionChange(changeDescription);
}
}

class Log
{
static string evSource = "TestService";
static string evLog = "TestService";

public static void log(string message)
{
if (!EventLog.SourceExists(evSource))
EventLog.CreateEventSource(evSource, evLog);

System.Console.WriteLine(
DateTime.Now.ToString(
"yyyy-MM-dd HH:mm:ss.fff ") + message);
EventLog.WriteEntry(evSource, message);
}
}

[RunInstaller(true)]
public class TestInstaller : Installer
{
public TestInstaller()
:
this("Test", "TestService", ServiceAccount.LocalSystem,
ServiceStartMode.Automatic, null, null)
{
Log.log("TestInstaller constructed by installutil.exe");
}

public TestInstaller(string serviceName, string displayName,
ServiceAccount account, ServiceStartMode startMode,
string username, string password)
{
Log.log("TestInstaller(" + serviceName + ")");

ServiceProcessInstaller serviceProcessInstaller =
new ServiceProcessInstaller();
ServiceInstaller serviceInstaller = new ServiceInstaller();

serviceProcessInstaller.Account = account;
serviceProcessInstaller.Username = username;
serviceProcessInstaller.Password = password;

serviceInstaller.DisplayName = displayName;
serviceInstaller.StartType = startMode;

serviceInstaller.ServiceName = serviceName;

this.Installers.Add(serviceProcessInstaller);
this.Installers.Add(serviceInstaller);
}
}
}



==== END SOURCE ====

-
Want to understand why politicians do what they do? Simple: when you're
a big, gray, greasy rat, walking around on two hind legs, you have a
lot to gain by turning the world into a garbage heap.
-- Memoirs of Lucille G. Kropotkin
 
J

Jeff Dege

I posted, a week or so ago, about problems I was having with a Windows
Service, written in C# and .NET 2.0, that would install and run fine, on
some machines, and would install, but would hang on startup, on other
machines.

On exploring it further, it seems that the machines that this service
would work on were running Windows XP or Windows 2003 Server, and that
the machines that it failed on were all running Windows 2000.

The Windows 2000 machines had .NET 2.0 installed, and Service Pack 4.

Has anyone else seen this problem? We still have customers running
Windows 2000, so this is a problem.

I put together a moderately short sample service, that does nothing but
install itself and write to the event log. and it showed the same
problem.

OK - I've put together a simpler test service, without the complexities
of running as a console app, or of self-installing. And with this, I've
identified the problem, if not the cause.

The following service will hang on startup, when run under Windows 2000,
if the OnSessionChange() function is present and enabled. If the
function is commented out, and CanHandleSessionChangeEvent is set to false,
the service starts up correctly.

== BEGIN SOURCE ==

using System;
using System.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;


namespace SimpleService
{
class SimpleService : ServiceBase
{
static void Main(string[] args)
{
SimpleService simpleService = new SimpleService(args);
Log.log("Before ServiceBase.Run()");
ServiceBase.Run(simpleService);
Log.log("After ServiceBase.Run()");
}

public SimpleService(string[] args)
{
Log.log("SimpleService constructed");

/* OnSessionChangeEvent hangs service on startup, under Win2K */
CanHandleSessionChangeEvent = true;


CanStop = true;
}

public void work()
{
try
{
for (int i = 0; ; i++)
{
if (shutdownRequested)
break;

if ((i % 100) == 0)
Log.log("in work loop...");

Thread.Sleep(100);
}
}
catch (Exception ex)
{
Log.log("Exception: " + ex);
}
}

private Thread workerThread;

private bool shutdownRequested = false;

protected override void OnStart(string[] args)
{
Log.log("OnStart()");
workerThread = new Thread(new ThreadStart(work));
workerThread.Start();
base.OnStart(args);
}

protected override void OnStop()
{
Log.log("OnStop()");
shutdownRequested = true;
workerThread.Join(2000);
base.OnStop();
}

/* OnSessionChangeEvent hangs service on startup, under Win2K */
protected override void OnSessionChange(
SessionChangeDescription changeDescription)
{
Log.log("OnSessionChange()");
base.OnSessionChange(changeDescription);
}
}

class Log
{
static string evSource = "SimpleLog";
static string evLog = "SimpleLog";

public static void log(string message)
{
if (!EventLog.SourceExists(evSource))
EventLog.CreateEventSource(evSource, evLog);

EventLog.WriteEntry(evSource, message);
}
}

[RunInstaller(true)]
public class SimpleInstaller : Installer
{
public SimpleInstaller()
{
Log.log("SimpleInstaller()");

ServiceProcessInstaller serviceProcessInstaller =
new ServiceProcessInstaller();
ServiceInstaller serviceInstaller = new ServiceInstaller();

serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
serviceProcessInstaller.Username = null;
serviceProcessInstaller.Password = null;

serviceInstaller.DisplayName = "SimpleService";
serviceInstaller.StartType = ServiceStartMode.Automatic;

serviceInstaller.ServiceName = "Simple";

this.Installers.Add(serviceProcessInstaller);
this.Installers.Add(serviceInstaller);
}
}
}


== END SOURCE ==


--
[A]fter having thus successively taken each member of the community in
its powerful grasp, and fashioned him at will, the supreme power then
extends its arm over the whole community. It covers the surface of
society with a network of complicated rules, minute and uniform, through
which the most original minds and the most energetic characters cannot
penetrate to rise above the crowd. The will of man is not shattered but
softened, bent and guided; men are seldom forced to act, but they are
constantly restrained from acting. Such a power does not destroy, but it
prevents existence; it does not tyrannize, but it compresses, enervates,
extinguishes, and stupifies a people, till each nation is reduced to be
nothing better than a flock of timid and industrial animals, of which
government is the shepard. - I have always thought that servitude of
the regular, quiet, and gentle kind which I have just described might
be combined more easily than it is commonly believed with some of the
outward forms of freedom and that it might even establish itself under
the wing of the sovereignty of the people.
- Alexis de Tocqueville
 

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