1

I have a batch that runs a series of commands that will be long and CPU intensive. I don't mind when it will be finished so I would prefer it to use only idle CPU cycles. If I set the priority of the process that is running the batch (some cmd.exe) in the same batch, any child process should inherit its priority. I know you can change a process priority using wmic. For example:

wmic process where name="cmd.exe" CALL setpriority "idle"

will indeed make the job BUT it will also lower any other cmd.exe instance that is running then, what may not always be suitable.

Is there a way to identify what process is running the batch and set ONLY its priority to idle?

To clarify: I am looking for a general method you can include in any batch of this kind or a batch which any .bat can call to be lowered.

This is a problem that sometimes I find but, in a specific .bat I am writing now, I pass a variable number of files to be processed one at a time.

4 Answers 4

2

One approach is to start out by finding the PID (which is short for "Process ID").

The WMIC Process role supports more than just setting the priority. You can also get details. e.g. the following (run on a single line):

WMIC process where name="cmd.exe" get Caption,CommandLine,Name,ParentProcessId,ProcessId /FORMAT:LIST

(Naturally, this is meant to be run from a traditional command prompt, like the sample from your question. Using PowerShell would require additional escaping.)

However, you might have multiple copies of CMD.EXE which could cause some issue. This might be easier if you have some control over how your batch file is started. Dean's StackOverflow.com question, “getting process ID of exe running in Bat File” provides multiple solutions to get the PID into an environment variable. I suggest you look look over those solutions to determine which one looks most pleasant for you, but here is one possible approach:

C:\> wmic Process WHERE "CommandLine LIKE '%%MyProgWMICflag%%'" Get ParentProcessID /format:list

That should show you the ParentProcessID to the WMIC command, which will be the PID of the program that called WMIC (presumably your shell).

This is based on foxidrive's answer in the above hyperlinked page of solutions of obtaining the PID. (That answer also shows getting this into an environment variable if you like.)

Then, include the ProcessId in your WHERE clause. e.g.:

WMIC process where "name='cmd.exe' AND ProcessId='12345'" CALL setpriority "idle"

Note: This is just one approach. There may be some other common traits that you find are nicer to test for. You can see a list of available properties to check by using something like:
WMIC /OUTPUT:"Results.txt" Process Get /FORMAT:LIST

3
  • So it all boils down to pinpoint the actual cmd.exe PID. This is almost an answer. But right now I cannot think of a way to do this. I'll send this thinking to background. Maybe some idea appears.
    – cdlvcdlv
    Jun 11, 2018 at 8:39
  • Well, that's why I mentioned that Dean's StackOverflow.com question, "getting process ID of exe running in Bat File" provides multiple solutions. I saw no benefit in trying to guess which approach (mentioned in that answer) might be most convenient for you, and then try to copy the information from that one method while leaving out the details of the other possible solutions. I've played with this a bit further and added some info (see "one possible approach")
    – TOOGAM
    Jun 11, 2018 at 9:18
  • I found this question and was working on it but that %%MyProgWMICflag%% trick is clever!
    – cdlvcdlv
    Jun 11, 2018 at 10:00
1

This is a working SetMyPrio.bat MyPrio.bat that you can use from another to set the priority of your batch. If you copy it to a directory in your %PATH% you can call it anytime you need it, etc. as usual. Thanks to TOOGAM, that gave me the tools and method to write this. The variable UniqueString ensures that the pinpointed process is exactly which you need and not any other instance of your batch (improbable, but possible scenario).

@echo off

GOTO :EndOfReminder

<HELP>

USAGE:

From any batch file or command prompt, write
   CALL MyPrio [/?|/h] [<priority>|get]
to set the priority of the caller cmd. Default for <priority> is idle.

/?, /h        This help.

<priority>    If you want your priority change to 
                 idle          -- no parameter, 64, idle or "idle"
                 below normal  -- 16384 or "below normal"
                 normal        -- 32, normal or "normal"
                 above normal  -- 32768 or "above normal"
                 high priority -- 128 or "high priority"
                 real time     -- 256, realtime or "realtime"
get           Returns current priority as errorlevel

TO DO
   - Parameter error control.
   - Support for Unicode characters in help.
</HELP>

:EndOfReminder
setlocal
set Priority=%~1

if /I ["%Priority%"]==["/h"] ( call :PrintHelp & exit /b )
if ["%Priority%"]==["/?"] ( call :PrintHelp & exit /b )

