Start TrayIcon from service - c#

I have a C# service (.NET 4.6.2) that polls an online API. Tied to that is also a tray icon. The service is supposed to keep the tray icon running, and the icon is supposed to keep the service running.
If the service has to restart the tray icon for any reason, then it does, but the icon doesn't come back. I am trying to solve that because the users think that if the icon isn't there that it isn't working. When they restart the application and get the icon back, it runs through this:
Process process = Process.GetCurrentProcess();
var dupl = (Process.GetProcessesByName(process.ProcessName));
if (dupl.Length > 1)
{
foreach (var p in dupl)
{
if (p.Id != process.Id)
p.Kill();
}
}
to make sure there is only a single copy at any one time. I realize that services aren't supposed to access the UI directly, but isn't there an indirect method? Can I do it by passing my call through another DLL or something?
I have looked into this quite a bit and find so many resources that end up being outdated. Even this one from last year, which seemed promising doesn't work.
How to create process as user with arguments
This service is deployed on computers from Windows 32bit to Windows 10. There is not always a user logged in, but let's assume for simplicity's sake that there is and I can error handle the rest later.

Indeed, your service is not supposed to interact with a user and if you find an article that sports a hack on how to do it anyway, it's bound to fail sooner or later.
The correct way to do it is to have two applications. A service and a user interface. The service runs as a service. The user interface app is started as the user (maybe from their autostart). Then those two applications communicate via some kind of IPC, maybe sockets or pipes. The user interface can display a tray icon and display status messages it gets from the service or it can send commands to the service that it gets from the user via for example the tray icons context menu.
So do not try to fake a solution. It will not work properly, because it's not supposed to work properly. Build two applications.

Related

Proper way to handle Windows Service Tray App / NotifyIcon startup

For the record, I want to point out two things:
I know how to create a Windows Service, I know how to create Tray applications and how to communicate with my Windows Service
I have read countless articles on this subject, almost all of them suggest hacks, putting applications in the Startup menu, (old) interactive desktop services, startup scripts etc. I also found very resources for the subject dated after 2010 (aka light years in the world of technology)
What I'm looking for is the proper way to make sure that when my service is installed, every user gets a nice icon in his tray whenever he/she logs on to his/her computer. This is slightly more complex than it seems, and most of the hackish solutions won't work anyway (not that I'd want to use them anyway).
What I want to achieve is twofold. I want to know how to:
Start the Tray application WITHOUT modifying any user settings. This means not adding anything to the user startup, not modifying the registry, no startup-scripts or whatever. My company doesn't have fixed seats in almost all of its departments. Any user can log on to nearly every workstation at every time. Ideally the Tray application would be launched from the service detecting a user logging on or the shell being started, and then spawning an instance of the tray application for that user.
I'm also looking for a way to handle the startup of the service. For example, when I start up the service and multiple users are logged on to a single machine (which is entirely possible), the service should spawn a tray application instance for each of the users with an interactive logon. This is especially important should the service need to recover. The same goes for when the service is stopped or restarted.
Just to give you an idea, the application I'm writing consists out of a Windows Service, a Tray application that gives feedback to users via notifications and allows them to start op a configuration program that also contains additional information for them, and finally said configuration program that communicates with the service via named pipes. It's all very simple.

Windows Service and Application interaction

TL;DR: I have a service, a gui, and a tray icon. I want to know how to get the tray icon to run on user login/start-up and be able to stop the service and start the gui
Some Background Info:
I have a windows service I've made that uses a xml file to collect
files from other computers on my network and store them on the local
pc (running the service). the xml has some structures called
'profiles' which have info like FileDestination, LocationToTakeFrom,
FileTypeToTake, and IsProfileActive...
The service basically takes all the active profiles and every hour
scans the location for files created within a 1hr window of the
current date/time on local PC.
My GUI allows the user to make profiles / modify profiles, as
well as determine which profile(s) should be active for collection. I
dont want this gui running at all times, so I plan to have a
systemTrayIcon to allow this GUI to be opened and shutdown.
I'm using Visual Studio 2010 .NET 4.0 everything is in C#, I have 1 solution with separate projects(gui and service)
I'm wondering about the following things as far as the System Tray Icon goes:
1) how do I have the icon start on user login (note that this will be distributed via an installer, not just my personal use. so It has to be done via code)
2) Stop a service via sysTrayIcon
3) where to place the SysTrayIcon... do I make a 3rd project? add it in the GUI project? not quite sure here.
4) if SysTrayIcon IS in a seperate project how can I have it create instances of the GUI?
ie how can I start the GUI application from code in a different project
the project requirements are:
-upon installation the GUI must start, after that the Gui should only be accessed through the tray icon.
-user should be able to stop the service any time via system tray icon
Start the client
There are plenty of ways to start an application on Login under Windows. Just grab SysInternals AutoRuns to get an idea. The obvious ones are (a) the good ol' Startup group and (b) one of the
\SOFTWARE\Microsoft\Windows\CurrentVersion\Run*
keys under HKCU and/or HKLM. That's the typical task of an setup utility, which makes sense since you have to install the service anyway. To do it in code:
Registry: Open the reg key, add the entry, close the reg key.
Startup: determine the value of CSIDL_STARTUP or CSIDL_COMMON_STARTUP using SHGetFolderPath, then create a Shell Link in that folder.
Service communication & control
The GUI part implements the TrayIcon and communicates through some channnel with your service. Again, there are plenty of possibilities how to do this, like disk files or memory mapped files, named pipes, even sockets. It would be too broad to list all the ways here, so I'd suggest to decide for one way and ask again if you have specific questions on that one.
To stop your service from code, use the ControlService() function and pass SERVICE_CONTROL_STOP as the dwControl parameter. To start a service, there's another function named (big surprise) StartService() to achieve that. Note that you may need to start an elevated copy of your app to control services. A quick & dirty way is to simply launch net start/stop MyService elevated with the necessary args.

