Enable/Disable/Sign agents without opening DDE

A couple of days ago, Thomas Adrian posted a new idea on aha.io Allow Enable/Disable/Sign agents from Domino Administrator without opening DDE.

This is a pretty cool idea, I think. Although there already is a great tool available from Ytria ( agentEZ ), handling of agents should be a part of the core features of Notes / Domino.

Notes / Domino contains some of the requested features and for example, you can enable an agent via adminp. You can simply create a new admin request in the admin4.nsf; if you know, which values to set. Unfortunately, this is not documented and you have to do a lot of try and error before the admin request is processed. The advantage of using adminp would be documentation of who did what.
As a downside, you cannot disable an agent; there is no adminp request for that.

Back in 2007, I already demonstrated, how you can add your own adminp requests to the adminp Delete Group Members Using The Administration Process

After reading Thomas’ idea, I decided to spend some hours on building a Domino server addin that would enable / disable or toggle the status of scheduled agents in an application. The application path, agent name and what to do should be passed as parameters to the addin.

Project “AMgr2” was born.

Over the years, I have created my own Notes cAPI CPP framework. This is still work in progress, as I add new methods and properties when I need them.

The framework is a great help when it comes to RAD in Notes / Domino using c/c++. I tried to name methods and properties as close as possible to what we have in LotusScript or Java to make the resulting source code readable and maintainable. Here is an example how I determine if an agent is of type “scheduled” within my framework

bool cNotesAgent::isScheduled() {
		cNotesDocument doc(db_h, agnt_id, OPEN_NOVERIFYDEFAULT);
		cNotesItemText trigger = doc.getItemText("$AssistTrigger");
		if(!trigger.compare("1")) {return TRUE;} else {return FALSE;} 
		}

Most of the magic happens in the framework, so the source code for AMgr2 is pretty short.

// main.h

#ifndef _MAIN_H_
#define _MAIN_H_

#include <string>

#if defined (_MSC_VER) &amp;&amp; !defined(ND64)
#pragma pack(push, 1)
#endif
#include <global.h>
#include <miscerr.h>
#include <addin.h>
#if defined (_MSC_VER) &amp;&amp; !defined (ND64)
#pragma pack(pop)
#endif

#include "cNotesFramework.h"
#include "cNotesAgent.h"
#include "cmdline.h"

#if defined (W64)
#define HANDLE DHANDLE
#undef NOTEHANDLE
#define NOTEHANDLE DHANDLE
#else
#define DHANDLE HANDLE
#undef NOTEHANDLE
#define NOTEHANDLE HANDLE
#endif

#define ADDIN_STATUS_LINE	"AMgr2"
#define APP_NAME	"AMgr2: "
#undef MSG
#define MSG(fmt) APP_NAME fmt
#define ERROR -1
using namespace std;

#endif

// main.cpp

/*
* Amgr2
*
* eknori at eknori dot de  www.eknori.de FEBRUARY 2019
*
* copyright (c) 2019 Ulrich Krause www.eknori.de
*/

#pragma warning(disable:4005) 

#include "main.h"

