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.
Related
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\"
Do I need to call Run() inside Main() method? So it will be call on daily basis at mentioned time in code.
public class Program
{
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
static void Main()
{
var host = new JobHost();
// The following code ensures that the WebJob will be running continuously
host.RunAndBlock();
}
// This method will be called on weekly basis
public static void Run([TimerTrigger(typeof(MyDailySchedule))] TimerInfo timerInfo, TextWriter log)
{
log4net.Config.XmlConfigurator.Configure();
try
{
MainA.Wait();
}
catch (Exception ex)
{
}
}
static async Task MainA()
{
WebJob1 Service = new WebJob1();
await Service.DeletData();
}
}
public class MyDailySchedule : DailySchedule
{
public MyDailySchedule() :
//Schedule
base("2:00:00", "14:00:00", "15:00:00")
{ }
}
You don't need to use the WebJobs SDK to achieve this. Instead:
Write a simple Console app that directly does what you need to when it's launched (i.e. don't use any JobHost).
Deploy it as a Scheduled WebJob using cron expressions (see doc for details).
I am unable to get FluentScheduler working in .Net Framework 4.5.2 Web api. Few days ago, I asked a similar question about scheduling through Console application and could get it to work with help but unfortunately facing issues with Web Api now. Below is the code.
[HttpPost]
[Route("Schedule")]
public IHttpActionResult Schedule([FromBody] SchedulerModel schedulerModel)
{
var registry = new Registry();
registry.Schedule<MyJob>().ToRunNow();
JobManager.Initialize(registry);
JobManager.StopAndBlock();
return Json(new { success = true, message = "Scheduled!" });
}
Below is the job I want to schedule which for now is just writing text to a file
public class SampleJob: IJob, IRegisteredObject
{
private readonly object _lock = new object();
private bool _shuttingDown;
public SampleJob()
{
HostingEnvironment.RegisterObject(this);
}
public void Execute()
{
lock (_lock)
{
if (_shuttingDown)
return;
//Schedule writing to a text file
WriteToFile();
}
}
public void WriteToFile()
{
string text = "Random text";
File.WriteAllText(#"C:\Users\Public\TestFolder\WriteText.txt", text);
}
public void Stop(bool immediate)
{
lock (_lock)
{
_shuttingDown = true;
}
HostingEnvironment.UnregisterObject(this);
}
Got this resolved finally. It turns out the issue was with my Registry class. I had to change it as follows.
public class ScheduledJobRegistry: Registry
{
public ScheduledJobRegistry(DateTime appointment)
{
//Removed the following line and replaced with next two lines
//Schedule<SampleJob>().ToRunOnceIn(5).Seconds();
IJob job = new SampleJob();
JobManager.AddJob(job, s => s.ToRunOnceIn(5).Seconds());
}
}
[HttpPost]
[Route("Schedule")]
public IHttpActionResult Schedule([FromBody] SchedulerModel schedulerModel)
{
JobManager.Initialize(new ScheduledJobRegistry());
JobManager.StopAndBlock();
return Json(new { success = true, message = "Scheduled!" });
}
Another point to note: I could get this to work but hosting Api in IIS makes it tricky because we have to deal with App Pool recycles, idle time etc. But this looks like a good start.
I've been trying to get my WPF client app to receive a SignalR message sent by the WCF service. I've tried many things and have now resorted to hacking away in the hopes that something just works. I've followed tutorials and examples online, and I simply can't get my WPF OnSignalRMessage() method to get called. Where am I going wrong here?
My hub:
public class PrestoHub : Hub
{
public void Send(string message)
{
Clients.All.OnSignalRMessage(message);
}
}
My startup class:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HubConfiguration { EnableCrossDomain = true };
app.MapHubs("http://localhost:8084", config);
}
}
The method that starts my SignalR host (within my WCF service host):
private void StartSignalRHost()
{
const string url = "http://localhost:8084";
WebApplication.Start<Startup>(url);
}
The code to actually send some message:
GlobalHost.ConnectionManager.GetHubContext<PrestoHub>().Clients.All.OnSignalRMessage("snuh");
Console.WriteLine("Sent 'snuh' to all clients...");
My WPF client methods:
private void InitializeSignalR()
{
var hubConnection = new Connection("http://localhost:8084");
hubConnection.Start();
hubConnection.Received += OnSignalRMessage;
}
private void OnSignalRMessage(string data)
{
MessageBox.Show(data);
}
While I'm still struggling to understand the how and why, I was able to get it working. +1 to N. Taylor Mullen for pointing me in the right direction. In addition to his suggestion on the client side, I had to change some server code as well, namely using an empty hub and a simplified Startup class.
My hub:
public class PrestoHub : Hub{}
Note: The hub is empty because we're not calling methods within it. As we'll see later, we get the hub context and send messages to the clients.
My startup class:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapHubs();
}
}
The above code seems to be what fixed the problem. This also works:
var config = new HubConfiguration { EnableCrossDomain = true };
app.MapHubs(config);
But as soon as I specify a URL, my client doesn't receive the messages (tried with and without the "SignalR" part):
app.MapHubs("http://localhost:8084/SignalR", config);
The method that starts my SignalR host (within my WCF service host):
private void StartSignalRHost()
{
const string url = "http://localhost:8084";
WebApplication.Start<Startup>(url);
}
The code to actually send some message:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<PrestoHub>();
hubContext.Clients.All.OnSignalRMessage("snuh");
My WPF client method:
private void InitializeSignalR()
{
var hubConnection = new HubConnection("http://localhost:8084");
var prestoHubProxy = hubConnection.CreateHubProxy("PrestoHub");
prestoHubProxy.On<string>("OnSignalRMessage", (data) =>
{
MessageBox.Show(data);
});
hubConnection.Start();
}
You're creating a PersistentConnection not a hub connection. In order to get messages from your PrestoHub you first need to connect with a HubConnection and then you need to handle the event "OnSignalRMessage".
So your client code would now look like:
private void InitializeSignalR()
{
var hubConnection = new HubConnection("http://localhost:8084");
var prestoHubProxy = hubConnection.CreateHubProxy("PrestoHub");
// Bind the "OnSignalRMessage" to a function
prestoHubProxy.On<string>("OnSignalRMessage", (data) => {
MessageBox.Show(data);
});
hubConnection.Start();
}
If your methods on the server side are asynchronous make sure they return a task instead of void. That is you should have
public async Task Method(){ }
and not
public async void Method(){ }
About programming Windows services: how to stop my windows service?
Here is a very simplified example code(C#):
// Here is my service class (MyTestService.cs).
public class MyTestService:ServiceBase{
// Constructor.
public MyTestService(){
this.ServiceName = "My Test Service";
return;
}
};
// My application class (ApplicationClass.cs).
public static class ApplicationClass{
// Here is main Main() method.
public static void Main(){
// 1. Creating a service instance
// and running it using ServiceBase.
MyTestService service = new MyTestService();
ServiceBase.Run(service);
// 2. Performing a test shutdown of a service.
service.Stop();
Environment.Exit(0);
return;
};
};
So: I've just created "My Test Service" started it and stopped. But when I'm looking into my Services.msc - "My Test Service" is continues to running and stops ONLY when I click a "Stop" link. Why? - why service.Stop() command does nothing?
ServiceController.Stop() also does nothing!
How can I stop my service from Main() method?
The Stop-function sends a stop-signal. It does not wait till the signal is received and processed.
You will have to wait till the Stop-signal has done it's work. You can do that by calling WaitForStatus:
service.Stop();
service.WaitForStatus(ServiceControllerStatus.Stopped);
See for more info: http://msdn.microsoft.com/nl-nl/library/system.serviceprocess.servicecontroller.waitforstatus(v=vs.71).aspx
Environment.Exit is a nasty one. DO NOT USE IT! It aborts your application the hard way, without performing any cleanup in finally blocks, without calling finalizer methods by the GC, it terminates all other forground threads, etc. I can imagine that your application is aborted before the stop-signal even left your application.
I am using following functions in my project
public static ServiceController GetService(string serviceName)
{
ServiceController[] services = ServiceController.GetServices();
return services.FirstOrDefault(_ => Contracts.Extensions.CompareStrings(_.ServiceName, serviceName));
}
public static bool IsServiceRunning(string serviceName)
{
ServiceControllerStatus status;
uint counter = 0;
do
{
ServiceController service = GetService(serviceName);
if (service == null)
{
return false;
}
Thread.Sleep(100);
status = service.Status;
} while (!(status == ServiceControllerStatus.Stopped ||
status == ServiceControllerStatus.Running) &&
(++counter < 30));
return status == ServiceControllerStatus.Running;
}
public static bool IsServiceInstalled(string serviceName)
{
return GetService(serviceName) != null;
}
public static void StartService(string serviceName)
{
ServiceController controller = GetService(serviceName);
if (controller == null)
{
return;
}
controller.Start();
controller.WaitForStatus(ServiceControllerStatus.Running);
}
public static void StopService(string serviceName)
{
ServiceController controller = GetService(serviceName);
if (controller == null)
{
return;
}
controller.Stop();
controller.WaitForStatus(ServiceControllerStatus.Stopped);
}
In your code example service.Stop() and ServiceController.Stop() commands does nothing because they are not called while service is running since ServiceBase.Run(service) is blocking operation and it returns only on stop of the service.