How to make a Windows service with parameters? - c#

I have written a Windows service, of which I want to have 1 instance running per customer. This is because the customers each have their own DB with identical schemas; the only difference between the Windows services is that they will each have a different parameter corresponding to the customer DB that they're designated to serve. (And I can't have one service with multiple worker threads, because the DB connection uses a static variable, which I can't fiddle with across threads.)
I found this neat little tutorial about how to make a Windows Service, but it only shows me how to set it up for a single service. I want to set up n instances of the service, each one with a display name that includes the customer name, running with the command line parameter that denotes the customer ID.
The tutorial linked above has a class called MyWindowsServiceInstaller, which installs the windows service on the local system, and I'm guessing this would be a logical place to set up a foreach loop through all my customers, setting up one service for each. But I can't see anywhere on the interfaces provided that would allow me to set up a command line parameter for the new service.
How do you do it?

All I wanted was to send one parameter to the service I have created.
As it turns out, all you have to do is (carefully!) edit the registry at HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ and add the parameter in ImagePath, after the quotes.
Eg. ImagePath Value Data: "C:\Program Files\myservice\myservice.exe" param1
I found the solution in this link http://social.msdn.microsoft.com/Forums/is/csharpgeneral/thread/38242afa-7e40-4c06-975e-aa97d3cc782f

Wil Peck wrote a good article about how to install multiple instances of a windows service on a single box. The basic idea is that you have to trick the installer into thinking they are different services by giving them different names.
Having said that, it seems like it would be easier (and more maintainable) to redesign your database connection code so that it can support multiple worker threads.

You can pass parameters to your installer using installutil, for example ServiceName and DisplayName.
ProjectInstaller.cs
public partial class ProjectInstaller : Installer
{
protected override void OnBeforeInstall(IDictionary savedState)
{
SetServiceName();
base.OnBeforeInstall(savedState);
}
protected override void OnBeforeUninstall(IDictionary savedState)
{
SetServiceName();
base.OnBeforeUninstall(savedState);
}
private string AppendParameter(string path, char parameter, string value)
{
if (!path.StartsWith("\""))
path = $"\"{path}\"";
if (value.Contains(" "))
value = $"\"{value}\"";
return $"{path} -{parameter}{value}";
}
private void SetServiceName()
{
if (Context.Parameters.ContainsKey("ServiceName"))
serviceInstaller.ServiceName = Context.Parameters["ServiceName"];
if (Context.Parameters.ContainsKey("DisplayName"))
serviceInstaller.DisplayName = Context.Parameters["DisplayName"];
Context.Parameters["assemblypath"] = AppendParameter(Context.Parameters["assemblypath"], 's', serviceInstaller.ServiceName);
}
}
This will append a parameter to the path stored with the service, for example:
Before: "C:\Service.exe"
After: "C:\Service.exe" -s"Instance 1"
You can then read this parameter when you start the service and pass to your services constructor.
Program.cs
static void Main(string[] args)
{
string serviceName = args.Single(x => x.StartsWith("-s")).Substring("-s".Length);
ServiceBase service = new Service(serviceName);
ServiceBase.Run(service);
}
Service.cs
public partial class Service : ServiceBase
{
public Service(string serviceName)
{
InitializeComponent();
ServiceName = serviceName;
}
}
Usage
installutil /ServiceName="Instance 1" /DisplayName="Instance 1 Service" "C:\Service.exe"
installutil /ServiceName="Instance 2" /DisplayName="Instance 2 Service" "C:\Service.exe"

You basically need to install the service several times, and customise it with it's exe.config file.
Alternatively, you can have one service that runs different worker threads for each client.
Update
exe.Config is an Application Configuration File
I have no idea how to use that installer component to install several instances of the service, I wasn't aware you could.
Where we need several instances of one of our services to run on one machine, we actually only install it once, then literally copy the installed folder and change the exe name for the second instance. The second instance is then configured in it's own Application Configuration File.

