1

My goal is to modify a single setting in an INI file over the network on multiple PC's using an external list of PCs, and output the results to a log file.

  • Needs to be a windows batch file. Network is locked out of running scripts such as PS.
  • Security is not an issue, as read\write access is open at my level to all targeted PCs.
  • The content of the INI file to be modified, is unique to the PC, so blanket overwrite/copy is not an option.
  • The removal or addition of blank lines in the INI is not an issue.
  • The file PCList.txt, could contain 1-100 PC names or IP's, one per line.
  • Output to a log file with PCNAME: 'Success' or 'Fail'

So far, I have found and modified a script that will edit a file locally, but have been unable to get it to work with a FOR/DO loop to process that action for each PC in the list - OR - add the logging output

@Echo Off
SetLocal EnableDelayedExpansion

Set _PathtoFile=C:\Test\Sample.ini
Set _OldLine=Reboot=
Set _NewLine=Reboot=1

Call :_Parse "%_PathtoFile%"
Set _Len=0
Set _Str=%_OldLine%
Set _Str=%_Str:"=.%987654321

:_Loop
If NOT "%_Str:~18%"=="" Set _Str=%_Str:~9%& Set /A _Len+=9& Goto _Loop
Set _Num=%_Str:~9,1%
Set /A _Len=_Len+_Num
PushD %_FilePath%
If Exist %_FileName%.new Del %_FileName%.new
If Exist %_FileName%.old Del %_FileName%.old
Set _LineNo=0
For /F "Tokens=* Eol=" %%I In (%_FileName%%_FileExt%) Do (
    Set _tmp=%%I
    Set /A _LineNo+=1
    If /I "!_tmp:~0,%_Len%!"=="%_OldLine%" (
        >>%_FileName%.new Echo %_NewLine%
    ) Else (
        If !_LineNo! GTR 1 If "!_tmp:~0,1!"=="[" Echo.>>%_FileName%.new
        SetLocal DisableDelayedExpansion
        >>%_FileName%.new Echo %%I
        EndLocal
    ))
Ren %_FileName%%_FileExt% %_FileName%.old
Ren %_FileName%.new %_FileName%.ini
PopD
Goto :EOF

:_Parse
Set _FilePath=%~dp1
Set _FileName=%~n1
Set _FileExt=%~x1
Goto :EOF

Here is the sample files: Settings.ini

[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=0
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567

PCList.txt

MY-PC
YOUR_PC
NETSERVER
192.168.10.100

Still trying to wrap my head around everything that this script is doing - this was the only information provided in the tech's answer (source of the initial script) It preserves the original file by renaming it with a .old extension It will remove all blank lines, but will insert a blank line before any line that starts with [ (unless it's the first line in the file)

