Problem
I am trying to pass arguments from the command line to my service class that inherits ServiceBase before the service installation. I found a way to accept parameters when using InstallUtil to run my service installer. However, the the main function running ServiceBase.Run(new Service()); triggers before those parameters can be accessed. Therefore, I don't know how to pass command line parameters into my Service() class before it is run.
I am using ConfigStream as a static class to read and store parameters from a text config file. My goal is to import the settings from the config file and apply them to my Service class before the installer is run. I'd like to be able to point to the location of that config file from an input in the command line.
Attempted Solutions
I've tried applying the parameters to the ConfigStream class within the OnBeforeInstall function of the Installer, but that still runs after the main function, so it doesn't apply the settings to my Service class.
I also tried following a tutorial from microsoft, but also had no luck. The parameters were never passed to Main. Tutorial: Create Windows Service - Add Optional Params
Main Class
public static class MainClass
{
///Function: Main
///File: ServiceWrapperV2.cs
///Author: Luke Maple
///Purpose: Main function of program, start up the service portion of program.
static void Main(String[] args)
{
// Test if input arguments were supplied
Console.WriteLine("Args: ");
Console.WriteLine(args);
if (args.Length > 0)
{
// set config location if provided
// otherwise assume in working directory
ConfigStream.setConfigstream(args[0]);
}
// run service
ServiceBase.Run(new Service());
}
}
Installer Attempt 1
[RunInstaller(true)]
public class ServiceWrapperInstaller : Installer
{
private ServiceProcessInstaller processInstaller;
private ServiceInstaller serviceInstaller;
public ServiceWrapperInstaller()
{
//inilizing installer objects
processInstaller = new ServiceProcessInstaller();
serviceInstaller = new ServiceInstaller();
//Service will use the windows local system acount. Service will be run as admin
processInstaller.Account = ServiceAccount.LocalSystem;
//sets service start mode to automatic. service will auto boot on restarts
serviceInstaller.StartType = ServiceStartMode.Automatic;
// set service parameters
// Console.WriteLine(ConfigStream.getSetting("Service Name"));
serviceInstaller.ServiceName = ConfigStream.getSetting("Service Name");
// Console.WriteLine("\nService Name: " + serviceInstaller.ServiceName + "\n");
serviceInstaller.Description = ConfigStream.getSetting("Service Description");
//passing object to be installed
Installers.Add(serviceInstaller);
Installers.Add(processInstaller);
}
private void _setConfigLocation()
{
if (Context.Parameters.ContainsKey("config"))
{
ConfigStream.setConfigstream(Context.Parameters["config"]);
}
}
protected override void OnBeforeInstall(IDictionary savedState)
{
_setConfigLocation();
base.OnBeforeInstall(savedState);
}
}
Installer Attempt 2
[RunInstaller(true)]
public class ServiceWrapperInstaller : Installer
{
private ServiceProcessInstaller processInstaller;
private ServiceInstaller serviceInstaller;
public ServiceWrapperInstaller()
{
//inilizing installer objects
processInstaller = new ServiceProcessInstaller();
serviceInstaller = new ServiceInstaller();
//Service will use the windows local system acount. Service will be run as admin
processInstaller.Account = ServiceAccount.LocalSystem;
//sets service start mode to automatic. service will auto boot on restarts
serviceInstaller.StartType = ServiceStartMode.Automatic;
// set service parameters
// Console.WriteLine(ConfigStream.getSetting("Service Name"));
serviceInstaller.ServiceName = ConfigStream.getSetting("Service Name");
// Console.WriteLine("\nService Name: " + serviceInstaller.ServiceName + "\n");
serviceInstaller.Description = ConfigStream.getSetting("Service Description");
//passing object to be installed
Installers.Add(serviceInstaller);
Installers.Add(processInstaller);
}
protected override void OnBeforeInstall(IDictionary savedState)
{
string parameter = "Config";
Context.Parameters["assemblypath"] = "\"" + Context.Parameters["assemblypath"] + "\" \"" + parameter + "\"";
base.OnBeforeInstall(savedState);
}
}
ConfigStream
public static class ConfigStream
{
// set class properties of ConfigStream
public static string config { get; private set; } = GlobalVariables.cwd + "\\" + GlobalVariables.configFileName;
// constructor with config as an argument
public static void setConfigstream(string config_location)
{
string config = config_location;
}
///Function: (static) getSetting
///Purpose: get requested value form formatted config file
public static string getSetting(string setting)
{
...
}
...
}
You can try something like this:
[RunInstaller(true)]
public partial class ServiceWrapperInstaller : Installer
{
private const string nameKey = "name";
private const string displayNameKey = "displayname";
private const string descriptionKey = "description";
protected override void OnBeforeInstall(IDictionary savedState)
{
// Set installer parameters
SetParameters();
base.OnBeforeInstall(savedState);
}
private void SetParameters()
{
// Set service name
_serviceInstaller.ServiceName = this.Context.Parameters[nameKey];
// Set the display name (if provided)
if (Context.Parameters.ContainsKey(displayNameKey))
{
_serviceInstaller.DisplayName = this.Context.Parameters[displayNameKey];
}
// Set the description (if provided)
if (Context.Parameters.ContainsKey(descriptionKey))
{
_serviceInstaller.Description = this.Context.Parameters[descriptionKey];
}
_serviceInstaller.StartType = ServiceStartMode.Automatic;
_serviceInstaller.DelayedAutoStart = true;
}
}
You can use it like this:
InstallUtil /u /name= /displayname=<\"Display Name\"> /description=<\"Description of Service\"
Related
Following the tutorial here I'm self hosting a WCF service inside of a windows service. My WCF service contains a global object that I update at regular intervals. I want to serialize that object to JSON and return that JSON string via a service endpoint. When I access the endpoint that calls the serialize method on the service, I get what appears to be a brand new instance of the global. The service is set to [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
I'm instantiating in the same way as the tutorial:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class MyWindowsService: ServiceBase
{public ServiceHost serviceHost = null;
public Service()
{
ServiceName = "MyService";
}
public static void Main()
{
ServiceBase.Run(new MyWindowsService());
}
protected override void OnStart(string[] args)
{
if (serviceHost != null)
{
serviceHost.Close();
}
serviceHost = new ServiceHost(typeof(MyWCFService));
serviceHost.Open();
}
protected override void OnStop()
{
if (serviceHost != null)
{
serviceHost.Close();
serviceHost = null;
}
}
And my WCF service looks like this:
public class MyWCFService: IWCFService
{
private myObject = mySerializableObject;
public MyWCFService()
{
myObject = new MySerializableObject();
myObject.Init();
}
public Stream GetJSON()
{
MemoryStream stream = new MemoryStream();
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(MySerializableObject));
ser.WriteObject(stream, myObject);
string jsonString = Encoding.ASCII.GetString(stream.ToArray());
byte[] resultBytes = Encoding.UTF8.GetBytes(jsonString);
WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";
return new MemoryStream(resultBytes);
}
}
If I GET the GetJSON endpoint, the string returned is a brand new initialization of the object. If I break on the GetJSON method, myObject shows all newly initialized values. Placing a breakpoint in the MySerializableObject update code shows that the updates are occurring correctly and being saved to the object in memory.
Running the same code in a normal console application works fine. Why the discrepancy? Am I handling the global incorrectly?
Figured it out ... I had a couple of issues going on.
As indicated here, my InstanceContextMode declaration
wasn't decorating the right service, so it was still running in the
default per-call context - hence, a new state object on every connection.
O'Reilly showed me that I wasn't creating my singleton
instance correctly. Side note, when creating a ServiceHost using an instance, you can specify a base URI either explicitly in the constructor or in App.config. That caused me some confusion as many of the examples I ran across passed it in the constructor rather than the config.
Here's my working implementation for posterity:
[ServiceContract(Namespace = "http://my.super.original.namespace")]
public interface IWCFService
{
[OperationContract, WebGet]
Stream GetJSON();
}
//Decorator goes on WCF service, not the Windows service
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class MyWCFService : IWCFService
{
private StateObject _myStateObject;
public MyWCFService()
{
_myStateObject = new StateObject();
_myStateObject.Init();
}
public Stream GetJSON()
{
.
.
.
return "JSON String Here";
}
}
public class MyWindowsService : ServiceBase
{
public ServiceHost serviceHost = null;
private readonly MyWcfService _wcfSingleton;
public MyWindowsService()
{
ServiceName = "WindowsServiceNameHere";
_wcfSingleton = new MyWCFService();
}
public static void Main()
{
ServiceBase.Run(new MyWindowsService());
}
// Start the Windows service.
protected override void OnStart(string[] args)
{
if (serviceHost != null)
{
serviceHost.Close();
}
//load WCF Singleton Instance and open it for connections
serviceHost = new ServiceHost(_wcfSingleton);
serviceHost.Open();
}
protected override void OnStop()
{
if (serviceHost != null)
{
serviceHost.Close();
serviceHost = null;
}
}
}
I'm trying to get a TopShelf service to execute.At the moment I have just some test code to see if I could get the service to install.So far I have been able to get the services Start() and execute when in debug mode.From there I install the service, eg ConsoleApp.exe Install.The Service will show in Services, however when I start the service, it doesn't do anything, eg at the moment I have it creating a txt file on my desktop, which it does when using Visual Studio, however when I start the service from services, it wont.
Here is what I have so far.
First I create the Service Class
class FooService
{
public void Start()
{
string DESKTOP = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string Foo = "foo.txt";
string fullPath = System.IO.Path.Combine(DESKTOP, Foo);
using (System.IO.StreamWriter sw = new StreamWriter(fullPath))
{
sw.WriteLine("Hello World");
}
}
public void Stop()
{
Console.WriteLine("Service Stopped");
}
}
As you can see, I'm just creating a txt file on the desktop (which works in VS).From there I configure the service.
internal static class ConfigureService
{
internal static void Config(string serviceName, string serviceDescription)
{
if (string.IsNullOrEmpty(serviceName) | string.IsNullOrEmpty(serviceDescription))
{
return;
}
HostFactory.Run(Config =>
{
Config.Service<FooService>(service =>
{
service.ConstructUsing(s => new FooService());
service.WhenStarted(s => s.Start()); // start event.
service.WhenStopped(s => s.Stop()); // stop event.
});
// Create Event.
Config.RunAsLocalSystem(); // user.
Config.SetServiceName(serviceName); // service name.
Config.SetDescription(serviceDescription); // service description.
});
}
}
And finally I execute it with the following;
// Entry Point.
static void Main()
{
ConfigureService.Config("Foo Service", "some discription....Idk");
Console.ReadLine();
}
When starting the service, it says it Running however the file text file isn't appearing on the desktop.
I have a custom Installer class that is used when installing my Windows Service. Stripped down to the necessary details the class looks like this.
[RunInstaller(true)]
public class MyWindowsServiceInstaller : Installer
{
public MyWindowsServiceInstaller()
{
ServiceProcessInstaller processInstaller = new ServiceProcessInstaller();
ServiceInstaller serviceInstaller = new ServiceInstaller();
processInstaller.Account = ServiceAccount.LocalSystem;
serviceInstaller.DisplayName = Program.ServiceDetails.Name;
serviceInstaller.Description = Program.ServiceDetails.Description;
//Must be the same as what was set in Program's constructor
serviceInstaller.ServiceName = Program.ServiceDetails.Name;
Installers.Add(processInstaller);
Installers.Add(serviceInstaller);
}
}
The service is then installed through code by calling the following class, depending on arguments passed into the service, like so. This is called from inside of Main.
using (ServiceHandler serviceHandler = new ServiceHandler(program.ModuleName, typeof(Program).Assembly))
{
serviceHandler.InstallService();
}
Where the ServiceHandler class is (again stripped down to remove noise).
public class ServiceHandler : IDisposable
{
private ServiceController _serviceController;
private AssemblyInstaller _assemblyInstaller;
public ServiceHandler(string serviceName, Assembly assembly)
{
_serviceController = new ServiceController(serviceName);
_assemblyInstaller = new AssemblyInstaller(assembly, null);
_assemblyInstaller.UseNewContext = true;
}
public void InstallService()
{
if (IsServiceInstalled())
{
return;
}
IDictionary state = new Hashtable();
try
{
_assemblyInstaller.Install(state);
_assemblyInstaller.Commit(state);
}
catch
{
try
{
_assemblyInstaller.Rollback(state);
}
catch { }
throw;
}
}
public bool IsServiceInstalled()
{
try
{
ServiceControllerStatus status = _serviceController.Status;
}
catch
{
return false;
}
return true;
}
}
However, at the moment all of our services use the same MyWindowsServiceInstaller but copied into each project separately. To resolve this I was going to move that class to a common assembly with some other functionality (and remove the coupling of the class with Program) but I'm not sure if it's possible to have the Installer in another assembly.
Is this possible? If so how do I go about it?
I imagine another problem with my approach is the typeof(Program).Assembly call to create the ServiceHandler but I'm not sure.
Installer looks into the assembly and looking for [RunInstaller(true)] attribute. Only think you should do: Mark for installer witch is your installer class. Put an inherited empty class into your main assembly.
Common Assembly:
//[RunInstaller(true)] <<-- REMOVE this
public class MyWindowsServiceInstaller : Installer
{
public MyWindowsServiceInstaller(){
ServiceProcessInstaller processInstaller = new
ServiceProcessInstaller();
ServiceInstaller serviceInstaller = new ServiceInstaller();
processInstaller.Account = ServiceAccount.LocalSystem;
serviceInstaller.DisplayName = Program.ServiceDetails.Name;
serviceInstaller.Description = Program.ServiceDetails.Description;
//Must be the same as what was set in Program's constructor
serviceInstaller.ServiceName = Program.ServiceDetails.Name;
Installers.Add(processInstaller);
Installers.Add(serviceInstaller);
}
}
Main assembly
[RunInstaller(true)] // <<-- put it here
public class ProjectInstaller : MyWindowsServiceInstaller { }
I've made a powershell Cmdlet for creating an AppFabric a region, the code:
[Cmdlet(VerbsCommon.New, "CacheRegion")]
public class NewCacheRegion : Cmdlet {
[Parameter(Mandatory = true, Position = 1)]
public string Cache { get; set; }
[Parameter(Mandatory = true, Position = 2)]
public string Region { get; set; }
protected override void ProcessRecord() {
base.ProcessRecord();
DataCacheFactory factory = new DataCacheFactory(); // exception
DataCache cache = factory.GetCache(Cache);
try {
cache.CreateRegion(Region);
}
catch (DataCacheException ex) {}
}
}
It's installed with import-module appfabriccmdlet.dll and the code executes when running new-cacheregion.
but the line
DataCacheFactory factory = new DataCacheFactory();
throws an exception that server collection is empty which means that no dataCacheClient section is found in app.config. So I want to a client that but Im not sure in which config file to add the appfabric sections. I've tried finding out from what executable the cmdlet dll is running with
Assembly a = Assembly.GetEntryAssembly();
but that returns null.
So where do I need to put config sections that a cmdlet dll has access to?
never mind.
fixed it by programmatically adding the server without config
DataCacheServerEndpoint[] servers = new DataCacheServerEndpoint[1];
servers[0] = new DataCacheServerEndpoint("localhost", 22233);
DataCacheFactoryConfiguration conf = new DataCacheFactoryConfiguration();
conf.Servers = servers;
DataCacheFactory factory = new DataCacheFactory(conf);
I am using an IoC container (Unity) to register interfaces and to resolve/instantiate objects.
It is working fine and all classes which have dependencies on interfaces are injected in the constructor, however i am running into a design issue.
I have a CronJob service, which calls registered delegates at specific times, it is also instantiated by the service locator.
Since jobs can be registered, i am referencing the service locator Unity container from within the instantiated class, i am doing this as i do not know at compile time what objects will be passed into the constructor since jobs can be registered dynamically.
However although i am new to IoC and unity, as i understand it is bad to reference the static service locator from within a service, all dependencies should be passed in the constructor - so I would appreciate thoughts on other ways that this could be done or is it OK to reference the service locator in this case?
Thanks,
Chris
Code is below:
Service locator using Unity
// ServiceManager provides service location facilities, logging facilities, and database access via IUnitOfWork interface
public class ServiceManager
{
private static readonly UnityContainer m_ServicesContainer = new UnityContainer();
private static readonly ServiceManager m_Manager = new ServiceManager();
public static ServiceManager Instance { get { return m_Manager; } }
private ILogger Logger { get { return Resolve<ILogger>(); } }
public T Resolve<T>()
{
return m_ServicesContainer.Resolve<T>();
}
private ServiceManager()
{
// register the unit of work class first!!
RegisterType<IUnitOfWork, UnitOfWork>();
// always register the logger (without logging)
RegisterType<ILogger, NLogForEntityFrameworkLogger>(true);
// always register the settings manager (without logging)
RegisterType<ISettingsService, SettingsService>();
RegisterType<IPluginManagerService, PluginManagerService>(true);
RegisterType<ICronJobService, CronJobService>(true);
RegisterType<IReminderGeneratorService, ReminderGeneratorService>();
RegisterType<IInvoiceService, InvoiceService>();
}
public void RegisterType<TFrom, TTo>(bool isSingleton = false)
{
if (isSingleton == false)
m_ServicesContainer.RegisterType(typeof(TFrom), typeof(TTo));
else
m_ServicesContainer.RegisterType(typeof(TFrom), typeof(TTo), new ContainerControlledLifetimeManager());
}
}
CronJob class
public static class CronJobDelegates
{
public static void SyncRecords(BusinessUnit businessUnit)
{
ISynchronisationService syncService = ServiceManager.Instance.Resolve<ISynchronisationService>();
syncService.Sync(businessUnit);
}
}
class CronJobService : ServiceBaseWithUnitOfWork, ICronJobService
{
public CronJobService(IUnitOfWork unitOfWork, ILogger logger, ISettingsService settings)
: base(unitOfWork, logger)
{
m_Settings = settings;
RegisterCronJob("SyncAccountRecords", CronJobDelegates.SyncRecords,"*1****");
}
ISettingsService m_Settings;
public class RegisteredCronJob
{
public RegisteredCronJob(string jobName, EventJobDelegate job)
{
JobName = jobName;
Job = job;
}
public string JobName { get; private set; }
public EventJobDelegate Job { get; private set; }
}
static object Lock = new object();
Dictionary<string, EventJobDelegate> CronJobs = new Dictionary<string, EventJobDelegate>();
public void RegisterCronJob(string jobName, EventJobDelegate jobCallback, string jobSetting)
{
lock(Lock)
{
if(CronJobs.ContainsKey(jobName))
{
LogMessage("Job '" + jobName + "' already registered", LogLevel.Warn);
// warning job already registered
}
else
{
CronJob cronJobRecord = UnitOfWork.CronJobRepository.GetByID(jobName);
if (cronJobRecord == null)
{
CronJob newCronJob = new CronJob()
{
JobName = jobName,
JobSetting = jobSetting
};
UnitOfWork.CronJobRepository.Insert(newCronJob);
}
else
jobSetting = cronJobRecord.JobSetting;
LogMessage("Job '" + jobName + "' registered using settings: " + jobSetting + ". Next run due on UTC " + NCrontab.CrontabSchedule.Parse(jobSetting).GetNextOccurrence(DateTime.UtcNow), LogLevel.Info);
CronJobs.Add(jobName, jobCallback);
UnitOfWork.Save();
}
}
}
public void ProcessEvents()
{
foreach(BusinessUnit businessUnit in UnitOfWork.BusinessUnitRepository.Get())
{
foreach (CronJob cronJob in UnitOfWork.CronJobRepository.Get())
{
lock(Lock)
{
NCrontab.CrontabSchedule schedule = NCrontab.CrontabSchedule.Parse(cronJob.JobSetting);
if (schedule.GetNextOccurrence(cronJob.LastRan) > DateTime.UtcNow.AddHours(businessUnit.GmtOffset))
{
EventJobDelegate jobDelegate;
if (CronJobs.TryGetValue(cronJob.JobName, out jobDelegate) == true )
{
jobDelegate(businessUnit);
cronJob.LastRan = DateTime.UtcNow;
UnitOfWork.CronJobRepository.Update(cronJob);
LogMessage("Job '" + cronJob.JobName + "' ran, next schedule on " + schedule.GetNextOccurrence(cronJob.LastRan));
}
}
}
}
}
UnitOfWork.Save();
}
}
You could inject a factory, which then resolves an instance of ISynchronisationService calling the container as you did in SyncRecords.
For examples of implementing the factory approach see here, where several alternatives are listed.