As far as I known it is impossible to provide startup parameters using either ServiceInstaller, ServiceProcessInstaller or installutil. However, it is possible to provide startup parameters using some COM api's from advapi.dll (check the left menu). A complete collection of the required calls can be found here. It's a class (also) called ServiceInstaller that contains the required external methods and some utility methods.
You'd want to use the utility method InstallAndStart. It accepts a service name, a display name and a path to the executable that represents your Windows service. You can call it like this:
InstallAndStart("MyService", "My Service For User 1",
"c:\\pathtoexe\MyService.exe user1");
If you have the following service the parameter startupParam will receive the value user1.
class Program : ServiceBase
{
private string startupParam;
static void Main(string[] args)
{
string arg = args[0];
ServiceBase.Run(new Program(arg));
}
public Program(string startupParam)
{
this.ServiceName = "MyService";
this.startupParam = startupParam;
}
...
}

Related

Is it possible to persist an environment setting from within an Azure function?

I've created an Azure Function that retrieves new form inputs from a website, processes them and stores the result in another system by using an API call. I only want to retrieve the form inputs that have not been processed before. This is supported by the website.
I'm reading the timestamp of the most recent form input that has already been processed. This works fine.
I'm using the following function to read the setting from the Azure function environment:
private static string GetEnvironmentVariable(string name)
{
return System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);
}
After I've processed a form input, I store the timestamp of the form with the following function:
private static void SetEnvironmentVariable(string name, string value)
{
System.Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.Process);
}
Everything seems to be working fine. I see in the logs that form inputs don't get processed more than once. However, when I take a look at the environment variables in the Azure dashboard, I can see that the initial value of the variable is still present. This initial value will be used when the environment 'shuts down' and is restarted (e.g. after changing the value of another environment variable).
I've tried to change the target from 'Process' to 'Machine', but this results in access control errors. There are some questions on SO that are related to my issue, but none of them provides me with an answer for my situation.
I would like to know whether:
Environment variables are the / a suited solution for my use case;
If so, how can I prevent that a variable will be reset to its initial value after resetting the Azure environment.
Thanks in advance!
Firstly, the Environment.SetEnvironmentVariable method already worked in your case.
Here is an answer from Hury Shen:
When you set the variable by Environment.SetEnvironmentVariable, it
will not show in application setting. But we can use it by
Environment.GetEnvironmentVariable as expected. Although the solution
you mentioned is not so good, but it can implement your requirement.
The adverse effect is when you restart the function app, the variables
will be lost.
About the target Machine: The environment variable is stored or retrieved from the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment key in the Windows operating system registry. This value should be used on .NET implementations running on Windows systems only.
One way to achieve but not set inside code:
In App Service, you can set app settings outside of your app code.
Then you can access them in any class using the standard ASP.NET Core
dependency injection pattern:
using Microsoft.Extensions.Configuration;
namespace SomeNamespace
{
public class SomeClass
{
private IConfiguration _configuration;
public SomeClass(IConfiguration configuration)
{
_configuration = configuration;
}
public SomeMethod()
{
// retrieve nested App Service app setting
var myHierarchicalConfig = _configuration["My:Hierarchical:Config:Data"];
// retrieve App Service connection string
var myConnString = _configuration.GetConnectionString("MyDbConnection");
}
}
}

Can I omit ServiceBase.ServiceName when starting Windows Service?

Looking at the documentation it seems like Windows uses it in 2 scenarios:
The ServiceBase.ServiceName needs to be the same name as when it is installed, however when starting my service I am able to call ServiceBase.Run() without specifying the service name or specifying a different service name altogether and my application still starts correctly. I am using a separate WiX project to install my service and define the service name there depending on some TRANSFORMS.
Windows uses the ServiceBase.ServiceName to specify the EventLog.Source. I am successfully able to use Log4Net's EventLogAppender to log to the EventLog, manually specifying the applicationName in my log4net configs.
I want to make sure that I don't run into any repercussions down the road in the case that I don't specify the ServiceName correctly, however I am currently able to hit all my typical use cases as is. After calling ServiceBase.Run() I am able to use System.Management to determine my service name in case of any additional needs.
My main concern with avoiding setting the service name here is because my MSI installer can install different instances of my exe as different services via TRANSFORMs I create a sort of chicken-and-egg problem where I can't call GetServiceName() without calling ServiceBase.Run(), but I can't call ServiceBase.Run() without defining the ServiceBase.ServiceName.
Some example code of what I am running:
public aync Task<int> RunAsync()
{
var serviceToRun = new ServiceBase{/*ServiceName = "Avoiding.."*/};
var runServiceTask = Task.Run(() => ServiceBase.Run(serviceToRun));
logger.Warn($"ServiceName : '{GetServiceName()}'");
logger.Warn($"Service ShortName : '{serviceToRun.ServiceName}'");
await runServiceTask.ConfigureAwait(false);
return serviceToRun.ExitCode;
}
public string GetServiceName()
{
var processId = Process.GetCurrentProcess().Id;
var query = $"SELECT * FROM Win32_Service where ProcessId = {processId}";
var managementObject = new ManagementObjectSearcher(query).Get().Cast<ManagementObject>().FirstOrDefault();
if (managementObject == null)
{
throw new Exception("Could not get service name");
}
var serviceName = managementObject["Name"].ToString();
return serviceName;
}

