3

Let's suppose that I have this code:

@echo off
set "var=value"
echo %var%

Now my question is: how to echo all variables set in the file?

I know you can do:

set

And it will display all variables. It displays:

ALLUSERSPROFILE=C:\ProgramData
APPDATA=C:\Users\foma\AppData\Roaming
CommonProgramFiles=C:\Program Files\Common Files
CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
CommonProgramW6432=C:\Program Files\Common Files
...

I want that it displays:

var=value

I know I can do set var to display all variables starting with var, but in my case all my variables are "imported" from another batch file, so I don't know how the variable name starts. Any ideas?

aschipfl
  • 33,626
  • 12
  • 54
  • 99
HackR_360
  • 192
  • 2
  • 9
  • Clear the environment of all variables then set the ones you need. Saw a discussion about this technique because it helps with performance in some instances. – Squashman Jan 26 '17 at 02:23
  • @Squashman I wouldn't advise that solution. It requires an in depth study of all commands you're going to use and all the environment variables that the OS has configured. It would even be easier to just follow a naming convention and decide to let variables in a script start with a given pattern and just look for that pattern with `find` or `findstr`. But even that has the inconvenience that changes to already existing variables will not be detected. Maybe the OP can give more specifications. – J.Baoby Jan 26 '17 at 10:51
  • 1
    @HackR_360 what will you use it for? Is it just to undo the changes on the environment? Do you also need to detect changes made to variables that existed before the execution of the script? Answers to these questions may lead to easier (one command) solutions. – J.Baoby Jan 26 '17 at 10:55
  • @J.Baoby , i want that my program: 1. echoes all variables in the called batch file to a .txt file2. lets the user modify variables of the called batch file 3. creates another batch file with all variables of the called batch file. – HackR_360 Jan 26 '17 at 17:38

3 Answers3

7
@echo off
setlocal
set>originalfile.txt
...
:: list environment changes
set|findstr /x /L /g:originalfile.txt

Should work - snapshot the starting values, then the findstr line should show you all changes made (except deletions) - at least in theory, this is just an on-the-fly suggestion.

