Question About Batch Variables and the Call Method - windows

I am trying to figure out how to use call appropriately to pass variables backwards (to the original batch file that calls other batch files).
The way I have my batch files currently setup is my main batch file calls other batch files which may then even call a third batch file which creates the variable.
Here are some examples of my scripts so far, for this example I will be focusing on main.bat, VerifyCredentials.bat, and the CreateAdminCreds.bat files. In these I am trying to pass the %AdminUser% and %AdminPass% variables back to main.bat:
main.bat example:
REM Define all the variables that will be updated by other batch files.
set NewCompName= & set DomainUser= & set DomainPass= & set AdminUser= & set AdminPass=
REM Create the variables for the domain tech account and local admin account. Verify tech account can access SCCM file share.
set CredTestVar=0
:CredTestLoop
call CreateCompName.bat & call VerifyCredentials.bat & call TestCredentials.bat
if %CredTestVar% LSS 1 goto CredTestLoop
REM Activate the local admin account and set password
ECHO Enabling Administrator account
net user administrator /active:yes
net user administrator "%AdminPass%"
ECHO.
VerifyCredentials.bat example:
#ECHO off
if not exist Credentials\DomainUser.txt call CreateDomainCreds.bat
if not exist Credentials\DomainPass.txt call CreateDomainCreds.bat
for /f "delims=" %%x in (Credentials\DomainUser.txt) do set DomainUser=%%x
for /f "delims=" %%x in (Credentials\DomainPass.txt) do set DomainPass=%%x
if not exist Credentials\AdminUser.txt call CreateAdminCreds.bat
if not exist Credentials\AdminPass.txt call CreateAdminCreds.bat
for /f "delims=" %%x in (Credentials\AdminUser.txt) do set AdminUser=%%x
for /f "delims=" %%x in (Credentials\AdminPass.txt) do set AdminPass=%%x
CreateAdminCreds.bat example:
#ECHO off
REM Create and define the local administrator credentials
:CreateAdmin
cls
echo.
echo Please enter a new password for the local Administrator account
echo.
set "AdminUser=Administrator"
set /p "AdminPass=Administrator Password:"
goto :ResponseAdmin
:ResponseAdmin
set "AdminAnswer="
cls
echo.
echo The Administrator password is "%AdminPass%"
echo.
set /p "AdminAnswer=Is this correct? (y/n):"
if %AdminAnswer%==y () else (if %AdminAnswer%==n (goto :CreateAdmin) else (goto :ResponseAdmin))
(echo=%AdminUser%) > "Credentials\AdminUser.txt"
(echo=%AdminPass%) > "Credentials\AdminPass.txt"
set "AdminAnswer="
So all of this individually works fine.. but when I get to the step in main.bat where I try to use that variable %AdminPass% for the tech to define the local admin account it doesn't change the password to anything (on restart/lock you can login to local admin without a password)
Can anyone help explain to me why the variable %AdminPass% is not being passed back to main.bat?
I have tested this passing theory like this:
test1.bat
#ECHO off
set testvar=
echo testvar is currently equal to %testvar%
REM this returns "testvar is currently equal to"
call test2.bat
echo testvar is now equal to %testvar%
REM this returns "testvar is now equal to something"
test2.bat
#ECHO off
set testvar="something"
and this successfully changes testvar to something for the second echo in the test1.bat file.
Thank you in advance for any help.

When a cmd instance starts, it is provided with a set of string variables which form the environment (always strings - if the value of any string is all-numeric, then it can often be used in calculations.)
Any batch file that runs within that instance can modify, add to, or delete variables. Hence running batch files A and B in that order would allow B to see values established by A, but in the reverse order, those values would not be set.
It's therefore usual practice to follow the initial #echo off command (which turns command-regurgitation to the console for debugging OFF) by a setlocal command, which itself has various options. Fundamentally, a setlocal establishes a local environment with a copy of the environment as it stood when the setlocal was executed.
setlocals may be nested, and are closed by an endlocal command or by reaching the physical end-of-file, whereupon the environment is restored to its condition at the time the matching setlocal was executed. Note that this applies only to the environment, it does not affect the setting of the durrent directory or drive for instance.
So, if A calls B then B will NOT manipulate A's environment if B uses setlocal. If B has no setlocal then it will manipulate A's environment BUT if run from the prompt, it will manipulate the cmd instance's environment, which is generally undesirable.
There is a method to transmit the variable "over the endlocal barrier" like this:
endlocal&set "fred=%fred%"
which seems quite bizarre. What this does is use cmd's parser. The entire line is parsed, and %fred% is replaced by the current value of the variable fred within the inner environment, and then the line endlocal&set "fred=fredsvalue" is executed; two commands, serially. So the inner environment is discarded and then the variable in the calling environment is set to the value.
So - you pays your money, you takes your choice. Use setlocal in the called routines, and you'll need to jump the endlocal barriers. Omit the setlocal and the called routine will manipulate the current environment.

