Using InstallUtil to install a Windows service with startup parameters - c#

I am using InstallUtil to install my service and I just cannot figure out how to specify the startup parameters for it!
Here is my Installer subclass:
[RunInstaller(true)]
public class ServerHostInstaller : Installer
{
private ServiceInstaller m_serviceInstaller;
private ServiceProcessInstaller m_serviceProcessInstaller;
private static string s_usage = "Usage:\ninstallutil /i /username=<user_name> /password=<user_password> NCStub.Server.Host.exe";
public ServerHostInstaller()
{
m_serviceInstaller = new ServiceInstaller();
m_serviceInstaller.ServiceName = Program.ServiceName;
m_serviceInstaller.DisplayName = Program.ServiceName;
m_serviceInstaller.StartType = ServiceStartMode.Automatic;
m_serviceProcessInstaller = new ServiceProcessInstaller();
m_serviceProcessInstaller.Account = ServiceAccount.User;
Installers.Add(m_serviceInstaller);
Installers.Add(m_serviceProcessInstaller);
}
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
string userName = this.Context.Parameters["username"];
if (userName == null)
{
Console.WriteLine(s_usage);
throw new InstallException("Missing parameter 'username'");
}
string userPass = this.Context.Parameters["password"];
if (userPass == null)
{
Console.WriteLine(s_usage);
throw new InstallException("Missing parameter 'password'");
}
m_serviceProcessInstaller.Username = userName;
m_serviceProcessInstaller.Password = userPass;
}
}
Can anyone indicate how do I specify the service startup parameters?

Found it.
I have rewritten the Install method like so:
public override void Install(IDictionary stateSaver)
{
string userName = this.Context.Parameters["username"];
if (userName == null)
{
Console.WriteLine(s_usage);
throw new InstallException("Missing parameter 'username'");
}
string userPass = this.Context.Parameters["password"];
if (userPass == null)
{
Console.WriteLine(s_usage);
throw new InstallException("Missing parameter 'password'");
}
m_serviceProcessInstaller.Username = userName;
m_serviceProcessInstaller.Password = userPass;
var path = new StringBuilder(Context.Parameters["assemblypath"]);
if (path[0] != '"')
{
path.Insert(0, '"');
path.Append('"');
}
path.Append(" --service");
Context.Parameters["assemblypath"] = path.ToString();
base.Install(stateSaver);
}
Although, I give the predefined command line parameters (--service), the code is easily adaptable to pass real command line arguments, just use the same pattern for passing the username and password parameters.

I know this is an old post but thought I'd post my response. I did this in a .net 4 service using the BeforeInstall event.
The ServiceProcessInstaller's BeforeInstall event:
private void serviceProcessInstaller1_BeforeInstall(object sender, InstallEventArgs e)
{
System.ServiceProcess.ServiceProcessInstaller installer = sender as System.ServiceProcess.ServiceProcessInstaller;
if (installer != null)
{
//Get the existing assembly path parameter
StringBuilder sbPathWIthParams = new StringBuilder(installer.Context.Parameters["assemblypath"]);
//Wrap the existing path in quotes if it isn't already
if (!sbPathWIthParams[0].Equals("\""))
{
sbPathWIthParams.Insert(0, "\"");
sbPathWIthParams.Append("\"");
}
//Add desired parameters
sbPathWIthParams.Append(" test");
//Set the new path
installer.Context.Parameters["assemblypath"] = sbPathWIthParams.ToString();
}
}
The installed service looks as follows:
It executes fine, and I can examine the parameters from within the main function of the service.

Related

Messages are being mixed up through Database.Log by multiple threads

