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) && !defined(ND64)
#pragma pack(push, 1)
#endif
#include <global.h>
#include <miscerr.h>
#include <addin.h>
#if defined (_MSC_VER) && !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 (&hMod, &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& 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.