Related

How to assign values to environment variables with dynamic name while parsing similar named XML elements?

I have an XML file in the following manner:
<pools>
<pool>aaa</pool>
<pool>bbb</pool>
<pool>ccc</pool>
<pool>ddd</pool>
<pool>eee</pool>
</pools>
I want to parse these tags in such a way that they will be assigned to variables as
Pool1 = aaa
Pool2 = bbb
and so on
I have tried the below code:
echo off
set /a x=0
SETLOCAL enableextensions enabledelayedexpansion
for /f "tokens=2 delims=<>" %%a in ('find /i "<pool>" ^< "pool_info.xml"') do (
set /a "x+=1"
call ECHO pool%%x%%=%%a
)
And it just prints them properly. I tried the set command for assigning them, but it does not work.
I went through many Stack Overflow problems, but was not able to find any solution that would match my requirement. If anyone could please help me out.
PS: The <pool> tags count here is 5, however, the count can change, so I want it to be flexible.
The task can be done with:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Delete all environment variables of which name starts with Pool.
for /F "delims==" %%I in ('set Pool 2^>nul') do set "%%I="
set "PoolCount=0"
for /F "tokens=2 delims=<> " %%I in ('%SystemRoot%\System32\findstr.exe /I /L /C:"<pool>" "pool_info.xml"') do (
set /A PoolCount+=1
call set "Pool%%PoolCount%%=%%I"
)
rem Output all environment variables of which name starts with Pool.
set Pool
endlocal
ATTENTION: The delimiters are the two angle brackets, a horizontal tab character and a normal space character. Please make sure that the batch file contains exactly those four characters after delims= in that order.
The horizontal tab and the normal space are needed as delimiters to have a working solution independent on leading spaces/tabs on the lines with the pool elements.
The wrong token respectively the missing delimiters tab/space resulted with posted code in question in getting element name pool output instead of the values of the XML element pool.
There is no need to use delayed environment variable expansion in this case.
However, the usage of call to force a second parsing of the command line
call set "Pool%%PoolCount%%=%%I"
modified already during parsing of the entire command block to
call set "Pool%PoolCount%=%I"
before execution of set is slower in comparison to using delayed expansion as used in the code below.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem Delete all environment variables of which name starts with Pool.
for /F "delims==" %%I in ('set Pool 2^>nul') do set "%%I="
set "PoolCount=0"
for /F "tokens=2 delims=<> " %%I in ('%SystemRoot%\System32\findstr.exe /I /L /C:"<pool>" "pool_info.xml"') do (
set /A PoolCount+=1
set "Pool!PoolCount!=%%I"
)
rem Output all environment variables of which name starts with Pool.
set Pool
endlocal
The reason is explained by jeb in the DosTips forum post CALL me, or better avoid call. The Windows command processor searches with using call set "Pool%%PoolCount%%=%%I" in the batch file in current directory and next in all directories of environment variable PATH for a file matching the wildcard pattern set.*. If there is indeed a file found like set.txt in one of the directories, it searches next in that directory for set.COM, set.EXE, set.BAT, set.CMD, ... according to list of file extensions of environment variable PATHEXT. If there is really an executable or script found by cmd.exe with file name set in current directory or another other directory of PATH with a file extension of PATHEXT, it executes the executable/script instead of running internal command SET.
For that reason it is definitely better to use delayed expansion solution as it is faster and more safe.
The disadvantage is that a pool value with one or more ! is not correct processed with enabled delayed expansion. So once again cmd.exe proves itself that the Windows command processor is designed for executing commands and executables, but not for processing data in text files.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /? ... used for double parsing the command line before execution of set.
echo /?
endlocal /?
findstr /?
for /?
rem /?
set /?
setlocal /?
Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded set command line with using a separate command process started in background with %ComSpec% /c and the command line within ' appended as additional arguments.
...
set /a "x+=1"
call SET pool%%x%%=%%a
)
SET pool
The first set assigns the value in %%a to the variable pool?
The second set displays all of the currently-set environment variables whose name starts pool.
setx is a command designed to record a variable assignment for future instances of cmd. It's an entirely different matter and should be raised as a separate question, but there's plenty of SO items about setx so raising it (again) as a separate issue will likely be closed as a duplicate. Best use the search facility for setx.

