diff --git a/ctrl/modopts.ini b/ctrl/modopts.ini
index 17858a2b0a24ab11eaeea4b0c94983452d13515e..d4d256699d861da7ca0a9bbfbc2356f2bd0cb50c 100644
--- a/ctrl/modopts.ini
+++ b/ctrl/modopts.ini
@@ -59,6 +59,11 @@
         backup_level = 10
 
 [xtrn_sec]
+; Set to true to disable execution of prextrn.js and postxtrn.js for external programs
+; when they are running as a logon event
+  disable_xtrnpre_on_logon_event = false
+  disable_xtrnpost_on_logon_event = false
+  
 ; Enable multi-column display (when more than 10 external programs in a section)
 	multicolumn = true
 ; Sort the list of external programs alphabetically by name
diff --git a/ctrl/text.dat b/ctrl/text.dat
index a2b4bf512a306bcace791e5175a661b764b99472..9a4594afef769f9cc4fc129022bfb8f7bd47e52e 100644
--- a/ctrl/text.dat
+++ b/ctrl/text.dat
@@ -1023,4 +1023,5 @@
 "\xdc\xdd\xdf\xde"                                        841 SpinningCursor8
 "\xfa\xf9\xfe\xf9"                                        842 SpinningCursor9
 "\1_\1b\1h[\1c@CHECKMARK@\1b] \1yTerminal columns "\      843 HowManyColumns
-	"[\1wAuto\1y]: \1n"
\ No newline at end of file
+	"[\1wAuto\1y]: \1n"
+"\r\nExternal program not found\r\n"           844 NoXtrnProgram
diff --git a/exec/postxtrn.js b/exec/postxtrn.js
new file mode 100644
index 0000000000000000000000000000000000000000..7eb3b3e13db3ba1d3d1b7667e64bb331c00eee3b
--- /dev/null
+++ b/exec/postxtrn.js
@@ -0,0 +1,62 @@
+// postxtrn.js
+
+// External Program Post Module
+// These actions execute after an external program is launched via bbs.exec_xtrn()
+
+// $Id: xtrn_sec.js,v 1.29 2020/05/09 10:11:23 rswindell Exp $
+
+"use strict";
+
+load("sbbsdefs.js");
+
+/* text.dat entries */
+load("text.js");
+
+var options, program;
+
+if((options=load({}, "modopts.js","xtrn_sec")) == null)
+	options = {};	// default values
+
+if(options.restricted_user_msg === undefined)
+	options.restricted_user_msg = bbs.text(R_ExternalPrograms);
+
+if(options.clear_screen === undefined)
+	options.clear_screen = true;
+
+function exec_xtrn_post(program)
+{
+	if ((options.disable_xtrnpost_on_logon_event) && (bbs.node_action == NODE_LOGN)) {
+		return;
+	}
+
+	console.attributes = 0;
+	console.attributes = LIGHTGRAY;
+
+	load('fonts.js', 'default');
+
+	if(options.eval_after_exec)
+		eval(options.eval_after_exec);
+}
+
+
+/* main: */
+{
+	if(!argv[0]) {
+		write(bbs.text(NoXtrnProgram));
+	} else {
+		xtrn_area.sec_list.some(function(sec) {
+			sec.prog_list.some(function (prog) {
+				if (prog.code.toLowerCase() == argv[0]) {
+					program = prog;
+					return true;
+				}
+			});
+		});
+
+		if (!program) {
+			write(bbs.text(NoXtrnProgram));
+		} else {
+			exec_xtrn_post(program);
+		}
+	}
+}
diff --git a/exec/prextrn.js b/exec/prextrn.js
new file mode 100644
index 0000000000000000000000000000000000000000..40c0e339c25e8ab9d01d470039053b7d8dc6d1f1
--- /dev/null
+++ b/exec/prextrn.js
@@ -0,0 +1,73 @@
+// prextrn.js
+
+// External Program Pre Module
+// These actions execute before an external program is launched via bbs.exec_xtrn()
+
+// $Id: xtrn_sec.js,v 1.29 2020/05/09 10:11:23 rswindell Exp $
+
+"use strict";
+
+load("sbbsdefs.js");
+
+/* text.dat entries */
+load("text.js");
+
+var options, program;
+
+if((options=load({}, "modopts.js","xtrn_sec")) == null)
+	options = {};	// default values
+
+
+function exec_xtrn_pre(program)
+{
+	if ((options.disable_xtrnpre_on_logon_event) && (bbs.node_action == NODE_LOGN)) {
+		return;
+	}
+	
+	if(options.restricted_user_msg === undefined)
+		options.restricted_user_msg = bbs.text(R_ExternalPrograms);
+
+	if(user.security.restrictions&UFLAG_X) {
+		write(options.restricted_user_msg);
+		return;
+	}
+
+	if(bbs.menu_exists("xtrn/" + program.code)) {
+		bbs.menu("xtrn/" + program.code);
+		console.pause();
+		console.line_counter=0;
+	}
+
+	console.attributes = LIGHTGRAY;
+
+	if(options.clear_screen_on_exec)
+		console.clear();
+
+	if(options.eval_before_exec)
+		eval(options.eval_before_exec);
+
+	load('fonts.js', 'xtrn:' + program.code);
+}
+
+
+/* main: */
+{
+	if(!argv[0]) {
+		write(bbs.text(NoXtrnProgram));
+	} else {
+		xtrn_area.sec_list.some(function(sec) {
+			sec.prog_list.some(function (prog) {
+				if (prog.code.toLowerCase() == argv[0]) {
+					program = prog;
+					return true;
+				}
+			});
+		});
+
+		if (!program) {
+			write(bbs.text(NoXtrnProgram));
+		} else {
+			exec_xtrn_pre(program);
+		}
+	}
+}
diff --git a/exec/xtrn_sec.js b/exec/xtrn_sec.js
index f312d5e904e21e13c78e8f041bb96e021feff914..b55d3f10cd7262667522f027a47986bd439ebd15 100644
--- a/exec/xtrn_sec.js
+++ b/exec/xtrn_sec.js
@@ -80,22 +80,6 @@ function sort_by_name(a, b)
 	return 0;
 }
 