How can I get the MainWindowHandle of a Windows 7 application running as user <foo> from within a service running as Local System?

I've created a service that runs as the Local System user. This service launches and monitors a Silverlight Out-of-browser application using native interop and the CreateProcessAsUser() method (to run it as the currently logged-in user, rather than Local System). I'm able to get a handle on the spawned Process and do things like Kill() it, however, I've become aware that the service is unable to get a handle to the main window of the child application because the child application is running as a different user. I'm running on Windows 7.
My end goal is to respond properly to when the Process stops responding (i.e. Process.Responding == false) so that I can kill the application and restart it. However, Process.Responding requires a handle to the main window of the process (Process.MainWindowHandle, to be exact), however, in this scenario, Process.MainWindowHandle always returns 0.
I'm stumped here. Is there any way for one user to get a window handle to a process running as another user in Win 7?
Thanks in advance for any and all help.
No, that's not possible. Windows Services are completely isolated from user-mode applications for security reasons. If you could get the handle to a window, you could manipulate and otherwise interact with that window, leaving open a huge security vulnerability.
More information is available here:
How can a Windows Service start a process when a Timer event is raised?
How can I run an EXE program from a Windows Service using C#?
windows service (allow service to interact with desktop)
Need suggestion on replacing Windows Service by invisible WinForm Application
Strictly speaking, what you're using the Windows Service for in the first place is bad design. It shouldn't be creating or launching any user-mode processes, or interacting with the user in any way. Remember that services run even when there is no user logged in! Thus, they shouldn't be starting applications.
A better solution is a simple background application, set to launch automatically when the user logs in. This background application could then launch the Silverlight application, monitor its state, and interact with it as necessary, because both would be running under the context of the same local user account. The effect is similar to a service, but without any of the drawbacks of isolation. The easiest way to do this in Visual Studio is to create a WinForms application (or possibly a WPF application, I have less experience in that area) that simply doesn't show any forms/windows.

C# Run Windows Form Application from Service (and in Vista)