STATUS LNPUBLIC AddInMain (HMODULE hModule, int argc, char *argv[]) {

	STATUS				error = NOERROR;
	HANDLE				hStatusLine;
	HANDLE				hStatusLineDesc;
	HMODULE				hMod;

	AddInQueryDefaults (&amp;hMod, &amp;hStatusLine);
	AddInDeleteStatusLine (hStatusLine);
	hStatusLineDesc = AddInCreateStatusLine(ADDIN_STATUS_LINE);
	AddInSetDefaults (hMod, hStatusLineDesc);
	AddInSetStatusText("Initialising");

	CmdLine *cmdline = new CmdLine();

	cmdline->addUsage("  amgr2, V1.0.0.0, (c) 2019, Ulrich Krause\n");
	cmdline->addUsage("Usage: lo amgr2 [options] [flags]\n");

	cmdline->addUsage( "Options:\n" );
	cmdline->addUsage( "-d    --db\t\tdatabase path" );
	cmdline->addUsage( "-a    --agent\t\tagent name" );

	cmdline->addUsage( "" );
	cmdline->addUsage( "Flags:\n" );
	cmdline->addUsage( "-h    --help\t\tPrints this help" );
	cmdline->addUsage( "      --enable\tEnable agent" );
	cmdline->addUsage( "      --disable\tDisable agent" );
	cmdline->addUsage( "      --toggle\tToggle agent status" );

	cmdline->setOption( "db", 'd' );
	cmdline->setOption( "agent", 'a' );

	cmdline->setFlag ( "help", 'h' ); 
	cmdline->setFlag ( "enable");
	cmdline->setFlag ( "disable");
	cmdline->setFlag ( "toggle");

	cmdline->processCommandArgs( argc, argv );

	if( ! cmdline->hasOptions()) {
		cmdline->printUsage2();
		delete cmdline;
		return ERROR;
		}

	if( cmdline->getFlag( "help" ) 
		|| cmdline->getFlag( 'h' ) ) {
			cmdline->printUsage2();
			return NOERROR;
		}

	string file_path;
	if( cmdline->getValue( 'd' ) != NULL  
		|| cmdline->getValue( "db" ) != NULL  ){
			file_path = cmdline->getValue( 'd' );
		} 

	string agent_name;
	if( cmdline->getValue( 'a' ) != NULL  
		|| cmdline->getValue( "agent" ) != NULL  ){
			agent_name = cmdline->getValue( 'a' );
		} 

	cNotesDatabase _NotesDatabase;

	try{

		_NotesDatabase.open(
			file_path.c_str());

		cNotesAgent _NotesAgent(
			_NotesDatabase.h, agent_name.c_str());

		if(_NotesAgent.isScheduled()) {

			if( cmdline->getFlag( "enable" )) {
				_NotesAgent.enable();

				AddInLogMessageText(
					MSG("... agent '%s' in database '%s' has been enabled\n"), 
					NOERROR, 
					_NotesAgent.name.c_str(), 
					_NotesDatabase.filePath().c_str());
				}

			if( cmdline->getFlag( "disable" )) {
				_NotesAgent.disable();

				AddInLogMessageText(
					MSG("... agent '%s' in database '%s' has been disabled\n"), 
					NOERROR, 
					_NotesAgent.name.c_str(), 
					_NotesDatabase.filePath().c_str());
				}

			if( cmdline->getFlag( "toggle" )) {
				if(_NotesAgent.isEnabled()) {
					_NotesAgent.disable();

					AddInLogMessageText(
						MSG("... agent '%s' in database '%s' has been disabled\n"), 
						NOERROR, 
						_NotesAgent.name.c_str(), 
						_NotesDatabase.filePath().c_str());
					}
				else {
					_NotesAgent.enable();

					AddInLogMessageText(
						MSG("... agent '%s' in database '%s' has been enabled\n"), 
						NOERROR, 
						_NotesAgent.name.c_str(), 
						_NotesDatabase.filePath().c_str());
					}
				}

			} else { // is_scheduled

				AddInLogMessageText(
					MSG("... agent '%s' in database '%s' is not a scheduled agent\n"), 
					NOERROR, 
					_NotesAgent.name.c_str(), 
					_NotesDatabase.filePath().c_str());

			}

		} catch (cNotesErr&amp; err) {
			delete cmdline;
			_NotesDatabase.close();

			AddInLogMessageText(
				MSG("Notes Error: %s\n"), 
				NOERROR, err.what());
			return ERROR;

		} catch (...) {
			delete cmdline;
			_NotesDatabase.close();

			AddInLogMessageText("Unexpected Error\n", NOERROR);
			return ERROR;
			}

		delete cmdline;
		_NotesDatabase.close();
		return error;
	}

To build the cmdline parser, I use another framework that I wrote a couple of years ago. I used the Boost.Program_options in a couple of other projects before, but it is a lot of overhead for such a small project like AMgr2.
CmdLine is much smaller. Despite of its simplicity, it is reliable and produces nice help screens.