managing static data within WCF

I am working within a solution that has a static logging object in a library that is shared among the projects. This is how it is structured:
public class AppLog
{
private static string _logFile;
private static string _appName;
public static string AppName
{
get { return _appName; }
set
{
_appName = value;
InitLogFilePath(); // initializes _logFile according to _appName
}
}
public static Write(string msg)
{
// writes to _logFile
}
}
It works fine for the various Windows apps and Windows services: They can initialize AppLog.AppName upon startup and AppLog.Write can be called throughout the code. Shared modules write to a file named according to the initialization of AppName.
The problem I have is using this within WCF web services. The web services are configured for InstanceContextMode.PerCall. AppLog.AppName is being initialized according to ServiceHostBase.Description.Name. But since multiple web services run within the same AppDomain this static data is shared. So one ws call sets AppLog.AppName and it is changed by the next call, which may have a different ServiceHostBase.Description.Name.
How can this be restructured so that AppLog.Write can still be used throughout the projects in my solution but handle the naming differently for each web service?
If could tell whether the code is running within a web service, and if I could retrieve the ServiceHostBase.Description of the service, then I could maintain a lookup for the appropriate file name. But I have not yet found a way to do this.
Given the way your logging is structured there is not a good solution.
Most logging libraries are structured so that you create an instance of the logger, pass the instance any application specific data (like AppName), and then store that instance in a private static member. The static storage is in the application, not the logging library. This avoids the sharing conflict that you have and still only creates a small fixed number of logger instances.
To illustrate the point, here's a standard log4net example from CodeProject log4net tutorial. This code passes the current class name to the instance of the logger.
private static readonly log4net.ILog log = log4net.LogManager.GetLogger
(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
My suggestion is to look at changing to log4net or any of the other logging packages available on NuGet.
Given your situation, AppName is not where is should be. You need a per-webservice logging facade that hold the AppName and pass the core "Write" logic down to your current AppLog. Then each of the web service has its own LogFacade instance.
class LogFacade
{
public string AppName {get; private set;}
LogFacade(string appName)
{
AppName = appName;
}
public void Write(string msg)
{
AppLog.Write(string.format("[{0}]{1}", AppName, msg));
}
}
Or as ErnieL said, take a look at log4net.

"using" of two different libraries with almost identical functions

I'm consuming a SOAP web service. The web service designates a separate service URL for each of its customers. I don't know why they do that. All their functions and parameters are technically the same. But if I want to write a program for the service I have to know for each company is it intended. That means for a company called "apple" i have to use the following using statement:
using DMDelivery.apple;
and for the other called "orange"
using DMDelivery.orange;
But I would like to my program to work for all of them and have the name of the company or the service reference point as a parameter.
Update: If I have to write a separate application for each customer then I would have to keep all of them updated with each other with every small change and that would be one heck of an inefficient job as the number of customers increase.
Can anyone think of a solution? I'll be grateful.
If you have a base contract (interface) for all your services you can use a kind of factory to instantiate your concrete service and only have a reference to your interface in your client code (calling code).
//service interface
public interface IFruitService{
void SomeOperation();
}
//apple service
public class AppleService : IFruitService{
public void SomeOperation(){
//implementation
}
}
Having for example a kind of factory class (you can put your using statements here)
public static class ServiceFactory{
public static IFruitService CreateService(string kind){
if(kind == "apple")
return new AppleService();
else if(kind == "orange")
return new OrangeService();
else
return null;
}
}
And in your calling code (you just add an using statement for the namespace containing your interface):
string fruitKind = //get it from configuration
IFruitService service = ServiceFactory.CreateService( fruitKind );
service.SomeOperation();
You can also use the Dependency Injection principle.
If everything is the same and it's only the endpoint address that is different, maybe you can try changing only that before invoking the web service methods.
MyWebServiceObject ws= new MyWebServiceObject();
ws.Endpoint.Address = new System.ServiceModel.EndpointAddress("http://www.blah.com/apple.asmx");
Use any one client in your implementation. ex. Apple
Write a message inspector and attach this into the out going point
In message inspector replace the name space of the type with appropriate client name space.
EX:
Before Message inspector :MyClinet.Apple.Type
After Message Inspector : MyClient.Orange.Type, if the Provider is Orange.

Generic Windows Service for multiple client databases and FTP accounts

I have to build a windows service that grabs data from n number of client databases, convert the result set to XLS format and send it to corresponding (client specific) FTP account at client specified interval,
Here's another way of putting it:
Same Windows Service will connect to multiple databases, sends files to different FTP accounts and runs at different intervals based on which client DB it is connected to.
My question is, how should I design it so that it's flexible to handle multiple scenarios and is more configurable.
The basic idea behind this is to minimize the implementation time in future when a new client asks for the same service.
I am considering the following idea where an individual client can be set to a separate worker thread. I know something is terribly wrong with this approach but can't seem to figure out the best way.
Here's the partial code:
private static void Main(string[] args)
{
// Initialize the first worker thread.
NewUserThread newUserThread = new NewUserThread();
// Specify properties of this worker thread.
newUserThread.Name = "New User Check";
newUserThread.Delay = 0;
newUserThread.Interval = 2 * 60 * 1000;
// Initialize the second worker thread.
UserUpdateThread userUpdateThread = new UserUpdateThread();
// Specify properties of this worker thread.
userUpdateThread.Name = "User Update Check";
userUpdateThread.Delay = 30 * 1000;
userUpdateThread.Interval= 5 * 60 * 1000;
// Initialize the first Windows service objects.
WindowsService userCheckService = new WindowsService();
userCheckService.ServiceName = UserCheckServiceName;
// Initialize the second Windows service objects.
WindowsService emailService = new WindowsService();
emailService.ServiceName = EmailServiceName;
// Add services to an array.
ServiceBase[] services = new ServiceBase[]
{
userCheckService,
emailService,
};
// Launch services.
SendFiles("Launching services...");
Run(services, args);
}
internal static void (string message, params object[] args)
{
// Call to DB
// Convert dataset to XLS
// Send to FTP
}
Let me know if I am not making any sense and I am open to explore a completely new approach.
Code sample will help.
Thanks all in advance!
Well i am gonna write the architecting stuff so that the application stays extensible in future.
Pattern Used: Dependency Injection
Make a Interface named IDatabaseSources and implement the interface in the different datasourceclasses
A sample method for your IDatabaseSource interface would be Connect(),FetchData(). When you program the connect method in the implemented classes fetch the connection string from web.config.
public class SQLDataSource:IDatabaseSources { will have all the methods defined in the interface}
public class SQLDataSource2:IDatabaseSources{ will have all the methods defined in the interface}
Make a interface named IFTPSources and implement the interface in the different classes.
A sample method for your IDatabaseSource interface would be Connect(),SendData(). When you program the connect method in the implemented classes fetch the FTP information from web.config.
public class FTPSource1:IFTPSources{ will have all the methods defined in the interface}
public class FTPSource2:IFTPSources{ will have all the methods defined in the interface}
Further these dependency's should be injected in the windows service as per your scheduler
Although if there are 10 FTP destinations then you'll have 10 FTP source class. Yes it increases number of classes but that's what single responsibility principle is plus that way you'll be able to maintain/extend the application.

Categories