How do I return a variable from a called batch file to the caller?

I want to be able to call a batch file like it were a function in Java or something of the like. Would it be possible to do something such as
set answer=call Calculate.bat %input1% %input2%
Where calculate.bat would calculate the sum of the two inputs, then it would assign it to the answer variable in the original batch file. Is this possible in some way?
If you use setlocal in your script like #SomethingDark said, you could to decide to transmit only certain variable
#ECHO OFF
setlocal
REM your code here, for example:
set /A "output=%1 + %2" > NUL
endlocal & set "answer=%output%"
REM variable %output% does not exist anymore
REM only %answer% remain changed (and stay the same once the script end)
like #Harry johnston said, if you set a variable in the batch file you called the variable will set in the caller to. if you do not want set any variables in the batch file you call, do:
for /f %%a in ('calculate.bat %input1% %input2%') do set "output=%%a"
the output of your command is stored in %output%

Check Batch File Location before Executing

I have wrote a batch file to Backup User Profile, but I would like to make it idiot proof.
A few times I have actually left the batch file on my desktop and run it on my own profile and as the batch file contains a robocopy command to copy the Desktop folder, it creates an infinite loop of duplicate folder inside one and other, which are difficult to get rid off due to the 256 character limit.
I am trying to get the batch file to check its own location is not within the user profile before executing.
I was messing around last night with the IF command and this is what i came up with:
#ECHO off
SET /P %UserName%=Enter Username:
IF "%~dp0"=="C:\Users\%UserName%\NUL" (
GOTO ERROR
) ELSE (
GOTO PROFILEBACKUP
)
:PROFILEBACKUP
REM To Be replaced by Profile Backup Script
ECHO Correct Location
:ERROR
ECHO Move ProfileBackup.CMD to another location
But the only output I get is:
Enter Username: smithsl
Correct Location
Move ProfileBackup.CMD to another location
Any help would be appreciated
Here is a suggestion for a batch code checking if location of batch file is somewhere in user's profile directory tree.
#echo off
setlocal EnableDelayedExpansion
set "BatchPath=%~dp0"
:EnterName
rem Define as default name of current user.
set "NameOfUser=%UserName%"
set /P "NameOfUser=Enter user name (default: %NameOfUser%): "
rem Remove double quotes from entered string.
set "NameOfUser=!NameOfUser:"=!"
rem Were something different than just double quotes entered?
if "%NameOfUser%"=="" goto EnterName
rem Remove also angle brackets and exclamation marks.
set "NameOfUser=!NameOfUser:<=!"
if "%NameOfUser%"=="" goto EnterName
set "NameOfUser=!NameOfUser:>=!"
if "%NameOfUser%"=="" goto EnterName
set "NameOfUser=%NameOfUser:!=%"
if "%NameOfUser%"=="" goto EnterName
rem Starts the path of the batch file with C:\Users\entered user name?
if not "!BatchPath:C:\Users\%NameOfUser%=!" == "%BatchPath%" goto LocationError
rem To be replaced by profile backup script
echo Correct Location
endlocal
goto :EOF
:LocationError
echo Move %~nx0 to another location.
endlocal
goto :EOF
Although some tests are made for checking invalid user input, it is still not idiot proof as this is nearly impossible when a user can enter something.
The mistake you made was assigning entered value to %UserName% which means if your user account name is for example Scott, the entered string is assigned to an environment variable with name Scott and not to environment variable with name UserName. It is of course no good idea to overwrite the predefined UserName environment variable value by a batch script.
Open a command prompt window, execute there set /? and read the help printed into the console window to understand the string replacements used in this batch code using additionally delayed environment variable expansion.

How to set a variable for the current OS session only

