Batch variables not expanding at execution time - windows

I'm writing a small inventory script for my IT department and I've run into a little stumbling block while trying to save myself some time.
My goal is to store the current location in a temporary local file (this batch file will be run on a flash drive) so that when I move from one computer to the next, I only have to change the location if it is not accurate. Basically so that if I start in Unit 1, Building A, Room 3, I only have to confirm what is correct and change what isn't.
Perhaps the location confirmation code will give you a better idea of what's going on here.
#echo off
setlocal
setlocal EnableDelayedExpansion
if not exist location.tmp (
echo , , > location.tmp
)
for /f "tokens=1-3 delims=," %%i in (location.tmp) do (
if not "%%i" == " " (
set unit=%%i
)
if not "%%j" == " " (
set building=%%j
)
if not "%%k" == " " (
set room=%%k
)
)
for %%i in (unit,building,room) do (
call :SUB_GET %%i
)
echo !unit!,!building!,!room!> location.tmp
endlocal
exit /b 0
:SUB_GET
if not defined %1 (
set /p %1=What %1?
goto :EOF
)
set /p new%1=What %1? (default is !%1!)
if not "!new%1!" == "" (
set %1=!new%1!
)
Everything works, except for when I try to allow it to keep the default. I want to be able to just hit enter if the value is correct, (e.g. "What room? (default is 403)" hit enter and then it keeps 403)
The problem is, when I do that, even though the code checks the new environment variable (the input) to see if it is empty, it still assigns the old variable the value of the new one (empty string).
What's even more confusing is if I create an existing location.tmp with defaults (like 1,A,4) and hit enter to confirm each one, the batch statement echo !unit!,!building!,!room!>location.tmp executes, but location.tmp is unchanged.
Any ideas what's going on here?

set /p %1=What %1? (default is !%1!)
If you simply reply Enter to set /p then the variable remains unchanged.
So - no need for new%1 - and even then new would have been sufficient as there is no obvious need for newROOM etc. You'd have had to clear new judiciously to avoid retaining stale data though.

The delayed execution of the variables in the line echo !unit!,!building!,!room!>location.tmp is causing them to be expanded too late and thus the statement doesn't properly execute for whatever reason. Replacing them with normal %variables% fixes both issues. It turns out the comparison was working correctly, just not getting expanded in time.
EDIT: No idea why, but the above is incorrect. The below script works perfectly fine...
#echo off
setlocal
setlocal EnableDelayedExpansion
if not exist location.tmp (
echo , , > location.tmp
)
for /f "tokens=1-3 delims=," %%i in (location.tmp) do (
if not "%%i" == " " (
set unit=%%i
)
if not "%%j" == " " (
set building=%%j
)
if not "%%k" == " " (
set room=%%k
)
)
for %%i in (unit,building,room) do (
call :SUB_GET %%i
)
echo !unit!,!building!,!room!> location.tmp
endlocal
exit /b 0
:SUB_GET
if not defined %1 (
set /p %1=What %1?
goto :EOF
)
set /p %1=What %1? (default is !%1!)

Related

Windows batch if not equal do not execute if clause

