2

I am writing an uninstallation script, so I would like to 'undo' modifications the installation made to the system. In achieving this goal, I would like to parse the PATH variable, and remove any values that the installation added to the PATH.


To do so, I developed the following pseudocode -

  • Save the contents of PATH to a temporary variable
  • Split the PATH into tokens, using the ; character as a delimiter, and loop through each token
  • (In Loop) Identify if the current token is one added by the installation
  • (In Loop) If the current token was not added by the installation, save it to be added to the updated PATH (in a temporary variable)
  • Save the updated PATH

I expected this to be relatively straightforward to implement.


The first step, storing the PATH is simple.

SET TEMP_PATH=%PATH% 

However, when I try to loop through each token, it will not work as I expected.

FOR /F "delims=;" %%A IN (%TEMP_PATH%) DO ECHO %%A 

This command only outputs the first token, and no subsequent tokens are echoed out.


So, I have two questions -

  • How can I loop through an unknown number of tokens and work with each one?
  • Is there another way to achieve the same goal which may be simpler?

Thank you.

Eilidh
  • 1,354
  • 5
  • 21
  • 43
  • Seems % are missing from TEMP_PATH – Jean-François Fabre Jul 29 '16 at 10:31
  • @Jean-FrançoisFabre Apologies that is my copying the code into the question, it has % in the code. Thank you. I will update the question. – Eilidh Jul 29 '16 at 10:32
  • 1
    Problem with path variable is that it is a concat between system and user variables. If you save them back to registry it will be a problem. You have to read from user or system path from registry not environment – Jean-François Fabre Jul 29 '16 at 10:36
  • @Jean-FrançoisFabre would you mind explaining that in a little more detail, please? – Eilidh Jul 29 '16 at 10:38
  • Some related questions to try and see why you are doing this and if there is a better way. Did you also write the installation script in batch? If the software came with its own installer, why doesn't it get uninstalled using a similar uninstaller as the installer? – Klitos Kyriacou Jul 29 '16 at 10:48
  • Yes the installation process is ours, using `.ps1` and `.bat` files. As part of the process other software dependencies are installed (so we can do things such as call `msiexec` to uninstall them as you may be alluding to), but not for the whole thing. – Eilidh Jul 29 '16 at 11:02
  • You should think about removing elements from the PATH in the same way they were added. To see what Eilidh was referring to, try changing the PATH in one command window, then start another command window and you'll see the PATH hasn't changed. – Klitos Kyriacou Jul 29 '16 at 11:20
  • If you still want to use a batch file, use the REG command. Type `REG /?` for help. Look at `reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"` and `reg query "HKCU\Environment"`. – Klitos Kyriacou Jul 29 '16 at 13:54

2 Answers2

3

The batch code below removes one or more folder paths as defined at top of the script with PathToRemove1, PathToRemove2, ... from

  • user PATH of current user account stored in the Windows registry under the key
    HKEY_CURRENT_USER\Environment
  • system PATH stored in the Windows registry under the key
    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment

The update of system PATH requires administrator privileges which means the batch file must be executed as administrator if user account control (UAC) is not disabled for the user account executing the batch file which belongs also to local Administrators group.

The Windows command SETX is available by default on Windows Vista and later versions of Windows. It is not available on Windows XP or even former versions of Windows. Please read the SS64 article about SetX and Microsoft's SetX documentation for more information about the availability of the command SETX.

For the reg.exe output differences on Windows XP versus later Windows versions see Rob van der Woude's Reading NT's Registry with REG Query. The different output of reg.exe is taken into account by the batch code below.

For an explanation why not using local PATH as currently defined on execution of the batch file read the questions, answers and comments of

Commented batch code for folder path removal from user and system PATH:

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "PathToRemove1=C:\Temp\Test"
set "PathToRemove2=C:\Temp"

