diff --git a/exec/install-xtrn.js b/exec/install-xtrn.js
index ded2392bca380079860efa02adae69c673d2c8a6..7b5deb824466c7689cc2ed475944197be6903da2 100644
--- a/exec/install-xtrn.js
+++ b/exec/install-xtrn.js
@@ -25,6 +25,12 @@
 //
 // The .ini sections and keys supported (zero or more of each may be included):
 //
+// [pre-exec:<file>.js [args]] ; execute file.js before installing programs
+//		startup_dir		= directory to make current before execution
+//
+// [pre-eval:<js-expression>] ; evaluate js-expression before installing progs
+//      cmd             = evaluate this string rather than the js-expression
+//
 // [prog:<code>]
 // 		name 			= program name or description (40 chars max)
 //      cats            = additional target installation categories (sections)
@@ -61,10 +67,10 @@
 // [service:<protocol>]
 //      see ctrl/services.ini 
 //
-// [exec:<file>.js [args]]  ; execute file.js with these arguments
+// [exec:<file>.js [args]] ; execute file.js with these arguments - after
 //		startup_dir		= directory to make current before execution
 //
-// [eval:<js-expression>]
+// [eval:<js-expression>] ; evaluate js-expression after installing programs
 //      cmd             = evaluate this string rather than the js-expression
 //
 // [ini:<filename.ini>[:section]]
@@ -75,8 +81,9 @@
 // Additionally, each section can have the following optional keys that are
 // only used by this script (i.e. not written to any configuration files):
 //		note			= note to sysop displayed before installation
+//		fail			= note to sysop displayed upon failure
 //      prompt          = confirmation prompt (or false if no prompting)
-//		required		= if true, this item must be installed to continue
+//		required		= if true, this item must be successful to continue
 //      last            = if true, this item will be the last of its type
 //      done            = if true, no more installer items will be processed
 //
@@ -91,7 +98,7 @@
 
 "use strict";
 
-const REVISION = "3.18b";
+const REVISION = "3.18c";
 const ini_fname = "install-xtrn.ini";
 
 load("sbbsdefs.js");
@@ -210,6 +217,76 @@ function install_xtrn_item(cnf, type, name, desc, item, cats)
 	return true;
 }
 
+function install_exec_cmd(ini_file, section, startup_dir)
+{
+	var list = ini_file.iniGetAllObjects("cmd", section);
+	for (var i = 0; i < list.length; i++) {
+		var item = list[i];
+		var js_args = item.cmd.split(/\s+/);
+		var js_file = js_args.shift();
+
+		if (file_getext(js_file).toLowerCase() != ".js")
+			return "Only '.js' files may be executed: " + js_file;
+
+		if (item.note)
+			print(item.note);
+
+		var prompt = "Execute: " + item.cmd;
+		if (item.prompt !== undefined)
+			prompt = item.prompt;
+
+		if (prompt && !confirm(prompt)) {
+			if (item.required == true)
+				return prompt + " is required to continue";
+			continue;
+		}
+
+		if (item.startup_dir === undefined)
+			item.startup_dir = startup_dir;
+		if (!item.args)
+			item.args = "";
+		var result = js.exec.apply(null
+			,[js_file, item.startup_dir, {}].concat(js_args));
+		if (result !== 0 && item.required)
+			return item.fail || ("Error " + result + " executing " + item.cmd);
+		if (item.last === true)
+			break;
+		if (item.done)
+			return false;
+	}
+	return true;
+}
+
+function install_eval_cmd(ini_file, section, startup_dir)
+{
+	var list = ini_file.iniGetAllObjects("str", section);
+	for (var i = 0; i < list.length; i++) {
+		var item = list[i];
+		if (!item.cmd)
+			item.cmd = item.str; // the str can't contain [], so allow cmd to override
+		var prompt = "Evaluate: " + item.cmd;
+		if (item.prompt !== undefined)
+			prompt = item.prompt;
+		if (prompt && !confirm(prompt)) {
+			if (item.required == true)
+				return prompt + " is required to continue";
+			continue;
+		}
+		try {
+			var result = eval(item.cmd);
+		} catch(e) {
+			return e;
+		}
+		if (!result && item.required)
+			return item.fail || ("Truthful evaluation of '" + item.cmd + "' is required to continue");
+		if (item.last === true)
+			break;
+		if (item.done)
+			return false;
+	}
+	return true;
+}
+
 function install(ini_fname)
 {
 	ini_fname = fullpath(ini_fname);
@@ -254,6 +331,14 @@ function install(ini_fname)
 	var startup_dir = ini_fname.substr(0, Math.max(ini_fname.lastIndexOf("/"), ini_fname.lastIndexOf("\\"), 0));
 	startup_dir = relpath.get(system.ctrl_dir, startup_dir);
 
+	var result = install_exec_cmd(ini_file, "pre-exec:", startup_dir);
+	if(result !== true)
+		return result;
+
+	result = install_eval_cmd(ini_file, "pre-eval:", startup_dir);
+	if(result !== true)
+		return result;
+
 	const types = {
 		prog:	{ desc: "External Program", 	struct: "xtrn" },
 		event:	{ desc: "External Timed Event", struct: "event" },
@@ -276,7 +361,7 @@ function install(ini_fname)
 				return false;
 			if(item.last === true)
 				break;
-			done = item.done;
+			done = Boolean(item.done);
 		}
 	}
 	
