diff --git a/Resources/UK/CmdHelp b/Resources/UK/CmdHelp
index 38a105bf5e6399367c2e1c87e9954f48ec15dc2c..0f89259bba7213866e12d193e96858e5c0535c7b 100644
Binary files a/Resources/UK/CmdHelp and b/Resources/UK/CmdHelp differ
diff --git a/Resources/UK/Messages b/Resources/UK/Messages
index 73b35252360daeb522f5b6188d79636afb52b095..5936e4f319f5f77fa8be0ffe6ee0310a9a6bada1 100644
--- a/Resources/UK/Messages
+++ b/Resources/UK/Messages
@@ -2,3 +2,4 @@
 NoFS:File server '%0' not found
 BadFile:Corrupt CMOS file
 BadVer:CMOS file is for a different OS version
+WantStr:System variable must contain a string
diff --git a/c/main b/c/main
index 6df64ce263366f9d53a025a0bea458b48bea3daf..f07755cb46034028a82ed67a70bcd3668da2081b 100644
--- a/c/main
+++ b/c/main
@@ -28,6 +28,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdbool.h>
+#include <ctype.h>
 #include "kernel.h"
 
 /*From exports*/
@@ -134,6 +135,22 @@ static int get_nvm_size(void)
    return rg.r[0]?osbyte_CONFIGURE_CHECKSUM+1:rg.r[1];
 }
 
+static int stricmp(const char *first, const char *second)
+
+{  for (;;)
+   {  unsigned int a = *(const unsigned char *) first++;
+      unsigned int b = *(const unsigned char *) second++;
+
+      if (a == 0) return -b;
+      if (a != b)
+      {  unsigned int c = (unsigned int) tolower(a);
+         unsigned int d = (unsigned int) tolower(b);
+         signed int result = c - d;
+         if (result != 0) return result;
+      }
+   }
+}
+
 /*------------------------------------------------------------------------*/
 static os_error *Add_App (const char *tail)
 
@@ -858,7 +875,7 @@ finish:
 /*------------------------------------------------------------------------*/
 static os_error *Safe_Logon (const char *tail)
 
-{  struct safelogon_args {char *fs, *user, *password; char argb [os_CLI_LIMIT_RO4 + 1];} *argl;
+{  struct {char *fs, *user, *password; char argb [os_CLI_LIMIT_RO4 + 1];} argl;
    int station, net, context, collate;
    fileswitch_fs_no temp_fs;
    netfs_file_server_context file_server_context;
@@ -868,10 +885,6 @@ static os_error *Safe_Logon (const char *tail)
 
    tracef ("Safe_Logon\n");
 
-   argl = malloc(sizeof(struct safelogon_args));
-   if (argl == 0)
-       goto finish;
-
    /*Fix bug: check that the temporary filing system is indeed NetFS. JRC
       16th Feb 1995*/
    if ((error = xosargs_read_temporary_fs (&temp_fs)) != NULL)
@@ -879,23 +892,23 @@ static os_error *Safe_Logon (const char *tail)
 
    if (temp_fs == fileswitch_FS_NUMBER_NETFS)
    {  if ((error = xos_read_args ("fs/a,user/a,password", tail,
-            (char *) argl, sizeof *argl, NULL)) != NULL)
+            (char *) &argl, sizeof argl, NULL)) != NULL)
          goto finish;
 
-      tracef ("fs \"%s\"\n" _ argl->fs);
-      tracef ("user \"%s\"\n" _ argl->user);
+      tracef ("fs \"%s\"\n" _ argl.fs);
+      tracef ("user \"%s\"\n" _ argl.user);
       tracef ("password \"%s\"\n" _
-            argl->password != NULL? argl->password: "NULL");
+            argl.password != NULL? argl.password: "NULL");
 
       /*If the first character of the fs is not ':', we steer clear.*/