I am writing an application in C# that needs to run as a service but also have user interaction. I understand that services have no UI, etc, so I've divided up my program into a windows form application and a service that can communicate with each other.
The problem I'm having is that I need the service to make sure the windows form application is always running and restart it if it is not. I'm able to detect if it is running, and restart it with the following code on Windows 2000/XP:
System.Diagnostics.Process.Start("ExePath");
but on Vista, it runs the new process as a Local/System process which is invisible to the user. Does someone way around this? Is there some way to detect which user is currently logged on and run the new process as that user? I don't need to account for fast-user switching at this point. Something - anything - basic would suffice.
I would be grateful for any help or tips you have on the subject.
I need to clarify that I am setting the "Allow service to interact with desktop" option when the service is installed. This is what allows it to work on 2000/XP. However, Vista still has the aforementioned problem.
The general idea for this sort of thing is, if the user needs to interact with a service, they should launch a separate application. If you want to help them out, you can configure that separate application to start with windows by placing a shortcut in the start up menu. You can also build crash recovery into your application so it can automatically restart.
You shouldn't really rely on monitoring the forms application, what if no one is logged in? What if multiple people are logged in? It just gets messy doing things this way.
Having the service just sit there and broadcast to listeners is the way to go. When the forms application starts it can notify the service it wants to listen to events.
See the question: How can a Windows Service execute a GUI application?. It addresses the same question from C/C++ (short answer: CreateProcessAsUser), but the answer's still valid (with some P/Invoke) for C#.
In this case, you will have to have a third monitor process which detects if the program fails and restart it in that case.
However, you end up with an unsolvable problem here, as the monitor process will have to be watched to make sure it doesn't get shut down, and so on, and so on, and so on.
You might want to reconsider this approach.
Its a tough situation. As mentioned in a couple places, if you must have a UI then technically you shouldn't be using a service. Afterall, services run without a user even logged on. If nobody is logged in, you cannot have a UI.
Normally, when I need a service needs to communicate with the outside world, there are two things I opt for. I can either place an entry in the event log, or I can drop a message in a queue.
In your case I'd use a queue. When a user logs in, you can auto start an app for them that monitors the queue. If the app is running, when the message is received, they are alerted that way as well. However, if the user closes the app then the same thing occurs... they won't know.
First, a quick answer: Does the 'Allow service to interact with desktop' option (service -> Properties -> LogOn) or specifying an account allow what you're wanting? If so, both of these can be configured on your service installer class.
Like the others, I suspect there is a better approach to this and either one of the following is true:
-The code inside the service could be included in the winforms app (perhaps running in a background thread), and added to windows startup. Both will be running
-The winforms app can just listen to the service when it's on, and doesn't need to be started from the service. Or similarly, the app could be added to startup.
To have your service run the application as a user (which seems to be what you are trying to do) you need to do the following:
System.Security.SecureString ss = new System.Security.SecureString();
foreach (char c in password)
ss.AppendChar(c);
System.Diagnostics.Process proc = Process.Start(path, arguments, username, ss, domain);
Where:
path = full path (including filename) of the executable.
arguments = string of arguments (use an empty string is none)
username = The name of an user account on your server/computer
domain = your network domain (if your using a network account- blank if none)
Also, In order for your service to have permission to launch an application, it must be running as a service also. To do this, you need to add these lines to your service installer class:
serviceProcessInstaller.Account = ServiceAccount.User;
serviceProcessInstaller.Username = "yourdomain\\yourAccountName"; //Or just "AccountName" for local accounts..
serviceProcessInstaller.Password = "yourPassword";
In Windows 2000 and XP, there is an option (checkbox) on the Logon tab of the service properties window to allow the service to interact with the desktop. I believe this is what you are looking for. I just wrote a quick service in VB.NET with a Process.Start("calc.exe") and Windows Calculator opened just fine.
I'm not 100% sure this works the same way in Vista though.
Sounds like you might not need half of it running as a service (unless there's a requirement of higher privileges), as your service would need to cope with when there is no interactive user logged on as well.

Windows services with windows forms in the same process

I have a c# application that runs as a windows service controlling socket connections and other things.
Also, there is another windows forms application to control and configure this service (systray with start, stop, show form with configuration parameters).
I'm using .net remoting to do the IPC and that was fine, but now I want to show some real traffic and other reports and remoting will not meet my performance requirements. So I want to combine both applications in one.
Here is the problem:
When I started the form from the windows service, nothing happened. Googling around I've found that I have to right click the service, go to Log on and check the "Allow service to interact with desktop" option. Since I don't want to ask my users to do that, I got some code googling again to set this option in the user's regedit during installation time. The problem is that even setting this option, it doesn't work. I have to open the Log On options of the service (it is checked), uncheck and check again.
So, how to solve that? How is the best way to have a windows service with a systray control in the same process, available to any user logging in?
UPDATE: Thanks for the comments so far, guys. I agree it is better to use IPC and I know that it is bad to mix windows services and user interfaces. Even though, I want to know how to do that.
Two separate processes that communicate using your technology of choice. Services with UI is a bad idea. Don't go down this road - you'll regret it.
I've had very good results having service communication through a simple socket connection - document your service protocol well, keep it as simple as possible, and it'll be easier than you think.
In practice you should not couple your service with the management UI.
I agree with Greg. Perhaps you could examine a different IPC mechanism. Perhaps use sockets and your own protocol. Or, if your service control app can only control the service on the local machine, you can use named pipes (even faster).
Here is a way mixing up Services and Forms
http://www.codeproject.com/KB/system/SystemTrayIconInSvc.aspx
I figured out how to do this from this article (click on the "Change" link in the Methods table).
string wmiPath = "Win32_Service.Name='" + SERVICE_NAME + "'";
using (ManagementObject service = new ManagementObject(wmiPath))
{
object[] parameters = new object[11];
parameters[5] = true; // Enable desktop interaction
service.InvokeMethod("Change", parameters);
}
I have the solution in a few steps, this is the plan
we are not going to create a service project with a a windows form, instead we are going to create a visual studio solution that contains a windows service project, a windows form project and a setup project.
The idea is to have a database or a file or anything you are comfortable with storing data in which you would store the parameters your windows service will always use to run. So your windows service and your windows form application should be able to modify and retrieve data from it.
To the Main Form Of Your Windows Application drag and drop a NotifyIcon on the form, in the properties tab, browse and select an .ico image(You can sreate one in visual studio but that is another topic you can get on google or contact me) That it will display in the system tray when you run the application and the main form is active or shown, try it, run the application.
Add both of them as outputs in the setup project of the solution. To Add a project to a setup project they must be in the same solution. Right click the setup project in the solution explorer, highlight add and then select project output, add the windows service and the windows form outputs and you will see them in the solution explorer under the setup project.
adding a windows service goes further than this but that also is another topic google it
Creating shortcut for the windows application and adding it to the startup folder is also another topic google or contact me.
NOTE Program your form in such a way that the close button doesn't show and the form goes Me.visible = false and double clicking the icon in the system tray is the only way to set me.visible=true.that way anytime the computer starts up, your windows form application is also started and visible is immediately set to false but since it has a notifyicon with an icon image, it will show in the system tray and double clicking it makes the form visible to edit the settings that you are storing for the service, the service also starts automatically since you would have set it in setting up the service in the setup project.
my mail is iamjavademon#gmail.com for a better illustration using screen shots And explain in full
It is very simply - your need to create one thread for perform application events.
Like this( source code for C++ with CLR, but your can make this in C#):
ref class RunWindow{
public:
static void MakeWindow(Object^ data)
{
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
Application::Run(gcnew TMainForm());
};
};
And create thread in main
int main(array<System::String ^> ^args)
{
bool bService = RunAsService(L"SimpleServiceWithIconInTrayAndWindow");
if (bService)
{
System::Threading::Thread ^thread = gcnew System::Threading::Thread(gcnew ParameterizedThreadStart(RunWindow::MakeWindow));
thread->Start();
ServiceBase::Run(gcnew simpleWinService());
Application::Exit();
}
else
{
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
// Create the main window and run it
Application::Run(gcnew TMainForm());
}
return 0;
}
The main problems with interactive services are:
Security - other process could send it messages through its message pump, thereby gaining access to a SYSTEM/LOCAL process.
Incompleteness - an interactive service never sees shell messages, hence it can't interact with Notification Area icons.
We regularly use TCP and UDP connections to pass info from services to other exes, and, in some cases, MSMQ.

Categories

Resources