Block Selected Applications from Loading

Our customers sometimes ask for the ability to prevent users from starting certain applications.
There is no generic way to do so in Windows. While you can use SpyWorks to prevent particular
applications (such as File Explorer) from launching programs, there are so many different ways to do this (such as the Run item in the Start menu, or creating a VB program that shells the desired application) that it quickly becomes a Herculean task. There are even ways to start programs beyond SpyWorks' ability to influence, such as the Command Prompt.

However, it is possible to detect when a program has started and then close it down right away. We will use two features of SpyWorks: the WinHook control and the sample code for getting information about currently running processes. The WinHook control has the ability to detect messages on a system-wide basis. By detecting the WM_CREATE message, we can tell whenever a new window has been created. When such an event takes place, we can use the process functions to
determine the application filename that the window belongs to. With a few API function calls, we can then shut down the offending application.

Here is the fully commented VB6 sample code for such a utility. This
technique will also work in Visual Studio .NET (with minor code changes). Simply create a form containing a SpyWorks WinHook control and a textbox named "Text1". The textbox contains the name of the application you do not want the user to start (such as "notepad.exe"). Copy the code and place it in the code window of the form.

This kind of program is most useful if it starts the moment the user has logged in. You can do that by adding a new item to the Registry. In the key:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

Add a new string value with the application title as the value name and the full path location of the application as the value data. Be very careful
when modifying the Registry.

The command prompt is not a normal window, and you will not get any WM_CREATE message when it is launched. If you want to block it, you will need to periodically poll the list of running programs (using the full suite of task listing functions from the SpyWorks samples), and then use TerminateProcess on it when you see "cmd.exe" in the list.