I have not yet published CmdLine on Github. If you are interested in the source code, send me an email and I will send you the sources.

Putting it all together, we get a 64bit binary amgr2.exe Copy it to your Domino program directory and you are ready to go.

Open the Domino server console and type

lo amgr2 -h  and you should get

  amgr2, V1.0.0.0, (c) 2019, Ulrich Krause

  Usage: lo amgr2 [options] [flags]

  Options:

  -d    --db		database path
  -a    --agent		agent name
  
  Flags:

  -h    --help		Prints this help
        --enable	Enable agent
        --disable	Disable agent
        --toggle	Toggle agent status

To enable a scheduled agent in an application type

lo amgr2 -d names.nsf -a test --enable

[219C:0002-1CE8] 17.02.2019 08:39:56   AMgr2: ... agent 'test' in database 'names.nsf' has been enabled 

Use –disable to disable an agent or –toggle to change the status of an agent accordingly.

AMgr2 only works for scheduled agents. If an agent does not match this criteria, you’ll get the following message on the server console

lo amgr2 -d names.nsf -a test2 --toggle

[1F94:0002-15D8] 17.02.2019 08:47:53   AMgr2: ... agent 'test2' in database 'names.nsf' is not a scheduled agent

You also can build a simple Notes application that scans all applications on a server for scheduled agents.
Next you can use NotesSession.SendConsoleCommand to enable / disable / toggle one or more agents.

Here is some sample code

Sub Click(Source As Button)
	Dim session As New NotesSession
	serverName$ = "serv01/singultus"
	consoleCommand$ = Inputbox$("Type command:", _
	"Send console command")
	consoleReturn$ = session.SendConsoleCommand( _
	serverName$, consoleCommand$)
	Messagebox consoleReturn$,, consoleCommand$
End Sub

This might not be what Thomas asked for, but it is a good starting point. There is a lot room for improvements and enhancements. AMgr2 will sign the agent with the server id. This might not always be intended. It is not rocket science to implement code and add a couple of parameters to the cmdline parse to use a different id for signing. It is more work to make sure, this id is stored in a secure place and cnnot be accessed by any unauthorized person.

For now, this is it.

AMgr2 once again proves that you can do everything with Notes / Domino. The creators gave us tools that let us add functionallity that is not in the core code. OK, I admit that c/c++ is not the preferred programming language for most of the Notes / Domino developers.


Build LSX 8.0 solutions with Visual C++ 2008 Express

This is a quick update for the article I wrote yesterday about  the “LStatus definition problem with winreg.h“.

VS 2008 Express Edition does install the Microsoft Platform SDK Version 6.1 during setup. This version conflicts with the Lotus eXtension Toolkit 8.0.

Unfortunately, there is no option to not install the SDK. So here is what I did:

  • Run setup to install VC++ 2008 with all components.
  • Remove the Platform SDK via Windows System Control ( Add / Remove software )
  • Install the Platform SDK for Windows Server 2003 SP1
  • Configure VC++ to point to the SDK

The LSX samples will now compile properly. If you get errors on linking, be sure to add uuid.lib user32.lib and Ole32.lib as additional dependencies.

I’m not sure, if there are any side effects when replacing the SDK. If anyone has some deeper knowledge on this topic, pls. feel free to leave a comment.


Delete Group Members Using The Administration Process

Recently, our company is evaluating a 3rd party mail archiving solution. This solution stores mails together with a snapshot of the ACL. (non-Notes) This is a clever solution, because you have Notes security on these archived documents ( client and web ). I do not want to dig too deep into this product, but we found out during evaluation that the â??ACL snapshotâ? feature will get you into trouble when you delegate your mail file.

Imagine a scenario, where User A delegates his mail file to user B. User B has author access (this necessarily includes reader access. ï? ) When the mail archive process starts, it will save the ACL together with the document. If user B retrieves a document from the archive, thereâ??ll be no problem at all.

