Continued from “Building a Windows Service – Part 4: Extending the Service Installer”
So with our installer working it would be nice to have a way to just run the install right from the command-line. There are several options here, one way would be to use the Process object, set the start parameters to not use shell execute, and just run and wait for exit. The other option, though a little ‘hackish’ is to leverage the fact that InstallUtil itself is a managed application. Because it is managed we can simply take the Assembly’s full path and hand it over to the AppDomain.ExecuteAssembly method. The nice thing about this approach is that we can capture and redirect output (via Console.SetOut()) or just let it do it’s thing. In addition, we have the luxury of not needing to encode and escape the command-line arguments, instead we just supply them as a string array. So let’s build a little utility class to deal with this for us:
class
InstallUtil
{
private
readonly
AssemblyName _assemblyName;
private
readonly
string
_baseDirectory;
private
readonly
string
_fileName;
private
readonly
string
_installUtilExe;
public
InstallUtil(Assembly assembly)
{
_assemblyName = assembly.GetName();
_fileName = Path.GetFullPath(assembly.Location);
_baseDirectory = Path.GetDirectoryName(_fileName);
_installUtilExe = Path.GetFullPath(Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(),
"InstallUtil.exe"
));
if
(!File.Exists(_installUtilExe))
throw
new
FileNotFoundException(
"InstallUtil.exe not found."
, _installUtilExe);
}
public
void
Install(IEnumerable<
string
> moreargs)
{
if
(0 != Run(
true
, moreargs))
throw
new
ApplicationException(String.Format(
"InstallUtil failed to install {0}."
, _assemblyName.Name));
}
public
void
Uninstall(IEnumerable<
string
> moreargs)
{
if
(0 != Run(
false
, moreargs))
Console.Error.WriteLine(
"InstallUtil failed to uninstall {0}."
, _assemblyName.Name);
}
private
int
Run(
bool
install, IEnumerable<
string
> moreargs)
{
string
apppath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
string
appdata = Path.Combine(apppath, _assemblyName.Name);
if
(!Directory.Exists(appdata))
Directory.CreateDirectory(appdata);
List<
string
> arguments =
new
List<
string
>(moreargs);
if
(!install)
arguments.Add(
"/uninstall"
);
arguments.AddRange(
new
[]
{
"/ShowCallStack=true"
,
String.Format(
"/LogToConsole=true"
),
String.Format(
"/LogFile={0}"
, Path.Combine(appdata,
"install.log"
)),
String.Format(
"/InstallStateDir={0}"
, _baseDirectory),
String.Format(
"{0}"
, _fileName),
});
int
result = AppDomain.CurrentDomain.ExecuteAssembly(
_installUtilExe,
AppDomain.CurrentDomain.Evidence,
arguments.ToArray()
);
return
result;
}
}
And then we can simply crack open our Commands class from part 1 and add two more static methods:
public
static
void
Install(ICollection<
string
> args)
{
new
InstallUtil(
typeof
(Program).Assembly)
.Install(args);
}
public
static
void
Uninstall(ICollection<
string
> args)
{
new
InstallUtil(
typeof
(Program).Assembly)
.Uninstall(args);
}
Following that we add these methods to Program’s Actions dictionary and we are done. Now we can simply run our service exe with the commands we defined:
C:\Projects\ServiceTemplate> ServiceTemplate.exe install C:\Projects\ServiceTemplate> ServiceTemplate.exe uninstall
Continued on “Building a Windows Service – Part 6: Adding resources and event logging”