Introduction
This is Part 2 of my previous article. The main goal of this project is to create a Windows service in C# which will monitor all the appearing windows and kill the unwanted ones. After Part 1 has been published, I found (thanks to the forum attached to that article) that it does not work on Vista because of the enhanced security there. That means, Windows services are running in the hidden session #0, and they cannot interact with user sessions (#1, #2, ...). All PInvoke methods, such as SendMessage
, do not work with windows/processes running on user desktops. After some search on CodeProject, I found a great article by Stefan Repas which shows how to allow a Windows service written in .NET to communicate with desktop applications using an out-of-proc COM server. To run a .NET COM-visible assembly as an out-of-proc COM server, he used COMAdmin.dll to register the DLL as an out-of-proc COM server.
Background
Any COM visible assembly (or .dll) created in .NET using Visual Studio is hosted by dllhost.exe, which is called a "COM Surrogate". By default, it runs on Vista in session #0, which has its User Name as SYSTEM
in the Task Manager. A Windows service can communicate with this COM server, but this server runs in the same session #0 as the service, and it does not see the user windows/applications. To force a COM DLL to be hosted in dllhost.exe, which is running in a user session, we have to register it under a user account using COMAdmin.dll. I created a class DesktopManager
which has a rudimentary interface, to work with desktop windows/applications. Of course, this simple class could be easily extended to whatever you need. This class is implemented as a COM+ object in the DesktopManagerDll assembly registered as an out-of-proc COM server using COMAdmin.dll, and runs under a user account. All methods from the WindowFinder
class (see my previous article) are moved to the DesktopManager
class.
Desktop Manager
This COM object exposes the following IDesktopManager
interface:
[ComVisible(true)]
[Description("Exposed DesktopManager interface")]
[Guid("258E4449-6ACC-40d8-8C37-4F47476D30C2")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IDesktopManager
{
[DispId(1)]
int FindTopWindow(string wndClass, string wndTitle);
[DispId(2)]
int FindChildWindow(int hwndParent, string wndClass, string wndTitle);
[DispId(3)]
bool CloseWindow(int hwnd, string wndClass);
[DispId(4)]
void ExitProcess(string processToExit);
[DispId(5)]
void MessageBoxShow(string text, string caption, string icon);
[DispId(6)]
string GetMainWindowTitle(string process);
}
This interface is implemented in the DesktopManager
class:
[ComVisible(true)]
[Description("ServicedComponenet class to control IDesktopManager Interface")]
[Guid("56919971-22CB-4de7-9393-D55C72A20A1C")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("DesktopManagerDll.DesktopManager")]
public class DesktopManager : ServicedComponent, IDesktopManager
{
...
}
Notice that this class is derived from the ServicedComponent
class which is needed to expose the DesktopManager
as a COM+ object. This object implements all the IDesktopManager
interface methods. To implement the FindChildWindow
method, for example, this class uses the same technique as was used in the WindowFinder
class from my previous article.
To make this object an out-of-proc COM+ server, there is a class DesktopManagerInstaller
which performs the following steps:
- Install the assembly using RegistrationHelper.
- Create an
ICOMAdminCatalog
object.
- Get all the roles for the current COM+ application.
- Find the role
AverageUser
.
- Assign accounts to the found
AverageUser
role.
- Get all COM+ components for the current application.
- Check if the
DesktopManager
COM+ component exists.
- Grant access to users in the role
AverageUser
Using the DesktopManager COM+ object
First, you have to create a DesktopManager
object:
m_desktopManager = new DesktopManagerDll.DesktopManager();
Second, use m_desktopManager
in the WindowsFinder
class:
m_windowFinder.KillPopupWindow(m_titleToKill, classToKill);
where:
public bool KillPopupWindow(string titleToKill, PopupWindowType popupType)
{
...
m_hwndFound = FindTopWindow(classToKill, titleToKill);
if (m_hwndFound != IntPtr.Zero)
{
return m_iDesktopManager.CloseWindow(m_hwndFound.ToInt32(), classToKill);
}
}
Third, release m_desktopManager
when done:
Marshal.ReleaseComObject(m_desktopManager);
Finally, shutdown the COM+ application:
ICOMAdminCatalog cac = (ICOMAdminCatalog)Activator.CreateInstance(
Type.GetTypeFromProgID("COMAdmin.COMAdminCatalog"));
cac.ShutdownApplication("{75FA7F8B-7F94-4023-B4B8-7CCF6F988713}");
Installation
To install this service, you can use the MS Installer output produced by the WindowKillerSetup project which is a part of the WindowKiller solution, or do manual registration of the two assemblies: DesktopManagerDll and WIndowKiller.exe, using InstallUtil, which is a part of the .NET Framework:
InstallUtil.exe DesktopManagerDll.dll
InstallUtil.exe WindowKiller.exe
Note: The WindowKiller EXE has implemented a self-installing feature - see my previous article. So, you can install/uninstall this service by calling it from the command line, like this...
WindowKiller -a
... where the parameter -a means "auto" install/uninstall depending on the current service status. To get help on the other command line parameters, you can call the service with the -h parameter.
History
This is Part 2 of my previous article.