Now user B quits the company and he /she is replaced by user C. User A modifies the delegation profile according to the new situation.

But what happens, when user C wants to access documents that have been archived, before he / she had access to the mail file?

To make a long story short, this will not work â?¦

To solve the issue, we decided to put a group into each mail file ACL which has the following format: #ARC-FirstNameLastName-READER.
As the name implies, the access level for this group is READER.

When we now send a mail document to the archive, this group is archived as well. (ACL)

Now we can put user C into this group and immediately this user has access to all archived mails of user A.
Keep in mind that regardless of which access level is given to a user by delegation, he needs at least reader access to access documents from within the archive.
Manually adding members to a group or deleting them is not a good idea, because YOU would have to do the work �.

One of the proposals from the vendor of the mail archiving system was to modify the delegation process in Lotus Notes. Not a good idea at all, because you would have to write a completely new CalendarProfile to achive the goal.

The smallest solution is to have the above mentioned group in the ACL (and names.nsf ) and add code to the CalendarProfile to add / remove members to / from the group. This would keep the code provided by IBM intact. In addition to that you can update to a higher version of Notes and Domino and easily add your modifications to the new template.

The basic algorithm is to add all mail delegates to the group and remove a name from this group when the mail file owner revokes access to his database.

HINT: This article is not ment to be a solution which can be copied and pasted. You will not find any source code here. I will only post a few code snippets.

AdminP is a server task for automating administrative tasks in the background on a schedule. The Domino administration process (AdminP) is a server-side mechanism for automating administrative tasks in the background on a specified schedule. Domino’s AdminP supports everything from user renames to file replications.

Starting with version 6 of Lotus Notes and Domino, you can use the NotesAdministrationProcess class to create AdminP request programmatically with Lotus Script.

One of the methods of the NotesAdministrationProcess class is â??AddGroupMembersâ?.
This method adds members (passed as a parameter in the method call) to an existing group or creates the group first when it does not exist and adds the members to the newly created group.

This is a great feature if you want to enable some users in your organization to maintain groups in the names.nsf without giving them any author or editor rights. Exactly what we need.

But, when doing so, how can they delete users from existing groups using AdminP ? You do not find any method like â??RemoveGroupMembersâ? in the NotesAdministrationProcess class.
Since IBM does not provide us with such a function, I had to create it on my own.

Admin4.nsf - view

Bob Balfe of IBM published an article back in 2003 on developerworks ( LINK )

This is a great starting point to write your own AdminP request handlers using the Notes C API.
As described in the article, I created a new form in the admin4.nsf to contain all the fields needed for the new Administration Request.

adminplus request

I saved the compiled nadminplus.exe to the Domino executable directory and started it typing load nadminplus at the server console.

server console with adminplus

You can now create new RemoveGroupMembers request directly in the admin4.nsf or use the following code to create the requests programmatically with Lotus Script.

'/* Put the following code into the declaration section of an action */
'/* or create a new script library to contain the code */
Const DB_ADMIN4 = "admin4.nsf"
Const FLD_FORM = "CustomRequest"
Const FLD_PROXYACTION = "5005" ' RemoveGroupMembers | 5001

Class NotesAdministrationProcessPlus

	Private szServer As String

	Public Sub new (szServerName As String)
		Dim s As New NotesSession
		Dim nn As NotesName
		Set nn = s.CreateName (szServerName)
		szServer = nn.Canonical
	End Sub

	Public Function  RemoveGroupMembers (ListName As String, Members As Variant) As String
		RemoveGroupMembers = ""
		If  (Ubound (members) = 1 And members(0) ="") Or Trim(ListName) = ""  Then
			Exit Function
		Else
			Dim s As New NotesSession
			Dim db As New NotesDatabase( szServer, DB_ADMIN4 )
			Dim doc As NotesDocument

			If db.IsOpen Then
				Set doc = db.CreateDocument
				doc.Form = FLD_FORM
				doc.ProxyAction = FLD_PROXYACTION
				doc.ProxyServer = szServer
				doc.ListName = ListName
				doc.Members = Members
				Call doc.ComputeWithForm(False, False)
				Call doc.Sign
				Call doc.Save(False, True)
				RemoveGroupMembers = doc.NoteID
			Else

			End If
		End If

	End Function