-      if (argl->fs [0] == ':')
-      {  argl->fs++;
+      if (argl.fs [0] == ':')
+      {  argl.fs++;
 
          /*Station number or name?*/
-         named_fs = xeconet_read_station_number (argl->fs, NULL, &station,
+         named_fs = xeconet_read_station_number (argl.fs, NULL, &station,
                &net) != NULL;
          if (named_fs)
-            tracef ("fs is named \"%s\"\n" _ argl->fs);
+            tracef ("fs is named \"%s\"\n" _ argl.fs);
          else
             tracef ("fs is numbered %d.%d\n" _ net _ station);
 
@@ -920,7 +933,7 @@ static os_error *Safe_Logon (const char *tail)
                   file_server_context.user_name);
 
             if (named_fs)
-            {  if ((error = xterritory_collate (territory_CURRENT, argl->fs,
+            {  if ((error = xterritory_collate (territory_CURRENT, argl.fs,
                      file_server_context.disc_name, territory_IGNORE_CASE,
                      &collate)) != NULL)
                   goto finish;
@@ -932,7 +945,7 @@ static os_error *Safe_Logon (const char *tail)
                   file_server_context.net_no == net;
 
             if (found_fs)
-            {  if ((error = xterritory_collate (territory_CURRENT, argl->user,
+            {  if ((error = xterritory_collate (territory_CURRENT, argl.user,
                      file_server_context.user_name, territory_IGNORE_CASE,
                      &collate)) != NULL)
                   goto finish;
@@ -944,7 +957,7 @@ static os_error *Safe_Logon (const char *tail)
                      on? JRC 19th Dec 1994*/
                   netfs_read_user_info info;
 
-                  sprintf (info AS request.user_name, "%s\r", argl->user);
+                  sprintf (info AS request.user_name, "%s\r", argl.user);
 
                   tracef ("doing fs op ...\n");
                   if ((error = xnetfs_do_fs_op_to_given_fs
@@ -963,7 +976,6 @@ static os_error *Safe_Logon (const char *tail)
    }  }  }  }  }
 
 finish:
-   free(argl);
    /*Ignore errors up to this point. If there have been any, it's a safe bet
       that |logon_required| is TRUE.*/
    if (error != NULL)
@@ -981,6 +993,111 @@ finish:
    return error;
 }
 /*------------------------------------------------------------------------*/
+static os_error *AppPrepRem_Path (const char *tail, int which)
+
+{  struct {char *varname, *element; char argb [os_CLI_LIMIT_RO4 + 1];} argl;
+   os_error *error = NULL;
+   os_var_type type;
+   size_t existing, extra, len;
+   char *comma, *next, *editpath = NULL;
+
+   tracef ("App|Prep|RemPath subreason %d\n" _ which);
+
+   if ((error = xos_read_args ("v/a,e/a", tail, (char *) &argl,
+         sizeof argl, NULL)) != NULL)
+      goto finish;
+
+   /*Read the existing path variable, it could be any size*/
+   xos_read_var_val_size (argl.varname, 0, os_VARTYPE_STRING, (int *) &existing, NULL, &type);
+   if (existing == 0)
+   {  /*Be silent on remove of non-existent variables*/
+      if (which == main_REM_PATH) goto finish;
+      tracef ("Variable '%s' doesn't exist, acting as create\n" _ argl.varname);
+      which = main_APP_PATH;
+   }
+   else
+   {  if (type != os_VARTYPE_STRING)
+      {  error = main_error_lookup (ErrorBase_BootCommands + 3, "WantStr");
+         goto finish;
+      }
+      /*Length of contents of existing path variable*/
+      existing = ~existing;
+   }
+
+   /*For append or prepend, overallocate to allow space for the insertion*/
+   extra = (which != main_REM_PATH) ? (strlen (argl.element) + 1 /*Possible comma*/)
+                                    : 0;
+   editpath = (char *) malloc (existing + 1 /*Terminator*/ + extra);
+   if (editpath == NULL)
+   {  error = main_error_lookup (ErrorBase_BootCommands + 2, "NoMem");
+      goto finish;
+   }
+   if (existing)
+   {  error = xos_read_var_val (argl.varname, editpath, existing, 0, os_VARTYPE_STRING,
+                                NULL, NULL, NULL);
+      if (error != NULL) /*Where did it go?!*/
+         goto finish;
+   }
+   editpath [existing] = '\0';
+
+   /*Scan the existing path elements, deleting matches with the given element*/
+   next = editpath;
+   while (*next)
+   {  comma = strchr (next, ',');
+      if (comma) *comma = '\0'; /*Isolate that portion*/
+      len = strlen (next);
+      if (comma) len++; /*Comma counted as part of the element length*/
+      if (stricmp (next, argl.element) == 0)
+      {  /*Delete it*/
+         tracef ("  element '%s' matches subject, deleting\n" _ next);
+         if (!comma && (next > editpath))
+         {  /*Removing the last element, just trim with a terminator*/
+            next [-1] = '\0';
+            break;
+         }
+         memmove (next, &next [len], strlen (&next [len]) + 1 /*Terminator*/);
+      }
+      else
+      {  /*Step next*/
+         tracef ("  element '%s'\n" _ next);
+         next = next + len;
+         if (comma) *comma = ','; /*Restore the comma*/
+      }
+   }
+   tracef ("After removing duplicates '%s'\n" _ editpath);
+
+   if (which != main_REM_PATH)
+   {  /*Firm up on that possible comma now duplicates are removed*/
+      len = strlen (editpath);
+      if (len == 0) which = main_APP_PATH;
+
+      /*Append or prepend (any remove was done implicitly)*/
+      if (which == main_APP_PATH)
+      {  if (len) strcat (editpath, ",");
+         strcat (editpath, argl.element);
+      }
+      else
+      {  memmove (&editpath [extra], editpath, strlen (editpath));
+         strcpy (editpath, argl.element);
+         editpath [extra - 1] = ',';
+      }
+   }
+   tracef ("After edits '%s'\n" _ editpath);
+   error = xos_set_var_val (argl.varname, (byte *) editpath, strlen (editpath),
+                            0, os_VARTYPE_STRING, NULL, NULL);
+
+finish:
+   free (editpath);
+   tracef ("App|Prep|RemPath DONE\n");
+   if (error != NULL)
+      tracef ("with error %s\n" _ error->errmess);
+   return error;
+}
+
+static os_error *App_Path (const char *tail)  { return AppPrepRem_Path (tail, main_APP_PATH); }
+static os_error *Prep_Path (const char *tail) { return AppPrepRem_Path (tail, main_PREP_PATH); }
+static os_error *Rem_Path (const char *tail)  { return AppPrepRem_Path (tail, main_REM_PATH); }
+/*------------------------------------------------------------------------*/
 static os_error *Shrink_RMA(const char *tail)
 
 {
@@ -1097,6 +1214,9 @@ _kernel_oserror *main_initialise (char *tail, int podule_base,
    Commands [main_SAVE_CMOS]  = &Save_CMOS;
    Commands [main_REPEAT]     = &Repeat;
    Commands [main_SAFE_LOGON] = &Safe_Logon;
+   Commands [main_APP_PATH]   = &App_Path;
+   Commands [main_PREP_PATH]  = &Prep_Path;
+   Commands [main_REM_PATH]   = &Rem_Path;
    Commands [main_FREE_POOL]  = &Free_Pool;
    Commands [main_SHRINK_RMA] = &Shrink_RMA;
    Commands [main_ADD_TO_RMA] = &Add_To_RMA;
diff --git a/cmhg/header b/cmhg/header
index 3732200c25df424e2c2d9550fa0ff43e67f85c61..6b741f4ade96568e698e7743af0364304546ac13 100644
--- a/cmhg/header
+++ b/cmhg/header
@@ -68,6 +68,21 @@ command-keyword-table: main_command
       add-syntax:,
       invalid-syntax: "Syntax:	*SafeLogon [[:]<station number>|:<File server name>] <user name> [[:<CR>]<Password>]",
       help-text: "*SafeLogon initialises the current (or given) file server for your use, except that if you are already logged on, it does nothing\n"),
+   AppPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      add-syntax:,
+      invalid-syntax: "Syntax:	*AppPath <variable> <path element>",
+      help-text: "*AppPath appends a path element to a path variable, ensuring there are no duplicates\n"),
+   PrepPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      add-syntax:,
+      invalid-syntax: "Syntax:	*PrepPath <variable> <path element>",
+      help-text: "*PrepPath prepends a path element to a path variable, ensuring there are no duplicates\n"),
+   RemPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      add-syntax:,
+      invalid-syntax: "Syntax:	*RemPath <variable> <path element>",
+      help-text: "*RemPath removes a path element from a path variable\n"),
    FreePool( min-args: 0, max-args: 0,
       gstrans-map: 0,
       add-syntax:,
@@ -139,6 +154,21 @@ command-keyword-table: main_command
       international:,
       invalid-syntax: "SafeLogonSyntax",
       help-text: "SafeLogonHelp"),
+   AppPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      international:,
+      invalid-syntax: "AppPathSyntax",
+      help-text: "AppPathHelp"),
+   PrepPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      international:,
+      invalid-syntax: "PrepPathSyntax",
+      help-text: "PrepPathHelp"),
+   RemPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      international:,
+      invalid-syntax: "RemPathSyntax",
+      help-text: "RemPathHelp"),
    FreePool( min-args: 0, max-args: 0,
       gstrans-map: 0,
       international:,
diff --git a/h/main b/h/main
index 78fa9572eff8bfafcf1789238242d5e935ecc9f4..711ec718dd3378d5ad140a14209d9aebb07127d6 100644
--- a/h/main
+++ b/h/main
@@ -39,12 +39,15 @@
 #define main_SAVE_CMOS     5
 #define main_REPEAT        6
 #define main_SAFE_LOGON    7
-#define main_FREE_POOL     8
-#define main_SHRINK_RMA    9
-#define main_ADD_TO_RMA    10
-#define main_APP_SLOT      11
-#define main_X             12
-#define main_COMMAND_COUNT 13
+#define main_APP_PATH      8
+#define main_PREP_PATH     9
+#define main_REM_PATH      10
+#define main_FREE_POOL     11
+#define main_SHRINK_RMA    12
+#define main_ADD_TO_RMA    13
+#define main_APP_SLOT      14
+#define main_X             15
+#define main_COMMAND_COUNT 16
 
 #define X_ENVVAR           "X$Error"
 #define RES_APP_PATH       "Resources:$.Apps"