Search code examples
for-loopbatch-filecmdparentheseswmic

Waiting for Process to terminate on a remote PC using for loop


Should be able to sort this but I'm going round in circles. I know this has to do with setlocal EnableDelayedExpansion, but I'm missing something.

Goal: Execute a windows (cleanmgr.exe) script on a remote machine, wait till Cleanmgr.exe closes then have the initiating script "type" the resultant log file (generated via cleanup script) from the remote system in the CMD window.

What's working: The script running on the remote machine runs fine, it echo's C: free drive space into a log file, then cleans up the PC, and then re runs the disk space report and echo's result into same log file, so the user can see (/have transparency of) the reclaimed space via the before & after results.

What's Broken: The WMIC command to check for Cleanmgr.exe on the target PC, only works once, when it waits to retry the variable containing the Hostname has been wiped out. I can see the behavior by echoing the variable back.

Fix Attempts: I have a hunch this has to do with the variable being lost once the if statement is ran within the Parentheses. I have tried lots of options but they all behave the same. I have tried jumping the process out to loop outside the original code using %1 instead of %%i but just cant quite get there.

Thanks for any improvements.

@echo off
pushd %~dp0
color 1e
setlocal EnableDelayedExpansion
title HDD Space Checker...
for /f %%i in (hostnames.txt) do (
xcopy /y cleanupwindows-sfd.bat \\%%i\C$\IT
WMIC /node:"%%i" process call create "C:\IT\cleanupwindows-sfd.bat"
echo Waiting For Processes...
timeout -t 10 /nobreak >nul
:loop
WMIC /node:"%%i" process where name="cleanmgr.exe" get name |find "cleanmgr.exe">nul
IF "!errorlevel!"=="0" set running=var
IF "!running!"=="var" timeout -t 4 >nul & echo Still Running & goto :loop
IF "!running!"=="" timeout -t 4 >nul & type \\%%i\C$\IT\%%i_HHD_Space.log
)
pause
exit

Solution

  • There is at least two points to see.

    1. Your running variable, once set, is never reset, triggering an infinite loop
    2. Your goto statement inside the enclosing parenthesis drives the command interpreter (cmd.exe) to stop evaluating the block, thus your script loose the %%i and leave the for loop, thus when terminating the :loop cycle your script will leave the for loop without cycling to other values in hostnames.txt.

    To address that, put your process code in a subroutine called with CALL and reset the running variable at each :loop cycle :

    @echo off
    pushd %~dp0
    color 1e
    setlocal EnableDelayedExpansion
    title HDD Space Checker...
    for /f %%i in (hostnames.txt) do (
      CALL:Process "%%i"
    )
    pause
    exit
    :Process
    xcopy /y cleanupwindows-sfd.bat \\%~1\C$\IT
    WMIC /node:"%~1" process call create "C:\IT\cleanupwindows-sfd.bat"
    echo Waiting For Processes...
    timeout -t 10 /nobreak >nul
    :loop
    set "running="
    WMIC /node:"%~1" process where name="cleanmgr.exe" get name |find "cleanmgr.exe">nul
    IF "!errorlevel!"=="0" set "running=var"
    IF "!running!"=="var" timeout -t 4 >nul & echo Still Running & goto :loop
    IF "!running!"=="" timeout -t 4 >nul & type \\%~1\C$\IT\%~1_HHD_Space.log
    GOTO:EOF
    

    Explanations: The CALL statement implies that the command interpreter will store the current executed line of your script and its state before executing the associated subprocess/command/etc.. When the subprocess/command/etc.. finishes, the command interpreter resumes its execution of the script to the next line with a restored context. This avoids then the loose of the for loop context.