diff --git a/exec/badpasswords.js b/exec/badpasswords.js
deleted file mode 100644
index 90e1b8bacce82f387866a53aca9f2cca3a4410bc..0000000000000000000000000000000000000000
--- a/exec/badpasswords.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// $Id: badpasswords.js,v 1.3 2019/01/11 09:38:12 rswindell Exp $
-
-"use strict";
-require("sbbsdefs.js", 'USER_DELETED');
-
-var badpasswords=0;
-var usr = new User;
-var lastuser = system.lastuser;
-for(var u=1; u <= lastuser; u++) {
-	usr.number = u;
-	if(usr == null)
-		continue;
-	if(usr.settings&(USER_DELETED|USER_INACTIVE))
-	    continue;
-	if(!system.trashcan("password",usr.security.password))
-		continue;
-	printf("%-25s: %s\r\n", usr.alias, usr.security.password);
-	badpasswords++;
-}
-
-printf("%u bad passwords found\n", badpasswords);
\ No newline at end of file
diff --git a/exec/sutils.ini b/exec/sutils.ini
new file mode 100644
index 0000000000000000000000000000000000000000..9f63cdc0719f72a7bce1f2129ba99c4179914701
--- /dev/null
+++ b/exec/sutils.ini
@@ -0,0 +1,344 @@
+[categories]
+setup=Setup/Configuration
+user=User Functions
+chat=Chat Functions
+msg=Local Message Functions
+fido=FidoNet Message Functions
+qwk=QWK Network Functions
+files=File Area Functions
+xtrn=External Program Functions
+misc=Misc Functions
+
+;name = short name
+;desc = description
+;cmd = command to execute - relative to exec dir
+;filename = filename  - relative to exec dir (used to check for file exists)
+;showhelpcmd = this command is run to get the help screen
+;promptforargs = 0|1 ask for command line arguments
+;dopause = 0|1 pause after executing program, before going back to menu
+
+[setup:scfg]
+name=scfg
+desc=Synchronet Configurator
+cmd=scfg
+filename=scfg
+dopause=0
+
+[setup:check]
+name=chksetup
+desc=Check Configuration
+cmd=jsexec chksetup.js
+filename=chksetup.js
+dopause=1
+
+[setup:certtool]
+name=certtool
+desc=Certificate Management
+cmd=jsexec certtool.js
+filename=certtool.js
+promptforargs=1
+showhelptext=--csr Generate CSR, with zero or more --domain options||Example: --csr --domain nix.synchro.net --domain gallery.bbsdev.net > csr.pem||--import file Imports a certificate chain||Example: --import /tmp/le.cert
+dopause=1
+
+[setup:slog]
+name=slog
+desc=System/Node Statistics Log Viewer
+cmd=jsexec slog.js
+filename=slog.js
+showhelptext=/REVERSE /TOTALS
+promptforargs=1
+dopause=1
+
+[setup:update]
+name=update
+desc=Synchronet Updater (run after install new versions)
+cmd=jsexec update.js
+filename=update.js
+dopause=1
+
+[user:allusers]
+name=allusers
+desc=User Bulk Editor
+cmd=allusers
+filename=allusers
+showhelpcmd=allusers
+promptforargs=1
+dopause=1
+
+[user:makeuser]
+name=makeuser
+desc=Add user account
+cmd=jsexec makeuser.js
+filename=makeuser.js
+showhelpcmd=jsexec -n makeuser.js
+promptforargs=1
+dopause=1
+
+[msg:chksmb]
+name=chksmb
+desc=Check SMB (Synchronet Message Base)
+cmd=chksmb
+filename=chksmb
+showhelptext=WARNING: All nodes should be DOWN or inactive before using this utility.||This will check that mail and/or message bases are valid.||Example: /sbbs/data/mail /sbbs/data/subs/*.shd
+showhelpcmd=chksmb
+promptforargs=1
+dopause=1
+
+[msg:getnewsgrouplist]
+name=getnewsgrouplist
+desc=Query Newsgroup Server for Newsgroups List
+cmd=jsexec getnewsgrouplist.js
+filename=getnewsgrouplist.js
+showhelptext=This will query a specified NNTP server and query its list of newsgroups, which can be imported into Message Areas.
+showhelpcmd=jsexec -n getnewsgrouplist.js
+promptforargs=1
+dopause=1
+
+[msg:postmsgjs]
+name=postmsg
+desc=Replaces smbutil and allows auto-posting or e-mailing msgs
+cmd=jsexec postmsg.js
+filename=postmsg.js
+showhelpcmd=jsexec -n postmsg.js
+promptforargs=1
+dopause=1
+
+[msg:postpolljs]
+name=postpoll
+cmd=jsexec postpoll.js
+filename=postpoll.js
+desc=Create a poll
+showhelptext=(sub base)
+promptforargs=1
+dopause=1
+
+[msg:scrubmsgs]
+name=scrubmsgs
+desc=Scrub msg headers of invalid ctrl chars (sign of corruption)
+cmd=jsexec scrubmsgs.js
+filename=scrubmsgs.js
+dopause=1
+showhelptext=[-debug] [-scan (only)] FILENAME
+
+[fido:init]
+name=init_fidonet
+desc=Initialize a FidoNet-Style Network
+cmd=jsexec init-fidonet.js
+filename=init-fidonet.js
+dopause=1
+
+[fido:inittickit]
+name=init_tickit
+desc=Initialize TickIT (incoming file areas)
+cmd=jsexec init-tickit.js
+filename=init-tickit.js
+dopause=1
+
+[fido:binkit]
+name=binkit
+desc=BinkIt FidoNet Mailer
+cmd=jsexec binkit.js
+filename=binkit.js
+showhelptext=-p poll any links with Poll=Yes after running a normal outbound scan.||-P poll any links with the Poll=Yes option and DO NOT run an outbound scan.||-l [node] poll the specified node and DO NOT run a normal outbound scan
+promptforargs=1
+dopause=1
+
+[fido:deadechoes]
+name=deadechos
+desc=Check for dead or inactive network echo message areas
+cmd=jsexec deadechoes.js
+filename=deadechoes.js
+dopause=1
+
+[fido:echoareas]
+name=echoareas
+desc=Displays all the FTN-linked sub-boards
+cmd=jsexec echoareas.js
+filename=echoareas.js
+dopause=1
+
+[fido:echocfg]
+name=echocfg
+desc=FidoNet (SBBSEcho) Configuration
+cmd=echocfg
+filename=echocfg
+
+[fido:fmsgdump]
+name=fmsgdump
+desc=Dump FidoNet Stored Messages
+cmd=fmsgdump
+filename=fmsgdump
+showhelpcmd=fmsgdump
+promptforargs=1
+dopause=1
+
+[fido:freqitcfg]
+name=freqitcfg
+desc=Define areas for remote fidonet systems to file req
+cmd=jsexec freqit.js
+filename=freqit.js
+
+[fido:hatchit]
+name=hatchit
+desc=TIC File Processor
+cmd=jsexec hatchit.js
+filename=hatchit.js
+
+[fido:tickit]
+name=tickit
+desc=TIC Processor
+cmd=jsexec tickit.js
+filename=tickit.js
+
+[fido:tickitcfg.js]
+name=tickitcfg
+filename=tickitcfg
+desc=TIC Configuration
+cmd=jsexec tickitcfg.js
+
+[qwk:qnetftp]
+name=qnet-ftp
+desc=Module for doing FTP-based QWKnet call-outs
+cmd=jsexec qnet-ftp.js
+filename=qnet-ftp.js
+showhelptext=hub-id [address] [password] [port]
+promptforargs=1
+dopause=1
+
+[files:delfiles]
+name=delfiles
+desc=Remove files from Synchronet File Database
+cmd=delfiles
+filename=delfiles
+showhelpcmd=delfiles
+promptforargs=1
+dopause=1
+
+[files:dupefind]
+name=dupefind
+desc=File De-duplication Utility
+cmd=dupefind
+filename=dupefind
+showhelpcmd=dupefind
+promptforargs=1
+dopause=1
+
+[files:filelist]
+name=filelist
+desc=Generate Synchronet File Directory Listings
+cmd=filelist
+filename=filelist
+showhelpcmd=filelist
+promptforargs=1
+dopause=1
+
+[misc:dstsedit]
+name=dstsedit
+desc=Sychronet Daily Statistics Editor
+cmd=dstsedit
+filename=dstsedit
+showhelptext=This will modify ctrl/dsts.dab which is the daily statistics.||Pass the path to the ctrl directory to edit system statistics.||Pass the path to a node dir to edit node statistics.
+promptforargs=1
+
+[misc:hexdump]
+name=hexdump
+desc=Utility to hex-dump data files
+cmd=jsexec hexdump.js
+filename=hexdump.js
+promptforargs=1
+showhelptext=Enter filename you wish to dump
+dopause=1
+
+[misc:jsonsvcctrl]
+name=json-svc-ctrl
+desc=Utility to control json-svc
+cmd=jsexec json-svn-ctrl.js
+filename=json-svn-ctrl.js
+
+[misc:md5sum]
+name=md5sum
+desc=Calculate a MD5 sum
+cmd=jsexec md5sum.js
+filename=md5sum.js
+showhelptext=-s STRING||-b BINARY_FILENAME||-t TEXT_FILENAME
+promptforargs=1
+dopause=1
+
+[misc:purgefiles]
+name=purgefiles
+desc=Purge files in given dir older than given # of days
+cmd=jsexec purgefiles.js
+filename=purgefiles.js
+showhelpcmd=jsexec -n purgefiles.js
+promptforargs=1
+dopause=1
+
+[misc:sauce]
+name=sauce
+desc=Util to read/view/modify/add ANSI SAUCE
+cmd=jsexec sauce.js
+filename=sauce.js
+showhelptext=-v (verbose) -E (edit tinfo) -a (add) -r (remove) FILENAME
+promptforargs=1
+dopause=1
+
+[misc:showansi]
+name=showansi
+desc=View .ans, .bin, or .asc file
+cmd=jsexec showansi.js
+filename=showansi.js
+showhelpcmd=jsexec -n showansi.js
+promptforargs=1
+dopause=1
+
+[misc:typeasc]
+name=typeasc
+desc=Convert plain-text (with opt Ctrl-A) to HTML
+cmd=jsexec typeasc.js
+filename=typeasc.js
+showhelpcmd=jsexec -n typeasc.js
+promptforargs=1
+dopause=1
+
+[misc:typehtml]
+name=typehtml
+desc=Convert HTML to plain-text (with opt Ctrl-A codes)
+cmd=jsexec typehtml.js
+filename=typehtml.js
+showhelpcmd=jsexec -n typehtml.js
+promptforargs=1
+dopause=1
+
+[misc:xbimage]
+name=xbimage
+desc=Util to display and create XBin image files
+cmd=jsexec xbimage.js
+filename=xbimage.js
+showhelptext=create|show|demo|info|modify||-fg -bg -delay -flags -cycle -invert -char -title -author -group -normal -v
+promptforargs=1
+dopause=1
+
+[misc:termcapture]
+name=termcapture
+desc=Terminal client capture script for telnet
+cmd=jsexec termcapture.js
+filename=termcapture.js
+showhelpcmd=jsexec -n termcapture.js
+promptforargs=1
+dopause=1
+
+[misc:wget]
+name=wget
+desc=Fetch a file via HTTP
+cmd=jsexec wget.js
+filename=wget.js
+showhelptext=URL FILENAME
+promptforargs=1
+dopause=1
+
+[xtrn:installxtrn]
+name=install-xtrn
+desc=Install Pre-included External Programs (Doors)
+cmd=jsexec install-xtrn.js
+filename=install-xtrn.js
+
diff --git a/exec/sutils.js b/exec/sutils.js
new file mode 100644
index 0000000000000000000000000000000000000000..7f6a1d047496e30864e3964f4162ca7aedb7496e
--- /dev/null
+++ b/exec/sutils.js
@@ -0,0 +1,135 @@
+
+"use strict";
+
+const REVISION = "$Revision: 1.0 $".split(' ')[1];
+
+load("sbbsdefs.js");
+
+print("******************************************************************************");
+print("*                         Synchronet Utilities v" + format("%-28s", REVISION) + " *");
+print("*                 Use Ctrl-C to abort the process if desired                 *");
+print("******************************************************************************");
+
+var file = new File("sutils.ini");
+var categories;
+if(file.open("r")) {
+    categories = file.iniGetObject('categories');
+    file.close();
+}
+
+
+var which, dopause;
+var categoryitems = [];
+//while((!which || which < 1) && !aborted()) {
+while((!which || which < 1) && !aborted()) {
+    dopause = false;
+    print("");
+    print(">>>> Main Menu");
+    print("");
+    if (typeof categories !== "undefined") {
+        var x = 1;
+        for (var catid in categories) {
+            printf("%2d. %s\n", x, categories[catid]);
+            categoryitems[x] = catid;
+            x++;
+        };
+        print("");
+        print(" Q. Quit");
+    }
+    print("");
+    var str = prompt("Which Selection? ");
+    if (str && str.toUpperCase() == 'Q') {
+        exit(0);
+    }
+    which = parseInt(str, 10);
+    if (which && (typeof categoryitems[which] !== "undefined")) {
+        docategorymenu(categoryitems[which], categories[categoryitems[which]]);
+    }
+
+    which = -1; // redisplay menu
+}
+
+function docategorymenu(catid, catname) {
+    var which, utilitemids;
+    var menuitems = [];
+    var utilitems = [];
+
+    if (file.open("r")) {
+        utilitemids = file.iniGetSections(catid);
+    }
+
+    for (var i in utilitemids) {
+        utilitems[utilitemids[i]] = file.iniGetObject(utilitemids[i]);
+    }
+
+    file.close();
+
+    while ((!which || which < 1) && !aborted()) {
+        print("");
+        print(">>>> " + catname);
+        print("");
+
+        if (typeof utilitems !== "undefined") {
+            var x = 1;
+            for (var iniid in utilitems) {
+                if (file_exists(js.exec_dir + utilitems[iniid].filename)) {
+                    printf("%2d. %-20s %s\n", x, utilitems[iniid].name, utilitems[iniid].desc);
+                    menuitems[x] = iniid;
+                    x++;
+                }
+            }
+            print("");
+            print(" Q. Return to Prior Menu");
+        }
+
+        print("");
+        var str = prompt("Which Selection? ");
+        if (str && str.toUpperCase() == 'Q') {
+            return;
+        }
+        which = parseInt(str, 10);
+        if (which && (typeof utilitems[menuitems[which]] !== "undefined")) {
+            var program = utilitems[menuitems[which]];
+
+            if (program.showhelpcmd) {
+                print("");
+                system.exec(js.exec_dir + program.showhelpcmd);
+                print("");
+            }
+            if (program.showhelptext) {
+                print("");
+                print("Program Help: ");
+                var helptexts = program.showhelptext.split("||");
+                for (var h in helptexts) {
+                    print(helptexts[h]);
+                }
+                print("");
+            }
+            if (program.promptforargs) {
+                var consoleargs = prompt("Enter command line arguments (Q to quit): ");
+                if (!consoleargs || (consoleargs && consoleargs.toUpperCase() != 'Q')) {
+                    print("Running " + js.exec_dir + program.cmd + " " + consoleargs);
+                    system.exec(js.exec_dir + program.cmd + " " + consoleargs);
+                }
+            } else {
+                print("Running " + js.exec_dir + program.cmd);
+                system.exec(js.exec_dir + program.cmd);
+            }
+            // pause so the redisplayed menu doesn't wipe out the prior program's output
+            if (program.dopause) {
+                var cont = prompt("Press ENTER to Continue");
+            }
+        }
+
+        which = -1; // redisplay menu4
+    }
+
+}
+
+function aborted()
+{
+    if (js.terminated || (js.global.console && console.aborted)) {
+        exit(1);
+    }
+    return false;
+}