In my case it's a web API project in Visual Studio. When I'm testing, the API is called multiple times concurrently.
I'm using the following to log the Raw SQL being sent to the SQL Server:
context.Database.Log = Console.WriteLine;
When SQL is logged, it's getting mixed up with queries on other threads. More specifically, it's most-often the parameters which get mixed up. This makes it next to impossible to correlate the right parameters with the right query. Sometimes the same API is called twice concurrently.
I am using async calls, but that wouldn't be causing the issue. It will be the fact there are multiple concurrent web requests on different completion threads.
I need accurate reliable logging so I can look back in the output window and review the SQL.
You need to buffer all log messages per-context, then write out that buffer upon disposal of your db context.
You need to be able to hook into your db context's dispose event
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (OnDisposed != null) OnDisposed(this, null);
}
public event EventHandler OnDisposed;
Then you need this class to manage the buffering per-context
class LogGroup
{
static bool ReferenceActiveGroups = true; //I'm not sure if this is needed. It might work fine without.
static HashSet<LogGroup> LogGroups = ReferenceActiveGroups ? new HashSet<LogGroup>() : null;
/// <summary>
/// For the currently being ran query, this outputs the Raw SQL and the length of time it was executed in the Output window (CTRL + ALT + O) when in Debug mode.
/// </summary>
/// <param name="db">The DbContext to be outputted in the Output Window.</param>
public static void Log(ApiController context, AppContext db)
{
var o = new LogGroup(context, db);
o.Initialise();
if (ReferenceActiveGroups) o.Add();
}
public LogGroup(ApiController context, AppContext db)
{
this.context = context;
this.db = db;
}
public void Initialise()
{
db.OnDisposed += (sender, e) => { this.Complete(); };
db.Database.Log = this.Handler;
sb.AppendLine("LOG GROUP START");
}
public void Add()
{
lock (LogGroups)
{
LogGroups.Add(this);
}
}
public void Handler(string message)
{
sb.AppendLine(message);
}
public AppContext db = null;
public ApiController context = null;
public StringBuilder sb = new StringBuilder();
public void Remove()
{
lock (LogGroups)
{
LogGroups.Remove(this);
}
}
public void Complete()
{
if (ReferenceActiveGroups) Remove();
sb.AppendLine("LOG GROUP END");
System.Diagnostics.Debug.WriteLine(sb.ToString());
}
}
It should work without saving a strong reference to the LogGroup object. But I haven't tested that yet. Also, you could include this kind of code directly on the context, so you definitely won't need to save a LogGroup reference object. But that wouldn't be as portable.
To use it in a controller action funtion:
var db = new MyDbContext();
LogGroup.Log(this, db);
Note, that I pass the controller reference, so the log can include some extra context information - the request URI.
Interpreting your log
Now that the log works, you'll find the commented parameters in the log output are a pain to work with. You would normally have to manually change them to proper SQL parameters, but even then it's difficult to run sub-sections of a larger SQL query with parameters.
I know there are one or two other ways to get EF to output the log. Those methods do provide better control over how parameters are output, but given the answer is about making Database.Log work I'll include this tool in WinForms, so it can rewrite your clipboard with a functional query.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
class parameter
{
public string Name;
public string Value;
public string Type;
public string FormattedValue
{
get
{
if (Type == "Boolean")
{
if (Value == "True")
return "1";
else
return "0";
}
else if (Type == "Int32")
{
return Value;
}
else
throw new Exception("Unsupported type - " + Type);
}
}
public override string ToString()
{
return string.Format("{0} - {1} - {2} - {3}", Name, Value, Type, FormattedValue);
}
}
private void button1_Click(object sender, EventArgs e)
{
var sb = new StringBuilder();
var data = Clipboard.GetText(TextDataFormat.UnicodeText);
var lines = data.Split(new string[] { "\r\n" }, StringSplitOptions.None);
var parameters = GetParmeters(lines);
parameters.Reverse();
foreach (var item in lines)
{
if (item.Trim().Length == 0)
continue;
if (item.TrimStart().StartsWith("--"))
continue;
var SQLLine = item;
foreach (var p in parameters)
{
SQLLine = SQLLine.Replace("#" + p.Name, p.FormattedValue);
}
sb.AppendLine(SQLLine);
}
Clipboard.SetText(sb.ToString());
}
private static List<parameter> GetParmeters(string[] lines)
{
var parameters = new List<parameter>();
foreach (var item in lines)
{
var trimed = item.Trim();
if (trimed.StartsWith("-- p__linq__") == false)
continue;
var colonInd = trimed.IndexOf(':');
if (colonInd == -1)
continue;
var paramName = trimed.Substring(3, colonInd - 3);
var valueStart = colonInd + 3;
var valueEnd = trimed.IndexOf('\'', valueStart);
if (valueEnd == -1)
continue;
var value = trimed.Substring(valueStart, valueEnd - valueStart);
var typeStart = trimed.IndexOf("(Type = ");
if (typeStart == -1)
continue;
typeStart += 8;
var typeEnd = trimed.IndexOf(',', typeStart);
if (typeEnd == -1)
typeEnd = trimed.IndexOf(')', typeStart);
if (typeEnd == -1)
continue;
var type = trimed.Substring(typeStart, typeEnd - typeStart);
var param = new parameter();
param.Name = paramName;
param.Value = value;
param.Type = type;
parameters.Add(param);
}
return parameters;
}
}