I get the length of the specified search line in case the old line in the file has trailing spaces.
If more than one line starts with the old line text, it will be changed as well. Example, if the file has these lines: test=line1 test=line1 again and you set _OldLine to test=line1, both lines will be changed.
If that might be a problem,
change this line:
If /I "!_tmp:~0,%_Len%!"=="%_OldLine%" (
to this:
If /I "!_tmp!"=="%_OldLine%" (
Just keep in mind that with this, if the old line in the file has trailing spaces, it won't be changed unless you include them in the _OldLine variable

The main thing I need at this point is getting this action to take place using the above script... Or something similar, on a list of PC's listed in an external TXT file - over the network. I'm open to alternative approaches provided they are windows batch scripting, and do not include calling an external application.

Wish list, (by no means required at this time):

  • Ability to specify or set the [SECTION] in the INI where the setting being modified is found, some potential for the same setting to be found in multiple sections within the same INI (not the case with my initial needs, but I can see it being the case in the future)
Compo
  • 30,301
  • 4
  • 20
  • 32
Tricktech
  • 15
  • 5
  • 1
    [related](https://stackoverflow.com/search?q=%5Bbatch-file%5D+edit+ini+file) – Stephan Jan 15 '21 at 19:00
  • Thank you Stephan, I have looked through and tested most every related article here, but none deal with getting the action working for an external list of PC's, all attempts I believe are related to how the variables get set and reset - or simple syntax\formatting I am failing to understand. So the root question remains of how to get the provided script to run for a list of PCs in an external file. – Tricktech Jan 15 '21 at 19:10
  • 2
    Can you please provide an actual task with genuine INI file content, an actual value name and content pair, the result you intend to achieve, and an explanation of how the code you have written is failing to do that. What you have currently posted appears to be generic, as if you are hoping for an answer you can copy and modify very easily yourself for whatever other task you have in future. That is not really how things work here, you effectively have multiple questions/issues in one post. Limit your question to a single reproducible issue, and edit your question, to define it better for us. – Compo Jan 15 '21 at 19:35
  • Thanks Compo - "The main thing I need at this point is getting this action to take place using the above script" - Meaning to complete this script for each PC on the list. The "Actual INI" or "PCList" is irrelevant since that will change based on the values of the set statements. This is a tool for repeated use. Yes, there are several criteria I want to make this work for and I listed them to avoid someone like yourself from asking them later anyway. There are not several questions here - just one primary. THANKS – Tricktech Jan 15 '21 at 19:54
  • Tip: Prepend the `\\MachineName\d$\`, where `d` is the drive letter of the path to the file on that machine, to the ini file path and run the script from an elevated console of a network admin's account. – jwdonahue Jan 15 '21 at 19:55
  • I'm sorry @Tricktech, but that is incorrect. Your question definitely contains multiple questions. It appears to me that there is no reason why you cannot take any existing answer which reads every line from a text file to propagate a variable with the line content. With that variable you can either call your batch file or a labelled section containing your existing code, using that variable as an argument. Those are basic procedures which have been supplied in many solutions within this site under the [[tag:batch-file]] tag. I advise that you use the search facility to find those examples. – Compo Jan 15 '21 at 20:46
  • For example: `@For /F "UseBackQ Delims=" %%G In ("P:\ath\To\PCList.txt") Do @Call ["BatchFile"|:Label] "%%G"`. The PC name ot IP would then be available as `%~1` for further use as necessary. – Compo Jan 15 '21 at 20:51
  • @Compo Ill give that a try - I was trying to put the script in the do( rather than calling a separate batch. Seems simple enough. – Tricktech Jan 15 '21 at 20:59
  • @Compo - to be clear - The for loop calling an external file, I have done many times... in fact by using many of the answers you are referencing... I just couldn't figure out how to get this one to work for reasons I mentioned in my first reply. thanks – Tricktech Jan 15 '21 at 21:10
  • You have not provided any code whatsover, using that methodology @Tricktech, _(which if that is your question, you should have done)_. As I've laready stated, we are supposed to be assisting with a single specific and reproducible issue with your provided code. If you have not provided that code, we cannot assist you to fix it. – Compo Jan 15 '21 at 21:14

3 Answers3

0

Batch file code for the task

I suggest to use the following code:

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SectionName=[SAMPLE SETTINGS]"
set "EntryName=Reboot"
set "EntryValue=1"
set "LogFile=%~dpn0.log"
set "TempFile=%TEMP%\%~n0.tmp"
set "ListFile=%~dp0PCList.txt"

if not exist "%ListFile%" (
    echo ERROR: List file %ListFile%" not found.>"%LogFile%"
    goto EndBatch
)

del /A /F "%LogFile%" 2>nul
for /F "usebackq delims=" %%I in ("%ListFile%") do call :UpateIniFile "\\%%I\C$\Test\Sample.ini"
goto EndBatch

:UpateIniFile
if not exist %1 (
    echo File not found:   %1>>"%LogFile%"
    goto :EOF
)
set "EmptyLines="
set "EntryUpdate="
set "CopyLines="
(for /F delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /N "^" %1 2^>nul') do (
    set "Line=%%I"
    setlocal EnableDelayedExpansion
    if defined CopyLines (
        echo(!Line:*:=!
        endlocal
    ) else if not defined EntryUpdate (
        echo(!Line:*:=!
        if /I "!Line:*:=!" == "!SectionName!" (
            endlocal
            set "EntryUpdate=1"
        )
    ) else (
        if /I "!Line:*:=!" == "!EntryName!=!EntryValue!" (
            endlocal
            goto ValueExists
        )
        if "!Line:*:=!" == "" (
            endlocal
            set /A EmptyLines+=1
        ) else (
            set "Line=!Line:*:=!"
            if "!Line:~0,1!!Line:~-1!" == "[]" (
                echo !EntryName!=!EntryValue!
                if defined EmptyLines for /L %%J in (1,1,!EmptyLines!) do echo(
                echo !Line!
                endlocal
                set "EntryUpdate=3"
                set "CopyLines=1"
            ) else (
                if defined EmptyLines for /L %%L in (1,1,!EmptyLines!) do echo(
                for /F delims^=^=^ eol^= %%J in ("!Line!") do (
                    if /I not "%%~J" == "!EntryName!" (
                        echo !Line!
                        endlocal
                    ) else (
                        echo !EntryName!=!EntryValue!
                        endlocal
                        set "EntryUpdate=2"
                        set "CopyLines=1"
                    )
                )
                set "EmptyLines="
            )
        )
    )
))>"%TempFile%"

if not defined EntryUpdate (
    >>"%TempFile%" echo %SectionName%
    >>"%TempFile%" echo %EntryName%=%EntryValue%
    set EntryUpdate=4
)
if %EntryUpdate% == 1 (
    >>"%TempFile%" echo %EntryName%=%EntryValue%
    set "EntryUpdate=3"
)

move /Y "%TempFile%" %1 2>nul
if errorlevel 1 (
    echo Failed to update: %1>>"%LogFile%"
    del "%TempFile%"
    goto :EOF
)

if %EntryUpdate% == 2 (
    echo Value updated in: %1>>"%LogFile%"
    goto :EOF
)
if %EntryUpdate% == 3 (
    echo Entry added to:   %1>>"%LogFile%"
    goto :EOF
)
if %EntryUpdate% == 4 (
    echo Section+entry to: %1>>"%LogFile%"
    goto :EOF
)

:ValueExists
echo Value existed in: %1>>"%LogFile%"
del "%TempFile%"
goto :EOF

:EndBatch
endlocal

File related variables defined at top

The log file is created in directory of the batch file with batch file name, but with file extension .log.

The temporary file is created on local machine in directory for temporary files with batch file name, but with file extension .tmp.

The list file containing the computer names or IP addresses must be PCList.txt in directory of the batch file.

Explanation of main code

For each line in the list file not starting with ; the first FOR loop calls a subroutine with the full file name of the INI file to update with appropriate UNC path. A semicolon at beginning of a line can be used to comment out a computer name or IP address.

Requirements for the INI file update routine

The subroutine is written to fulfill following requirements:

  1. The subroutine should not modify the INI file if it contains already the entry to update with the correct value in correct section. I don't like it setting archive attribute and modifying the last modification date of a file if the file contents is not really modified at all.
  2. It should replace the value of the entry in the correct section on being different to the value defined at top of the batch file.
  3. It should add the entry with the wanted value to the section if the section is already existing, but does not yet contain the entry. The entry should be added after last non-empty line of the section.
  4. It should add the section and the entry with the wanted value at end of the file if the file does not contain the section at all.

The subroutine is written to keep all empty lines in file, except empty lines at end of the file if just the entry or the section and the entry must be appended at end of the file. It does not add empty lines. It could be enhanced to additionally reformat an INI file with making sure that there is always exactly one empty line above a section except at top of the file.

Sample set of files

There were six files used in current directory for testing with using as first FOR loop the following command line instead of the command line in posted batch file code:

for %%I in (*.ini) do call :UpateIniFile "%%I"

File1.ini with having the section and the entry, but with wrong value:

[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=0

[SECTION2]
SETTINGX=1234

[SECTION3]
SETTINGX=4567

File2.ini with having the section and the entry with the wanted value:

[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=1
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567

File3.ini with having the section at top, but not containing the entry:

[SAMPLE SETTINGS]
SERVER=MYPC
[SECTION2]
SETTINGX=1234

[SECTION3]
SETTINGX=4567

File4.ini with missing the section completely:

[SECTION2]
SETTINGX=1234

[SECTION3]
SETTINGX=4567

File5.ini with having the section at end, but not containing the entry:

[SECTION2]
SETTINGX=1234

[SECTION3]
SETTINGX=4567

[SAMPLE SETTINGS]


SERVER=MYPC

File6.ini is like File1.ini, but read-only attribute is set for this file.

Results for sample set of files

The batch file writes to the log file for this sample set:

Value updated in: "File1.ini"
Value existed in: "File2.ini"
Entry added to:   "File3.ini"
Section+entry to: "File4.ini"
Entry added to:   "File5.ini"
Failed to update: "File6.ini"

File2.ini and File6.ini are not updated at all.

The other four files have the following lines after batch file execution:

File1.ini:

[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=1

[SECTION2]
SETTINGX=1234

[SECTION3]
SETTINGX=4567

File3.ini:

[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=1
[SECTION2]
SETTINGX=1234

[SECTION3]
SETTINGX=4567

File4.ini:

[SECTION2]
SETTINGX=1234

[SECTION3]
SETTINGX=4567
[SAMPLE SETTINGS]
Reboot=1

File5.ini:

[SECTION2]
SETTINGX=1234

[SECTION3]
SETTINGX=4567

[SAMPLE SETTINGS]


SERVER=MYPC
Reboot=1

So the batch file makes a good job for the sample set of files.

Explanation of INI file update routine

There is first checked if the file to update is found at all and an appropriate information is appended to the log file if the file cannot be found.

The subroutine undefines next three environment variables used in the code below.

The FOR loop used in subroutine UpateIniFile is explained in full detail by my answer on
How to read and print contents of text file line by line?

Every line in the INI file output by FINDSTR with line number and colon at beginning is assigned first to environment variable Line with delayed expansion not enabled to prevent line corruption on containing exclamation marks.

Next delayed expansion is enabled which results in creating also a copy of the existing environment variables. Please read lower half of this answer for details about the commands SETLOCAL and ENDLOCAL. It is really important to understand what these two commands do to understand the rest of the code.

The first IF condition is true if the entry of which value should be updated is already in the temporary file with the wanted value and so all remaining lines in the file can be copied to temporary file without any further processing. In this case the line read from file is output with removing the line number and the colon added by FINDSTR to the line.

The second IF condition is true if the section defined at top of the batch file was not found up to current line. In this case the line read from file is also output with line number and colon, but an additional case-insensitive string comparison is done to find out if this line contains the section of interest. If the section of interest is found, the environment variable EntryUpdate is defined with value 1 in the environment defined on entering the subroutine.

Otherwise the current line is below the section of interest.

If this line without line number and colon is case-insensitive equal the entry to update with wanted value, the processing of the lines can be stopped as the file contains the entry already with the wanted value. Command GOTO is used to exit the FOR loop and continue batch file processing below the batch label ValueExists where the temporary file is deleted and the information is written into the log file that the value exists already in the current file before leaving the subroutine.

If the line in section of interest without line number and colon is an empty line, the environment variable EmptyLines is incremented by one in previous environment as defined on entering the subroutine. The value 0 is used on EmptyLines not defined all as explained by help of command SET. There is nothing else done on line in section of interest is an empty line.

For a non-empty line in section of interest the environment variable Line is first redefined with removal of line number and colon for easier processing this line further.

The next condition compares case-sensitive the first and the last character of the line extra enclosed in double quotes with the string "[]" to find out if this line is the beginning of a new section. If this condition is true, the section of interest does not contain the entry at all. Therefore the entry with the wanted value is output now and next all empty lines found perhaps before the new section not yet output. Then the line with the new section is output and in environment defined on entering the subroutine the environment variable EntryUpdate is redefined with string value 3 and environment variable CopyLines is defined as all other lines in file can now be just copied to the temporary file.

But if the current line is not a new section, it is a non-empty line in section of interest. It could be the line with the entry of which value is not the wanted value or a different entry. Therefore first all empty lines are output first which are perhaps above the current entry line in section of interest.

Next one more command FOR is used to split up the current line and get assigned to the loop variable J the string left to first equal sign (after zero or more equal signs at beginning of the line which hopefully no INI file contains ever).

If the current line in section of interest is case-insensitive not the entry of which value should be updated, the entry line is simply output. Otherwise the entry line to update is found and so the line is output with the entry name and the wanted value before the environment variable EntryUpdate is redefined with string value 2 and environment variable CopyLines is defined to copy all other lines simply to the temporary file.

Everything output inside the FOR loop processing the lines of the INI file is redirected into the temporary file.

It could be that current INI file does not contain the section of interest at all which can be found out after processing all lines by checking if the environment variable EntryUpdate is still not defined. In this case the section and the entry with the wanted value is appended to the temporary file with ignoring environment variable EmptyLines to ignore all empty lines at bottom of the INI file.

It is also possible that the last section in the INI file is the section of interest, but the entry with the value to update is not found up to the end of the file. So one more IF condition is used to check if it is necessary to append at end of the file with ignoring all empty lines at end of the file the entry line with the wanted value.

Now the temporary file contains the lines which the just processed INI file should contain after finishing batch file execution. So the temporary file is moved to INI file directory with replacing the INI file if it is not read-only or write-protected by file permissions or write-protected by file access permissions because of being currently opened by the application using this INI file.

If the current INI file cannot be replaced with the wanted modification, the temporary file is deleted and this error condition is recorded in the log file. Otherwise the current INI file is successfully updated and that is recorded in the log file with the information how the update was done by the batch file.

Additional information

It should be clear now that the Windows command processor is not designed for file contents modification because it is designed for running commands and executables. There are lots of script interpreters more suitable for this task than cmd.exe.

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 /?
  • del /?
  • echo /?
  • endlocal /?
  • findstr /?
  • for /?
  • goto /?
  • if /?
  • move /?
  • set /?
  • setlocal /?

See also: Where does GOTO :EOF return to?

Mofi
  • 38,783
  • 14
  • 62
  • 115
  • GOLD STANDARD of responses! A slightly different approach, but I like it and IT WORKS! With your breakdown, I was able to learn a lot... SO helpful. Worked through some slight modifications to tweak the logging, variable for the target INI, and log handling - but the inclusion of the section handling is over the top! THANK YOU! Trust me, this script is going to get a workout. – Tricktech Jan 16 '21 at 00:31
0

What I ended up with:

@echo off
setlocal EnableExtensions Disabledelayedexpansion
set TODAY=%Date:~4,2%-%Date:~7,2%-%Date:~10,4%
set Target=SMSStart.ini
set SectionName=[Maintenance]
set EntryName=Reboot
set EntryValue=1
set LogFile=./INI_VAL_CHANGE_%TODAY%_Log.txt
set TempFile=%TEMP%\%~n0.tmp
set ListFile=PCList.txt

if not exist "./%ListFile%" (
    echo ERROR: List file %ListFile%" not found.>"%LogFile%"
   goto EndBatch
)

XCOPY /Y %~dp0*_log.txt %~dp0%LOGS>nul
ERASE %~dp0*_log.txt /Q

Echo PC list: %ListFile% 
Echo Run: %DATE%:%TIME%
Echo PC list: %ListFile%>>%LogFile%
Echo Run: %DATE%:%TIME%>>%LogFile%
Echo Target File: %Target%
Echo Target File: %Target%>>%LogFile%
Echo Search Key: %EntryName%= Update: "%EntryName%=%EntryValue%"
Echo Search Key: %EntryName%= Update: "%EntryName%=%EntryValue%">>%LogFile%
Echo =============================
Echo =============================>>%LogFile%

for /F "usebackq delims=" %%I in ("%ListFile%") do call :UpateIniFile "\\%%I\Storeman\%Target%"
goto EndBatch

:UpateIniFile
set "EmptyLines="
set "EntryUpdate="
set "CopyLines="
(for /F delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /N "^" %1 2^>nul') do (
    set "Line=%%I"
    setlocal EnableDelayedExpansion
    if defined CopyLines (
        echo(!Line:*:=!
        endlocal
    ) else if not defined EntryUpdate (
        echo(!Line:*:=!
        if /I "!Line:*:=!" == "!SectionName!" (
            endlocal
            set "EntryUpdate=1"
        )
    ) else (
        if /I "!Line:*:=!" == "!EntryName!=!EntryValue!" (
            endlocal
            goto ValueExists
        )
        if "!Line:*:=!" == "" (
            endlocal
            set /A EmptyLines+=1
        ) else (
            set "Line=!Line:*:=!"
            if "!Line:~0,1!!Line:~-1!" == "[]" (
                echo !EntryName!=!EntryValue!
                if defined EmptyLines for /L %%J in (1,1,!EmptyLines!) do echo(
                echo !Line!
                endlocal
                set "EntryUpdate=3"
                set "CopyLines=1"
            ) else (
                if defined EmptyLines for /L %%L in (1,1,!EmptyLines!) do echo(
                for /F delims^=^=^ eol^= %%J in ("!Line!") do (
                    if /I not "%%~J" == "!EntryName!" (
                        echo !Line!
                        endlocal
                    ) else (
                        echo !EntryName!=!EntryValue!
                        endlocal
                        set "EntryUpdate=2"
                        set "CopyLines=1"
                    )
                )
                set "EmptyLines="
            )
        )
    )
))>"%TempFile%"

if not defined EntryUpdate (
    >>"%TempFile%" echo %SectionName%
    >>"%TempFile%" echo %EntryName%=%EntryValue%
    set EntryUpdate=4
)
if %EntryUpdate% == 1 (
    >>"%TempFile%" echo %EntryName%=%EntryValue%
    set "EntryUpdate=3"
)

move /Y "%TempFile%" %1 2>nul
if errorlevel 1 (
    echo Failed to update: %1 : !time:~0,8!
    echo Failed to update: %1 : !time:~0,8!>>"%LogFile%"
    del "%TempFile%"
    goto :EOF
)

if %EntryUpdate% == 2 (
    echo !EntryName!=!EntryValue! updated in: %1 : !time:~0,8! 
    echo !EntryName!=!EntryValue! updated in: %1 : !time:~0,8!>>"%LogFile%"
    goto :EOF
)
if %EntryUpdate% == 3 (
    echo !EntryName!=!EntryValue! added to:   %1 : !time:~0,8!
    echo !EntryName!=!EntryValue! added to:   %1 : !time:~0,8!>>"%LogFile%"
    goto :EOF
)
if %EntryUpdate% == 4 (
    echo Section+ !EntryName!=!EntryValue! to: %1 : !time:~0,8!
    echo Section+ !EntryName!=!EntryValue! to: %1 : !time:~0,8!>>"%LogFile%"
    goto :EOF
)

:ValueExists
    echo !EntryName!=!EntryValue! existed in: %1 : !time:~0,8!
    echo !EntryName!=!EntryValue! existed in: %1 : !time:~0,8!>>"%LogFile%"
    del "%TempFile%"
    goto :EOF

:EndBatch
endlocal
pause
Tricktech
  • 15
  • 5
  • I struggled with the relative path and ended up using ./ because the script returned the error "File Not Found" with every other attempt. I know it may not be bulletproof, but its working in my environment. – Tricktech Jan 16 '21 at 00:36
  • Hint 1: The directory separator on Windows is ``\`` and not `/`. See the Microsoft documentation about [Naming Files, Paths, and Namespaces](https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file). Despite the automatic replacement of `/` by ``\`` in file/folder paths by Windows file I/O, the usage of `/` can result in unexpected behavior. For example run in a command prompt window `for %I in (C:/Windows/*.exe) do @if exist "%I" (echo Found file %I) else echo Not found %I` and then run the command line once again with using correct `C:\Windows\*.exe` and look on the output. – Mofi Jan 16 '21 at 12:44
  • Hint 2: I suggest to read my answer on [Time is set incorrectly after midnight](https://stackoverflow.com/a/60126994/3074564) and also the [answer written by Compo](https://stackoverflow.com/a/60127934/3074564) on same question to get knowledge about a better method to get the current date in a suitable format region (country) independent. The best date format in file and folder names is the international standardized date format yyyy-MM-dd which has the big advantage that files/folders with that date format in name ordered by name are at the same time ordered correct also chronologically. – Mofi Jan 16 '21 at 12:51
  • Hint 3: I recommend to read __Issue 1: File/folder argument strings not enclosed in quotes__ in [this answer](https://stackoverflow.com/a/60686543/3074564) and best also all other __issue__ chapters. Your batch file as currently written would fail completely on being stored in a directory with a name like `Development & Test!` which I use to test my batch files. – Mofi Jan 16 '21 at 12:55
  • @Mofi Much appreciated, TY – Tricktech Jan 17 '21 at 13:44
0

@Mofi - Trying to shore up the code as per your suggestions:

move /Y "%TempFile%" %1 2>nul
if errorlevel 1 (
    echo Failed to update: %1 : !time:~0,8!
    echo Failed to update: %1 : !time:~0,8!>>"%LogFile%"
    del "%TempFile%"
    goto :EOF

Trying to figure out why when the batch is unable to reach a PC on the network and outputs an errorlevel 1, the script returns !time:~0,8! rather than the actual timestamp?

Here is the current version I am working from:

@echo off
setlocal EnableExtensions Disabledelayedexpansion
set "TODAY=%Date:~4,2%-%Date:~7,2%-%Date:~10,4%"
set "Target=SMSStart.ini"
set "TarDIR=Storeman"
set "SectionName=[Maintenance]"
set "EntryName=Reboot"
set "EntryValue=1"
set "LogFile=.\INI_VAL_CHANGE_%TODAY%_Log.txt"
set "TempFile=%TEMP%\%~n0.tmp"
set "ListFile=PCList.txt"

if not exist ".\%ListFile%" (
    echo ERROR: List file %ListFile%" not found.>"%LogFile%"
   goto EndBatch
)

XCOPY /Y "%~dp0*_log.txt" %~dp0%LOGS>nul
ERASE "%~dp0*_log.txt" /Q
Echo INI File Updater
Echo INI File Updater>>%LogFile%
Echo ==============================
Echo ==============================>>%LogFile%
Echo PC list: %ListFile% 
Echo Run: %DATE%:%TIME%
Echo PC list: %ListFile%>>%LogFile%
Echo Run: %DATE%:%TIME%>>%LogFile%
Echo Target File: %Target%
Echo Target File: %Target%>>%LogFile%
Echo Target DIR: \%TarDIR%
Echo Target DIR: \%TarDIR%>>%LogFile%
Echo INI Section : %SectionName%
Echo INI Section : %SectionName%>>%LogFile%
Echo Search Key: %EntryName%= Update: "%EntryName%=%EntryValue%"
Echo Search Key: %EntryName%= Update: "%EntryName%=%EntryValue%">>%LogFile%
Echo ==============================
Echo.
Echo ==============================>>%LogFile%
Echo.>>%LogFile%

for /F "usebackq delims=" %%I in ("%ListFile%") do call :UpateIniFile "\\%%I\%TarDIR%\%Target%"
goto EndBatch

:UpateIniFile
set "EmptyLines="
set "EntryUpdate="
set "CopyLines="
(for /F delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /N "^" %1 2^>nul') do (
    set "Line=%%I"
    setlocal EnableDelayedExpansion
    if defined CopyLines (
        echo(!Line:*:=!
        endlocal
    ) else if not defined EntryUpdate (
        echo(!Line:*:=!
        if /I "!Line:*:=!" == "!SectionName!" (
            endlocal
            set "EntryUpdate=1"
        )
    ) else (
        if /I "!Line:*:=!" == "!EntryName!=!EntryValue!" (
            endlocal
            goto ValueExists
        )
        if "!Line:*:=!" == "" (
            endlocal
            set /A EmptyLines+=1
        ) else (
            set "Line=!Line:*:=!"
            if "!Line:~0,1!!Line:~-1!" == "[]" (
                echo !EntryName!=!EntryValue!
                if defined EmptyLines for /L %%J in (1,1,!EmptyLines!) do echo(
                echo !Line!
                endlocal
                set "EntryUpdate=3"
                set "CopyLines=1"
            ) else (
                if defined EmptyLines for /L %%L in (1,1,!EmptyLines!) do echo(
                for /F delims^=^=^ eol^= %%J in ("!Line!") do (
                    if /I not "%%~J" == "!EntryName!" (
                        echo !Line!
                        endlocal
                    ) else (
                        echo !EntryName!=!EntryValue!
                        endlocal
                        set "EntryUpdate=2"
                        set "CopyLines=1"
                    )
                )
                set "EmptyLines="
            )
        )
    )
))>"%TempFile%"

if not defined EntryUpdate (
    >>"%TempFile%" echo %SectionName%
    >>"%TempFile%" echo %EntryName%=%EntryValue%
    set EntryUpdate=4
)
if %EntryUpdate% == 1 (
    >>"%TempFile%" echo %EntryName%=%EntryValue%
    set "EntryUpdate=3"
)

move /Y "%TempFile%" %1 2>nul
if errorlevel 1 (
    echo Failed to update: %1 : !time:~0,8!
    echo Failed to update: %1 : !time:~0,8!>>"%LogFile%"
    del "%TempFile%"
    goto :EOF
)

if %EntryUpdate% == 2 (
    echo !EntryName!=!EntryValue! updated in: %1 : !time:~0,8! 
    echo !EntryName!=!EntryValue! updated in: %1 : !time:~0,8!>>"%LogFile%"
    goto :EOF
)
if %EntryUpdate% == 3 (
    echo !EntryName!=!EntryValue! added to:   %1 : !time:~0,8!
    echo !EntryName!=!EntryValue! added to:   %1 : !time:~0,8!>>"%LogFile%"
    goto :EOF
)
if %EntryUpdate% == 4 (
    echo Section+ !EntryName!=!EntryValue! to: %1 : !time:~0,8!
    echo Section+ !EntryName!=!EntryValue! to: %1 : !time:~0,8!>>"%LogFile%"
    goto :EOF
)

:ValueExists
    echo !EntryName!=!EntryValue! existed in: %1 : !time:~0,8!
    echo !EntryName!=!EntryValue! existed in: %1 : !time:~0,8!>>"%LogFile%"
    del "%TempFile%"
    goto :EOF

:EndBatch
Echo.
Echo.>>%LogFile%
Echo ---COMPLETE---
Echo ---COMPLETE--->>"%LogFile%"
Echo.
endlocal
pause
Tricktech
  • 15
  • 5
  • Delayed expansion has to be **en**abled for `!variable!` syntax to work. – Stephan Jan 18 '21 at 15:32
  • @Stephan each of the subsequent errorlevels seem to output the time correctly - It's enabled in the :UpdateInIFile sub, but I supposed if it cant get to the file, it never calls that sub and never enables... – Tricktech Jan 18 '21 at 15:35
  • well, it's enabled until the corresponding `endlocal` and you have an `endlocal` in every branch of the `if` "tree", so outside your subroutine, it's disabled. – Stephan Jan 18 '21 at 15:42