-function exec_xtrn(prog)
-{
-	console.attributes = LIGHTGRAY;
-	if(options.clear_screen_on_exec)
-		console.clear();
-	if(options.eval_before_exec)
-		eval(options.eval_before_exec);
-	load('fonts.js', 'xtrn:' + prog.code);
-	bbs.exec_xtrn(prog.code);
-	console.attributes = 0;
-	console.attributes = LIGHTGRAY;
-	load('fonts.js', 'default');
-	if(options.eval_after_exec)
-		eval(options.eval_after_exec);
-}
-
 function external_program_menu(xsec)
 {
     var i,j;
@@ -118,7 +102,7 @@ function external_program_menu(xsec)
 
 		// If there's only one program available to the user in the section, just run it (or try to)
 		if(options.autoexec && prog_list.length == 1) {
-			exec_xtrn(prog_list[0]);
+			bbs.exec_xtrn(prog_list[0].code);
 			break;
 		}
 		
@@ -190,11 +174,8 @@ function external_program_menu(xsec)
 		if((i=console.getnum(prog_list.length))<1)
 			break;
 		i--;
-		if(bbs.menu_exists("xtrn/" + prog_list[i].code)) {
-			bbs.menu("xtrn/" + prog_list[i].code);
-			console.line_counter=0;
-		}
-		exec_xtrn(prog_list[i]);
+
+		bbs.exec_xtrn(prog_list[i].code);
 	}
 }
 
diff --git a/src/sbbs2/xtrn_ovl.c b/src/sbbs2/xtrn_ovl.c
index 3066305eb9778d1bb7a4bdfbbd4cba62889715ce..c0c3a5493134ae2c6d245d1fb8f46250f5115b63 100644
--- a/src/sbbs2/xtrn_ovl.c
+++ b/src/sbbs2/xtrn_ovl.c
@@ -1275,7 +1275,6 @@ void exec_xtrn(uint xtrnnum)
     node_t node;
 	time_t start,end;
 
