Self Installing Service
Enter a topic name to show or a new topic name to create; then press
Enter
..
Start by writing a service. This involves deriving a class from
System.ServiceP rocess.ServiceB ase and overriding a few methods. It's
detailed in the documentation.
Once you have your service written, you'll need to add an installer
class. This should derive from System.Configur ation.Install.I nstaller.
In this class's constructor, you need to add a
System.ServiceP rocess.ServiceP rocessInstaller to your Installers
collection. This will install the process your service lives in. Set
its Username and Password properties to match the username and
password of the account you'd like the process to run as.
You'll also need to add an installer for the service itself. The class
that does this is System.ServiceP rocess.ServiceI nstaller. The only
thing you need to do to it before adding it to the Installers
collection is to set the ServiceName property. IMPORTANT! This must
match the ServiceName you used in your ServiceBase-derived class.
The code that does all this looks like this:
using System;
using System.Configur ation.Install ;
using System.ServiceP rocess ;
using System.Componen tModel ;
public class MyInstaller : Installer
{
public MyInstaller ()
{
ServiceProcessI nstaller spi = new ServiceProcessI nstaller ();
spi.Username = "ISENGARD\\ CheckURL ";
spi.Password = "ihsxa9up";
ServiceInstalle r si = new ServiceInstalle r ();
si.ServiceName = " CheckURL ";
this .Installers.Add ( spi );
this .Installers.Add ( si );
}
}
If you've done installers before, you know this is the same code that
the wizard will write for you. The only difference is that InstallUtil
(the usual way of doing things) relies on the presence of the
[RunInstaller(tr ue)] attribute on the installer class. Adding won't
hurt, but we don't need it so I've left it out.
The next thing we need to do is get the classes we've inherited from
to do all the work. You can put this next bit of code anywhere you
like, but I sort of like the idea of my service being self-installing,
so I've added code to my Main method to look for the /install and
/uninstall command-line switches, and to act appropriately.
"Appropriat ely" in this case means instantiating an instance of the
TransactedInsta ller class, adding our installer to its Installers
collection, and setting a few things up. One of these is to tell the
installer the path to the assembly we're installing. We can retrieve
this using the Location property of the Assembly class. Oddly, the way
the installation stuff wants this information is for us to pass it in
an array of strings that contain what are essentially command line
parameters. My guess is that the installer classes were built to work
with InstallUtil in mind.
The other thing we need to do is pass in an empty Hashtable if we're
calling Install. The installer will use this to store some things
internally. We don't need to worry about that. When calling Uninstall,
we just pass null. Other than that, the code for both cases is pretty
similar.
Here's the code:
public class MyService : ServiceBase
{
// Service stuff omitted for brevity
static void Main( string [] args )
{
string opt = null ;
if ( args.Length > 1)
{
opt = args [0];
}
if (opt != null && opt.ToLower () == "/install")
{
TransactedInsta ller ti = new TransactedInsta ller ();
MyInstaller mi = new MyInstaller ();
ti.Installers.A dd (mi);
String path = String.Format ("/ assemblypath ={0}",
System.Reflecti on.Assembly.Get ExecutingAssemb ly ().Location);
String[] cmdline = {path};
InstallContext ctx = new InstallContext ("", cmdline );
ti.Context ( ctx );
ti.Install ( new Hashtable ());
}
else if (opt != null && opt.ToLower () == "/uninstall")
{
TransactedInsta ller ti = new TransactedInsta ller ();
MyInstaller mi = new MyInstaller ();
ti.Installers.A dd (mi);
String path = String.Format ("/ assemblypath ={0}",
System.Reflecti on.Assembly.Get ExecutingAssemb ly ().Location);
String[] cmdline = {path};
InstallContext ctx = new InstallContext ("", cmdline );
ti.Context ( ctx );
ti.Uninstall ( null );
}
}
}
I haven't talked about things like creating a log file or any of that
because I haven't figured it out yet! But this worked for me, and I
needed it, because InstallUtil doesn't work with services written in
Managed C++.
Enter a topic name to show or a new topic name to create; then press
Enter
..
Start by writing a service. This involves deriving a class from
System.ServiceP rocess.ServiceB ase and overriding a few methods. It's
detailed in the documentation.
Once you have your service written, you'll need to add an installer
class. This should derive from System.Configur ation.Install.I nstaller.
In this class's constructor, you need to add a
System.ServiceP rocess.ServiceP rocessInstaller to your Installers
collection. This will install the process your service lives in. Set
its Username and Password properties to match the username and
password of the account you'd like the process to run as.
You'll also need to add an installer for the service itself. The class
that does this is System.ServiceP rocess.ServiceI nstaller. The only
thing you need to do to it before adding it to the Installers
collection is to set the ServiceName property. IMPORTANT! This must
match the ServiceName you used in your ServiceBase-derived class.
The code that does all this looks like this:
using System;
using System.Configur ation.Install ;
using System.ServiceP rocess ;
using System.Componen tModel ;
public class MyInstaller : Installer
{
public MyInstaller ()
{
ServiceProcessI nstaller spi = new ServiceProcessI nstaller ();
spi.Username = "ISENGARD\\ CheckURL ";
spi.Password = "ihsxa9up";
ServiceInstalle r si = new ServiceInstalle r ();
si.ServiceName = " CheckURL ";
this .Installers.Add ( spi );
this .Installers.Add ( si );
}
}
If you've done installers before, you know this is the same code that
the wizard will write for you. The only difference is that InstallUtil
(the usual way of doing things) relies on the presence of the
[RunInstaller(tr ue)] attribute on the installer class. Adding won't
hurt, but we don't need it so I've left it out.
The next thing we need to do is get the classes we've inherited from
to do all the work. You can put this next bit of code anywhere you
like, but I sort of like the idea of my service being self-installing,
so I've added code to my Main method to look for the /install and
/uninstall command-line switches, and to act appropriately.
"Appropriat ely" in this case means instantiating an instance of the
TransactedInsta ller class, adding our installer to its Installers
collection, and setting a few things up. One of these is to tell the
installer the path to the assembly we're installing. We can retrieve
this using the Location property of the Assembly class. Oddly, the way
the installation stuff wants this information is for us to pass it in
an array of strings that contain what are essentially command line
parameters. My guess is that the installer classes were built to work
with InstallUtil in mind.
The other thing we need to do is pass in an empty Hashtable if we're
calling Install. The installer will use this to store some things
internally. We don't need to worry about that. When calling Uninstall,
we just pass null. Other than that, the code for both cases is pretty
similar.
Here's the code:
public class MyService : ServiceBase
{
// Service stuff omitted for brevity
static void Main( string [] args )
{
string opt = null ;
if ( args.Length > 1)
{
opt = args [0];
}
if (opt != null && opt.ToLower () == "/install")
{
TransactedInsta ller ti = new TransactedInsta ller ();
MyInstaller mi = new MyInstaller ();
ti.Installers.A dd (mi);
String path = String.Format ("/ assemblypath ={0}",
System.Reflecti on.Assembly.Get ExecutingAssemb ly ().Location);
String[] cmdline = {path};
InstallContext ctx = new InstallContext ("", cmdline );
ti.Context ( ctx );
ti.Install ( new Hashtable ());
}
else if (opt != null && opt.ToLower () == "/uninstall")
{
TransactedInsta ller ti = new TransactedInsta ller ();
MyInstaller mi = new MyInstaller ();
ti.Installers.A dd (mi);
String path = String.Format ("/ assemblypath ={0}",
System.Reflecti on.Assembly.Get ExecutingAssemb ly ().Location);
String[] cmdline = {path};
InstallContext ctx = new InstallContext ("", cmdline );
ti.Context ( ctx );
ti.Uninstall ( null );
}
}
}
I haven't talked about things like creating a log file or any of that
because I haven't figured it out yet! But this worked for me, and I
needed it, because InstallUtil doesn't work with services written in
Managed C++.