setx permanently modifies environment variables.
set only makes variables available during batch script duration.
Is there any way to set a variable in order to hold its value until the system is restarted?
E.g. in my batch file, I'm checking if the variable is set like this:
if %MYVAR% == 1 (
<block>
)
#ECHO Off
IF NOT EXIST q25244121.org GOTO done
:: reset to original
FOR /f "tokens=1*delims==" %%a IN (q25244121.org) DO (
IF DEFINED %%a FOR /f "tokens=1*delims==" %%p IN ('set %%a') DO (
IF "%%a"=="%%p" IF "%%b" neq "%%q" SET "%%a=%%b"
)
)
:: Delete if not originally defined
FOR /f "tokens=1*delims==" %%p IN ('set') DO (
FINDSTR /L /i /c:"%%p=" q25244121.org >NUL
IF ERRORLEVEL 1 SET "%%p="
)
:done
:: Record current settings
set>q25244121.org
EXIT /b
This may work for you. You would need to change the SET instructions hown in CAPS to SETX. The tempfile would no doubt also need to be placed in a file where the username is part of the name you'd use.
If you were to include this batch in your startup directory, then it should restore the last-saved environment variables' values.
So, on first logon, the current variables' values are stored. On subsequent logons, the environment would be restored to those last stored, regardless of whether a setx had been executed.
You would however need to change procedures. This will restore to a known state. If you actually wanted to setx a value or install some software which adds new environment-variable values or changes existing ones (PATH would be favourite here) then you'd need to run this routine first, make the changes, delete the save file and re-run this routine. Awkward, I'll admit - but it's a way to do it.
Oh - and remember to set your variable after having setx it. You could even write a setxX batch to setx then set (or vice-versa) the required variable.
You may do that via a Batch-JScript hybrid script that use JScript's WshShell.Environment method. The documentation specify that there are four types of environments: System, User, Volatile and Process, and that Volatile type "Applies to current logon session and is not saved between logoffs and restarts", that is exactly what you want:
#if (#CodeSection == #Batch) #then
#echo off
rem Define a persistent variable for the current OS session only via JScript
Cscript //nologo //E:JScript "%~F0" MYVAR "This is the value"
goto :EOF
#end
var colEnvVars = WScript.CreateObject("WScript.Shell").Environment("Volatile");
colEnvVars(WScript.Arguments(0)) = WScript.Arguments(1);
You must save previous code in a .bat file and execute it as any Batch file. Note that the variable defined this way is not available in the current cmd.exe window, but only in future cmd.exe windows opened in the same OS session.
Tested on Windows 8.1.
Ok I found a much simpler (and obviously less hacky) way than the JScript method, but it put us in the right direction with the volatile environment manipulation.
Simply use the reg command to manipulate the volatile environment in the registry :
reg add "HKCU\Volatile Environment" /v "%MYVAR%" /d "%MYVALUE%" /f
reg add both can create and update the value.
\f bypasses the overwrite confirmation.
Setting an empty data with \d "" is equivalent to deleting the global variable. You won't see it in the shell with set, but you can still see it being empty in the registry.
Here is a reference: https://ss64.com/nt/reg.html

Concatenate file paths to an environment variable in batch script

I have made a bat script that should copy the list of folders to a variable but I don't get anything in the variable. In other words, when I echo the variable after my for loop I get the expected output, but in the shell outside after executing the script, I don't see anything set in my variable. How can I get all the variables to copy correctly?
I am using Windows 7.
Batch FIle (script.bat):
#echo off
setlocal enabledelayedexpansion enableextensions
for /r /D %%x in (*) do (
SET PATH_VALUE=%%x;!PATH_VALUE!
)
echo %PATH_VALUE%
Output of windows cmd utility
C:\test> script.bat
C:\test\1;C:\test\2
C:\test> echo %PATH_VALUE%
%PATH_VALUE%
How do I get the %PATH_VALUE% as an environment variable? I found a similar question here but it doesn't quite answer my case.
That is because of your SETLOCAL command that you use to enable delayed expansion. Yes it provides the delayed expansion you need, but it also localizes environment changes. As soon as your batch script ends, there is an implicit ENDLOCAL, and the old environment is restored.
You can pass the value across the ENDLOCAL barrier by adding the following to the end of your script:
endlocal&set "PATH_VALUE=%PATH_VALUE%"
or you could write it like:
(
endlocal
set "PATH_VALUE=%PATH_VALUE%"
)
Both of the above work because the blocks of code are expanded and parsed prior to the ENDLOCAL executing, but the SET statement with the expanded value is executed after the ENDLOCAL.

Resources