diff --git a/EditPath.md b/EditPath.md deleted file mode 100644 index 29a716c..0000000 --- a/EditPath.md +++ /dev/null @@ -1,118 +0,0 @@ -# EditPath - -EditPath is a Windows console (text-based, command-line) program for managing the system Path and user Path. - -# Author - -Bill Stewart - bstewart at iname dot com - -# License - -EditPath.exe is covered by the GNU Lesser Public License (LPGL). See the file `LICENSE` for details. - -# Download - -https://github.com/Bill-Stewart/PathMgr/releases/ - -# Background - -The system Path is found in the following location in the Windows registry: - -Root: `HKEY_LOCAL_MACHINE` -Subkey: `SYSTEM\CurrentControlSet\Control\Session Manager\Environment` -Value name: `Path` - -The current user Path is found in the following location in the registry: - -Root: `HKEY_CURRENT_USER` -Subkey: `Environment` -Value name: `Path` - -In both cases, the `Path` value is (or should be) the registry type `REG_EXPAND_SZ`, which means that it is a string that can contain values surrounded by `%` characters that Windows will automatically expand to environment variable values. (For example, `%SystemRoot%` will be expanded to `C:\Windows` on most systems.) - -The `Path` value contains a `;`-delimited list of directory names that the system should search for executables, library files, scripts, etc. Windows appends the content of the current user Path to the system Path and expands the environment variable references. The resulting string is set as the `Path` environment variable for processes. - -EditPath provides a command-line interface for managing the `Path` value in the system location (in `HKEY_LOCAL_MACHINE`) and the current user location (in `HKEY_CURRENT_USER`). - -# Usage - -The following describes the command-line usage for the program. Parameters are case-sensitive. - -**EditPath** [_options_] _type_ _action_ - -You must specify only one of the following _type_ parameters: - -| _type_ | Abbreviation | Description -| ------- | ------------ | ----------- -| **--system** | **-s** | Specifies the system Path -| **--user** | **-u** | Specifies the user Path - -You must specify only one of the following _action_ parameters: - -| _action_ | Abbreviation | Description -| -------- | ------------ | ----------- -| **--list** | **-l** | Lists directories in Path -| **--test "**_dirname_**"** | **-t "**_dirname_**"** | Tests if directory exists in Path -| **--add "**_dirname_**"** | **-a "**_dirname_**"** | Adds directory to Path -| **--remove "**_dirname_**"** | **-r "**_dirname_**"** | Removes directory from Path - -The following parameters are optional: - -| _options_ | Abbreviation | Description -| --------- | ------------ | ----------- -| **--quiet** | **-q** | Suppresses result messages -| **--expand** | **-x** | Expands environment variables (**--list** only) -| **--beginning** | **-b** | Adds to beginning of Path (**--add** only) - -# Exit Codes - -The following table lists typical exit codes when not using **--test** (**-t**). - -| Exit Code | Description -| --------- | ----------- -| 0 | No errors -| 2 | The Path value is not present in the registry -| 3 | The specified directory does not exist in the Path -| 5 | Access is denied -| 87 | Incorrect parameter(s) -| 183 | The specified directory already exists in the Path - -The following table lists typical exit codes when using **--test** (**-t**). - -| Exit Code | Description -| --------- | ----------- -| 1 | The specified directory exists in the unexpanded Path -| 2 | The specified directory exists in the expanded Path -| 3 | The specified directory does not exist in the Path - -# Remarks - -* Anything on the command line after **--test**, **--add**, or **--remove** is considered to be the argument for the parameter. To avoid ambiguity, specify the _action_ parameter last on the command line. - -* Uexpanded vs. expanded refers to whether the environment variable references (i.e., names between `%` characters) are expanded after retrieving the Path value from the registry. For example, `%SystemRoot%` is unexpanded but `C:\Windows` is expanded. - -* The **--add** (**-a**) parameter checks whether the specified directory exists in both the unexpanded and expanded copies of the Path before adding the directory. For example, if the environment variable `TESTAPP` is set to `C:\TestApp` and `%TESTAPP%` is in the Path, specifying `--add C:\TestApp` will return exit code 183 (i.e., the directory already exists in the Path) because `%TESTAPP%` expands to `C:\TestApp`. - -* The **--remove** (**-r**) parameter does not expand environment variable references. For example, if the environment variable `TESTAPP` is set to `C:\TestApp` and `%TESTAPP%` is in the Path, specifying `--remove "C:\TestApp"` will return exit code 3 (i.e., the directory does not exist in the Path) because **--remove** does not expand `%TESTAPP%` to `C:\TestApp`. For the command to succeed, you would have to specify `--remove "%TESTAPP%"` instead. - -* The program will exit with error code 87 if a parameter (or an argument to a parameter) is missing or not valid, if mutually exclusive parameters are specified, etc. - -* The program will exit with error code 5 if the current user does not have permission to update the Path value in the registry (for example, if you try to update the system Path using a standard user account or an unelevated administrator account). - -# Examples - -1. `EditPath --expand --system --list` - - This command outputs the directories in the system Path, with environment variables expanded. You can also write this command as `EditPath -x -s -l`. - -2. `EditPath --user --add "%LOCALAPPDATA%\Programs\MyApp"` - - Adds the specified directory name to the user Path. - -3. `EditPath -s -r "C:\Program Files\MyApp\bin"` - - Removes the specified directory from the system Path. - -4. `EditPath -s --test "C:\Program Files (x86)\MyApp\bin"` - - Returns an exit code of 3 if the specified directory is not in the system Path, 1 if the specified directory is in the unexpanded copy of the system Path, or 2 if the specified directory is in the expanded copy of the system Path. diff --git a/EditPath.pp b/EditPath.pp deleted file mode 100644 index 79022c6..0000000 --- a/EditPath.pp +++ /dev/null @@ -1,386 +0,0 @@ -{ Copyright (C) 2004-2023 by Bill Stewart (bstewart at iname.com) - - This program is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the Free - Software Foundation; either version 3 of the License, or (at your option) any - later version. - - This program is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more - details. - - You should have received a copy of the GNU Lesser General Public License - along with this program. If not, see https://www.gnu.org/licenses/. - -} - -{$MODE OBJFPC} -{$H+} -{$R *.res} -{$APPTYPE CONSOLE} - -program EditPath; - -uses - getopts, - Windows, - wsPathMgr, - wsUtilMsg, - wsUtilStr; - -type - TCommandLine = object - ErrorCode: Word; - ErrorMessage: UnicodeString; - ArgHelp: Boolean; // --help/-h - ArgQuiet: Boolean; // --quiet/-q - ArgAddToPath: UnicodeString; // --add/-a - ArgAddToBeginning: Boolean; // --beginning/-b - ArgList: Boolean; // --list/-l - ArgRemoveFromPath: UnicodeString; // --remove/-r - ArgPathSystem: Boolean; // --system/-s - ArgTest: UnicodeString; // --test/-t - ArgPathUser: Boolean; // --user/-u - ArgExpand: Boolean; // --expand/-x - function CountBits(N: DWORD): DWORD; - procedure Parse(); - end; - -var - CommandLine: TCommandLine; - PathType: TPathType; - PathAddType: TPathAddType; - PathFindType: TPathFindType; - OutputStr: UnicodeString; - -procedure Usage(); -const - NEWLINE: UnicodeString = #13 + #10; -var - UsageText: UnicodeString; -begin - UsageText := 'EditPath 4.0 - Copyright (C) 2004-2023 by Bill Stewart (bstewart at iname.com)' + NEWLINE - + NEWLINE - + 'This is free software and comes with ABSOLUTELY NO WARRANTY.' + NEWLINE - + NEWLINE - + 'Usage: EditPath [] ' + NEWLINE - + NEWLINE - + 'You must specify one of each parameter from and (see below).' + NEWLINE - + 'Other are optional. Parameters are case-sensitive.' + NEWLINE - + NEWLINE - + ' Abbreviation Description' + NEWLINE - + '-------------------------------------------------' + NEWLINE - + '--system -s Specifies the system Path' + NEWLINE - + '--user -u Specifies the user Path' + NEWLINE - + NEWLINE - + ' Abbreviation Description' + NEWLINE - + '-------------------------------------------------------------------' + NEWLINE - + '--list -l Lists directories in Path' + NEWLINE - + '--test "dirname" -t "dirname" Tests if directory exists in Path' + NEWLINE - + '--add "dirname" -a "dirname" Adds directory to Path' + NEWLINE - + '--remove "dirname" -r "dirname" Removes directory from Path' + NEWLINE - + NEWLINE - + ' Abbreviation Description' + NEWLINE - + '----------------------------------------------------------------------' + NEWLINE - + '--quiet -q Suppresses result messages' + NEWLINE - + '--expand -x Expands environment variables (--list only)' + NEWLINE - + '--beginning -b Adds to beginning of Path (--add only)' + NEWLINE - + NEWLINE - + 'Anything on the command line after --test, --add, or --remove is' + NEWLINE - + 'considered to be the argument for the parameter. To avoid ambiguity,' + NEWLINE - + 'specify the parameter last on the command line.' + NEWLINE - + NEWLINE - + 'Typical exit codes when not using --test (-t):' + NEWLINE - + '0 - No errors' + NEWLINE - + '2 - The Path value is not present in the registry' + NEWLINE - + '3 - The specified directory does not exist in the Path' + NEWLINE - + '5 - Access is denied' + NEWLINE - + '87 - Incorrect parameter(s)' + NEWLINE - + '183 - The specified directory already exists in the Path' + NEWLINE - + NEWLINE - + 'Typical exit codes when using --test (-t):' + NEWLINE - + '1 - The specified directory exists in the unexpanded Path' + NEWLINE - + '2 - The specified directory exists in the expanded Path' + NEWLINE - + '3 - The specified directory does not exist in the Path'; - WriteLn(UsageText); -end; - -function TCommandLine.CountBits(N: DWORD): DWORD; -var - Count: DWORD = 0; -begin - // Counts the number of bits set in N - while N <> 0 do - begin - Count := Count + (N and 1); - N := N shr 1; - end; - result := Count; -end; - -procedure TCommandLine.Parse(); -const - REQ_ARGS_ACTION_NONE = 0; - REQ_ARGS_ACTION_ADD = 1; - REQ_ARGS_ACTION_LIST = 2; - REQ_ARGS_ACTION_REMOVE = 4; - REQ_ARGS_ACTION_TEST = 8; - REQ_ARGS_PATHTYPE_NONE = 0; - REQ_ARGS_PATHTYPE_SYSTEM = 1; - REQ_ARGS_PATHTYPE_USER = 2; -var - LongOpts: array[1..11] of TOption; - Opt: Char; - I: LongInt; - ReqArgsAction, ReqArgsPathType: DWORD; -begin - // Set up array of options; requires final option with empty name; - // set Value member to specify short option match for GetLongOps - with LongOpts[1] do - begin - Name := 'add'; - Has_arg := Required_Argument; - Flag := nil; - Value := 'a'; - end; - with LongOpts[2] do - begin - Name := 'beginning'; - Has_arg := No_Argument; - Flag := nil; - Value := 'b'; - end; - with LongOpts[3] do - begin - Name := 'help'; - Has_arg := No_Argument; - Flag := nil; - Value := 'h'; - end; - with LongOpts[4] do - begin - Name := 'list'; - Has_arg := No_Argument; - Flag := nil; - Value := 'l'; - end; - with LongOpts[5] do - begin - Name := 'quiet'; - Has_arg := No_Argument; - Flag := nil; - Value := 'q'; - end; - with LongOpts[6] do - begin - Name := 'remove'; - Has_arg := Required_Argument; - Flag := nil; - Value := 'r'; - end; - with LongOpts[7] do - begin - Name := 'system'; - Has_arg := No_Argument; - Flag := nil; - Value := 's'; - end; - with LongOpts[8] do - begin - Name := 'test'; - Has_arg := Required_Argument; - Flag := nil; - Value := 't'; - end; - with LongOpts[9] do - begin - Name := 'user'; - Has_arg := No_Argument; - Flag := nil; - Value := 'u'; - end; - with LongOpts[10] do - begin - Name := 'expand'; - Has_arg := No_Argument; - Flag := nil; - Value := 'x'; - end; - with LongOpts[11] do - begin - Name := ''; - Has_arg := No_Argument; - Flag := nil; - Value := #0; - end; - // Initialize defaults - ErrorCode := 0; - ErrorMessage := ''; - ArgAddToPath := ''; // --add/-a - ArgAddToBeginning := false; // --beginning/-b - ArgHelp := false; // --help/-h - ArgList := false; // --list/-l - ArgQuiet := false; // --quiet/-q - ArgRemoveFromPath := ''; // --remove/-r - ArgPathSystem := false; // --system/-s - ArgTest := ''; // --test/-t - ArgPathUser := false; // --user/-u - ArgExpand := false; // --expand/-x - ReqArgsAction := REQ_ARGS_ACTION_NONE; - ReqArgsPathType := REQ_ARGS_PATHTYPE_NONE; - OptErr := false; // no error outputs from getopts - repeat - Opt := GetLongOpts('a:bhlqr:st:ux', @LongOpts, I); - case Opt of - 'a': - begin - ReqArgsAction := ReqArgsAction or REQ_ARGS_ACTION_ADD; - ArgAddToPath := StringToUnicodeString(OptArg, CP_ACP); - end; - 'b': ArgAddToBeginning := true; - 'h': ArgHelp := true; - 'l': - begin - ReqArgsAction := ReqArgsAction or REQ_ARGS_ACTION_LIST; - ArgList := true; - end; - 'q': ArgQuiet := true; - 'r': - begin - ReqArgsAction := ReqArgsAction or REQ_ARGS_ACTION_REMOVE; - ArgRemoveFromPath := StringToUnicodeString(OptArg, CP_ACP); - end; - 's': - begin - ReqArgsPathType := ReqArgsPathType or REQ_ARGS_PATHTYPE_SYSTEM; - ArgPathSystem := true; - end; - 't': - begin - ReqArgsAction := ReqArgsAction or REQ_ARGS_ACTION_TEST; - ArgTest := StringToUnicodeString(OptArg, CP_ACP); - end; - 'u': - begin - ReqArgsPathType := ReqArgsPathType or REQ_ARGS_PATHTYPE_USER; - ArgPathUser := true; - end; - 'x': ArgExpand := true; - '?': - begin - ErrorCode := ERROR_INVALID_PARAMETER; - ErrorMessage := 'Incorrect parameter(s). Use --help (-h) for usage.'; - end; - end; //case Opt - until Opt = EndOfOptions; - if ErrorCode = 0 then - begin - if (ReqArgsAction = REQ_ARGS_ACTION_NONE) or (ReqArgsPathType = REQ_ARGS_PATHTYPE_NONE) then - begin - ErrorCode := ERROR_INVALID_PARAMETER; - ErrorMessage := 'Required parameter(s) missing. Specify --help (-h) for usage.'; - end - else if (CountBits(ReqArgsAction) > 1) or (CountBits(ReqArgsPathType) > 1) then - begin - ErrorCode := ERROR_INVALID_PARAMETER; - ErrorMessage := 'Mutually exclusive parameter(s) specified. Specify --help (-h) for usage.'; - end; - end; -end; - -function TranslateErrorCode(const Code: DWORD): UnicodeString; -begin - case Code of - ERROR_FILE_NOT_FOUND: - result := 'The Path value is not present in the registry. (2)'; - ERROR_PATH_NOT_FOUND: - result := 'The specified directory does not exist in the Path. (3)'; - ERROR_ALREADY_EXISTS: - result := 'The specified directory already exists in the Path. (183)'; - else - result := GetWindowsMessage(Code, true); - end; //case -end; - -procedure WriteOutput(const S: UnicodeString); -begin - if not CommandLine.ArgQuiet then - if ExitCode <> 0 then - WriteLn(ErrOutput, S) - else - WriteLn(S); -end; - -begin - // Parse the command line using getopts - CommandLine.Parse(); - - // --help/-h or /? - if CommandLine.ArgHelp or (ParamStr(1) = '/?') then - begin - Usage(); - exit(); - end; - - ExitCode := CommandLine.ErrorCode; - if ExitCode <> 0 then - begin - WriteLn(CommandLine.ErrorMessage); - exit(); - end; - - PathType := UserPath; - if CommandLine.ArgPathSystem then - PathType := SystemPath - else if CommandLine.ArgPathUser then - PathType := UserPath; - - if CommandLine.ArgAddToPath <> '' then - begin - if CommandLine.ArgAddToBeginning then - PathAddType := AppendPathToDir - else - PathAddType := AppendDirToPath; - ExitCode := wsAddDirToPath(CommandLine.ArgAddToPath, PathType, PathAddType); - WriteOutput(TranslateErrorCode(ExitCode)); - end - else if CommandLine.ArgList then - begin - ExitCode := wsGetPath(PathType, CommandLine.ArgExpand, OutputStr); - if (ExitCode = 0) and (OutputStr <> '') then - WriteLn(OutputStr) - else if ExitCode <> 0 then - WriteOutput(TranslateErrorCode(ExitCode)); - end - else if CommandLine.ArgRemoveFromPath <> '' then - begin - ExitCode := wsRemoveDirFromPath(CommandLine.ArgRemoveFromPath, PathType); - WriteOutput(TranslateErrorCode(ExitCode)); - end - else if CommandLine.ArgTest <> '' then - begin - OutputStr := ''; - ExitCode := wsIsDirInPath(CommandLine.ArgTest, PathType, PathFindType); - if ExitCode = 0 then - begin - case PathFindType of - FoundInUnexpandedPath: - begin - ExitCode := LongInt(FoundInUnexpandedPath); - OutputStr := 'The specified directory exists in the unexpanded Path. (1)'; - end; - FoundInExpandedPath: - begin - ExitCode := LongInt(FoundInExpandedPath); - OutputStr := 'The specified directory exists in the expanded Path. (2)'; - end; - end; //case - end; - if OutputStr <> '' then - WriteOutput(OutputStr) - else - WriteOutput(TranslateErrorCode(ExitCode)); - end; - -end. diff --git a/EditPath.rc b/EditPath.rc deleted file mode 100644 index c071c8d..0000000 --- a/EditPath.rc +++ /dev/null @@ -1,29 +0,0 @@ -1 VERSIONINFO -FILEVERSION 4,0,4,0 -PRODUCTVERSION 4,0,4,0 -FILEOS 0x4 -FILETYPE 1 -{ - BLOCK "StringFileInfo" - { - BLOCK "040904E4" - { - VALUE "CompanyName", "" - VALUE "FileDescription", "EditPath.exe" - VALUE "FileVersion", "4.0.4.0" - VALUE "InternalName", "EditPath.exe" - VALUE "LegalCopyright", "Copyright 2004-2023 by Bill Stewart (bstewart at iname.com)" - VALUE "OriginalFilename", "EditPath.exe" - VALUE "ProductName", "" - VALUE "ProductVersion", "4.0.4.0" - } - } - - BLOCK "VarFileInfo" - { - VALUE "Translation", 0x0409, 0x04E4 - } -} - -APPICON ICON "EditPath.ico" -MAINICON ICON "EditPath.ico" diff --git a/PathMan.ico b/PathMan.ico new file mode 100644 index 0000000..2801cae Binary files /dev/null and b/PathMan.ico differ diff --git a/EditPath.iss b/PathMan.iss similarity index 92% rename from EditPath.iss rename to PathMan.iss index b70d571..62fd363 100644 --- a/EditPath.iss +++ b/PathMan.iss @@ -1,4 +1,4 @@ -; Copyright (C) 2021-2023 by Bill Stewart (bstewart at iname.com) +; Copyright (C) 2021-2024 by Bill Stewart (bstewart at iname.com) ; ; This program is free software; you can redistribute it and/or modify it under ; the terms of the GNU Lesser General Public License as published by the Free @@ -32,14 +32,15 @@ #endif [Setup] -AppId={{A17D2D05-C729-4F2A-9CC7-E04906C5A842} -AppName=EditPath -AppVersion=4.0.4.0 +AppId={{82425B5C-8B45-4F8C-9830-3D8E2DB64581} +AppName=PathMan +AppVersion=2.0.0.0 UsePreviousAppDir=false -DefaultDirName={autopf}\EditPath +DefaultDirName={autopf}\PathMan +ChangesEnvironment=yes Uninstallable=true OutputDir=. -OutputBaseFilename=EditPath_Setup +OutputBaseFilename=PathMan-Setup ArchitecturesInstallIn64BitMode=x64 PrivilegesRequired=none PrivilegesRequiredOverridesAllowed=dialog @@ -52,9 +53,9 @@ PrivilegesRequiredOverridesAllowed=dialog Source: "i386\PathMgr.dll"; DestDir: "{app}"; Flags: uninsneveruninstall ; Other files to install on target system -Source: "i386\EditPath.exe"; DestDir: "{app}"; Check: not Is64BitInstallMode() -Source: "x86_64\EditPath.exe"; DestDir: "{app}"; Check: Is64BitInstallMode() -Source: "EditPath.md"; DestDir: "{app}" +Source: "i386\PathMan.exe"; DestDir: "{app}"; Check: not Is64BitInstallMode() +Source: "x86_64\PathMan.exe"; DestDir: "{app}"; Check: Is64BitInstallMode() +Source: "PathMan.md"; DestDir: "{app}" [Tasks] Name: modifypath; Description: "&Add to Path" diff --git a/PathMan.md b/PathMan.md new file mode 100644 index 0000000..d9ff139 --- /dev/null +++ b/PathMan.md @@ -0,0 +1,141 @@ +# PathMan + +PathMan is a Windows console (text-based, command-line) program for managing the system Path and user Path. + +# Author + +Bill Stewart - bstewart at iname dot com + +# License + +PathMan.exe is covered by the GNU Lesser Public License (LPGL). See the file `LICENSE` for details. + +# Download + +https://github.com/Bill-Stewart/PathMgr/releases/ + +# Background + +The system Path is found in the following location in the Windows registry: + +Root: `HKEY_LOCAL_MACHINE` +Subkey: `SYSTEM\CurrentControlSet\Control\Session Manager\Environment` +Value name: `Path` +Value type: `REG_EXPAND_SZ` + +The current user Path is found in the following location in the registry: + +Root: `HKEY_CURRENT_USER` +Subkey: `Environment` +Value name: `Path` +Value type: `REG_EXPAND_SZ` + +The registry value type `REG_EXPAND_SZ` means the string caan contain values surrounded by `%` characters that Windows will automatically expand to environment variable values. (For example, `%SystemRoot%` will be expanded to `C:\Windows` on most systems.) + +The `Path` value contains a `;`-delimited list of directory names that the operating system should search for executables, library files, scripts, etc. Windows appends the content of the current user Path to the system Path, and expands the environment variable references, and sets the resulting string as the `Path` environment variable for new processes. + +PathMan provides a command-line interface for managing the `Path` value in the system location (in `HKEY_LOCAL_MACHINE`) and the current user location (in `HKEY_CURRENT_USER`). + +# Usage + +The following describes the command-line usage for the program. Parameters are case-sensitive. + +**PathMan** _scope_ _action_ [_option_ [...]] + +You must specify only one of the following _scope_ parameters: + +| _scope_ | Abbreviation | Description +| ------- | ------------ | ----------- +| **--system** | **-s** | Specifies the system Path +| **--user** | **-u** | Specifies the current user Path + +You must specify only one of the following _action_ parameters: + +| _action_ | Abbreviation | Description +| -------- | ------------ | ----------- +| **--list** | **-l** | Lists directories in Path +| **--test "**_dirname_**"** | **-t "**_dirname_**"** | Tests if directory is in Path +| **--add "**_dirname_**"** | **-a "**_dirname_**"** | Adds directory to Path +| **--remove "**_dirname_**"** | **-r "**_dirname_**"** | Removes directory from Path + +The following parameters are optional: + +| _option_ | Abbreviation | Description +| --------- | ------------ | ----------- +| **--expand** | **-x** | Expands environment variables (**--list** only) +| **--beginning** | **-b** | Adds to beginning of Path (**--add** only) +| **--quiet** | **-q** | Suppresses result and error messages + +# Exit Codes + +The following table lists typical exit codes when not using **--test** (**-t**): + +| Exit Code | Description +| --------- | ----------- +| 0 | No errors +| 2 | The Path value is missing from the registry +| 3 | The specified directory does not exist in the Path +| 5 | Access is denied +| 87 | Incorrect parameter(s) +| 183 | The specified directory already exists in the Path + +The following table lists typical exit codes when using **--test** (**-t**): + +| Exit Code | Description +| --------- | ----------- +| 1 | The specified directory exists in the unexpanded Path +| 2 | The specified directory exists in the expanded Path +| 3 | The specified directory does not exist in the Path + +# Remarks + +* "Unexpanded" vs. "expanded" refers to whether PathMan expands environment variable references (i.e., names between `%` characters) after retrieving the Path value from the registry. For example, `%SystemRoot%` is unexpanded but `C:\Windows` is expanded. + +* The **--add** (**-a**) parameter checks whether the specified directory exists in both the unexpanded and expanded copies of the Path before adding the directory. For example, if the environment variable `TESTAPP` is set to `C:\TestApp` and `%TESTAPP%` is in the Path, specifying `--add C:\TestApp` will return exit code 183 (i.e., the directory already exists in the Path) because `%TESTAPP%` expands to `C:\TestApp`. + +* The **--remove** (**-r**) parameter does not expand environment variable references. For example, if the environment variable `TESTAPP` is set to `C:\TestApp` and `%TESTAPP%` is in the Path, specifying `--remove "C:\TestApp"` will return exit code 3 (i.e., the directory does not exist in the Path) because **--remove** does not expand `%TESTAPP%` to `C:\TestApp`. For the command to succeed, you would have to specify `--remove "%TESTAPP%"` instead. + +* The program will exit with error code 87 if a parameter (or an argument to a parameter) is missing or not valid, if mutually exclusive parameters are specified, etc. + +* The program will exit with error code 5 if the current user does not have permission to update the Path value in the registry (for example, if you try to update the system Path using a standard user account or an unelevated administrator account). + +* Working with environment variable strings at the cmd.exe command prompt can be tricky because cmd.exe always expands environment variable references. One way to work around this is to set a temporary environment variable using the `^` character to escape each `%` character, then use the temporary environment variable in the PathMan command. See **Examples**, below, for an example. This issue doesn't occur in PowerShell because PowerShell doesn't expand environment variables names enclosed in `%` characters. + +* If a directory name contains the `;` character, PathMan will add it to the Path in the registry with surrounding quote characters (`"`). The quotes around the directory name are required to inform the operating system that the enclosed string is a single directory name. For example, consider the following Path string: + + C:\dir 1;"C:\dir;2";C:\dir3 + + Without the quote marks enclosing the `C:\dir;2` directory, the system would incorrectly "split" the path name into the following directory names: + + C:\dir 1 + C:\dir + 2 + C:\dir3 + + In other words, the `"` characters around the `C:\dir;2` directory "protect" the `;` character and inform the system that `C:\dir;2` is a single directory name. (The `"` marks themselves are not part of the directory name.) + +# Examples + +1. List directories in the system Path, expanding all environment varable references: + + PathMan --system --list --expand + + You can also write this command as `PathMan -s -l -x`. + +2. Add a directory to the current user from the cmd.exe command line: + + set _T=^%LOCALAPPDATA^% + PathMan --user --add "%_T%\Programs\My App" + set _T= + + This sequence of commands adds the directory `%LOCALAPPDATA%\Programs\My App` to the current user Path. The first command sets a temporary environment variable to the literal string `%LOCALAPPDATA%` (the `^` characters "escape" the `%` characters). The second command adds the directory to the current user Path (cmd.exe expands `%_T%` to the literal string `%LOCALAPPDATA%`), and the third command removes the temporary variable from the environment. + +3. Remove a directory from the system Path: + + PathMan -s -r "C:\Program Files\MyApp\bin" + +4. Tests if a directory is in the path: + + PathMan -s --test "C:\Program Files (x86)\MyApp\bin" + + This command returns an exit code of 3 if the specified directory is not in the system Path, 1 if the specified directory is in the unexpanded copy of the system Path, or 2 if the specified directory is in the expanded copy of the system Path. diff --git a/PathMan.pp b/PathMan.pp new file mode 100644 index 0000000..b161f6d --- /dev/null +++ b/PathMan.pp @@ -0,0 +1,379 @@ +{ Copyright (C) 2024 by Bill Stewart (bstewart at iname.com) + + This program is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your option) any + later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more + details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see https://www.gnu.org/licenses/. + +} + +program PathMan; + +{$MODE OBJFPC} +{$MODESWITCH UNICODESTRINGS} +{$R *.res} + +// wargcv and wgetopts: https://github.com/Bill-Stewart/wargcv +uses + windows, + wargcv, + wgetopts, + WindowsMessages, + WindowsPath; + +const + PROGRAM_NAME = 'PathMan'; + PROGRAM_COPYRIGHT = 'Copyright (C) 2024 by Bill Stewart'; + +type + // Must specify only one action param, + TActionParamGroup = ( + ActionParamAdd, + ActionParamHelp, + ActionParamList, + ActionParamRemove, + ActionParamTest); + TActionParamSet = set of TActionParamGroup; + // ...and only one scope param + TScopeParamGroup = ( + ScopeParamSystem, + ScopeParamUser); + TScopeParamSet = set of TScopeParamGroup; + TCommandLine = object + ActionParamSet: TActionParamSet; + ScopeParamSet: TScopeParamSet; + Error: DWORD; + AddBeginning: Boolean; + Expand: Boolean; + Quiet: Boolean; + DirName: string; + procedure Parse(); + end; + +function IntToStr(const I: Integer): string; +begin + Str(I, result); +end; + +function GetFileVersion(const FileName: string): string; +var + VerInfoSize, Handle: DWORD; + pBuffer: Pointer; + pFileInfo: ^VS_FIXEDFILEINFO; + Len: UINT; +begin + result := ''; + VerInfoSize := GetFileVersionInfoSizeW(PChar(FileName), // LPCWSTR lptstrFilename + Handle); // LPDWORD lpdwHandle + if VerInfoSize > 0 then + begin + GetMem(pBuffer, VerInfoSize); + if GetFileVersionInfoW(PChar(FileName), // LPCWSTR lptstrFilename + Handle, // DWORD dwHandle + VerInfoSize, // DWORD dwLen + pBuffer) then // LPVOID lpData + begin + if VerQueryValueW(pBuffer, // LPCVOID pBlock + '\', // LPCWSTR lpSubBlock + pFileInfo, // LPVOID *lplpBuffer + Len) then // PUINT puLen + begin + with pFileInfo^ do + begin + result := IntToStr(HiWord(dwFileVersionMS)) + '.' + + IntToStr(LoWord(dwFileVersionMS)) + '.' + + IntToStr(HiWord(dwFileVersionLS)); + // LoWord(dwFileVersionLS) intentionally omitted + end; + end; + end; + FreeMem(pBuffer); + end; +end; + +procedure Usage(); +begin + WriteLn(PROGRAM_NAME, ' ', GetFileVersion(ParamStr(0)), ' - ', PROGRAM_COPYRIGHT); + WriteLn('This is free software and comes with ABSOLUTELY NO WARRANTY.'); + WriteLn(); + WriteLn('SYNOPSIS'); + WriteLn(); + WriteLn('Provides tools for management of the Path environment variable.'); + WriteLn(); + WriteLn('USAGE'); + WriteLn(); + WriteLn(PROGRAM_NAME, '