Get Windows Firewall prompt for console app but not service

I have a c# .NET app that receives TCP and UDP streams from other devices on the network.
When I run it as console app, the Windows Firewall prompts me: "Windows Firewall has blocked some features of this program" and it ask me to allow vshost32.exe to communicate on the network.
I agree and the app works fine.
However when I run the app as a service (I have a separate console and service wrappers) I get no such prompt and I can only get it to work if switch off the firewall.
Is this expected for services? ()
Also, I have read some code snippets that suggest you can manually add exceptions to Windows Firewall list. Is this just for console apps or will it work for services also?
Some my code that listens on the ports in case this is usefull...
//
// Setup UDP listening
//
if (protocol == "UDP")
{
m_udp = new UdpConn("RedwallReceiver UDP", m_local, new NetAddress());
m_udp.Receive(new VDataHandler(ReceiveData));
}
//
// Setup TCP listening
//
if (protocol == "TCP")
{
m_listener = new TcpListener(m_local);
m_listener.Start();
m_listener.BeginAcceptSocket(AcceptSocket, null);
}
Services execute under restricted environments and are allowed to have very little or no interaction with the UI. His answer covers all the reasoning and here is how to achieve the same.
I would recommend adding an additional project to your solution (let's call it Configurator) which can be launched as part of the installation process. As far as I remember, adding a rule to the firewall requires administrative privileges. Here are the steps:
Create the Configurator project as a Console or WinForms application. No UI is needed here.
Add an application manifest file to the Configurator project. right-click project, Add > New Item > Application Manifest File. Change the <requestedExecutionLevel> tag to read <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />.
Add the output of the Configurator project to your setup/deployment project.
Select the deployment project and navigate to the Custom Actions tab. Add a new custom action under the Commit node and make it point to the output of the Configurator project.
In the Configurator project, add a reference to NetFwTypeLib from COM references.
Add the code below to the Configurator project.
Modify the Main method of the Configurator project to return an int (0 for success, non-zero for failure) and use the following code. Note that I've pasted this from my project directly so you may need to fix some decleration errors, etc.
private static int Main (string [] args)
{
var application = new NetFwAuthorizedApplication()
{
Name = "MyService",
Enabled = true,
RemoteAddresses = "*",
Scope = NET_FW_SCOPE_.NET_FW_SCOPE_ALL,
IpVersion = NET_FW_IP_VERSION_.NET_FW_IP_VERSION_ANY,
ProcessImageFileName = "ServiceAssemblyName.dll",
};
return (FirewallUtilities.AddApplication(application, out exception) ? 0 : -1);
}
namespace MySolution.Configurator.Firewall
{
using System;
using System.Linq;
using NetFwTypeLib;
public sealed class NetFwAuthorizedApplication:
INetFwAuthorizedApplication
{
public string Name { get; set; }
public bool Enabled { get; set; }
public NET_FW_SCOPE_ Scope { get; set; }
public string RemoteAddresses { get; set; }
public string ProcessImageFileName { get; set; }
public NET_FW_IP_VERSION_ IpVersion { get; set; }
public NetFwAuthorizedApplication ()
{
this.Name = "";
this.Enabled = false;
this.RemoteAddresses = "";
this.ProcessImageFileName = "";
this.Scope = NET_FW_SCOPE_.NET_FW_SCOPE_ALL;
this.IpVersion = NET_FW_IP_VERSION_.NET_FW_IP_VERSION_ANY;
}
public NetFwAuthorizedApplication (string name, bool enabled, string remoteAddresses, NET_FW_SCOPE_ scope, NET_FW_IP_VERSION_ ipVersion, string processImageFileName)
{
this.Name = name;
this.Scope = scope;
this.Enabled = enabled;
this.IpVersion = ipVersion;
this.RemoteAddresses = remoteAddresses;
this.ProcessImageFileName = processImageFileName;
}
public static NetFwAuthorizedApplication FromINetFwAuthorizedApplication (INetFwAuthorizedApplication application)
{
return (new NetFwAuthorizedApplication(application.Name, application.Enabled, application.RemoteAddresses, application.Scope, application.IpVersion, application.ProcessImageFileName));
}
}
}
namespace MySolution.Configurator.Firewall
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using NetFwTypeLib;
public static class FirewallUtilities
{
public static bool GetApplication (string processImageFileName, out INetFwAuthorizedApplication application, out Exception exception)
{
var result = false;
var comObjects = new Stack<object>();
exception = null;
application = null;
if (processImageFileName == null) { throw (new ArgumentNullException("processImageFileName")); }
if (processImageFileName.Trim().Length == 0) { throw (new ArgumentException("The argument [processImageFileName] cannot be empty.", "processImageFileName")); }
try
{
var type = Type.GetTypeFromProgID("HNetCfg.FwMgr", true);
try
{
var manager = (INetFwMgr) Activator.CreateInstance(type);
comObjects.Push(manager);
try
{
var policy = manager.LocalPolicy;
comObjects.Push(policy);
var profile = policy.CurrentProfile;
comObjects.Push(profile);
var applications = profile.AuthorizedApplications;
comObjects.Push(applications);
foreach (INetFwAuthorizedApplication app in applications)
{
comObjects.Push(app);
if (string.Compare(app.ProcessImageFileName, processImageFileName, true, CultureInfo.InvariantCulture) == 0)
{
result = true;
application = NetFwAuthorizedApplication.FromINetFwAuthorizedApplication(app);
break;
}
}
if (!result) { throw (new Exception("The requested application was not found.")); }
}
catch (Exception e)
{
exception = e;
}
}
catch (Exception e)
{
exception = e;
}
finally
{
while (comObjects.Count > 0)
{
ComUtilities.ReleaseComObject(comObjects.Pop());
}
}
}
catch (Exception e)
{
exception = e;
}
finally
{
}
return (result);
}
public static bool AddApplication (INetFwAuthorizedApplication application, out Exception exception)
{
var result = false;
var comObjects = new Stack<object>();
exception = null;
if (application == null) { throw (new ArgumentNullException("application")); }
try
{
var type = Type.GetTypeFromProgID("HNetCfg.FwMgr", true);
try
{
var manager = (INetFwMgr) Activator.CreateInstance(type);
comObjects.Push(manager);
try
{
var policy = manager.LocalPolicy;
comObjects.Push(policy);
var profile = policy.CurrentProfile;
comObjects.Push(profile);
var applications = profile.AuthorizedApplications;
comObjects.Push(applications);
applications.Add(application);
result = true;
}
catch (Exception e)
{
exception = e;
}
}
catch (Exception e)
{
exception = e;
}
finally
{
while (comObjects.Count > 0)
{
ComUtilities.ReleaseComObject(comObjects.Pop());
}
}
}
catch (Exception e)
{
exception = e;
}
finally
{
}
return (result);
}
public static bool RemoveApplication (string processImageFileName, out Exception exception)
{
var result = false;
var comObjects = new Stack<object>();
exception = null;
if (processImageFileName == null) { throw (new ArgumentNullException("processImageFileName")); }
if (processImageFileName.Trim().Length == 0) { throw (new ArgumentException("The argument [processImageFileName] cannot be empty.", "processImageFileName")); }
try
{
var type = Type.GetTypeFromProgID("HNetCfg.FwMgr", true);
try
{
var manager = (INetFwMgr) Activator.CreateInstance(type);
comObjects.Push(manager);
try
{
var policy = manager.LocalPolicy;
comObjects.Push(policy);
var profile = policy.CurrentProfile;
comObjects.Push(profile);
var applications = profile.AuthorizedApplications;
comObjects.Push(applications);
applications.Remove(processImageFileName);
result = true;
}
catch (Exception e)
{
exception = e;
}
}
catch (Exception e)
{
exception = e;
}
finally
{
while (comObjects.Count > 0)
{
ComUtilities.ReleaseComObject(comObjects.Pop());
}
}
}
catch (Exception e)
{
exception = e;
}
finally
{
}
return (result);
}
}
}