I have a .bat file to execute in a windows server 2012 R2 machine. Problem is that I need to add an "if not equals" but when I try to execute it the code does not work.
I know that problem is in the if because I tried without the if and it works.
setlocal enabledelayedexpansion
set anno=%date:~6,4%
set mese=%date:~3,2%
set giorno=%date:~0,2%
set ore=%time:~0,2%
set minuti=%time:~3,2%
set secondi=%time:~6,2%
e:
mkdir "E:\Presenze\%anno%%mese%%giorno%%ore%%minuti%%secondi%"
move \\gbjob09\Info-Bit\Sql_Ges\PRESENZE\2019\*.old "E:\Presenze\%anno%%mese%%giorno%%ore%%minuti%%secondi%"
cd "E:\Presenze\%anno%%mese%%giorno%%ore%%minuti%%secondi%"
for /f %%a in ('dir /b *.old') do (
set originalname=%%a
set timbrnumber=!originalname:~4,4!
if NOT %timbrnumber% == "0584" (
if NOT %timbrnumber% == "0585" (
set shortname=9!originalname:~4,4!
echo !shortname!
for /f "tokens=*" %%b in (%%a) do (
echo %%b!shortname!>> "Presenze.txt"
)
)
)
)
copy "E:\Presenze\%anno%%mese%%giorno%%ore%%minuti%%secondi%\Presenze.txt" "E:\Presenze\presenze.txt"
exit
Like you can see the problem is only this really small part:
if NOT %timbrnumber% == "0584" (
I already tried to remove the double if but it still does not work.
I know that the old code (this one) works:
setlocal enabledelayedexpansion
set anno=%date:~6,4%
set mese=%date:~3,2%
set giorno=%date:~0,2%
set ore=%time:~0,2%
set minuti=%time:~3,2%
set secondi=%time:~6,2%
e:
mkdir "E:\Presenze\%anno%%mese%%giorno%%ore%%minuti%%secondi%"
move \\gbjob09\Info-Bit\Sql_Ges\PRESENZE\2019\*.old "E:\Presenze\%anno%%mese%%giorno%%ore%%minuti%%secondi%"
cd "E:\Presenze\%anno%%mese%%giorno%%ore%%minuti%%secondi%"
for /f %%a in ('dir /b *.old') do (
set originalname=%%a
set shortname=9!originalname:~4,4!
echo !shortname!
for /f "tokens=*" %%b in (%%a) do (
echo %%b!shortname!>> "Presenze.txt"
)
)
copy "E:\Presenze\%anno%%mese%%giorno%%ore%%minuti%%secondi%\Presenze.txt" "E:\Presenze\presenze.txt"
exit
I only added a variable:
set timbrnumber=!originalname:~4,4!
that should work (it shouold get 4 characters after exclude 4 characters of the originalname variable) like shortname print 9 and 4 characters after exclude 4 characters of the originalname variable, but I'm not really sure because I don't really know windows batch (this is the first time that I use it and the original code was not mine).
After that I only need to check if timbrnumber is not "0584" and it's not "0585" but everything I tried was a fail.
In short, as your variable is being set and used inside a code block and double quoting is required both sides of the ==, this section should fix your issue:
if NOT "1!timbrnumber! == "10584" (
if NOT "!1timbrnumber!" == "10585" (
which can be better written as:
if 1!timbrnumber! neq 10584 (
if 1!timbrnumber! neq 10585 (
Another observation, your time:
set ore=%time:~0,2%
set minuti=%time:~3,2%
set secondi=%time:~6,2%
can be better created as:
set mytime=%time::=%
echo %mytime:0,6%

The syntax of the command is incorrect. at endlocal & set [batch]

I have a function which returns value by reference in batch.
:errorCheck
setlocal
set "test_command=%~1"
set "err_code=%~2"
FOR /F "delims=" %%a IN ('%test_command% 2^>^&1 1^>NUL') DO (
set err_msg="%%~a"
)
if [%err_msg%] neq [] (
if not x%err_msg:%err_code%=%==x%err_msg% (
set "error=true"
)
)
if [%err_msg%]==[] (
set "error=false"
)
endlocal & set "%3=%error%"
exit /b
The function executes correctly and the return value is also correct but at line endlocal & set "%3=%error%" while executing set "%3=%error%" part it gives me error :
The syntax of the command is incorrect.
i am unable to comprehend why is it happening though the return value is correct.
The problem is not in the return value, but in the substring operation. Your syntax is not allowed. The expression is not evaluated as you think. The start and end of variables are
%err_msg:%err_code%=%
^........^ ^.^
var1 var2
To use a variable in a substring operation in another variable you will need to use delayed expansion. Try with
:errorCheck
setlocal enableextensions disabledelayedexpansion
set "test_command=%~1"
set "err_code=%~2"
set "error=false"
set "err_msg="
FOR /F "delims=" %%a IN ('
%test_command% 2^>^&1 1^>NUL
') DO set "err_msg=%%~a"
if defined err_msg (
setlocal enabledelayedexpansion
if not "!err_msg:%err_code%=!"=="%err_msg%" (
endlocal
set "error=true"
) else ( endlocal )
)
endlocal & set "%3=%error%"
exit /b
Now the variables seen by the parser are
!err_msg:%err_code%=!
^........^
^...................^
But as not all characters are allowed in a substrig operation, depending on the contents of err_code it is possible that it will also fail.
If it can be the case, you can change the substring operation into a piped command searching for the required error code
:errorCheck testCommand errorCode returnVariable
setlocal enableextensions disabledelayedexpansion
( %~1 2>&1 1>nul | find "%~2" > nul ) && ( set "error=true" ) || ( set "error=false" )
endlocal & set "%~3=%error%"
exit /b
That is:
execute the command (%~1) with, as in the original code, the stdout redirected to null and stderr redirected to stdout, so we read the error stream.
The output of the command is filtered with find, searching the error code. If it is found, find will not raise error level, if it is not found, errorlevel will be raised.
Using conditional execution, the error variable is set. If the previous command does not raise errorlevel, the code after the && is executed. If the command raised errorlevel, the code after the || is executed.
Environment space is restored and the return variable asigned

Escape asterisk in Windows Batch File's FOR Loop

When running the following code in a windows batch file everything works aside from the string containing the asterisk, which is skipped. Checking the passed parameters by number (i.e. echo(%~6) I can see the asterisk - it's only when passed to the FOR loop that I have an issue:
#echo off
setlocal enableextensions enabledelayedexpansion
call:Concat cmd "this is a demo" " of concat functionality." " Hopefully it will work;" " but it doesn't when I pass an" " * asterisk" " character"
echo !cmd!
#goto:end
#goto:eof
:Concat
::Concatenates a given list of strings without including their quotes
::1 - output variable
::2* - strings to concat
echo(%*
set /a xx=0
set Concat_tempFlag=0
set Concat_temp=
for %%A in (%*) do (
set /a xx=!xx!+1
echo !xx! - %%A
if !Concat_tempFlag!==1 (
set Concat_temp=!Concat_temp!%%~A
) else (
set Concat_tempFlag=1
)
)
set "%~1="%Concat_temp%""
#goto:eof
:End
echo(Bye
exit /b 0
I've attempted for /F (tokens=*) %%A in ('echo(%*') do ( as suggested here: Batch FOR loop with asterisk (and variations thereof) but with no luck. Any ideas? Thanks in advance.
Found the solution here: I need to match or replace an asterisk * in a batch environmental variable using only native Windows commands. Is this possible?
Full code below:
#echo off
setlocal enableextensions enabledelayedexpansion
set DEFAULT_AsteriskMarker=_xAsteriskMarkerx_
call:Concat cmd "this is a demo" " of concat functionality." " Hopefully it will work;" " but it doesn't when I pass an" " * asterisk" " character"
echo !cmd!
#goto:end
#goto:eof
:Concat
::Concatenates a given list of strings without including their quotes
::1 - output variable
::2* - strings to concat
set Concat_StringsToConcat=%*
echo(%Concat_StringsToConcat%
call:AsteriskFix Concat_StringsToConcat
set /a xx=0
set Concat_tempFlag=0
set Concat_temp=
for %%A in (%Concat_StringsToConcat%) do (
set /a xx=!xx!+1
echo !xx! - %%A
if !Concat_tempFlag!==1 (
set Concat_temp=!Concat_temp!%%~A
) else (
set Concat_tempFlag=1
)
)
set "%~1="!Concat_temp:%DEFAULT_AsteriskMarker%=*!"
#goto:eof
:AsteriskFix
::https://stackoverflow.com/questions/11685375/i-need-to-match-or-replace-an-asterisk-in-a-batch-environmental-variable-using
set AsteriskFix_temp=!%~1!
if "%~2"=="" (
set AsteriskFix_marker=%DEFAULT_AsteriskMarker%
) else (
set AsteriskFix_marker=%~2
)
call:StrLen AsteriskFix_temp AsteriskFix_len
for /l %%x in (0,1,%AsteriskFix_len%) do if not "!AsteriskFix_temp:~%%x,1!"=="" if "!AsteriskFix_temp:~%%x,1!"=="*" (
set /a AsteriskFix_plusone=%%x+1
for /l %%y in (!AsteriskFix_plusone!, 1, !AsteriskFix_plusone!) do (
set AsteriskFix_temp=!AsteriskFix_temp:~0,%%x!%AsteriskFix_marker%!AsteriskFix_temp:~%%y!
)
)
set "%~1=!AsteriskFix_temp!"
#goto:eof
:StrLen
::http://www.dostips.com/DtCodeCmdLib.php#strLen
set "StrLen_str=A!%~1!" &:: keep the A up front to ensure we get the length and not the upper bound
::it also avoids trouble in case of empty string
set "StrLen_len=0"
for /L %%A in (12,-1,0) do (
set /a "StrLen_len|=1<<%%A"
for %%B in (!StrLen_len!) do if "!StrLen_str:~%%B,1!"=="" set /a "StrLen_len&=~1<<%%A"
)
IF "%~2" NEQ "" SET /a %~2=%StrLen_len%
#goto:eof
:End
echo(Bye
exit /b 0
Thanks to James K
The link you provided leads to the right answer:
There is no way to preserve an asterisk (nor a question mark) in the set of a normal (no /F option) FOR command (they are always changed to file names); you need to separate the parameters in a FOR /F command. If you also want to process each parameter in a FOR loop, then the second FOR can NOT be in the same context, so you must CALL a subroutine to change the context

In a batch file, how can I get the value of an environment variable whose name is the value of another environment variable?

If I know that one environment variable contains the name of another, how can I get the value of the second environment variable?
Assume I have a file java.properties alongside my batch file with the following contents.
JAVA_HOME_OVERRIDE_ENV_VAR=JAVA_HOME_1_7_0_17
What I want to do is check if JAVA_HOME_1_7_0_17 is set and, if so, do the equivalent of set JAVA_HOME=%JAVA_HOME_1_7_0_17%. I can figure out what environment variable I'm looking for, but I don't know how to get its value. This is what I have so far...
#echo off
setlocal enabledelayedexpansion
if exist %~dp0\java.properties (
echo "Found java properties."
for /F "tokens=1* usebackq delims==" %%A IN (%~dp0\java.properties) DO (
if "%%A"=="JAVA_HOME_OVERRIDE_ENV_VAR" set JAVA_HOME_OVERRIDE_ENV_VAR=%%B
)
if not [!JAVA_HOME_OVERRIDE_ENV_VAR!] == [] (
echo "Override var is !JAVA_HOME_OVERRIDE_ENV_VAR!"
REM This is where I'm stuck!!!
REM Assume JAVA_HOME_OVERRIDE_ENV_VAR is JAVA_HOME_1_7_0_17
)
)
endlocal & set JAVA_HOME=%JAVA_HOME%
What I want to do is check if the environment variable JAVA_HOME_1_7_0_17 exists and, if it does, use its value to set JAVA_HOME.
Updated
I think the nested if statements are making things more difficult then needed. I got rid of them and the following seems to work.
#echo off
setlocal enabledelayedexpansion
if not exist "%~dp0\java.properties" (
goto:EOF
)
for /F "tokens=1* usebackq delims==" %%A IN ("%~dp0\java.properties") DO (
if "%%A"=="JAVA_HOME_OVERRIDE_ENV_VAR" set JAVA_HOME_OVERRIDE_ENV_VAR=%%B
)
if [!JAVA_HOME_OVERRIDE_ENV_VAR!] == [] (
goto:EOF
)
set JAVA_HOME=!%JAVA_HOME_OVERRIDE_ENV_VAR%!
endlocal & set JAVA_HOME="%JAVA_HOME%"
Try set JAVA_HOME=%!JAVA_HOME_OVERRIDE_ENV_VAR!%.
EDIT: This should not work if !JAVA_HOME_OVERRIDE_ENV_VAR! was set on the same line. Try
call set JAVA_HOME=!%JAVA_HOME_OVERRIDE_ENV_VAR%!
a downside being that since it will search the disk for a file/executable with the name set, the command should take slightly longer to finish, though it should only be noticeable in large loops.
EDIT 2: Try this too...
(add set override=0 in front, add set override=1 under if not, and replace the endlocal line)
#echo off
setlocal enabledelayedexpansion
set override=0
if exist %~dp0\java.properties (
echo "Found java properties."
for /F "tokens=1* usebackq delims==" %%A IN (%~dp0\java.properties) DO (
if "%%A"=="JAVA_HOME_OVERRIDE_ENV_VAR" set JAVA_HOME_OVERRIDE_ENV_VAR=%%B
)
if not [!JAVA_HOME_OVERRIDE_ENV_VAR!] == [] (
echo "Override var is !JAVA_HOME_OVERRIDE_ENV_VAR!"
set override=1
REM Assume JAVA_HOME_OVERRIDE_ENV_VAR is JAVA_HOME_1_7_0_17
)
)
endlocal & if override=1 set JAVA_HOME=!%JAVA_HOME_OVERRIDE_ENV_VAR%!
I would use FINDSTR to filter out the relevant line, IF DEFINED to validate the existence of the variable, and delayed expansion within the loop to get the appropriate value.
Your code could be as simple as:
#echo off
setlocal enableDelayedExpansion
for /f "tokens=1* delims==" %%A in (
'2^>nul findstr /bil "JAVA_HOME_OVERRIDE_ENV_VAR=" "%~dp0\java.properties"'
) do if defined %%B set "JAVA_HOME=!%%B!"
endlocal & set "JAVA_HOME=%JAVA_HOME%"

Is there any way to programmatically add a startup script to the local group policy?

I need to write a script that can add itself to the startup scripts in the local group policy so that it can run even when no users are logged in. This can be done using gpedit.msc and going into Computer Configuration > Windows Settings > Scripts > Startup. However, I haven't found a way to do this programmatically.
I've looked into simply editing the registry. I found the relevant location to be HKLM\Software\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Startup, but simply adding my own entry does not have any effect. The computer is not part of a domain.
Does anyone know how to do this? Is there a WMI approach?
I think you have to modify %windir%\system32\GroupPolicy\gpt.ini, appending [{42B5FAAE-6536-11D2-AE5A-0000F87571E3}{40B6664F-4972-11D1-A7CA-0000F87571E3}] to the gPCMachineExtensionNames line and incrementing the Version value by one. (source).
Try adding and removing a script via group policy editor and you can watch how gpt.ini changes. When you add a script, you can also use the structure created in HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Startup\0 as a template.
For anyone coming across this thread whose machine is a member of a domain, I've noticed that domain-defined group policies appear in the registry after local policies. So if you've already got a domain policy at ...\Scripts\Startup\0, you should copy it to ...\Scripts\Startup\1 before creating your local machine policy.
In any case, expirement with the GUI and see how stuff changes before attempting programmatically.
You'll also need to run gpupdate to refresh group policies.
I am working on a script for this and my testing shows you do not have to edit the registry at all. Follow these steps and it will work
Find the last script number in scripts.ini (There are two lines for each script "0CmdLine=" and "0Parameters=".
Add two lines for each of your scripts to be added (e.g. "1CmdLine=myscript.vbs" and "1Parameters="
Increment the "version=" number in gpt.ini
Run Gpupdate to apply it
Important Note for scripting a solution: gpt.ini uses UTF-8 encoding, scripts.ini uses Unicode. Cheers M$!
Hope this helps people.
Shaun
Just configure it manually on one machine and run gpupdate /force. Then copy %systemroot%\System32\GroupPolicy from your source machine to %systemroot%\System32\GroupPolicy on the rest of your machines.
even though it's an older post, i think people might still be looking for the same scenario (as was i).
please find below a batch of mine for extending the scripts.ini.
you only need 2 or 3 parameters, example at the end of the script.
also, keep in mind to edit the gpt.ini if required!
more information on the gpt.ini here
easiest way to determine the GUIDs is to edit in gpedit.msc and watch the changes.
please be careful with the script and test it before use in productive environment!
#echo off
setlocal enabledelayedexpansion
REM get parameter for scripts.ini changes
if not "%~1"=="" (
set type=%1
) else (
goto enderror
)
if not "%~2"=="" (
set cmd=%2
) else (
goto enderror
)
if not "%~3"=="" (
set params=%3
) else (
set params=
)
if not exist scripts.ini echo. 2>scripts.ini
if exist scripts.ini (
set ctr=0
for /f %%a in (scripts.ini) do (
echo %%a | findstr /C:"[Logon]" 1>nul
if not errorlevel 1 (
set /a ctr+=1
)
)
if !ctr!==0 (
echo [Logon]>>scripts.ini
)
set ctr=0
for /f %%a in (scripts.ini) do (
echo %%a | findstr /C:"[Logoff]" 1>nul
if not errorlevel 1 (
set /a ctr+=1
)
)
if !ctr!==0 (
echo [Logoff]>>scripts.ini
)
)
REM remove scripts-new.ini if exists
if exist scripts-new.ini (
del /F /Q scripts-new.ini
)
REM ctr = number at front for each cmd-param pair - subctr = counter for lines --> pairs - diff = change from Logon to Logoff or vice versa
set ctr=0
set subctr=0
set diff=0
set used=0
for /f %%a in (scripts.ini) do (
set line=%%a
echo !line! | findstr /C:"[Logoff]" 1>nul
if not errorlevel 1 (
if !diff!==1 goto endlogon
)
echo !line! | findstr "CmdLine=!cmd!" 1>nul
if not errorlevel 1 (
set /a used+=1
)
if !diff!==1 (
echo !ctr!!line:~1!>>scripts-new.ini
set /a subctr+=1
if !subctr!==2 (
set /a ctr+=1
set subctr=0
)
)
echo !line! | findstr /C:"[Logon]" 1>nul
if not errorlevel 1 (
set diff=1
echo !line!>>scripts-new.ini
)
)
:endlogon
if /I !type!==logon if !used!==0 (
echo !ctr!CmdLine=!cmd!>>scripts-new.ini
echo !ctr!Parameters=!params!>>scripts-new.ini
)
set ctr=0
set diff=0
set used=0
for /f %%a in (scripts.ini) do (
set line=%%a
echo !line! | findstr /C:"[Logon]" 1>nul
if not errorlevel 1 (
if !diff!==1 goto endlogoff
)
echo !line! | findstr "CmdLine=!cmd!" 1>nul
if not errorlevel 1 (
set /a used+=1
)
if !diff!==1 (
echo !ctr!!line:~1!>>scripts-new.ini
set /a subctr+=1
if !subctr!==2 (
set /a ctr+=1
set subctr=0
)
)
echo !line! | findstr /C:"[Logoff]" 1>nul
if not errorlevel 1 (
set diff=1
echo !line!>>scripts-new.ini
)
)
:endlogoff
if /I !type!==logoff if !used!==0 (
echo !ctr!CmdLine=!cmd!>>scripts-new.ini
echo !ctr!Parameters=!params!>>scripts-new.ini
)
goto end
:enderror
echo Usage: scripts-extender.bat [LOGON ^| LOGOFF] [Script Name] "[optional Parameters for Script - WITH QUOTES!]"
echo Example: scripts-externder.bat logon netlogon.bat "param1 param2"
:end
move /Y scripts.ini scripts-old.ini
move /Y scripts-new.ini scripts.ini

Resources