Windows Service which Kills Unwanted Windows
Written in C# Windows service which monitors all Windows and kills unwanted ones.
Introduction
The main goal of this project is to create a Windows service in C# which will monitor all appearing Windows and kill unwanted ones.
This sample project is an illustration for two topics:
- Use of PInvoke, and
- Writing a Windows service in C#
Background
Some time ago, I had two phone interviews and one on-site interview with Google. That was fun (you know those crazy puzzles they like to ask at Google interviews!) and I enjoyed that, but when I was denied without any reasonable explanation, I became so mad that I decided to write this service to kill any Internet browser on my PC as soon as user dialed google.com in the address bar. Now I think this project could be useful for those who want to automatically kill some unwanted popups on their computers. For example, this approach could be used to kill some Windows ToolTips after some timeout because they can stay on the desktop forever if there is no user activity.
Using the Code
There are two stages if you want to kill some window:
- Find a handle to this window, and
- Close this window
It looks like to find a window there is an API call FindWindowEx
and that was my first approach, but very soon I figured out that it doesn't work well for all kind of windows, for example, it doesn't work for tooltip popups because their title is their content. So, I've tried another approach - using another API call EnumWindows
. This API call requires two parameters - pointer to callback function and some structure - to be passed. In the code snippet below, we use member m_hwndFound
to return the result of a search:
CallBackPtr callBackPtr = new CallBackPtr(ReportWindow);
m_hwndFound = IntPtr.Zero;
EnumWindows(callBackPtr, new PopupWinow(classToKill, titleToKill));
if (m_hwndFound != IntPtr.Zero)
{... do something if window is found ...}
This code uses class PopupWindow
...
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public class PopupWinow
{
public string wClass = "tooltips_class32";
public string wTitle = "";
public PopupWinow(string wclass, string wtitle)
{
wClass = wclass;
wTitle = wtitle;
}
}
... and callback method ReportWindow
which must return true
to continue enumeration and false
to stop it if a window is found:
private IntPtr m_hwndFound = IntPtr.Zero;
private bool ReportWindow(IntPtr hwnd, PopupWinow lparam)
{
// Set the user interface to display in the
// same culture as that set in Control Panel.
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
string classToKill = lparam.wClass;
string titleToKill = lparam.wTitle;
// Get the window title
StringBuilder sbTitle = new StringBuilder(1024, 1024);
IntPtr nbytes = SendMessage(hwnd, /*WM_GETTEXT =
*/0x000D, new IntPtr(sbTitle.Capacity), sbTitle);
string wTitle = sbTitle.ToString();
//Get the window class name
StringBuilder sbClass = new StringBuilder(100);
int res = GetClassName(hwnd, sbClass, sbClass.Capacity);
if (res == 0) return true; // just continue enumeration
string wClass = sbClass.ToString();
// Check if requested window is found
if ((wTitle == "" && titleToKill == "") || (wTitle != "" && wClass == classToKill))
{
if (wTitle.IndexOf(titleToKill) != -1)
{
m_hwndFound = hwnd;
return false; // stop enumeration
}
}
return true; // continue enumeration
}
After the window handle is found we can close it. Note that for different classes of Windows, we have to use different "close window" calls:
switch (popupType)
{
case PopupWindowType.Tooltip:
// tooltip window found --> gently close it
// (if use WM_CLOSE message the balloon will be closed forever!)
nbytes = SendMessage(m_hwndFound, /*WM_LBUTTONDOWN = */0x0201, 0, IntPtr.Zero);
return;
case PopupWindowType.Edit:
case PopupWindowType.IEFrame:
case PopupWindowType.MozillaUIWindowClass:
case PopupWindowType.MessageBox:
// message box window found --> close it
nbytes = SendMessage(m_hwndFound, /*WM_CLOSE = */0x0010, 0, IntPtr.Zero);
return;
default:
return;
}
If you want to kill some Windows other than default for this solution just change call to KillPopupWindow
:
m_windowFinder.KillPopupWindow(<some word from window title>, <some PopupWindowType>);
To find out a window title and class, you can use Microsoft Spy++ utility which comes with SDK.
To install this service, use regular InstallUtil.exe utility which is a part of the .NET Framework.
Service Startup Parameters
Recently I used this service in my practice. I opened some website in one of the tabs in Internet Explorer and was faced with a lot of error message boxes with 'Yes' / 'No' choices. I was tired of clicking on the 'No' button and did not want to kill Internet Explorer because I did not want to lose many other opened tabs there. So, I decided to use this service to kill all those error Windows automatically. The trick is that it's not possible to close Yes/No Windows just sending them WM_CLOSE
message. To close them, we need to find their 'Yes' or 'No' child button window and send it WM_LBUTTONUP
message. Here is how to do that:
switch (popupType)
{
...
case PopupWindowType.MessageBox:
// message box window found --> close it
if (m_buttonToClick == "")
{
nbytes = SendMessage(m_hwndFound, /*WM_CLOSE = */0x0010, 0, IntPtr.Zero);
}
else
{
// Find child button window and click it!
IntPtr button = FindWindowEx(m_hwndFound, IntPtr.Zero, "Button", m_buttonToClick);
if (button != IntPtr.Zero)
{
nbytes = SendMessage(button, /*WM_LBUTTONDOWN = */0x0201, 0, IntPtr.Zero);
nbytes = SendMessage(button, /*WM_LBUTTONUP = */0x0202, 0, IntPtr.Zero);
}
}
return;
...
}
I also modified the OnStart
method to parse parameters to make it possible to start the service to kill different types of Windows. For details, see the downloaded solution. Here is the example of service startup parameters if you want to kill popups with title "Error" and buttons "Yes" and "No" (500 is polling interval in ms):
Error #32770 500 &No
Make Service Self-Installable
For me, it's very painful to install/uninstall services using InstallUtil
application. To avoid this, we can use another set of API functions to control Service Control Manager. I added a special class ServiceInstaller
to the solution to manage that. This class is a helpful wrapper for API calls to advapi32.dll to perform install, uninstall, start and stop service.
Here is the most complicated method InstallService
:
public static bool InstallService(string svcPath, string svcName,
string svcDispName, string svcDescription, bool bRun)
{
// Open Service Control Manager
IntPtr hSCM = OpenSCManager(
null, // lpMachineName
null, // lpSCDB
(uint)Win32Methods.SCM_ACCESS_TYPE.SC_MANAGER_CREATE_SERVICE // scParameter
);
if (hSCM == IntPtr.Zero) return false;
// Create the service
IntPtr hService = CreateService(
hSCM, // SC_HANDLE
svcName, // lpSvcName
svcDispName,// lpDisplayName
Win32Methods.SERVICE_ALL_ACCESS, // dwDesiredAccess
(uint)Win32Methods.SERVICE_TYPE.SERVICE_WIN32_OWN_PROCESS, // dwServiceType
(uint)Win32Methods.SERVICE_START_TYPE.SERVICE_AUTO_START, // dwStartType
(uint)Win32Methods.SERVICE_ERROR_CODE.SERVICE_ERROR_NORMAL,// dwErrorControl
svcPath, // lpPathName
null, // lpLoadOrderGroup
IntPtr.Zero,// lpdwTagId
null, // lpDependencies
null, // lpServiceStartName
null // lpPassword
);
if (hService == IntPtr.Zero)
{
CloseServiceHandle(hSCM);
return false;
}
// Change service description
if (!ChangeServiceConfig2(
hService, // handle to service
SERVICE_INFO_LEVEL.SERVICE_CONFIG_DESCRIPTION, // change: description
ref svcDescription // value: new description
))
{
CloseServiceHandle(hSCM);
return false;
}
CloseServiceHandle(hSCM);
// Set check box "Allow service to interact with desktop"
// checked to allow service show popup messages.
OperatingSystem os = Environment.OSVersion;
if (os.Version.Major < 6) // 6 is Vista: service cannot interact with desktop on Vista
{
System.ServiceProcess.ServiceController serviceController =
new System.ServiceProcess.ServiceController();
serviceController.ServiceName = svcName;
ConnectionOptions coOptions = new ConnectionOptions();
coOptions.Impersonation = ImpersonationLevel.Impersonate;
ManagementScope mgmtScope =
new System.Management.ManagementScope(@"root\CIMV2", coOptions);
mgmtScope.Connect();
ManagementObject wmiService = new ManagementObject
("Win32_Service.Name='" + svcName + "'");
ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
InParam["DesktopInteract"] = true;
ManagementBaseObject OutParam = wmiService.InvokeMethod("Change", InParam, null);
serviceController.Start();
}
// Run service
if (bRun) return RunService(svcName);
return true;
}
This code uses calls to advapi32.dll functions implemented in Win32Methods
class (see downloaded solution for details).
Now you can install/uninstall this service 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 other command line parameters, you can call the service with -h
parameter.