@@ -318,7 +403,7 @@ function install(ini_fname)
 			return false;
 		if(item.last === true)
 			break;
-		done = item.done;
+		done = Boolean(item.done);
 	}
 
 	var services_ini = new File(file_cfgname(system.ctrl_dir, "services.ini"));
@@ -358,69 +443,21 @@ function install(ini_fname)
 			return false;
 		if(item.last === true)
 			break;
-		done = item.done;
+		done = Boolean(item.done);
 	}
 	
-	var list = ini_file.iniGetAllObjects("cmd", "exec:");
-	for (var i = 0; i < list.length && !done; i++) {
-		var item = list[i];
-		var js_args = item.cmd.split(/\s+/);
-		var js_file = js_args.shift();
-		
-		if (file_getext(js_file).toLowerCase() != ".js")
-			return "Only '.js' files may be executed: " + js_file;
-
-		if (item.note)
-			print(item.note);
-		
-		var prompt = "Execute: " + item.cmd;
-		if (item.prompt !== undefined)
-			prompt = item.prompt;
-	
-		if (prompt && !confirm(prompt)) {
-			if (item.required == true)
-				return prompt + " is required to continue";
-			continue;
-		}
-
-		if (item.startup_dir === undefined)
-			item.startup_dir = startup_dir;
-		if (!item.args)
-			item.args = "";
-		var result = js.exec.apply(null
-			,[js_file, item.startup_dir, {}].concat(js_args));
-		if (result !== 0 && item.required)
-			return "Error " + result + " executing " + item.cmd;
-		if(item.last === true)
-			return true;
-		done = item.done;
+	if(done === false) {
+		result = install_exec_cmd(ini_file, "exec:", startup_dir);
+		if(typeof result !== 'boolean')
+			return result;
+		done = !result;
 	}
-	
-	var list = ini_file.iniGetAllObjects("str", "eval:");
-	for (var i = 0; i < list.length && !done; i++) {
-		var item = list[i];
-		if (!item.cmd)
-			item.cmd = item.str; // the str can't contain [], so allow cmd to override
-		var prompt = "Evaluate: " + item.cmd;
-		if (item.prompt !== undefined)
-			prompt = item.prompt;
-		if (prompt && !confirm(prompt)) {
-			if (item.required == true)
-				return prompt + " is required to continue";
-			continue;
-		}
-		try {
-			var result = eval(item.cmd);
-		} catch(e) {
-			return e;
-		}
-		if (!result) {
-			if (item.required == true)
-				return "Truthful evaluation of '" + item.cmd + "' is required to continue";
-		}
-		if(item.last === true)
-			return true;
-		done = item.done;
+
+	if(done === false) {
+		result = install_eval_cmd(ini_file, "eval:", startup_dir);
+		if(typeof result !== 'boolean')
+			return result;
+		done = !result;
 	}
 
 	if (installed) {