Desaware Home
Products    Purchase    Publishing    Articles   Support    Company    Contact    
Articles
.NET
COM

 

 

bluebar
Contact Desaware and order today

bluebar
Sign up for Desaware's Newsletter for the latest news and tech tips.

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.

articles
Related Products:
 
Products    Purchase    Articles    Support    Company    Contact
Copyright© 2012 Desaware, Inc. All Rights Reserved.    Privacy Policy