Problem
Today topic is covering how to make windows service interact with any desktop application. I am sure every windows service developer would face a challenge with the "no user interface" windows service nature behavior whereby you cannot know whether your service is actually doing its job or actually hanging.The windows services are running in session 0, an environment where the application is running with no user context, no user interface, they are separated from desktop applications group. Last time in Windows XP/2003 or earlier OS, windows services allow to interact with any desktop application, and because of having this flexibility, windows service possess security threat and always become a common attack target. Therefore, in Windows Vista/2008 or newer OS, the windows services are protected and isolated from the desktop applications. From then onwards, windows service cannot direct interact with any desktop application. More info about session 0 isolation.
Solution
Even with session 0 isolation, the windows service is not actually entirely locked down, we can still make it to interact it with other application by using RPC (Remote Procedure Call) or .NET Remoting, but they are old, today I am sharing how to use WCF instead.I have created a WPF application which is used to monitor the windows service activity. You will see all the logs written by the windows service appearing in the monitor console in real time.
Concept
The WPF application is hosting a WCF host with named pipe communication channel since the monitoring console and the windows service are sitting in the same machine. Whenever the windows service is writing log, it will call the WCF service. The WCF service receive the log and write it to a text box in WPF application.Challenge
The windows service is running all the time even without user logon, but the monitoring console can only be run with a logon user. Therefore, the windows service cannot always call the WCF service, it would encounter error when there is no active user session and monitoring console is not running. Therefore, it should call only when it detect the monitor console is running.
Implementation
Monitoring Console
WCF Service
Create a service that receive log from windows service.
The service contract definition:
[ServiceContract]
public interface ILoggingService
{
[OperationContract]
void WriteLog(string value);
}
The service implementation:
public class LoggingService : ILoggingService
{
public void WriteLog(string value)
{
//Locate
the text box and write the receive value into it
MainWindow main = (MainWindow)Application.Current.MainWindow;
main.LogText.Text += value + Environment.NewLine;
}
}
WPF Application
Create a simple interface that contain a text box. This text box will display all the log sent by the windows service.
<Window x:Class="WinSvcMonitoringTool.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox x:Name="LogText" Margin="10"
TextWrapping="Wrap"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
private ServiceHost _host = null;
public MainWindow()
{
InitializeComponent();
}
protected override void OnContentRendered(EventArgs e)
{
//Start
WCF service automatically when the window is activated
try
{
_host = new ServiceHost(typeof(LoggingService));
//Create
Metadata exchange for the service
ServiceMetadataBehavior mexBehavior = new ServiceMetadataBehavior();
_host.Description.Behaviors.Add(mexBehavior);
//Add
service endpoints for the service and mex
_host.AddServiceEndpoint(typeof(ILoggingService), new NetNamedPipeBinding(), "net.pipe://localhost/WinSvcMonitoringService/LoggingService.svc");
_host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexNamedPipeBinding(),
"net.pipe://localhost/WinSvcMonitoringService/LoggingService.svc/mex");
_host.Open();
LogText.Text += "WCF Host started succeessfully." + Environment.NewLine;
}
catch (Exception ex)
{
if (_host != null)
_host.Abort();
MessageBox.Show("Service Host
Error : " + ex.Message);
}
base.OnContentRendered(e);
}
protected override void OnClosed(EventArgs e)
{
//Close
the WCF service host when the form is closed
_host.Close();
base.OnClosed(e);
}
}
That's all for the monitoring console. It is ready for the windows service to call and consume. Now start coding the windows service.
Windows Service
The windows service is doing a simple write log activity for every second. Therefore, during the service OnStart event:
Task task = new Task(() =>
{
while (true)
{
WriteLog("Hello
World!");
Thread.Sleep(1000);
}
},
_source.Token);
task.Start();
Then, use the proxy class as follow:
try
{
using (LoggingServiceClient proxy = new LoggingServiceClient())
{
proxy.WriteLog(log);
proxy.Close();
}
}
catch(Exception ex)
{
//Error
occur, write the detail to the event log
this.EventLog.WriteEntry(ex.Message, EventLogEntryType.Error);
}
At this point of time, the windows service should be able to call the WCF service hosted in the WPF monitoring console. You will notice Hello World! is written to the text box every second.
Detect Monitoring Console
However, whenever the monitoring console is closed, the WCF host is closed as well, and the windows service will encounter error because the WCF service no longer exists. In order to solve this challenge, I have to implement something that the windows service know if the monitoring console is running or not. If it is running, it will call the WCF service, otherwise, do nothing or write the log to text file.
Windows Management Instrumentation (WMI)
WMI can be used to manage almost anything related to OS. For this case, I am going to use the ManagementEventWatcher class library to watch out for my monitoring console is opened or closed. In order to do that, I need to write a WMI query to monitor the process name that I want:
//The
query to monitor WinSvcMonitoringTool.exe process is started or stopped
private const string _pStartQuery = "SELECT
* FROM Win32_ProcessStartTrace WHERE ProcessName = 'WinSvcMonitoringTool.exe'";
private const string _pStopQuery = "SELECT
* FROM Win32_ProcessStopTrace WHERE ProcessName =
'WinSvcMonitoringTool.exe'";
Note: WinSvcMonitoringTool.exe is my WPF application name.
Pass in the query to the ManagementEventWatcher constructor.
_pStartWatcher
= new ManagementEventWatcher(new WqlEventQuery(_pStartQuery));
_pStartWatcher.EventArrived
+= new EventArrivedEventHandler(ProcessStartEvent);
_pStartWatcher.Start();
_pStopWatcher
= new ManagementEventWatcher(new WqlEventQuery(_pStopQuery));
_pStopWatcher.EventArrived
+= new EventArrivedEventHandler(ProcessStopEvent);
_pStopWatcher.Start();
Then, subscribe EventArrived event handler. So that, when it detected the monitoring console process is started, it will flag the window service to start call WCF. And also if the process is closed, the window service will stop the WCF call.
private void ProcessStartEvent(object sender, EventArrivedEventArgs e)
{
//flag
to call WCF service
_monitorConsoleIsDetected = true;
}
private void ProcessStopEvent(object sender, EventArrivedEventArgs e)
{
//flag
to stop calling WCF service
_monitorConsoleIsDetected = false;
}
So, my write log method has become like this after making use of the flag.
private void WriteLog(string log)
{
//Only
write log when there is any user session is active and the monitoring tool is
opening
if (_monitorConsoleIsDetected)
{
try
{
using (LoggingServiceClient proxy = new LoggingServiceClient())
{
proxy.WriteLog(log);
proxy.Close();
}
}
catch(Exception ex)
{
//Error
occur, write the detail to the event log
this.EventLog.WriteEntry(ex.Message, EventLogEntryType.Error);
}
}
}
Summary
After done with the WMI event watcher. Start the windows service first, it will do nothing. When you run the monitoring console exe, the windows service will automatically detect its existence, then start calling the WCF service hosted in the monitoring console. Then, the log will start appearing in the monitoring console. If you shutdown the application or logoff from the OS, the windows service will know it and then stop calling the WCF service. The only drawback about this console monitoring is you have to run it with Administrator mode, otherwise the WCF service host will not run properly.If you are interested with my source code, feel free to download it from HERE.
No comments:
Post a Comment