-
 if(!chk_ar(xtrn[xtrnnum]->run_ar,useron)
 	|| !chk_ar(xtrnsec[xtrn[xtrnnum]->sec]->ar,useron)) {
 	bputs(text[CantRunThatProgram]);
diff --git a/src/sbbs3/scfg/scfgsys.c b/src/sbbs3/scfg/scfgsys.c
index d6b0e86fc103da75db9adf434a565487250694e8..271e677047111f420cb45751b2716e88064ea9a9 100644
--- a/src/sbbs3/scfg/scfgsys.c
+++ b/src/sbbs3/scfg/scfgsys.c
@@ -1669,6 +1669,8 @@ void sys_cfg(void)
 					sprintf(opt[i++],"%-16.16s%s","Auto Message",cfg.automsg_mod);
 					sprintf(opt[i++],"%-16.16s%s","Text Section",cfg.textsec_mod);
 					sprintf(opt[i++],"%-16.16s%s","Xtrn Section",cfg.xtrnsec_mod);
+					sprintf(opt[i++],"%-16.16s%s","Xtrn Prog Pre",cfg.xtrnprogpre_mod);
+					sprintf(opt[i++],"%-16.16s%s","Xtrn Prog Post",cfg.xtrnprogpost_mod);
 					sprintf(opt[i++],"%-16.16s%s","Read Mail",cfg.readmail_mod);
 					sprintf(opt[i++],"%-16.16s%s","Scan Msgs",cfg.scanposts_mod);
 					sprintf(opt[i++],"%-16.16s%s","Scan Subs",cfg.scansubs_mod);
@@ -1696,6 +1698,8 @@ void sys_cfg(void)
 						"`Auto Message` Executed when a user chooses to edit the auto-message\n"
 						"`Text Section` Executed to handle general text file (viewing) section\n"
 						"`Xtrn Section` Executed to handle external programs (doors) section\n"
+						"`Xtrn Prog Pre` Executed before external programs (doors) run\n"
+						"`Xtrn Prog Post` Executed after external programs (doors) run\n"
 						"\n"
 						"Full module command-lines may be used for the operations listed below:\n"
 						"\n"
@@ -1759,34 +1763,42 @@ void sys_cfg(void)
 								,cfg.xtrnsec_mod,sizeof(cfg.xtrnsec_mod)-1,K_EDIT);
 							break;
 						case 10:
+							uifc.input(WIN_MID|WIN_SAV,0,0,"External Program Pre Module"
+								,cfg.xtrnprogpre_mod,sizeof(cfg.xtrnprogpre_mod)-1,K_EDIT);
+							break;
+						case 11:
+							uifc.input(WIN_MID|WIN_SAV,0,0,"External Program Post Module"
+								,cfg.xtrnprogpost_mod,sizeof(cfg.xtrnprogpost_mod)-1,K_EDIT);
+							break;														
+						case 12:
 							uifc.input(WIN_MID|WIN_SAV,0,0,"Read Mail Command"
 								,cfg.readmail_mod,sizeof(cfg.readmail_mod)-1,K_EDIT);
 							break;
-						case 11:
+						case 13:
 							uifc.input(WIN_MID|WIN_SAV,0,0,"Scan Msgs Command"
 								,cfg.scanposts_mod,sizeof(cfg.scanposts_mod)-1,K_EDIT);
 							break;
-						case 12:
+						case 14:
 							uifc.input(WIN_MID|WIN_SAV,0,0,"Scan Subs Command"
 								,cfg.scansubs_mod,sizeof(cfg.scansubs_mod)-1,K_EDIT);
 							break;
-						case 13:
+						case 15:
 							uifc.input(WIN_MID|WIN_SAV,0,0,"List Msgs Command"
 								,cfg.listmsgs_mod,sizeof(cfg.listmsgs_mod)-1,K_EDIT);
 							break;
