I have a service with multiple instances with different parameters for each instance, at the moment I'm setting these parameters manually (in another code to be exact) to Image Path of the service in Registry (e.g. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\MyService$i00). so our service installation is done in two steps.
I'm really interested to merge these steps in Topshelf installation for example like
MyService.exe install -instance "i00" -config "C:\i00Config.json"
First Try
I tried AddCommandLineDefinition from TopShelf but it seems it only works during installation and running through console not the service itself (will not add anything to service Image Path).
Second Try
I tried to see if its possible to do this with AfterInstall from Topshelf without any luck. here is a test code to see if it going to work or not (but unfortunately Topshelf overwrites the registry after AfterInstall call).
HostFactory.Run(x =>
{
x.UseNLog();
x.Service<MyService>(sc =>
{
sc.ConstructUsing(hs => new MyService(hs));
sc.WhenStarted((s, h) => s.Start(h));
sc.WhenStopped((s, h) => s.Stop(h));
});
x.AfterInstall(s =>
{
using (var system = Registry.LocalMachine.OpenSubKey("SYSTEM"))
using (var controlSet = system.OpenSubKey("CurrentControlSet"))
using (var services = controlSet.OpenSubKey("services"))
using (var service = services.OpenSubKey(string.IsNullOrEmpty(s.InstanceName)
? s.ServiceName
: s.ServiceName + "$" + s.InstanceName, true))
{
if (service == null)
return;
var imagePath = service.GetValue("ImagePath") as string;
if (string.IsNullOrEmpty(imagePath))
return;
var appendix = string.Format(" -{0} \"{1}\"", "config", "C:\i00config.json"); //only a test to see if it is possible at all or not
imagePath = imagePath + appendix;
service.SetValue("ImagePath", imagePath);
}
});
x.SetServiceName("MyService");
x.SetDisplayName("My Service");
x.SetDescription("My Service Sample");
x.StartAutomatically();
x.RunAsLocalSystem();
x.EnableServiceRecovery(r =>
{
r.OnCrashOnly();
r.RestartService(1); //first
r.RestartService(1); //second
r.RestartService(1); //subsequents
r.SetResetPeriod(0);
});
});
I couldn't find any relevant information about how it can be done using TopShelf so the question is, is it possible to do this with TopShelf?
Ok, as Travis mentioned, It seems there is no built-in feature or simple workaround for this problem. so I wrote a little extension for Topshelf based on a Custom Environment Builder (most of the code is borrowed form Topshelf project itself).
I posted the code on Github, in case others may find it useful, here is the Topshelf.StartParameters extension.
based on the extension my code would be like:
HostFactory.Run(x =>
{
x.EnableStartParameters();
x.UseNLog();
x.Service<MyService>(sc =>
{
sc.ConstructUsing(hs => new MyService(hs));
sc.WhenStarted((s, h) => s.Start(h));
sc.WhenStopped((s, h) => s.Stop(h));
});
x.WithStartParameter("config",a =>{/*we can use parameter here*/});
x.SetServiceName("MyService");
x.SetDisplayName("My Service");
x.SetDescription("My Service Sample");
x.StartAutomatically();
x.RunAsLocalSystem();
x.EnableServiceRecovery(r =>
{
r.OnCrashOnly();
r.RestartService(1); //first
r.RestartService(1); //second
r.RestartService(1); //subsequents
r.SetResetPeriod(0);
});
});
and I can simply set it with:
MyService.exe install -instance "i00" -config "C:\i00Config.json"
To answer you question, no this isn't possible with Topshelf. I am excited you figured out how to manage the ImagePath. But that's the crux of the problem, there's been some discussion on the mailing list (https://groups.google.com/d/msg/topshelf-discuss/Xu4XR6wGWxw/8mAtyJFATq8J) on this topic and issues about it in the past.
The big problem is that managing expectations of behavior when applying custom arguments to the ImagePath will be unintuitive. For example, what happens when you call start with custom command line arguments? I'm open to implementing this or accepting a PR if we get something that doesn't confuse me just thinking about it, let alone trying to use. Right now, I strongly encourage you to use configuration, not command line arguments, to manage this, even if it means duplicating code on disk.
The following work-around is nothing more than a registry update. The update operation expects the privileges the installer requires in order to write our extended arguments.
Basically, we're responding to the AfterInstall() event. As of Topshelf v4.0.3, calling the AppendImageArgs() work-around from within the event will cause your args to appear before the TS args. If the call is deferred, your args will appear after the TS args.
The work-around
private static void AppendImageArgs(string serviceName, IEnumerable<Tuple<string, object>> args)
{
try
{
using (var service = Registry.LocalMachine.OpenSubKey($#"System\CurrentControlSet\Services\{serviceName}", true))
{
const string imagePath = "ImagePath";
var value = service?.GetValue(imagePath) as string;
if (value == null)
return;
foreach (var arg in args)
if (arg.Item2 == null)
value += $" -{arg.Item1}";
else
value += $" -{arg.Item1} \"{arg.Item2}\"";
service.SetValue(imagePath, value);
}
}
catch (Exception e)
{
Log.Error(e);
}
}
An example call
private static void AppendImageArgs(string serviceName)
{
var args = new[]
{
new Tuple<string, object>("param1", "Hello"),
new Tuple<string, object>("param2", 1),
new Tuple<string, object>("Color", ConsoleColor.Cyan),
};
AppendImageArgs(serviceName, args);
}
And the resulting args that would appear in the ImagePath:
-displayname "MyService Display Name" -servicename "MyServiceName" -param1 "Hello" -param2 "1" -Color "Cyan"
Notice the args appeared after the TS args, -displayname & -servicename. In this example, the AppendImageArgs() call was invoked after TS finished its installation business.
Command line args can be specified normally using Topshelf methods such as AddCommandLineDefinition(). To force processing of the args, call ApplyCommandLine().
Related
This is my first time playing with K8s api/client. I am trying to watch for events in all namespaces.
Below is the method, on running it prints:
Exception : Object reference not set to an instance of an object.
Closed
There aren't enough docs on how to use the C# client library. Can someone help me in understanding what I am doing wrong?
This is the method:
public async Task Temp() {
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
IKubernetes client = new Kubernetes(config);
var resourceVersion = client.ListEventForAllNamespaces().ResourceVersion();
var path = $"api/v1/events";
// wait for events
while (true)
{
var eventsWatcher = await client.WatchObjectAsync<V1EventList>(
timeoutSeconds: int.MaxValue,
path: path,
resourceVersion: resourceVersion,
onEvent: new Action<WatchEventType, V1EventList>((x, y) =>
{
Console.WriteLine(x.ToString());
}),
onClosed: new Action(() =>
{
Console.WriteLine("Closed");
}),
onError: new Action<Exception>((e) =>
{
Console.WriteLine($"Exception : {e.Message}");
}));
Thread.Sleep(1000);
}
}
On a side note, I am able to watch locally using http://localhost:8080/api/v1/events?watch=1&resourceVersion=27958468 after kubectl proxy
Found the issue. I was using the wrong "path". Instead of api/v1/events it should be api/v1/watch/events. The NullReferenceException was happening within the library's watcher.cs line 149
I am trying to figure out how to remove a command from the discord bot after it has been created. Here is how I create the command:
_commandService.CreateCommand("create").Parameter("message", ParameterType.Multiple).Do(async e =>
{
var message = e.Args.Skip(1).Aggregate("", (current, t) => current + (t + " "));;
_commandService.CreateCommand("hello").Do(async cc =>
{
await e.User.SendMessage(customCommand.Message);
});
});
The _commandService object is of type Discord.Commands.CommandService
Now, I want to be able to run:
_commandService.CreateCommand("delete").Parameter("message", ParameterType.Multiple).Do(async e =>
{
_commandService.DeleteCommand("hello");
});
However, no such method exists, nor am I able to access the commands inside _commandService object as everything is read only get;
Does anyone know how I can delete the command without having to restart the bot?
It's possible, but as of discord.net 1.0 you need to use the Modules system to do it. Unfortunately, it greatly complicates things. Hopefully they'll add a proper DeleteCommand(string commandName) in a future update.
Why you need to do this (this section not needed if you don't care about the discord.net source): The
class CommandMap (it stores the commands, unsurprisingly) exposes a method RemoveCommand that does what you're looking to do. The only reference to an object of this class in the source is in the private method RemoveModuleInternal in CommandService. This is exposed in one of two public methods: RemoveModuleAsync(ModuleInfo module) or RemoveModuleAsync<T>(). There is no other way to affect commands as of the 1.0 release.
How to do it: Get the ModuleInfo object for your module first. Preferably, the module you create will only contain the command you want to delete for reasons that should be obvious pretty soon. When you use CreateModuleAsync(string primaryAlias, Action<ModuleBuilder> buildFunc) (or one of the other methods used to add modules) you'll get the ModuleInfo object back. This does mean you need to use a ModuleBuilder instead of the simple commandService.CreateCommand method you use. Read up on how to do that here... if the process still confuses you, it's an excellent topic for another question.
You need to keep track of the ModuleInfo object that CreateModuleAsync returns in some manner (the method I would use is below) and then your second command becomes:
// private ModuleInfo createInfo
_commandService.CreateCommand("delete").Parameter("message", ParameterType.Multiple).Do(async e =>
{
if (createInfo != null)
{
await _commandService.DeleteModuleAsync(createInfo);
}
});
Do note that the entire module instance is getting deleted... that's why your "create" command should be the only thing in it.
An alternate solution (although significantly less elegant) if this whole Module business seems too complicated would be to store a boolean and simply toggle it to simulate the deletion of the command. That is:
// bool createNotDeleted = true;
_commandService.CreateCommand("create").Parameter("message", ParameterType.Multiple).Do(async e =>
{
if (createNotDeleted)
{
var message = e.Args.Skip(1).Aggregate("", (current, t) => current + (t + " "));;
_commandService.CreateCommand("hello").Do(async cc =>
{
await e.User.SendMessage(customCommand.Message);
});
}
else
{
// error handling
}
});
and
_commandService.CreateCommand("delete").Parameter("message", ParameterType.Multiple).Do(async e =>
{
if (createNotDeleted)
{
createNotDeleted = false
// return something indicating command is deleted
}
else
{
// error handling
}
});
I'm new to ReactiveUI. I have the following simple setup: a path to a csv can be specified and the containing datapoints will be displayed to the user (using oxyplot).
Now I'm trying to test the following subscription:
public GraphViewModel(IScreen hostScreen)
{
HostScreen = hostScreen;
setupGraphFormatting();
// Data Loading if path is valid
this.WhenAnyValue(viewModel => viewModel.PathToDataCsv)
.ObserveOn(RxApp.MainThreadScheduler)
.Throttle(TimeSpan.FromMilliseconds(500), RxApp.TaskpoolScheduler)
.Select(csvPath => csvPath?.Trim('"'))
.Where(csvPath => !string.IsNullOrEmpty(csvPath) && File.Exists(csvPath))
.Subscribe(csvPath =>
{
csvPath = csvPath?.Trim('"');
updatePlotModel(csvPath);
}, exception => {});
/* additional Code*/
}
And that's the corresponding UnitTest:
[Test]
public void If_PathToDataCsv_has_a_valid_value()
{
new TestScheduler().With(scheduler =>
{
string pathToValidCsvFile = "data.log";
var viewModel = new GraphViewModel(null);
scheduler.AdvanceByMs(1000);
viewModel.PathToDataCsv = pathToValidCsvFile;
scheduler.AdvanceByMs(1000);
viewModel.PlotModel.Series.Count.Should().Be(6);
});
}
My first implementation of WhenAnyValue didn't set any of the Schedulers specifically ( in Throttle and lacking any ObserverOn ):
public GraphViewModel(IScreen hostScreen)
{
HostScreen = hostScreen;
setupGraphFormatting();
// Data Loading if path is valid
this.WhenAnyValue(viewModel => viewModel.PathToDataCsv)
.Throttle(TimeSpan.FromMilliseconds(500))
.Select(csvPath => csvPath?.Trim('"'))
.Where(csvPath => !string.IsNullOrEmpty(csvPath) && File.Exists(csvPath))
.Subscribe(csvPath =>
{
csvPath = csvPath?.Trim('"');
updatePlotModel(csvPath);
}, exception => {});
/* additional Code*/
}
But then my Unittest failed. My assumption was that TestScheduler was being used for Throttle behind the scenes and I didn't have to do anything. Am I doing something wrong or is this the right way: If I want to use TestScheduler/TimeTravelâ„¢ I have to specify the schedulers the way I did?
Edit in response to Glenn Watsons answer:
Ok, now it's clear: The methods in question (Throttle, ObserverOn) of course do not use ReactiveUI's Schedulers, because these are methods from the Reactive Extensions Framework. So they can't be replaced implicitly by ReactiveUI in case of a UnitTest except I tell the methods to use the RxApp Schedulers...
RxApp provides the ThreadPoolScheduler when you are in release mode, and the testing scheduler when you are in unit test mode.
By default the reactive extensions (separate to ReactiveUI) will use their own default schedulers which are unaware of unit tests.
I'm using Topshelf to create a windows service (ServiceClass) and i'm thinking of sending custom commands using WhenCustomCommandReceived.
HostFactory.Run(x =>
{
x.EnablePauseAndContinue();
x.Service<ServiceClass>(s =>
{
s.ConstructUsing(name => new ServiceClass(path));
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
s.WhenPaused(tc => tc.Pause());
s.WhenContinued(tc => tc.Resume());
s.WhenCustomCommandReceived(tc => tc.ExecuteCustomCommand());
});
x.RunAsLocalSystem();
x.SetDescription("Service Name");
x.SetDisplayName("Service Name");
x.SetServiceName("ServiceName");
x.StartAutomatically();
});
However, i'm getting an error on the WhenCustomCommandReceived line:
Delegate 'Action< ServiceClass, HostControl, int>' does not take 1 arguments
The signature is
ServiceConfigurator<ServiceClass>.WhenCustomCommandReceived(Action<ServiceClass, HostControl, int> customCommandReceived)
I already have methods for start, stop, pause in my ServiceClass: public void Start(), etc. Can anyone point me in the right direction on how to setup the Action? Thanks!
So, as you can see in the signature of the method, the Action takes three parameters, not just one. This means that you need to set it up like this:
s.WhenCustomCommandReceived((tc,hc,command) => tc.ExecuteCustomCommand());
The interesting parameter in this case would be command which is of type int. This is the command number that is sent to the service.
You might want to change the signature of the ExecuteCustomCommand method to accept such command like this:
s.WhenCustomCommandReceived((tc,hc,command) => tc.ExecuteCustomCommand(command));
And in the ServiceClass:
public void ExecuteCustomCommand(int command)
{
//Handle command
}
This allows you to act differently based on the command that you receive.
To test sending a command to the service (from another C# project) you can use the following code:
ServiceController sc = new ServiceController("ServiceName"); //ServiceName is the name of the windows service
sc.ExecuteCommand(255); //Send command number 255
According to this MSDN reference, the command value must be between 128 and 256.
Be sure to reference the System.ServiceProcess assembly in your test project.
I've made a topshelf windows service that starts three tasks. But since it might happen that one of those task might crash (yes, I know about EnableServiceRecovery), it would be better to use one program to create 3 services with different names and install them using command line parameters.
So in theory the code would look like:
static void Main(string[] args)
{
// *********************Below is a TopShelf code*****************************//
HostFactory.Run(hostConfigurator =>
{
hostConfigurator.Service<MyService>(serviceConfigurator =>
{
serviceConfigurator.ConstructUsing(() => new MyService(args[0])); //what service we are using
serviceConfigurator.WhenStarted(myService => myService.Start()); //what to run on start
serviceConfigurator.WhenStopped(myService => myService.Stop()); // and on stop
});
hostConfigurator.RunAsLocalSystem();
//****************Change those names for other services*******************************************//
hostConfigurator.SetDisplayName("CallForwardService"+args[0]);
hostConfigurator.SetDescription("CallForward using Topshelf"+args[0]);
hostConfigurator.SetServiceName("CallForwardService"+args[0]);
hostConfigurator.SetInstanceName(args[0]);
});
}
But of course it won't, because (from what I've read) you can't simply use args[] but apparently you can use something like
Callforward.exe install --servicename:CallForward --instancename:Workshop
I am still not sure how to pass the parameter to be used later in the program (in example above you can see it in new MyService(args[0]))
Can I use single parameter to set up all three elements (name, instance and internal use)?
Solved using help from How can I use CommandLine Arguments that is not recognized by TopShelf?
string department = null;
// *********************Below is a TopShelf code*****************************//
HostFactory.Run(hostConfigurator =>
{
hostConfigurator.AddCommandLineDefinition("department", f => { department = f; });
hostConfigurator.ApplyCommandLine();
hostConfigurator.Service<MyService>(serviceConfigurator =>
{
serviceConfigurator.ConstructUsing(() => new MyService(department)); //what service we are using
serviceConfigurator.WhenStarted(myService => myService.Start()); //what to run on start
serviceConfigurator.WhenStopped(myService => myService.Stop()); // and on stop
});
hostConfigurator.EnableServiceRecovery(r => //What to do when service crashes
{
r.RestartService(0); //First, second and consecutive times
r.RestartService(1);
r.RestartService(1);
r.SetResetPeriod(1); //Reset counter after 1 day
});
hostConfigurator.RunAsLocalSystem();
//****************Change those names for other services*******************************************//
string d = "CallForwardService_" + department;
hostConfigurator.SetDisplayName(d);
hostConfigurator.SetDescription("CallForward using Topshelf");
hostConfigurator.SetServiceName(d);
});
This answer was posted as an edit to the question Single command line parameter to control Topshelf windows service by the OP Yasskier under CC BY-SA 3.0.