End Class

To create the request documents, use the following code:

Sub Click(Source As Button)
	Dim noteid As Variant
	Dim members(1) As String
	members(0) = "Hein Bloed/Maus/de"
	' ...

	Dim AdminPP As New NotesAdministrationProcessPlus ("yourserver")
	noteid = AdminPP.RemoveGroupMembers ("yourgroup", members)
	' ...

End Sub

See the code in action: http://screencast.com/t/htEtry53BSk

If you find this function useful and like to try it by yourself, send me an email or leave a comment. I have also posted an idea on ideajam.net. Perhaps you can promote the idea if you like it.


nKill – Restart Notes after a crash

Wer kennt das nicht. Der Notes Client ist mal wieder abgestürzt und läßt sich nun beim besten Willen nicht mehr zu einem Neustart bewegen. Schuld daran sind Prozesse, die nach einem Absturz im Speicher verbleiben und den Neustart verhindern.
Zwar gibt es bereits Tools auf dem Markt, die einem aus diesem Dilemma heraushelfen können. Aber in den meisten Fällen müssen diese Tools erst installiert und nach einem Crash erst aufgerufen werden.
Daher habe ich dieses Tool geschrieben. Für die Interessierten habe ich den Quelltext beigefügt. Das Tool lauft unter Win9.x/WinME/Win NT 3.x/Win NT 4.0/ Win 2000 und XP