-						case 14:
+						case 16:
 							uifc.input(WIN_MID|WIN_SAV,0,0,"List Logons Command"
 								,cfg.logonlist_mod,sizeof(cfg.logonlist_mod)-1,K_EDIT);
 							break;
-						case 15:
+						case 17:
 							uifc.input(WIN_MID|WIN_SAV,0,0,"List Nodes Command"
 								,cfg.nodelist_mod,sizeof(cfg.nodelist_mod)-1,K_EDIT);
 							break;
-						case 16:
+						case 18:
 							uifc.input(WIN_MID|WIN_SAV,0,0,"Who's Online Command"
 								,cfg.whosonline_mod,sizeof(cfg.whosonline_mod)-1,K_EDIT);
 							break;
-						case 17:
+						case 19:
 							uifc.input(WIN_MID|WIN_SAV,0,0,"Private Message Command"
 								,cfg.privatemsg_mod,sizeof(cfg.privatemsg_mod)-1,K_EDIT);
 							break;
diff --git a/src/sbbs3/scfgdefs.h b/src/sbbs3/scfgdefs.h
index 84d28f8a83df032c7764104ac140d567115905b3..37433c554a9442078705145d909ccb77946813cd 100644
--- a/src/sbbs3/scfgdefs.h
+++ b/src/sbbs3/scfgdefs.h
@@ -610,6 +610,8 @@ typedef struct
 	char			whosonline_mod[LEN_CMD+1];
 	char			privatemsg_mod[LEN_CMD+1];
 	char			logonlist_mod[LEN_CMD+1];
+    char			xtrnprogpre_mod[LEN_MODNAME+1];			/* External Program pre-execution module */
+    char			xtrnprogpost_mod[LEN_MODNAME+1];		/* External Program post-execution module */
 	char			scfg_cmd[LEN_CMD+1];	/* SCFG command line - unused! */
 	uchar			smb_retry_time; 		/* Seconds to retry on SMBs */
 	uint16_t		sec_warn;				/* Seconds before inactivity warning */
diff --git a/src/sbbs3/scfglib1.c b/src/sbbs3/scfglib1.c
index 2b8e2210a647c550ede4a9fc0c8615727378c127..543b5306e752b9041f3f9014be12870ec319881d 100644
--- a/src/sbbs3/scfglib1.c
+++ b/src/sbbs3/scfglib1.c
@@ -291,7 +291,15 @@ BOOL read_main_cfg(scfg_t* cfg, char* error)
 	get_str(cfg->logonlist_mod,instream);
 	if(cfg->logonlist_mod[0] == '\xff')
 		SAFECOPY(cfg->logonlist_mod, "logonlist");
-	for(i=0;i<126;i++)					/* unused - initialized to 0xff */
+
+	get_str(cfg->xtrnprogpre_mod,instream);
+	if(cfg->xtrnprogpre_mod[0] == '\xff') 
+	    SAFECOPY(cfg->xtrnprogpre_mod, "prextrn");
+	get_str(cfg->xtrnprogpost_mod,instream);
+	if(cfg->xtrnprogpost_mod[0] == '\xff') 
+	    SAFECOPY(cfg->xtrnprogpost_mod, "postxtrn");		
+		
+	for(i=0;i<117;i++)					/* unused - initialized to 0xff */
 		get_int(n,instream);
 
 	get_int(cfg->user_backup_level,instream);
diff --git a/src/sbbs3/scfgsave.c b/src/sbbs3/scfgsave.c
index 85877f9dd8d60f4dadfe2032e22cd44cb7b200c6..e891cb0aa21a4f80c4e162320ed13c9b42ded026 100644
--- a/src/sbbs3/scfgsave.c
+++ b/src/sbbs3/scfgsave.c
@@ -261,8 +261,12 @@ BOOL DLLCALL write_main_cfg(scfg_t* cfg, int backup_level)
 	put_str(cfg->whosonline_mod, stream);
 	put_str(cfg->privatemsg_mod, stream);
 	put_str(cfg->logonlist_mod, stream);