set UniqueString=%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%

if ["%Priority%"]==["get"] for /f "usebackq tokens=2 delims==" %%a in (`wmic Process WHERE "name='cmd.exe' AND CommandLine LIKE '%%%UniqueString%%%'" Get Priority /format:LIST`) do exit /b %%a

if not defined Priority set Priority=idle
for /f "usebackq tokens=2 delims==" %%a in (`wmic Process WHERE "name='cmd.exe' AND CommandLine LIKE '%%%UniqueString%%%'" Get ParentProcessID /format:LIST`) do set myPID=%%a
wmic process where "ProcessId='%myPID%'" CALL setpriority "%Priority%" > nul 2>&1

endlocal
goto :eof

:PrintHelp
setlocal DisableDelayedExpansion
call :eln 0
set "levelerror=^%%errorlevel^%%"
for /F "delims=" %%a in ('find /v /n "" "%~f0"') do (
   set "line=%%a"

   REM We store errorlevel in tmpel because setlocal will set errorlevel to 0...
   call set tmpel=%levelerror%
   setlocal EnableDelayedExpansion
   REM ... and now we restore it
   REM %levelerror% was expanded to %errorlevel% before running the iteration
   REM and the CALL SET allows actual errorlevel be stored in tmpel
   REM Finally, we set errorlevel again
   call :eln !tmpel!

   set "line=!line:*]=!"

   if /i "!line!"=="</HELP>" exit /b 0
   if errorlevel 1 echo(!line!
   if /i "!line!"=="<HELP>" call :eln 1
REM errorlevel is the only value that survives the following endlocal
   endlocal
)
exit /b 1

:eln
REM Sets errorlevel to %1
exit /b %1

Edit (2018-06-12)

  • Changed the name to MyPrio, added options /?, /h and get.
  • Options /? and /h print the text you write in the reminder between <HELP> and </HELP>. This method can be reused in any other batch you need it.

If you make a call to MyPrio.bat at the beginning of a batch, anytime you start it, no matter from a command line or dragging and dropping files from Windows Explorer (that become parameters), every part of it (and child processes) will run with your desired priority.

For example:

@echo off
call MyPrio

REM Here comes a set of commands that will run with idle priority.

call MyPrio normal

REM Here may come some interactive part to request information needed to continue.

call MyPrio

REM Back to idle (or put any other priority you prefer).

call MyPrio normal

REM You may want to set the priority to normal at the end just in case you call the batch from a command line.

COMMENTS

I made a batch named PrioTest.bat to test MyPrio.bat with every possible option, and ordered them so any call would change the priority to a different one. I made a subroutine (:test1) that:

1. Gets current priority and save it to myPrioBefore.
2. Calls MyPrio with the test parameter.
3. Gets new priority and save it to myPrioAfter.
4. Starts a hidden ping -t localhost.
5. Gets priority and PID of the ping process and saves them to pingPrio and pingPID.
6. Prints the values of myPrioBefore, the call to MyPrio, myPrioAfter and pingPrio.
7. Kills the started ping (being sure it's not another one).

@echo off

setlocal

set UniqueString=%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%
for /f "usebackq tokens=2 delims==" %%a in (`wmic Process WHERE "name='cmd.exe' AND CommandLine LIKE '%%%UniqueString%%%'" Get ParentProcessID /format:LIST`) do set myPID=%%a
for %%a in (64 16384 32 32768 128 256 idle "below normal" none normal "idle" "normal" "above normal" realtime "high priority" "realtime") do call :test1 %%a
CALL MyPrio normal

endlocal
goto :eof

:test1
for /f "usebackq tokens=2 delims==" %%b in (`wmic Process WHERE "ProcessID='%%myPID%%'" Get Priority /format:LIST`) do set myPrioBefore=%%b
if [%1]==[none] ( call MyPrio ) else ( call MyPrio %1 )
for /f "usebackq tokens=2 delims==" %%b in (`wmic Process WHERE "ProcessID='%%myPID%%'" Get Priority /format:LIST`) do set myPrioAfter=%%b
start "" /b ping -t localhost > nul
for /f "usebackq tokens=2 delims==" %%b in (`wmic Process WHERE "name='ping.exe' AND ParentProcessID='%%myPID%%'" Get Priority /format:LIST`) do set pingPrio=%%b
for /f "usebackq tokens=2 delims==" %%b in (`wmic Process WHERE "name='ping.exe' AND ParentProcessID='%%myPID%%'" Get ProcessID /format:LIST`) do set pingPID=%%b
echo myPrioBefore==%myPrioBefore% / CALL MyPrio %1 / myPrioAfter==%myPrioAfter% / pingPrio==%pingPrio%
taskkill /f /pid %pingPID% > nul
exit /b

This is the output of the tester batch:

myPrioBefore==4 / CALL MyPrio 64 / myPrioAfter==4 / pingPrio==4
myPrioBefore==4 / CALL MyPrio 16384 / myPrioAfter==6 / pingPrio==6
myPrioBefore==6 / CALL MyPrio 32 / myPrioAfter==8 / pingPrio==8
myPrioBefore==8 / CALL MyPrio 32768 / myPrioAfter==10 / pingPrio==8
myPrioBefore==10 / CALL MyPrio 128 / myPrioAfter==13 / pingPrio==8
myPrioBefore==13 / CALL MyPrio 256 / myPrioAfter==13 / pingPrio==8
myPrioBefore==13 / CALL MyPrio idle / myPrioAfter==4 / pingPrio==4
myPrioBefore==4 / CALL MyPrio "below normal" / myPrioAfter==6 / pingPrio==6
myPrioBefore==6 / CALL MyPrio none / myPrioAfter==4 / pingPrio==4
myPrioBefore==4 / CALL MyPrio normal / myPrioAfter==8 / pingPrio==8
myPrioBefore==8 / CALL MyPrio "idle" / myPrioAfter==4 / pingPrio==4
myPrioBefore==4 / CALL MyPrio "normal" / myPrioAfter==8 / pingPrio==8
myPrioBefore==8 / CALL MyPrio "above normal" / myPrioAfter==10 / pingPrio==8
myPrioBefore==10 / CALL MyPrio realtime / myPrioAfter==13 / pingPrio==8
myPrioBefore==13 / CALL MyPrio "high priority" / myPrioAfter==13 / pingPrio==8
myPrioBefore==13 / CALL MyPrio "realtime" / myPrioAfter==13 / pingPrio==8

As you can see, myPrioAfter does not match the requested in the call and sometimes, the priority of the ping is not equal to the parent cmd so there must be more to the values (look at fifth line: current priority is 10, you request 128, new priority is 13 and child process' 8, sorry?). Also, realtime seems not have effect (admin rights required I assume).

1

Here's my take at this: instead of lowering the current process priority, resume the batch file by spawning a new cmd.exe instance with lower priority; the environment will be (automatically) inherited and, if done near the beginning, it will act pretty much the "same", except for Ctrl+c handling (which start /b inhibits - use Ctrl+Break instead). This introduces no additional dependencies, at the (low) cost of an additional cmd.exe instance (and the associated conhost.exe).

Simply copy&paste this snippet:

rem | reduce process priority (uses an additional cmd.exe instance)
if not "%TARGET_LABEL%"=="" goto %TARGET_LABEL%
set TARGET_LABEL=RunLowPriority
start "" /b /belownormal /wait cmd /d /c "%~dpnx0" %*
goto :EOF
:RunLowPriority

What is happening:

  • [2nd instance] a previous setup is detected: jumps to the desired location (RunLowPriority)
  • [1st instance] sets up jump target (RunLowPriority) for 2nd instance
  • [1st instance] starts 2nd instance
    • use below normal priority
    • don't start a new console and wait for 2nd instance to end
    • use full path in quotes to avoid "surprises" (spaces, etc.)
    • pass on all parameters (%*)
  • [1st instance] quits when 2nd instance ends

A collateral effect of using two command prompt instances is that Ctrl+Break displays two Terminate batch job (Y/N)? - not a problem when running a script interactively (e.g., Windows Explorer) but a bit weird when running from an existing command prompt.

0

you would need to run your batch with START (from another batch) - then start can be set to have separate priority

START "" batch.bat /B /LOW
2
  • Maybe I didn't explain well. I know you can start processes with low priority this way, but that would force me to make a launcher for every .bat of this kind that I wrote. What I was looking for is a general method you can include in any batch like this. Or a batch which any .bat can call to be lowered.
    – cdlvcdlv
    Jun 10, 2018 at 21:55
  • @cdlvcdlv I was recommanded with prnwatch.com/prio - maybe it will helps you
    – gamer0
    Jun 10, 2018 at 22:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .