Its possible to log all commands and their output to file? [cmd] - windows

I'm trying to create log of silent installation of the application script and I need to log command and output to log file.
This is how it look like - its part of my .cmd file.
For now I need to create variable for every line (SET SETUP_0X=), but this solution its not a best, because I could have more command to write.
SET LogPath=C:\Logs
SET LogFile=%LogPath%\App_Install.log 2>&1
call :Loguj --------------------------------------------------------------------------------
call :Loguj --------------------------------LOG-Install-START-------------------------------
call :Loguj --------------------------------------------------------------------------------
ECHO. >>%LogFile%
:Commands
SET SETUP_01=App.exe /S
SET SETUP_02=regedit /s SomeReg.reg
SET SETUP_03=DIR
SET SETUP_04=...
(...)
(goto Installation)
:Installation
call :Loguj %SETUP_01%
%SETUP_01% >> %LogFile%
call :Loguj %SETUP_02%
%SETUP_02% >> %LogFile%
call :Loguj %SETUP_03%
%SETUP_03% >> %LogFile%
call :Loguj %SETUP_04%
%SETUP_04% >> %LogFile%
:Finishing
Do other commands, but don't log this.
ECHO. >>%LogFile%
call :Loguj --------------------------------------------------------------------------------
call :Loguj --------------------------------LOG-Install-STOP--------------------------------
call :Loguj --------------------------------------------------------------------------------
So its possible to improve it to not create variables, but log all commands and output from step :Installation until step :Finishing to log file?

Use a for /f loop to iterate over the variables and put the whole thing into a code block to redirect all of the output in one go:
:Installation
(for /f "tokens=2 delims==" %%a in ('set setup_') do (
call :loguj %%a
%%a
)) >> %LogFile% 2>&1
or without setting variables:
:Installation
(for %%a in ("app.exe /s" "regedit /s somereg.reg" "dir" "...") do (
call :loguj %%a
%%~a
)) >> %LogFile% 2>&1
goto :eof
:loguj
echo --- %~1 ---
Note: if there are many commands, you may hit the cmd line limit.
You can write it a bit more readable:
:Installation
(for %%a in (
"app.exe /s"
"regedit /s somereg.reg"
"dir"
"..."
) do (
call :loguj %%a
%%~a
)) > log.txt 2>&1

Related

Findstr too slow on UNC batch script

I am running a script that goes through network folders and saves the found files, however it is taking too long to run. I have tried isolating the findstr to a single folder and it runs at an okay time, so I assume it has something to do with the FOR loop.
#echo off
setlocal
set SERVERS=server1 server2 server3 server4
cls
echo Type below the query parameters:
set /p year=Year (4 digits):
set /p month=Month (2 digits):
set /p day=Day (2 digits):
set /p query=Query string:
cls
echo Results:
del /F /Q "C:\Users\%USERNAME%\Desktop\found_files\*" 2>nul
if not exist "C:\Users\%USERNAME%\Desktop\found_files" mkdir "C:\Users\%USERNAME%\Desktop\found_files"
for /f "tokens=*" %%a in ('for %%i in ^(%SERVERS%^) do #findstr /S /I /M /C:"%query%" "\\%%i\folder_structure\*%year%-%month%-%day%*.xml"') do copy /Y "%%a" "C:\Users\%USERNAME%\Desktop\found_files" >nul & echo %%a & set found=1
echo.
if "%found%"=="1" (
echo File^(s^) saved successfully!
) else (
echo No files found!
)
echo.
pause
if "%found%"=="1" explorer C:\Users\%USERNAME%\Desktop\found_files
Your script is already optimized pretty well. I don't think there is much you can do to speed things up.
I suspect your problem is that FINDSTR is running on your local machine, and it must scan the files on all UNC paths (almost surely not local). This means the entire content of every file must be transmitted across your network. If your system is anything like where I work, that could be a nightmare. Our network drive performance is pathetic (more than a factor of 100 slower than local drive)!
Squashman (and SomethingDark) were somewhat concerned about your outer FOR /F executing a nested FOR statement. But I believe that is the most efficient way. When FOR /F iterates command output, it must launch a new process to execute the command. Your current script only needs one sub-process.
The more "traditional" approach would be to move the %SERVERS% iteration outside the inner loop as follows:
for %%i in (%SERVERS%) do for /f "tokens=*" %%a in (
'findstr /S /I /M /C:"%query%" "\\%%i\folder_structure\*%year%-%month%-%day%*.xml"'
) do copy /Y "%%a" "C:\Users\%USERNAME%\Desktop\found_files" >nul & echo %%a & set found=1
But this is actually less efficient because it must launch a new sub-process for each UNC path within %SERVERS%. That being said, I don't think the difference is significant compared to the actual bottle neck of transmitting the file content across the network.
To show the impact of one vs. 100 sub-processes, I ran a quick comparison of the following logically equivalent (but meaningless) commands:
for /f "delims=" %%F in (
'for /l %%N in ^(1 1 100^) do #findstr /m "^" *'
) do echo %%F>nul
:: This took 39 seconds on my machine
for /l %%N in (1 1 100) do for /f %%F in (
'findstr /m "^" *'
) do echo %%F>nul
:: This took 60.9 seconds on my machine
#echo off
set SERVERS=server1,server2,server3,server4
cls
echo Type below the query parameters:
:: Type echo %date% on command prompt if its returns the current date dd/mm/yyyy format, you can load the variables using a substring:
set year=%date:~6,4%
set month=%date:~3,2%
set day=%date:~0,2%
set /p query=Query string:
:: set counter for files founded
set found=0
cls
echo Results:
if not exist "C:\Users\%USERNAME%\Desktop\found_files" (mkdir "C:\Users\%USERNAME%\Desktop\found_files") else (del /F /Q "C:\Users\%USERNAME%\Desktop\found_files\*" 2>nul)
for /f %%i in (%SERVERS%) do ('#find /i /c "%query%" "\\%%i\folder_structure\*%year%-%month%-%day%*.xml"') do (
if "%%i"=="1" (set /a found=%found%+1 && copy /Y "\\%%i\folder_structure\*%year%-%month%-%day%*.xml" "C:\Users\%USERNAME%\Desktop\found_files" >nul && echo File^(s^) saved successfully! & echo.) else (echo No files found!)
)
echo.
pause
if %found% gtr 0 (explorer C:\Users\%USERNAME%\Desktop\found_files)

Windows batch: combining start and FOR /f "tokens=1" with | escape coded symbols

I am trying to execute this command from standard windows batch file.
start /B for /f "tokens=1" %%a in ('Query SESSION ^| find /i "rdp"') do (echo yes |reset session %%a)
and it throws an error:
"| was unexpected at this time."
I just found that it fails because start in the beginning.
What's wrong with it?
From a batch file it's a bad idea to try to build a valid one line solution with start, it's very tricky to escape/quote a complex expression.
But it's quiet easy to use start and jump to a label in the same batch file.
#echo off
REM *** Thread redirector
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F
REM *** Start a new thread in this batch file at the label :myThread
start "" /b cmd /c "%~d0\:myThread:\..\%~pnx0"
echo Main
for /L %%n in (1 1 10) DO (
echo MainThread doing some stuff %%n
ping localhost -n 2 > nul
)
exit /b
:myThread
for /f "tokens=1" %%a in ('Query SESSION ^| find /i "rdp"') do (
echo myThread: Resetting session %%a
echo yes | reset session %%a
)
exit /b

Check for changes in specifics files with batch not working as expected

I have differents files, and I want to check for any changes in any of them, using batch. This is what I have:
#echo off
setlocal enableDelayedExpansion
set "list=test.txt test1.txt"
:loop
timeout -t 1 >nul
for %%j in (%list%) do (
REM Here only prints test.txt
echo %%j
for %%i in (%%j) do echo %%~ai|find "a">nul || goto :loop
echo file was changed %%j
attrib -a %%j
)
goto :loop
If I use only one file, works, but not with two or more. The idea is, that echo file was changed %%j only outputs the correct file, and not both.
Any ideas?
EDIT:
If I do some changes:
#echo off
title LAN chat reader
setlocal enableDelayedExpansion
set "list=test.txt test1.txt"
:loop
timeout -t 1 >nul
for %%j in (%list%) do (
echo %%j
echo %%~aj|find "a">nul || goto :loop
echo file was changed %%j
attrib -a %%j
)
goto :loop
works, but only alerts me the changes in the second file, after I make changes in the first. I can understand that the problem is in the loop, so there is where I'm confused.
I'd suggest a couple of things.
1: Use
call :CheckFile "%%j"
instead of a nested for loop - makes things easier to read.
2: you can use DIR to check the attribute a bit more directly:
dir /aa %%j > nul 2>&1 && attrib -a %%j
Both together looks something like:
:loop
timeout -t 1 >nul
for %%j in (%list%) do call :CheckFile "%%j"
goto :loop
:CheckFile
echo %1
dir /aa %1 > nul 2>&1 || goto :EOF
echo file was changed %1
attrib -a %1
goto :EOF

get output of different commands on one line

I'm having the below script to display some data on-screen, using one small CMD file:
#echo off
set description=%1
ver
date /t
time /t
echo %description%
This provides output, similar to:
Microsoft Windows [Version 6.1.7601]
ma 08/12/2014
17:00
sometext
But, I would like to have this output:
Microsoft Windows [Version 6.1.7601] [ma 08/12/2014 17:00] sometext
I can't think of easy coding (without including variables and/or temporary files) to have something like that achieved. Is it possible in an easy manner (like using backticks on Linux/Unix) ? How ?
As a bonus, it would be even more better if that one line does not end with a return character. That way, I can add additional text (also in 1 line format) to the file, just using ECHO. Knowing that ECHO itself will add a return eventually. I know I can do this, using Cygwin's PRINTF, but if possible I would like to avoid Cygwin, and use standard CMD.
EDIT: changed the question to include the "ver" command so that it matches the title : output of real commands, to be converted into 1 line
#echo off
set myVar=
set description=%1
for /F "delims=" %%I in ('ver') do (set myVar=%%I)
for /F "delims=" %%I in ('date /t') do (set myVar=%myVar% %%I)
for /F "delims=" %%I in ('time /t') do (set myVar=[%myVar% %%I])
set myVar=%myVar% %description%
echo %myVar%
How do I get the result of a command in a variable in windows?
No temporary file, no visible auxiliary variable (cf. no change to %xyz% variable formally used in the :echo subroutine). Simple to change echo anything to call :echo anything
#ECHO OFF >NUL
:: unrem next line to see no change
:: set "xyz=XYZ"
Call :echo %time%
Call :echo after 5 secs
ping 1.1.1.1 -n 1 -w 5000 > nul
Call :echo %time%
Call :echo another 5 secs
ping 1.1.1.1 -n 1 -w 5000 > nul
Call :echo %time%
:: add new line in next ECHO. command
echo.
:: ensure variable xyz is not defined / changed
set xyz
:: and STDOUT command output
for /F "tokens=* delims=" %%G in ('ver /?') do call :echo %%G
for /F "tokens=* delims=" %%G in ('ver') do call :echo %%G
goto :eof
:echo
<nul (set/p xyz=%* )
goto :eof
And to see your task solution:
#ECHO OFF >NUL
for /F "tokens=* delims=" %%G in ('ver') do call :echo %%G
for /F "tokens=* delims=" %%G in ('date /t') do call :echo %%G
for /F "tokens=* delims=" %%G in ('time /t') do call :echo %%G
call :echo sometext
call :echo
goto :eof
:echo
if "%*"=="" echo.&goto :eof
<nul (set/p xyz=%* ) 2>NUL
goto :eof
In last code snippet adhered to rule only one command for the same task: echo. replaced by call :echo to add new line to output. However, let echo ON and echo OFF apart...

Why doesn't this batch script work? Reading lines from file

ok, I give up. Why doesn't this work?
setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
FOR /F %%I in (myfile.txt) do (
echo I: %%i
set LINE=%%i
echo LINE: %LINE%
)
"echo I:" displays the lines correctly, but "echo LINE:" is empty
I have tried different variations with the same results, such as
set LINE=%i
set LINE=%i%
set LINE=!i!
Obviously there is something simple I am not understanding.
you enabled delayed expansion, so the only thing you have to do is: use it.
replace echo LINE: %LINE% with echo LINE: !LINE!
EDIT: solution without delayed extension
FOR /F %%I in (myfile.txt) do ( call DoIt %%I )
exit /b
:DoIt
echo I: %1
set LINE=%1
echo LINE: %LINE%
goto :eof

Resources