Can I omit ServiceBase.ServiceName when starting Windows Service? - c#

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;
}

Related

Existing role as event target apigateway execution role

I have created an event bus where the target is an existing apigateway. everything working well except the execution role for that particular target. I am using an existing IAM role as an execution role which has both policies attached to it "execute-api:Invoke", "execute-api:ManageConnections". Also assumed by "events.amazonaws.com". But this is not attaching to the target as an execution role. Below is the code to use an existing role
var role = Role.FromRoleName(this, roleId, roleName);
var rule = new Amazon.CDK.AWS.Events.CfnRule(this, ruleId, new Amazon.CDK.AWS.Events.CfnRuleProps
{
EventBusName =busName,
Name = ruleName,
Description = ruleDescription,
EventPattern = eventPattern,
State = "ENABLED",
Targets = new[]
{
new Amazon.CDK.AWS.Events.CfnRule.TargetProperty
{
Id = apiGatewayId,
Arn = apiGatewayArn,
InputTransformer = new Amazon.CDK.AWS.Events.CfnRule.InputTransformerProperty
{
InputPathsMap = inputPath,
InputTemplate = inputTemplate,
},
RoleArn = role.RoleArn,
},
}
});
role.roleArn is undefined because the Role.FromRoleName static method does not perform cloud-side lookups. To consume an imported role's ARN, use fromRoleArn instead. It doesn't perform cloud-side lookups either. It accepts a ARN, without verification.
In fact, all the CDK's various [Something].from....Name|Arn|Attributes static "import" methods behave the same way. They blindly accept what you give them. The returned constructs know what you explicitly told them. Other properties will be undefined.
CDK Context Methods like Vpc.fromLookup do actually perform synth-time "lookups" from your cloud environment and cache the results.
N.B. the ISomething interface constructs returned from import methods are read-only.
You might be better served by using an SDK method to extract and find the information at Synth - if its not something that is going to change between Synth of the template and Deployment, then this is perfectly fine way to do things (and indeed, what is often done behind the scenes)
Also, I notice you're using the Cfn methods. These are L1 constructs - they are bare bones and do not have the necessary hook to slot into most of the ISomething interfaces. Events is not a new library, you shouldn't need to be using CfnRule - there should be Rule just available for you as well. Only with the L2 or L3 constructs can you make use of the handy hooks like passing the construct to another for linking the two. - you really should be using this construct instead (choose your appropriate language) : https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_events.Rule.html

Data transfer from one to project to another in a Solution

I have a C# solution with multiple projects in it.
There is one project called Console which run as a service and this project has a reference to the hostSoftware project (hostSoftware doesn't have a reference to Console, obviously).
I want to transfer a bool value from Console to hostSoftware.
Console: Checks the license and I want to send the bool value to hostSoftware for further checks.
var StatusParameters1 = new StatusParameters();
{
StatusParameters1.Name = "Log";
int returnCode = 0;
bool logStatus = LHandler.GetLogStatus(out LogStatus);
var status = LHandler.ReadLFile(out returnCode);
if (logStatus)
{
if (status.SoftLisenceInfoList[0].SoftId2 == Constants.SoftwareId2)
{
StatusParameters1.Value = LogStatus;
}
else
{
StatusParameters1.Value = false;
}
hostSoftware: I want StatusParameters1.Value in my host project MainWindow.xaml.cs file.
Note: I tried to use a delegate but got this error:
No matching constructor found on type MainWindow
Is there any other approach?
EDIT:
I just discovered that there is another project called ServerService which communicated with Console Project and hostSoftware is communicating with ServerService so the data flow is : Console -> ServerService -> hostSoftware.
ServerService is a service which is being used as Service References in hostSoftware
I can understand that you want your service to verify the client, and then the client can call your service. If so, you can refer to Extending WCF, You can choose a suitable way to add security verification to your service.

How to reconfigure SQLTransport based NServicebus in Asp.net Web API?

I am using NServicebus(version 4.6.3) with SQLTransport in my ASP.net web api project. I have different connectionstrings for the queues for different environments (Dev,QA,etc). My configuration looks like below:
public class BusConfigurator
{
public static IStartableBus Bus { get; private set; }
public static void DisposeBus()
{
if (Bus == null)
return;
Bus.Shutdown();
Bus.Dispose();
Bus = null;
}
public static void InitializeServiceBus(string connectionString)
{
var configure = Configure.With()
.DefineEndpointName("MyEndPoint")
.Log4Net(new DebugAppender { Threshold = Level.Warn })
.UseTransport<SqlServer>(connectionString)
.PurgeOnStartup(false)
.SetDefaultTransactionLevel()
.UnicastBus(); // Error is thrown here on second call
configure.MyCustomSQLServerPersistence();
Bus = configure.CreateBus();
}
public static void StartBus()
{
Bus.Start(() => Configure.Instance.ForInstallationOn<NServiceBus.Installation.Environments.Windows>().Install());
}
}
I have a dropdown in the app so that the user can select the environment. Based on the selection, I want to reconfigure the bus. So, I call DisposeBus then pass the connection string to the IntializeServiceBus method followed by the startBus. It works first time but throws error below when it gets called again with different connectionstring:
Unable to set the value for key: NServiceBus.Transport.ConnectionString. The settings has been locked for modifications. Please move any configuration code earlier in the configuration pipeline
Source=NServiceBus.Core
Line=0
BareMessage=Unable to set the value for key: NServiceBus.Transport.ConnectionString. The settings has been locked for modifications. Please move any configuration code earlier in the configuration pipeline
Is NServicebus intended to be used/configured this way? (I am guessing probably not) If not then is there a workaround/different approach for this?
In V4 or below, there is no way to do it by normal human means. There is only one Bus per AppDomain. All of the configuration API is static, so if you try, you get exactly the problems you ran into.
By "human means", I mean that it might be possible to do something crazy with spinning up a new AppDomain within your process, setting up a Bus within that, and then tearing it down when you're finished. It might be possible. I haven't tried it. I wouldn't recommend it.
In V5, the configuration API is completely redesigned, is not static, and so this is possible:
var cfg = new BusConfiguration();
// Set up all the settings with the new V5 Configuration API
using (var justOneBus = NServiceBus.Bus.Create(cfg).Start())
{
// Use justOneBus, then it gets disposed when done.
}
That's right. It's disposable. Then you can do it again. In your case you wouldn't want to put it in a using block - you would want to set it up somewhere, and when the dropdown gets switched, call Dispose on the current instance and rebuild it with the new parameters.
Keep in mind, however, that the Bus is still pretty expensive to create. It's definitely still something you want to treat as an application-wide singleton (or singleton-like) instance. You definitely wouldn't want to spin up a separate one per web request.

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.

How to make a Windows service with parameters?

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;
}
...
}

Categories