Is it possible to access a variable in the Uninstall() proc that tells you the install directory?

I have a setup project that installs my service, gui, and a few other files. It installs them and creates the service and everything looks great! When the service runs, it creates a few text files and those text files stay in the install folder.
My question revolves around the uninstallation of the service/app. When I go to uninstall it does everything as expected but ends up leaving the program directory because the newly created text files that are still in there.
Is it possible to somehow get the original install path of the service and application when the Uninstall() proc gets fired up?
EX:
//Code to perform at the time of uninstalling application
public override void Uninstall(System.Collections.IDictionary savedState)
{
base.Uninstall(savedState);
try
{
//This is the variable I want to NOT be a static set hardcoded path...
string baseFolder = #"C:\Program Files (x86)\MyProgram\";
string[] allFileNames = System.IO.Directory.GetFiles(baseFolder, "*.*", System.IO.SearchOption.AllDirectories);
foreach (string filename in allFileNames)
{
FileAttributes attr = File.GetAttributes(filename);
File.SetAttributes(filename, attr & ~FileAttributes.ReadOnly);
}
System.IO.Directory.Delete(baseFolder, true);
}
catch (Exception)
{
//throw;
}
}
If this is a VS setup project, the answer is no unless you saved the location during the original install. The way you do this is to create a registry item in the IDE Registry view and give it the value [TARGETDIR] (case-sensitive) and that will resolve to the actual value. Then you can retrieve it from the registry.
It looks like the problem you're trying to solve is that the uninstall leaves some files behind, so perhaps that's the actual problem that could be fixed in some other way. The uninstall should be removing all the files that it installed and leave only those created by the app in the sane folder.
Answer:
YES! Just use the SaveState property and pass it along in the chain of the install process thread...
Solution:
Add an "Installer Class" file to your project, in this example it's called "InstallerActions.cs".
[RunInstaller(true)]
public partial class InstallerActions : System.Configuration.Install.Installer
{
public InstallerActions()
{
InitializeComponent();
}
//Code to perform at the time of installing application
public override void Install(System.Collections.IDictionary stateSaver)
{
//if (Debugger.IsAttached == false) Debugger.Launch();
CustomParameters cParams = new CustomParameters();
cParams.Add("InstallPath", this.Context.Parameters["targetdir"]);
cParams.SaveState(stateSaver);
//Continue with install process
base.Install(stateSaver);
}
//Code to perform at the time of uninstalling application
public override void Uninstall(System.Collections.IDictionary savedState)
{
//if (Debugger.IsAttached == false) Debugger.Launch();
CustomParameters cParams = new CustomParameters(savedState);
string sBase = cParams.GetValue("InstallPath");
//Continue with uninstall process
base.Uninstall(savedState);
try
{
//Delete all files in the base folder recursively except service or exe files
string[] files = System.IO.Directory.GetFiles(sBase, "*.*", System.IO.SearchOption.AllDirectories);
foreach (string f in files)
{
//Check the extention of the filename first
string sExt = Path.GetExtension(f).ToLower();
if (sExt != ".installstate" && sExt != ".exe")
{
FileAttributes attr = File.GetAttributes(f);
File.SetAttributes(f, attr & ~FileAttributes.ReadOnly);
System.IO.File.Delete(f);
}
}
//This would delete the base install directory but
//the installer will do this instead, I just need to
//clean up everything I made other the the exe's and
//service files
//System.IO.Directory.Delete(sBase, true);
}
catch
{
//Error, just ignore it
}
}
}
Create another class called "CustomParameters.cs"
public class CustomParameters
{
//Private variables
private Dictionary<string, string> dParams;
//Constructor
public CustomParameters()
{
//Reset the dictionary
dParams = new Dictionary<string, string>();
}
public CustomParameters(IDictionary savedState)
{
string sKey = "";
string sValue = "";
//Import saved state data
if (dParams == null) dParams = new Dictionary<string, string>();
foreach (var entry in (dynamic)savedState)
{
object dKey = entry.Key;
object dValue = entry.Value;
switch (dKey.GetType().ToString())
{
case "System.String":
//Save the key
sKey = (string)dKey;
switch (dValue.GetType().ToString())
{
case "System.String":
//Save the string value
sValue = (string)dValue;
break;
case "System.Int32":
//Save the int value
sValue = ((int)dValue).ToString();
break;
}
break;
}
//Save the keypair to the global dictionary
dParams.Add(sKey, sValue);
}
}
//Public functions
public void Add(string sParameterKey, string sParameterValue)
{
if (dParams == null) return;
//Add or update the key
dParams[sParameterKey] = sParameterValue;
}
public void Delete(string sParameterKey)
{
if (dParams == null) return;
//Delete the key if it exists
if (dParams.ContainsKey(sParameterKey))
{
dParams.Remove(sParameterKey);
}
}
public void SaveState(IDictionary savedState)
{
if (dParams == null) return;
foreach (KeyValuePair<string, string> param in dParams)
{
if (savedState.Contains(param.Key) == true)
savedState[param.Key] = param.Value;
else
savedState.Add(param.Key, param.Value);
}
}
public string GetValue(string sKey)
{
if (dParams.ContainsKey(sKey))
{
return dParams[sKey].ToString();
}
else
{
return "";
}
}
}