rem Get directly from Windows registry the system PATH variable value.
for /F "skip=2 tokens=1,2*" %%G in ('%SystemRoot%\System32\reg.exe query "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" /v "Path" 2^>nul') do (
    if /I "%%G" == "Path" (
        set "SystemPath=%%I"
        if defined SystemPath goto CheckSystemPath
    )
)
echo Error: System environment variable PATH not found with a value in Windows registry.
echo(
goto UserPath

:CheckSystemPath
setlocal EnableDelayedExpansion
rem Does the system PATH not end with a semicolon, append one temporarily.
if not "!SystemPath:~-1!" == ";" set "SystemPath=!SystemPath!;"
rem System PATH should contain only backslashes and not slashes.
set "SystemPath=!SystemPath:/=\!"

rem Check case-insensitive for the folder paths to remove as defined at top
rem of this batch script and remove them if indeed found in system PATH.
set "PathModified=0"
for /F "tokens=1* delims==" %%I in ('set PathToRemove') do (
    if not "!SystemPath:%%J;=!" == "!SystemPath!" (
        set "SystemPath=!SystemPath:%%J;=!"
        set "PathModified=1"
    ) else  if not "!SystemPath:%%J\;=!" == "!SystemPath!" (
        set "SystemPath=!SystemPath:%%J\;=!"
        set "PathModified=1"
    )
)

rem Replace all two or more ; in series by just one ; in system path.
:CleanSystem
if not "!SystemPath:;;=;!" == "!SystemPath!" set "SystemPath=!SystemPath:;;=;!" & goto CleanSystem

rem Remove the semicolon at end of system PATH if there is one.
if "!SystemPath:~-1!" == ";" set "SystemPath=!SystemPath:~0,-1!"
rem Remove a backslash at end of system PATH if there is one.
if "!SystemPath:~-1!" == "\" set "SystemPath=!SystemPath:~0,-1!"

rem Update system PATH using command SETX which requires administrator
rem privileges if the system PATH needs to be modified at all. SETX is
rem by default not installed on Windows XP and truncates string values
rem longer than 1024 characters to 1024 characters. So use alternatively
rem command REG to add system PATH if command SETX cannot be used or is
rem not available at all.
if %PathModified% == 1 (
    set "UseSetx=1"
    if not "!SystemPath:~1024,1!" == "" set "UseSetx="
    if not exist %SystemRoot%\System32\setx.exe set "UseSetx="
    if defined UseSetx (
        %SystemRoot%\System32\setx.exe Path "!SystemPath!" /M >nul
    ) else (
        set "ValueType=REG_EXPAND_SZ"
        if "!SystemPath:%%=!" == "!SystemPath!" set "ValueType=REG_SZ"
        %SystemRoot%\System32\reg.exe ADD "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" /f /v Path /t !ValueType! /d "!SystemPath!" >nul
    )
)
endlocal

:UserPath
rem Get directly from Windows registry the user PATH variable value.
for /F "skip=2 tokens=1,2*" %%G in ('%SystemRoot%\System32\reg.exe query "HKCU\Environment" /v "Path" 2^>nul') do (
    if /I "%%G" == "Path" (
        set "UserPath=%%I"
        if defined UserPath goto CheckUserPath
        rem User PATH exists, but with no value, delete user PATH.
        goto DeleteUserPath
    )
)
rem This PATH variable does often not exist and therefore nothing to do here.
goto PathUpdateDone

:CheckUserPath
setlocal EnableDelayedExpansion
rem Does the user PATH not end with a semicolon, append one temporarily.
if not "!UserPath:~-1!" == ";" set "UserPath=!UserPath!;"

rem Check case-insensitive for the folder paths to remove as defined at top
rem of this batch script and remove them if indeed found in user PATH.
set "PathModified=0"
for /F "tokens=1* delims==" %%I in ('set PathToRemove') do (
    if not "!UserPath:%%J;=!" == "!UserPath!" (
        set "UserPath=!UserPath:%%J;=!"
        set "PathModified=1"
        if not defined UserPath goto DeleteUserPath
    ) else if not "!UserPath:%%J\;=!" == "!UserPath!" (
        set "UserPath=!UserPath:%%J\;=!"
        set "PathModified=1"
        if not defined UserPath goto DeleteUserPath
    )
)

rem Replace all two or more ; in series by just one ; in user path.
:CleanUser
if not "!UserPath:;;=;!" == "!UserPath!" set "UserPath=!UserPath:;;=;!" & goto CleanUser

rem Remove the semicolon at end of user PATH if there is one.
if "!UserPath:~-1!" == ";" set "UserPath=!UserPath:~0,-1!"
if not defined UserPath goto DeleteUserPath

rem Update user PATH using command SETX which does not require administrator
rem privileges if the user PATH needs to be modified at all. SETX is
rem by default not installed on Windows XP and truncates string values
rem longer than 1024 characters to 1024 characters. So use alternatively
rem command REG to add user PATH if command SETX cannot be used or is
rem not available at all.
if %PathModified% == 1 (
    set "UseSetx=1"
    if not "!UserPath:~1024,1!" == "" set "UseSetx="
    if not exist %SystemRoot%\System32\setx.exe set "UseSetx="
    if defined UseSetx (
        %SystemRoot%\System32\setx.exe Path "!UserPath!" /M >nul
    ) else (
        set "ValueType=REG_EXPAND_SZ"
        if "!UserPath:%%=!" == "!UserPath!" set "ValueType=REG_SZ"
        %SystemRoot%\System32\reg.exe ADD "HKCU\Environment" /f /v Path /t !ValueType! /d "!UserPath!" >nul
    )
)
goto PathUpdateDone

:DeleteUserPath
rem Delete the user PATH as it contains only folder paths to remove.
%SystemRoot%\System32\reg.exe delete "HKCU\Environment" /v "Path" /f >nul

:PathUpdateDone
rem Other code could be inserted here.
endlocal
endlocal

The batch code above uses a simple case-insensitive string substitution and a case-sensitive string comparison to check if the current path to remove is present in user or system PATH. This works only if it is well known how the folder paths were added before and the user has not modified them in the meantime. For a safer method of checking if PATH contains a folder path see the answer on How to check if directory exists in %PATH%? written by dbenham.

Attention: This batch code is not designed to handle the very rare use case of system or user PATH contains a folder path with one or more semicolons in path string enclosed in double quotes to get ; interpreted by Windows inside the double quoted folder path string as literal character instead of a separator between the folder paths.

To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.

  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • reg /?
  • reg add /?
  • reg delete /?
  • reg query /?
  • rem /?
  • set /?
  • setlocal /?
  • setx /?

See also Microsoft's article about Using command redirection operators for an explanation of >nul and 2>nul with redirection operator > being escaped with ^ to use the redirection on execution of reg.exe instead of interpreting 2>nul misplaced for command FOR which would result in an exit of batch processing by Windows Command Processor because of a syntax error.

Mofi
  • 46,139
  • 17
  • 80
  • 143
  • +1! awesome answer. Well, I did the same in python because I did not want to take the way you took. That answers the question, but the real question is: is it reasonable to try to do that as a batch file? (even if it's built-in windows). Shouldn't Microsoft provide a safe way to do such things? – Jean-François Fabre Jul 29 '16 at 20:57
  • Well, in my point of view an application which must add 1 or more folder paths to __PATH__ is simply a purely coded application. An application developer should think of why the application (suite) is not running without having 1 or more folders added to system or user __PATH__ instead of thinking how to update the __PATH__ environment variable. Only the user should modify user or system __PATH__ at all when he/she wants to start applications from within a command line regularly not being located in standard Windows paths. – Mofi Jul 30 '16 at 15:57
  • I keep __PATH__ on my computers clean. There are application installers which add folder paths to __PATH__ although the installed application itself does not really need it. Why was the folder path added at all? If a user wants to use the application from command line, the user can do it. Other applications which really require 1 or more folders added to __PATH__ are modified by me. I put their folders and their environment variables to a batch file and modify the shortcuts used to start the application to first run the batch file setting up the environment and then starting the application. – Mofi Jul 30 '16 at 16:02
  • @Jean-FrançoisFabre To answer your question: Yes, Microsoft should provide a safe way. A __PATH__ modification by any application (installer/uninstaller/real application) should not be possible without a user notification and asking for confirmation by the user. If that would be implemented by Microsoft, those applications manipulating __PATH__ would be rewritten most likely very quickly to work without modification of __PATH__. The batch file above is used by me to clean up system __PATH__ after installing/updating applications always adding their folder paths to __PATH__ not wanted by me. – Mofi Jul 30 '16 at 16:11
  • @Mofi FWIW the reason multiple values are added to the PATH by our install process is our install process adds multiple dependencies to the system (for example, Java). In my personal case, it is for the uninstallation of dependencies to do this. – Eilidh Aug 01 '16 at 10:29
2

This command only outputs the first token, and no subsequent tokens are echoed.

FOR /F "delims=;" %%A IN (%TEMP_PATH%) DO ECHO %%A 

How can I loop through an unknown number of tokens and work with each one?

Use the following batch file.

SplitPath.cmd:

@echo off
setlocal
for %%a in ("%path:;=";"%") do (
  echo %%~a
  )
endlocal

Example Output:

F:\test>path
PATH=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\apps\WSCC\Sysinternals Suite;C:\apps\WSCC\NirSoft Utilities

F:\test>splitpath
C:\Windows\system32
C:\Windows
C:\Windows\System32\Wbem
C:\Windows\System32\WindowsPowerShell\v1.0\
C:\apps\WSCC\Sysinternals Suite
C:\apps\WSCC\NirSoft Utilities

Notes:

  • Modify the for loop as appropriate to implement the rest of your pseudocode.

Further Reading

DavidPostill
  • 7,734
  • 9
  • 41
  • 60