Magoo
  • 77,302
  • 8
  • 62
  • 84
  • a great implementation of reflection for bat files! – Eduardo Poço Jan 26 '17 at 01:05
  • 1
    +1 - This will work for most values. But it can fail if originalfile.txt contains `\"` or \\ due to FINDSTR escape issues when using `g:file` – dbenham Jan 26 '17 at 03:03
  • First doubling all `\ `in `originalfile.txt` then replacing each `"` by `\"` by some more code should do the trick for the code to work for (nearly almost) all values; shouldn't it, @dbenham? Also +1 from me! – aschipfl Jan 26 '17 at 09:30
  • 1
    I get the error message `FINDSTR: Search string too long.` But even if it worked, wouldn't this just print all the *unchanged* variables? – Klitos Kyriacou Jan 26 '17 at 10:43
  • Correct @KlitosKyriacou, this code would require the **`/V`** option with **`FindStr`**. If you add to that the fact you would have to double characters in almost every line then this is a very wrong answer, does not fulfil any of the requirements and I'm surprised it has received any up votes at all. – Compo Jan 26 '17 at 12:08
  • @compo - The idea was good, but the OP indirectly acknowledges it hadn't been tested, and obviously many of us also didn't test and missed the simple correction that was required to get a mostly functioning solution. Doubling all backslashes and adding `/V` would mostly solve the problems, except for [this FINDSTR bug](http://stackoverflow.com/q/8921253/1012053). – dbenham Jan 26 '17 at 12:32
  • @dbenham, this is a coding site, not an ideas site, the answer clearly doesn't work, and as you point out, even with the addition of a **`/V`** option and ther alteration of almost every line returned by the initial **`set`** command there could still be further problems. I think the least that should happen is that the answer should reflect the required changes in order to justify the points thus far received. – Compo Jan 26 '17 at 12:42
  • @Compo - You raise valid points. None of us are infallible. I would rescind my up vote if I could, but my vote has been "locked in". I've done some tests, and FINDSTR has [too many bugs](http://stackoverflow.com/q/8844868/1012053) that make it nonviable for this problem. Besides those already noted, search string length limits, regex `[charlist]` count limits. – dbenham Jan 26 '17 at 12:50
3

This compares the original cmd environment (saved to a file) with the current one. You can change the file creation if you need a different check

@echo off
    setlocal enableextensions disabledelayedexpansion

    rem We need a temporary file to store the original environment
    for %%f in ("%temp%\original_%random%%random%%random%.tmp") do (

        rem Retrieve the original environment to the temporary file
        start /i /wait /min "" "%comspec%" /c">""%%~ff"" set "

        rem We need two flag variables. Prepare two names that "should" not collide
        for /f "tokens=1,2" %%d in ("_&d&%random%%random%_ _&m&%random%%random%_") do (

            rem %%d will be used to determine if we will check for variable deletion 
            rem     on the first inner pass
            rem %%e will be used for matching variables between existing/original variables
            set "%%d="

            rem Retrieve the current environment contents
            for /f "delims= eol==" %%a in ('set') do (

                rem We have not found matching variables
                set "%%e="

                rem Search a match in original set of variables 
                for /f "usebackq delims= eol==" %%o in ("%%~ff") do (

                    rem If variables match, flag it, else check for variable 
                    rem deletion is this is the first loop over the original file
                    if %%a==%%o ( set "%%e=1" ) else if not defined %%d (
                        for /f "delims==" %%V in ("%%~o") do if not defined %%V (echo(%%V=)
                    )
                )

                rem If no match found, output changed value
                if not defined %%e (echo(%%a)

            rem Now all the variable deletion has been checked.
            ) & if not defined %%d set "%%d=1"

        rem Cleanup flag variables
        ) & set "%%d=" & set "%%e="

    rem Cleanup temporary file
    ) & del "%%~ff"

Not as much code as it seems once we remove the comments

@echo off
    setlocal enableextensions disabledelayedexpansion
    for %%f in ("%temp%\original_%random%%random%%random%.tmp") do (
        start /i /wait /min "" "%comspec%" /c">""%%~ff"" set "
        for /f "tokens=1,2" %%d in ("_&d&%random%%random%_ _&m&%random%%random%_") do (
            set "%%d="
            for /f "delims= eol==" %%a in ('set') do (
                set "%%e="
                for /f "usebackq delims= eol==" %%o in ("%%~ff") do (
                    if %%a==%%o ( set "%%e=1" ) else if not defined %%d (
                        for /f "delims==" %%V in ("%%~o") do if not defined %%V (echo(%%V=)
                    )
                )
                if not defined %%e (echo(%%a)
            ) & if not defined %%d set "%%d=1"
        ) & set "%%d=" & set "%%e="
    ) & del "%%~ff"

edited just for completion. As dbenham points the previous code can not handle variables with line feeds in their names or values. This is an hybrid batch file (save as ex. checkEnvironment.cmd file) that can handle this case.

@if (@This==@IsBatch) @then /* Batch zone ------------------------------------
@echo off
    setlocal enableextensions disabledelayedexpansion
    call :processCurrentEnvironment "%~f1"
    goto :eof

:processCurrentEnvironment 
    call "%systemroot%\system32\cscript.exe" //nologo //e:JScript "%~f0" /saved:"%~1"
    goto :eof

*/ @end
// Javascript zone -----------------------------------------------------------

var environment = WScript.CreateObject('WScript.Shell').Environment('PROCESS');
var savedFileName = WScript.Arguments.Named.Item('saved');


    if ( !savedFileName ){
        // If no saved file name present, output current environment
        for ( var e = new Enumerator(environment); !e.atEnd(); e.moveNext()){ 
            WScript.StdOut.Write( e.item() + '\0\r\n' );
        };

    } else {
        // Retrieve saved environment to compare against current one
        var savedEnvironment = retrieveSavedEnvironment( savedFileName );

        // For each element in the current environment
        for ( var e = new Enumerator(environment); !e.atEnd(); e.moveNext() ) {

            // retrieve { variable: , value: } representation of the variable 
            var buffer = splitEnvironmentVariable( e.item() );

            if ( buffer ) {
                // if the current variable does not exist in the saved version
                // or if the current value does not match the previous one
                // then dump the current variable
                if (
                    !  savedEnvironment[buffer.variable]
                    || savedEnvironment[buffer.variable].value !== buffer.value
                ) outputVariable( buffer );

                // in any case, this is a processed variable
                delete savedEnvironment[buffer.variable];
            }
        };

        // any saved variables still present are deleted variables, show them
        for (var e in savedEnvironment) outputVariable( e );
    };

// - Helper functions ----------------------------------------------------------

// Process the contents of the saved file to generate a { {variable: , value: }, ... }
// representation of the saved environment

function retrieveSavedEnvironment( savedFileName ) {
    var savedEnvironment = {};
    var buffer = readFile( savedFileName ).split('\0\r\n');
    for (var i in buffer) if ( buffer[i] = splitEnvironmentVariable(buffer[i]) ) {
        savedEnvironment[buffer[i].variable] = buffer[i];
    };
    return ( savedEnvironment );
};

// Read the contents of the saved file - FSO can not be used because we are 
// writing nulls (ascii 0x00 character) to separate variables

function readFile( savedFileName ){
    var buffer = "";
    if (
        WScript.CreateObject('Scripting.FileSystemObject').FileExists( savedFileName )
    ){
        var stream = WScript.CreateObject('ADODB.Stream');
        stream.Open();
        stream.Type = 2
        stream.Charset = 'ascii';
        stream.LoadFromFile( savedFileName );
        buffer = stream.ReadText();
        stream.Close();
    };
    return ( buffer );
};

// Convert variable=value to { variable: , value: }

function splitEnvironmentVariable( text ){
    text = (/^([\s\S][^=]*)=([\s\S]+)/).exec( text ); 
    return (
        text 
        ? { variable : text[1] , value : text[2] } 
        : text
    );
};

// Write the variable with its value or only the variable name 

function outputVariable( variable ){
    if ( typeof variable === 'object' ) {
        WScript.StdOut.WriteLine( variable.variable + '=' + variable.value );
    } else {
        WScript.StdOut.WriteLine( variable + '=' );
    };
};

Placed in the path or in the same folder that your batch files, you can use it as

@echo off
    setlocal enableextensions enabledelayedexpansion

(SET LF=^
%=this line is empty=%
)

    rem Variables to delete
    set "variableToDelete=1"
    set "my!lf!var1=my!lf!data"

    set "originalEnvironment=%temp%\original_%random%%random%%random%.tmp"

    rem save environment 
    >"%originalEnvironment%" call checkEnvironment.cmd

    rem Create new variable2
    set "thisIsNew=value"
    set "anotherOne=1"

    rem Create another problematic variable
    set "my!lf!var2=my!lf!data"

    rem Delete variables
    set "variableToDelete="
    set "my!lf!var1="

    rem Dump environment changes
    call checkEnvironment.cmd "%originalEnvironment%"

    rem Remove temporary file
    del /q "%originalEnvironment%"

to get an exit as

W:\>testEnv.cmd
anotherOne=1
my
var2=my
data
thisIsNew=value
my
var1=
variableToDelete=
W:\>
MC ND
  • 69,615
  • 8
  • 84
  • 126
  • 1
    Very nice. Works for me. Of course this will fail if a variable value (or name!) contains newline, but then I don't think there is any batch solution that can handle that. I believe the OP wants to probe what changes the most recent batch file made, not what changes have been made since the command session started. But you acknowledge that the file creation could be changed as needed. – dbenham Jan 26 '17 at 13:06
  • @dbenham, I don't see how to handle the new line from pure batch. Just in case someone could find it useful, I've included a hybrid version to handle it. – MC ND Jan 26 '17 at 16:42
0

anyways the easiest way:

@echo off
setlocal enabledelayedexpansion
set "oldfile=oldfile.txt"
set "newfile=newfile.txt"
del %oldfile% >nul 2>nul
del %newfile% >nul 2>nul
for /f %%a in ('set') do echo %%a>>%oldfile%
::---------------------set-vars-here---------------------
set "testone=testonestring"
set "testtow=12324"
::-------------------------------------------------------
for /f %%a in ('set') do echo %%a>>%newfile%
for /f %%a in (%newfile%) do (
find "%%a" %oldfile% >nul 2>nul
if !errorlevel! NEQ 0 (
set "string=%%a"
echo !string!
)
)
del %oldfile% >nul 2>nul
del %newfile% >nul 2>nul
pause
HackR_360
  • 192
  • 2
  • 9