Using SharpSvn to retrieve log revisions got Disk issue

I'm workin on a wcf service to get some info of svn log.
My service method:
public List<SvnLogInfo> ViewLog(Executable ejecutable) {
Configuration config = m_context.Configuration.SingleOrDefault();
if (config != null) {
SvnClient svnClient = new SvnClient();
SvnRevisionRange svnRevisionRange = new SvnRevisionRange(ejecutable.SvnRevisionFrom, ejecutable.SvnRevisionTo);
SvnLogArgs args = new SvnLogArgs(svnRevisionRange);
Collection<SvnLogEventArgs> logCollection;
svnClient.GetLog(config.RepositoryPath, args, out logCollection);
List<SvnLogInfo> logInfo = new List<SvnLogInfo>();
foreach (SvnLogEventArgs log in logCollection) {
logInfo.Add((SvnLogInfo)log);
}
return logInfo;
}
return null;
}
[Serializable]
public class SvnLogInfo {
public SvnLogInfo() {
}
private string m_message;
public string Mensaje {
get { return m_message; }
set { m_message = value; }
}
private string m_author;
public string Autor {
get { return m_author; }
set { m_author = value; }
}
public static explicit operator SvnLogInfo(SvnLogEventArgs e) {
SvnLogInfo info = new SvnLogInfo();
info.Mensaje = e.LogMessage;
info.Autor = e.Author;
return info;
}
}
It works, but when excetuing this line:
svnClient.GetLog(config.RepositoryPath, args, out logCollection);
Throws me this error message:
There is no disk in drive G.
As I mention, I'm using SharpSvn library. Is there a way to solve this issues?. By the way, the variable config.RepositoryPath has this value "C:\Users\carlos.vega.CONTAPERU\Desktop\Solucion ContaNet v3 Espero Funcione"