Option Explicit
Private Const MAX_PATH As Long = 260
' for OpenProcess
Private Const PROCESS_VM_READ As Long = &H10&
Private Const PROCESS_QUERY_INFORMATION As Long = &H400&
Private Const PROCESS_TERMINATE As Long = &H1&
' Messages
Private Const WM_CLOSE As Long = &H10&
Private Const WM_CREATE As Long = &H1&
' General function declarations
Private Declare Function GetVersion Lib "kernel32" () As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" _
(ByVal hwnd As Long, lpdwProcessId As Long) As Long
Private Declare Function OpenProcess Lib "kernel32" _
(ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Private Declare Function GetParent Lib "user32" _
(ByVal hwnd As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias _
"GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName _
As String, ByVal nMaxCount As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias _
"SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, lParam As Long) As Long
Private Declare Function TerminateProcess Lib "kernel32" _
(ByVal hProcess As Long, ByVal uExitCode As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" _
(ByVal hObject As Long) As Long
' WinNT, 2000, XP specific function declarations. Samples using
' a full set of these functions is a part of SpyWorks, including
' a complete task monitoring application.
Private Declare Function EnumProcessModules Lib "psapi.dll" _
(ByVal hProcess As Long, lphModule As Long, ByVal cb As Long, _
lpcbNeeded As Long) As Long
Private Declare Function GetModuleBaseNameA Lib "psapi.dll" _
(ByVal hProcess As Long, ByVal hModule As Long, ByVal _
lpBaseName As String, ByVal nSize As Long) As Long
' Win95, Win98, WinME specific function declarations. Samples
' using a full set of these functions is a part of SpyWorks,
' including a complete task monitoring application.
Private Const TH32CS_SNAPPROCESS = &H2
Private Type PROCESSENTRY32
dwSize As Long
cntUsage As Long
th32ProcessID As Long
th32DefaultHeapID As Long
th32ModuleID As Long
cntThreads As Long
th32ParentProcessID As Long
pcPriClassBase As Long
dwFlags As Long
szExeFile As String * MAX_PATH
End Type
Private Declare Function CreateToolhelp32Snapshot& Lib _
"kernel32" (ByVal dwFlags As Long, ByVal th32ProcessID As Long)
Private Declare Function Process32First& Lib "kernel32" _
(ByVal hSnapShot As Long, lppe As PROCESSENTRY32)
Private Declare Function Process32Next& Lib "kernel32" _
(ByVal hSnapShot As Long, lppe As PROCESSENTRY32)
Private Sub Form_Load()
' Set up the hook control. This can all be done in the design
' time property window, but it is done here for clarity.
WinHook1.Messages = WM_CREATE
WinHook1.HookType = shkCallWndProc
WinHook1.Monitor = shkEntireSystem
WinHook1.Notify = shkPosted
WinHook1.HookEnabled = True
End Sub
Private Sub WinHook1_WndMessage(wnd As Long, msg As Long, _
wp As Long, lp As Long, nodef As Integer)
Dim RetVal As Long
Dim hProcess As Long
Dim ProcessName As String
Dim ProcessID As Long
Dim ClassName As String
' Get the ProcessID for the newly created window
RetVal = GetWindowThreadProcessId(wnd, ProcessID)
' Get the filename of the process using the correct method
' depending on which operating system we are running.
If (OSIs95Based() = True) Then
ProcessName = GetProcessName95(ProcessID)
Else
' Open the process so we can get access to its information.
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION _
Or PROCESS_VM_READ, False, ProcessID)
If (hProcess = 0) Then
Debug.Print "We cannot gain access to the process"
Exit Sub
End If
ProcessName = GetProcessNameNT(hProcess)
RetVal = CloseHandle(hProcess) ' Don't forget this
End If
' You can keep a record of newly created windows.
' You cannot create new forms or put up a message
' box inside the WndMessage event - if you want to
' notify the user the reason they cannot start the
' application, try using a Timer control. Put it on
' on the form, but in a disabled state. Enable it
' here. In the Timer event, put up whatever message
' you need and then disable the Timer again.
' Debug.Print "Proc ID:" & Hex$(ProcessID) & " " & _
ProcessName & " created window " & Hex$(wnd)
' See if this program is the one we want to close.
' If not, exit.
If (StrComp(ProcessName, Text1.Text, vbTextCompare) <> 0) Then
Exit Sub
End If
' Check to see if this is a top level window - if it
' has no parent, then it is one. Child windows do not
' concern us.
If (GetParent(wnd) = 0) Then
' Check the classname to see if it is a top level window
' injected by SpyWorks - we ignore those.
ClassName = String$(30, 0)
RetVal = GetClassName(wnd, ClassName, Len(ClassName))
ClassName = Left$(ClassName, 17)
If (ClassName = "dwXferWindowClass") Then
' This is a top level window from SpyWorks - ignore it
Exit Sub
End If
' It is a real top level window -> try closing it. Closing
' it is done in the DelayedEvent. We pass the window
' handle as the parameter because we will need it in the
' DelayedEvent.
WinHook1.PostEvent = wnd
End If
Debug.Print Hex$(wnd), ProcessName, Hex$(GetParent(wnd))
End Sub
Private Sub WinHook1_DelayedEvent(lvalue As Long)
' This event is firing a short time after the WndMessage
' is over. We set this event into motion by setting the
' "PostEvent" property of the WinHook1 control below. The
' value we set the property to is passed as the "lvalue"
' here. We need to use a delay because it is not good
' practice to send messages (or call API functions that
' send messages) inside the WndMessage event (it can cause
' unpredictable behavior, even crashes).
Dim RetVal As Long
' We will be nice and try to close the program by sending
' it a "WM_CLOSE" message. This is prefered because it
' allows the application to clean up correctly.
RetVal = SendMessage(lvalue, WM_CLOSE, 0, 0)
' This should work on most applications. A less nice way
' of closing another application is TerminateProcess. The
' problem with this is that the application will leave
' allocated memory and resources behind without freeing them.
' To use this, we will need to open the Process with the
' correct flags.
' Dim ExitCode as long
' Dim hProcess as long
' RetVal = GetWindowThreadProcessId(lvalue, ProcessID)
' hProcess = OpenProcess(PROCESS_TERMINATE, False, ProcessID)
' If (hProcess = 0) Then
' Debug.Print "We cannot gain access to the process"
' Exit Sub
' End If
' RetVal = TerminateProcess(hProcess, ExitCode)
' RetVal = CloseHandle(hProcess) ' Don't forget this
End Sub
Private Function GetProcessName95(ByVal ProcessID As Long) _
As String
' For Windows 95-type operating systems, we will use the
' built in TOOLHELP32 functions. This is a little tricky -
' there is no direct way from a hProcess to a filename. We
' need to loop through all the running processes and get the
' name of the one that matches the parameter.
Dim RetVal As Long
Dim ModuleFileName As String
Dim ModuleBaseName As String
Dim ProcessName As String
Dim CanLoop As Boolean
Dim hSnapShotProc As Long
Dim ProcessEntry As PROCESSENTRY32
' Init some variables
ProcessName = ""
' Get a snapshot of the processes currently running
' on the system
hSnapShotProc = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
ProcessEntry.dwSize = LenB(ProcessEntry)
' Loop through all the processes in the snapshot
RetVal = Process32First(hSnapShotProc, ProcessEntry)
Do
' If the ProcessID's match..
If (ProcessEntry.th32ProcessID = ProcessID) Then
' Get the process name from the path supplied
ProcessName = ProcessEntry.szExeFile
ProcessName = Left$(ProcessName, _
InStr(ProcessName, Chr$(0)) - 1)
While (InStr(ProcessName, "\") > 0)
ProcessName = Right(ProcessName, _
Len(ProcessName) - InStr(ProcessName, "\"))
Wend
GoTo CleanUp
End If
Loop While (Process32Next(hSnapShotProc, ProcessEntry) _
<> False)
CleanUp:
RetVal = CloseHandle(hSnapShotProc)
GetProcessName95 = ProcessName
End Function
Private Function GetProcessNameNT(ByVal hProcess As Long) As String
' For Windows NT, we will need to use the PSAPI.DLL functions.
' This file should be included in Windows 2000 and XP. It
' will need to be obtained and copied into the System32
' directory on Windows NT 3.51 machines, but the file is
' commonly available.
Dim RetVal As Long
Dim cbNeeded As Long
Dim hmod As Long
Dim ProcessName As String
Dim ProcName As String
Dim ProcessID As Long
RetVal = EnumProcessModules(hProcess, hmod, _
LenB(hmod), cbNeeded)
ProcName = String$(MAX_PATH, 0)
RetVal = GetModuleBaseNameA(hProcess, hmod, ProcName, _
LenB(ProcName))
ProcessName = Left$(ProcName, InStr(ProcName, Chr$(0)) - 1)
GetProcessNameNT = ProcessName
End Function
Private Function OSIs95Based() As Boolean
OSIs95Based = GetVersion() And &H80000000
End Function

For notification when new articles are available, sign up for Desaware's Newsletter.