+	
+    put_str(cfg->xtrnprogpre_mod,stream);
+    put_str(cfg->xtrnprogpost_mod,stream);
+    
 	n=0xffff;
-	for(i=0;i<126;i++)
+	for(i=0;i<117;i++)
 		put_int(n,stream);
 
 	put_int(cfg->user_backup_level,stream);
diff --git a/src/sbbs3/text.h b/src/sbbs3/text.h
index 0578967d3928e2c284ada20927740b0b16c097e7..6e54d190d1add14ec1d1b4d1a568dfa891e52569 100644
--- a/src/sbbs3/text.h
+++ b/src/sbbs3/text.h
@@ -854,6 +854,7 @@ enum {
 	,SpinningCursor8
 	,SpinningCursor9
 	,HowManyColumns
+	,NoXtrnProgram
 
 	,TOTAL_TEXT
 };
diff --git a/src/sbbs3/text_defaults.c b/src/sbbs3/text_defaults.c
index 9261f22165e9060c2e1dfa88a8dd8b797021afb3..a9428805cee30ee02604dbc5a0993576d24fd892 100644
--- a/src/sbbs3/text_defaults.c
+++ b/src/sbbs3/text_defaults.c
@@ -1385,4 +1385,5 @@ const char * const text_defaults[TOTAL_TEXT]={
 	,"\xfa\xf9\xfe\xf9" // 842 SpinningCursor9
 	,"\x01\x5f\x01\x62\x01\x68\x5b\x01\x63\x40\x43\x48\x45\x43\x4b\x4d\x41\x52\x4b\x40\x01\x62\x5d\x20\x01\x79\x54\x65\x72\x6d\x69\x6e"
 		"\x61\x6c\x20\x63\x6f\x6c\x75\x6d\x6e\x73\x20\x5b\x01\x77\x41\x75\x74\x6f\x01\x79\x5d\x3a\x20\x01\x6e" // 843 HowManyColumns
+	,"\x0d\x0a\x4e\x6f\x20\x65\x78\x74\x65\x72\x6e\x61\x6c\x20\x70\x72\x6f\x67\x72\x61\x6d\x73\x20\x61\x76\x61\x69\x6c\x61\x62\x6c\x65\x0d\x0a" // 844 NoXtrnProgram
 };
diff --git a/src/sbbs3/xtrn_sec.cpp b/src/sbbs3/xtrn_sec.cpp
index d6654bcb95c6e47f5c2135571e4ab4ed279f189e..f8260144131f1f1da19375698c9163ca28ad2fcd 100644
--- a/src/sbbs3/xtrn_sec.cpp
+++ b/src/sbbs3/xtrn_sec.cpp
@@ -1487,6 +1487,11 @@ bool sbbs_t::exec_xtrn(uint xtrnnum)
 		subtract_cdt(&cfg,&useron,cfg.xtrn[xtrnnum]->cost); 
 	}
 
+    if(cfg.xtrnprogpre_mod[0] != '\0') {
+        SAFEPRINTF2(str, "%s %s", cfg.xtrnprogpre_mod,cfg.xtrn[xtrnnum]->code);
+        exec_bin(str, &main_csi);
+    }
+
 	if(!(cfg.xtrn[xtrnnum]->misc&MULTIUSER)) {
 		for(i=1;i<=cfg.sys_nodes;i++) {
 			getnodedat(i,&node,0);
@@ -1652,6 +1657,11 @@ bool sbbs_t::exec_xtrn(uint xtrnnum)
 	else
 		lncntr = 0;
 
+    if(cfg.xtrnprogpost_mod[0] != '\0') {
+        SAFEPRINTF2(str, "%s %s", cfg.xtrnprogpost_mod,cfg.xtrn[xtrnnum]->code);
+        exec_bin(str, &main_csi);
+    }
+    
 	return(true);
 }