Get the version information of an installed service?

I want to check programmatically that the latest version of my Windows Service is installed. I have:
var ctl = ServiceController.GetServices().Where(s => s.ServiceName == "MyService").FirstOrDefault();
if (ctl != null) {
// now what?
}
I don't see anything on the ServiceController interface that will tell me the version number. How do I do it?
I am afraid there is no way other than getting the executable path from the registry as ServiceController does not provide that information.
Here is a sample I had created before:
private static string GetExecutablePathForService(string serviceName, RegistryView registryView, bool throwErrorIfNonExisting)
{
string registryPath = #"SYSTEM\CurrentControlSet\Services\" + serviceName;
RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView).OpenSubKey(registryPath);
if(key==null)
{
if (throwErrorIfNonExisting)
throw new ArgumentException("Non-existent service: " + serviceName, "serviceName");
else
return null;
}
string value = key.GetValue("ImagePath").ToString();
key.Close();
if(value.StartsWith("\""))
{
value = Regex.Match(value, "\"([^\"]+)\"").Groups[1].Value;
}
return Environment.ExpandEnvironmentVariables(value);
}
After getting the exe path, just use FileVersionInfo.GetVersionInfo(exePath) class to get the version.
If you own the service, you can put version information into the DisplayName, e.g. DisplayName="MyService 2017.06.28.1517". This allows you to find an existing installation of your service and parse the version information:
var ctl = ServiceController
.GetServices()
.FirstOrDefault(s => s.ServiceName == "MyService");
if (ctl != null) {
// get version substring, you might have your own style.
string substr = s.DisplayName.SubString("MyService".Length);
Version installedVersion = new Version(substr);
// do stuff, e.g. check if installed version is newer than current assembly.
}
This may be useful if you want to avoid the registry. The problem is, that service entries can go to different parts of the registry depending on the installation routine.
If you are talking about getting the current version of your service automatically from the assembly properties then you can set up a property such as below in your ServiceBase class.
public static string ServiceVersion { get; private set; }
Then in your OnStart method add the following...
ServiceVersion = typeof(Program).Assembly.GetName().Version.ToString();
Full Example
using System.Diagnostics;
using System.ServiceProcess;
public partial class VaultServerUtilities : ServiceBase
{
public static string ServiceVersion { get; private set; }
public VaultServerUtilities()
{
InitializeComponent();
VSUEventLog = new EventLog();
if (!EventLog.SourceExists("Vault Server Utilities"))
{
EventLog.CreateEventSource("Vault Server Utilities", "Service Log");
}
VSUEventLog.Source = "Vault Server Utilities";
VSUEventLog.Log = "Service Log";
}
protected override void OnStart(string[] args)
{
ServiceVersion = typeof(Program).Assembly.GetName().Version.ToString();
VSUEventLog.WriteEntry(string.Format("Vault Server Utilities v{0} has started successfully.", ServiceVersion));
}
protected override void OnStop()
{
VSUEventLog.WriteEntry(string.Format("Vault Server Utilities v{0} has be shutdown.", ServiceVersion));
}
}
In the example above my event log displays the current version of my service...

Categories

Resources