#include "windows.h"
#include "tlhelp32.h"
#include "iostream."
#include "string.h"
#ifdef BORLANDC
#include "string.h"
#include "ctype.h"
#endif
int KILL_PROC_BY_NAME(const char *);
int main(int argc,char *argv[])
{
int iRes;
char string[] = "ldapsearch.exe, nadminp.exe, naldaemn.exe, namgr.exe, 
napdaemn.exe, nchronos.exe, ncollect.exe,nconvert.exe, ndiiop.exe, 
ndyncfg.exe, nhldaemn.exe, nhttp.exe, nhttpcgi.exe, nimapcl.exe, 
nlnotes.exe,nlogasio.exe, nminder.exe, nnntpcl.exe, nnotesmm.exe, 
stringn.exe, npop3.exe, nupdall.exe, nupdate.exe, nweb.exe,
nwrdaemn.exe, nxpcdmn.exe, rtfcnvt.exe, CLHAP32.EXE, ntaskldr.exe";
char separator[] = ",";
char *token;
token = strtok(string, separator);
while( token != NULL )
{
iRes=KILL_PROC_BY_NAME(token);
cout < < token << " Result code=" << iRes << endl;
token = strtok(NULL, separator);
}
    return 0;
}
int KILL_PROC_BY_NAME(const char *szToTerminate)
//   Return codes are as follows:
//   0   = Process was successfully terminated
//   603 = Process was not currently running
//   604 = No permission to terminate process
//   605 = Unable to load PSAPI.DLL
//   602 = Unable to terminate process for some other reason
//   606 = Unable to identify system type
//   607 = Unsupported OS
//   632 = Invalid process name
//   700 = Unable to get procedure address from PSAPI.DLL
//   701 = Unable to get process list, EnumProcesses failed
//   702 = Unable to load KERNEL32.DLL
//   703 = Unable to get procedure address from KERNEL32.DLL
//   704 = CreateToolhelp32Snapshot failed
{
BOOL bResult,bResultm;
DWORD aiPID[1000],iCb=1000,iNumProc,iV2000=0;
DWORD iCbneeded,i,iFound=0;
char szName[MAX_PATH],szToTermUpper[MAX_PATH];
HANDLE hProc,hSnapShot,hSnapShotm;
OSVERSIONINFO osvi;
HINSTANCE hInstLib;
int iLen,iLenP,indx;
HMODULE hMod;
PROCESSENTRY32 procentry;
MODULEENTRY32 modentry;
iLenP=strlen(szToTerminate);
if(iLenP<1 || iLenP>MAX_PATH) return 632;
for(indx=0;indx    szToTermUpper[indx]=toupper(szToTerminate[indx]);
szToTermUpper[iLenP]=0;
// PSAPI Function Pointers.
BOOL (WINAPI *lpfEnumProcesses)( DWORD *, DWORD cb, DWORD * );
BOOL (WINAPI *lpfEnumProcessModules)( HANDLE, HMODULE *,
DWORD, LPDWORD );
DWORD (WINAPI *lpfGetModuleBaseName)( HANDLE, HMODULE,
LPTSTR, DWORD );
// ToolHelp Function Pointers.
HANDLE (WINAPI *lpfCreateToolhelp32Snapshot)(DWORD,DWORD) ;
BOOL (WINAPI *lpfProcess32First)(HANDLE,LPPROCESSENTRY32) ;
BOOL (WINAPI *lpfProcess32Next)(HANDLE,LPPROCESSENTRY32) ;
BOOL (WINAPI *lpfModule32First)(HANDLE,LPMODULEENTRY32) ;
BOOL (WINAPI *lpfModule32Next)(HANDLE,LPMODULEENTRY32) ;
// First check what version of Windows we're in
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
bResult=GetVersionEx(&osvi);
if(!bResult)     // Unable to identify system version
return 606;
if((osvi.dwPlatformId != VER_PLATFORM_WIN32_NT) &&
(osvi.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS))
return 607;
if(osvi.dwPlatformId==VER_PLATFORM_WIN32_NT)
{
hInstLib = LoadLibraryA("PSAPI.DLL");
if(hInstLib == NULL)
return 605;
// Get procedure addresses.
lpfEnumProcesses = (BOOL(WINAPI *)(DWORD *,DWORD,DWORD*))
GetProcAddress( hInstLib, "EnumProcesses" ) ;
lpfEnumProcessModules = (BOOL(WINAPI *)(HANDLE, HMODULE *,
DWORD, LPDWORD)) GetProcAddress( hInstLib,
"EnumProcessModules" ) ;
lpfGetModuleBaseName =(DWORD (WINAPI *)(HANDLE, HMODULE,
LPTSTR, DWORD )) GetProcAddress( hInstLib,
"GetModuleBaseNameA" ) ;
if(lpfEnumProcesses == NULL ||
lpfEnumProcessModules == NULL ||
lpfGetModuleBaseName == NULL)
{
FreeLibrary(hInstLib);
return 700;
}
bResult=lpfEnumProcesses(aiPID,iCb,&iCbneeded);
if(!bResult)
{
// Unable to get process list, EnumProcesses failed
FreeLibrary(hInstLib);
return 701;
}

// How many processes are there?
iNumProc=iCbneeded/sizeof(DWORD);

// Get and match the name of each process
for(i=0;i    {
// Get the (module) name for this process

strcpy(szName,"Unknown");
// First, get a handle to the process
hProc=OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,FALSE,
aiPID[i]);
// Now, get the process name
if(hProc)
{
if(lpfEnumProcessModules(hProc,&hMod,sizeof(hMod),&iCbneeded) )
{
iLen=lpfGetModuleBaseName(hProc,hMod,szName,MAX_PATH);
}
}
CloseHandle(hProc);
// We will match regardless of lower or upper case
#ifdef BORLANDC
if(strcmp(strupr(szName),szToTermUpper)==0)
#else
if(strcmp(_strupr(szName),szToTermUpper)==0)
#endif
{
// Process found, now terminate it
iFound=1;
// First open for termination
hProc=OpenProcess(PROCESS_TERMINATE,FALSE,aiPID[i]);
if(hProc)
{
if(TerminateProcess(hProc,0))
{
// process terminated
CloseHandle(hProc);
FreeLibrary(hInstLib);
return 0;
}
else
{
// Unable to terminate process
CloseHandle(hProc);
FreeLibrary(hInstLib);
return 602;
}
}
else
{
// Unable to open process for termination
FreeLibrary(hInstLib);
return 604;
}
}
}
}

if(osvi.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS)
{
// Win/95 or 98 or ME

hInstLib = LoadLibraryA("Kernel32.DLL");
if( hInstLib == NULL )
return 702;

lpfCreateToolhelp32Snapshot=
(HANDLE(WINAPI *)(DWORD,DWORD))
GetProcAddress( hInstLib,
"CreateToolhelp32Snapshot" ) ;
lpfProcess32First=
(BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))
GetProcAddress( hInstLib, "Process32First" ) ;
lpfProcess32Next=
(BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))
GetProcAddress( hInstLib, "Process32Next" ) ;
lpfModule32First=
(BOOL(WINAPI *)(HANDLE,LPMODULEENTRY32))
GetProcAddress( hInstLib, "Module32First" ) ;
lpfModule32Next=
(BOOL(WINAPI *)(HANDLE,LPMODULEENTRY32))
GetProcAddress( hInstLib, "Module32Next" ) ;
if( lpfProcess32Next == NULL ||
lpfProcess32First == NULL ||
lpfModule32Next == NULL ||
lpfModule32First == NULL ||
lpfCreateToolhelp32Snapshot == NULL )
{
FreeLibrary(hInstLib);
return 703;
}

hSnapShot = lpfCreateToolhelp32Snapshot(
TH32CS_SNAPPROCESS, 0 ) ;
if( hSnapShot == INVALID_HANDLE_VALUE )
{
FreeLibrary(hInstLib);
return 704;
}

// Get the first process' information.
procentry.dwSize = sizeof(PROCESSENTRY32);
bResult=lpfProcess32First(hSnapShot,&procentry);

// While there are processes, keep looping and checking.
while(bResult)
{
// Get a handle to a Toolhelp snapshot of this process.
hSnapShotm = lpfCreateToolhelp32Snapshot(
TH32CS_SNAPMODULE, procentry.th32ProcessID) ;
if( hSnapShotm == INVALID_HANDLE_VALUE )
{
CloseHandle(hSnapShot);
FreeLibrary(hInstLib);
return 704;
}
// Get the module list for this process
modentry.dwSize=sizeof(MODULEENTRY32);
bResultm=lpfModule32First(hSnapShotm,&modentry);

// While there are modules, keep looping and checking
while(bResultm)
{
if(strcmp(modentry.szModule,szToTermUpper)==0)
{
// Process found, now terminate it
iFound=1;
// First open for termination
hProc=OpenProcess(PROCESS_TERMINATE,FALSE,procentry.th32ProcessID);
if(hProc)
{
if(TerminateProcess(hProc,0))
{
// process terminated
CloseHandle(hSnapShotm);
CloseHandle(hSnapShot);
CloseHandle(hProc);
FreeLibrary(hInstLib);
return 0;
}
else
{
// Unable to terminate process
CloseHandle(hSnapShotm);
CloseHandle(hSnapShot);
CloseHandle(hProc);
FreeLibrary(hInstLib);
return 602;
}
}
else
{
// Unable to open process for termination
CloseHandle(hSnapShotm);
CloseHandle(hSnapShot);
FreeLibrary(hInstLib);
return 604;
}
}
else
{  // Look for next modules for this process
modentry.dwSize=sizeof(MODULEENTRY32);
bResultm=lpfModule32Next(hSnapShotm,&modentry);
}
}

//Keep looking
CloseHandle(hSnapShotm);
procentry.dwSize = sizeof(PROCESSENTRY32);
bResult = lpfProcess32Next(hSnapShot,&procentry);
}
CloseHandle(hSnapShot);
}
if(iFound==0)
{
FreeLibrary(hInstLib);
return 603;
}
FreeLibrary(hInstLib);
return 0;
}

DOWNLOAD