4

The past few days I have been working on a script that I thought would be rather easy but it seems not, and I do understand why. My problem is how to get around it.

The batch script I need explained:

I have a script that runs in cmd.exe that does a bunch of things like moving a huge amount of files from a location to another. Lets call it movefile.cmd. This script works, but happens to stop sometimes (very rarely - lets not go into why and that script). Its important that this script always runs, so my idea here was to create a batch that exits cmd.exe and then re-opens the script each hour or so. Lets call this script restartcmd.bat

Sounds perfectly easy as I could do this:

@echo off

:loop

start c:\script\movefile.cmd 

Timeout /nobreak /t 3600

Taskkill cmd.exe

goto loop

But obviously this doesn't work because my new script also runs in cmd.exe, so it would kill this process as well.

What I've tried:

So I made a copy of cmd.exe and renamed it into dontkillthis.exe. I run dontkillthis.exe and then open the restardcmd.bat from dontkillthis.exe - this works perfectly! But I need to be able to just dobbleclick my script instead of doing that. Why? Because its supposed to be as easy as possible and I want my restartcmd.bat to be in my startup folder.

I've been looking at the ideas of getting the exact process ID of cmd.exe and shutting that so that my dontkillthis.exe will remain, but I can't seem to nail it. Tried all thats written in here how to kill all batch files except the one currently running , but I can't get it to work.

I'm not sure if I'm being confused or if it actually is a bit hard to do this.

I'd really appreciate some help here.

Best Regards

MO

Community
  • 1
  • 1
MadsTheMan
  • 705
  • 1
  • 10
  • 32
  • Dirty way: create a copy of `cmd` as `kill-me.exe`, use `start kill-me c:\script\movefile.cmd` & then `taskkill kill-me.exe` (This is a quick hack. There should be better ways possible.) – anishsane Apr 01 '15 at 11:22
  • [Get PID of current cmd.exe](https://social.msdn.microsoft.com/Forums/vstudio/en-US/270f0842-963d-4ed9-b27d-27957628004c/what-is-the-pid-of-the-current-cmdexe), then `taskkill` has option to filter based on `PID !=xxx` Check `taskkill /?`. – anishsane Apr 01 '15 at 11:26

5 Answers5

12

first you'll need the PID of the current CMD instance. The topic has been discussed here . I will offer you my solution - getCmdPID.bat

and here's the script (getCmdPID should in the same directory ):

@echo off

call getCmdPID
set "current_pid=%errorlevel%"

for /f "skip=3 tokens=2 delims= " %%a in ('tasklist /fi "imagename eq cmd.exe"') do (
    if "%%a" neq "%current_pid%" (
        TASKKILL /PID %%a /f >nul 2>nul
    )
)
npocmaka
  • 55,367
  • 18
  • 148
  • 187
  • 1
    Npocmaka, that actually (and surprisingly for me) worked like a charm! Thanks man. Exactly what I was looking for! Spot on. :) – MadsTheMan Apr 07 '15 at 07:45
1

Normally with the following command I should be able to find the PID. Unfortunately this is not the case.

title exclude &tasklist /NH /v /fo csv /FI "WINDOWTITLE ne exclude*" /FI "IMAGENAME eq cmd.exe" /FI "STATUS eq running"

So to achieve my goal, I used the following command: FIND /I "exclude" 1>NUL

@echo off
TITLE exclude
(for /f "usebackq  tokens=*" %%a in (`tasklist /NH /v /fo csv /FI "IMAGENAME eq cmd.exe" /FI "STATUS eq running"`) do (
  (
    echo %%a | FIND /I "exclude" 1>NUL
  ) || ( 
    for /f "usebackq tokens=2 delims=," %%i in (`echo %%a`) do (
      echo TASKKILL /PID %%~i /f
    )
  )
)
)>_output-taskill.txt
TYPE _output-taskill.txt

Another approach to kill all the processes in a single line is to use filters on the command taskkill with filters should look like:

TASKKILL /F /FI "PID ne XXXX" /FI "IMAGENAME eq cmd.exe" /IM cmd.exe

eq (equal)

ne (not equal)

gt (greater than)

lt (lesser than)

@echo off
TITLE exclude
(for /f "usebackq tokens=2 delims=," %%a in (`tasklist /NH /v /fo csv /FI "IMAGENAME eq cmd.exe" /FI "STATUS eq running" ^| FIND /I "exclude"`) do (
    echo TASKKILL /F /FI "PID ne %%~a" /FI "IMAGENAME eq cmd.exe" /IM cmd.exe
  )
)>_output-taskill.txt
TYPE _output-taskill.txt
Paul
  • 2,620
  • 2
  • 17
  • 27
1

I have found a solution that utilizes text files to keep track of all previous PIDs the bat file has had. It attempts to kill them silently and then adds the current PID to the list after.

If you don't want it to kill the old, already existing process, simply replace the line that has "taskkill" with whatever you were wanting to do with it.

(might require you to run as admin in order to have permissions to kill the duplicate process. see permission elevation code below for optional implementation if you don't want to have to run as admin every time.)

@echo off
set WorkingDir=%cd% 
if exist MostRecentPID.txt ( del "PIDinfo.txt"  /f /q ) > nul
cd ..\..\..\..\..\..\..
title mycmd
tasklist /v /fo csv | findstr /i "mycmd" > %WorkingDir%\PIDinfo.txt
set /p PIDinfo=<%WorkingDir%\PIDinfo.txt

REM below, the 11 means get substring starting a position 11 with length of 5 characters. The tasklist command gives a long and verbose value so this will get just the PID part of the string.
set PID5chars=%PIDinfo:~11,5%
set PID4chars=%PIDinfo:~11,4%

if exist PreviousPIDs.txt (
    for /F "tokens=*" %%A in (PreviousPIDs.txt) do taskkill.exe /F /T /PID %%A > nul 2>&1
    goto CheckIfFourCharPID
)

:CheckIfFourCharPID
if %PID4chars% gtr 8100 (
    for /F "tokens=*" %%A in (PreviousPIDs.txt) do taskkill.exe /F /T /PID %%A > nul 2>&1
    echo %PID4chars% >> "PreviousPIDs.txt"
) else (
    echo %PID5chars% >> "PreviousPIDs.txt"
)

Explanation: (warning: very technical)

-This solution gets a substring of the tasklist command to get just the PID. There will not be a PID for cmd.exe that is greater than 18100 so check if PID4chars is greater than 8100 so we know if it's a 4 digit or 5 digit number

case 1: a 5 digit PID like 17504 has a PID5chars val 17504 and a PID4chars val of 1750, so we add PID5chars to the text files of PIDs to kill

case 2: a 4 digit PID like 8205 has a PID5chars val of 8205" and a PID4chars val of 8205, so we add PID4chars to the text files of PIDs to kill

case 3: a 4 digit PID like 4352 has a PID5chars val of 4352" and a PID4chars val of 4352, so we add PID4chars to the text files of PIDs to kill


OPTIONAL PERMISSION ELEVATION CODE

(put this at the top of your bat file and it will auto-run it as admin.)

    @echo off
    setlocal DisableDelayedExpansion

    set "batchPath=%~0"
    for %%k in (%0) do set batchName=%%~nk
    cd ..\..\..\..\..\..\..\..
    if exist %cd%\Temp (
    set temp=%cd%\Temp
    goto vbsGetPrivileges
    )
    if exist %cd%\Windows\Temp (
    set temp=%cd%\Windows\Temp
    goto vbsGetPrivileges
    )
    set temp=%cd%

    :vbsGetPrivileges
    set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs"
    setlocal EnableDelayedExpansion

    :CheckIfRunningAsAdmin
    net session >nul 2>&1
    if %ERRORLEVEL% == 0 ( 
        goto gotPrivileges 
    ) else ( goto ElevatePermissions )

    :ElevatePermissions
    if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges)
    ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
    ECHO args = "ELEV " >> "%vbsGetPrivileges%"
    ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
    ECHO args = args ^& strArg ^& " "  >> "%vbsGetPrivileges%"
    ECHO Next >> "%vbsGetPrivileges%"
    ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%"
    "%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
    exit /B

    :gotPrivileges
    setlocal & pushd .
    cd /d %~dp0
    if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul  &  shift /1)
    net session >nul 2>&1
    if %ERRORLEVEL% == 0 (
        goto Continue
    ) else (
        REM unable to elevate permissions so tell user to run file as admin manually
        echo Please re-run this file as administrator. Press any key to exit...
        pause > nul
        goto Exit
     )

    :Continue
    <insert rest of code here>
Collin
  • 394
  • 5
  • 14
0

It would be much better to get the PID of your movefile.cmd. If you can edit it, add a title MyMoveFileProcess and get it's PID with

for /f "tokens=2" %%i in ('tasklist /v ^|find "MyMoveFileProcess"') do set PID=%%i

Then you can kill it with taskkill /pid %pid%

Instead of changing your movefile.cmd, you can also just start it with an title:

start "MyMoveFileProcess" c:\script\movefile.cmd 
Stephan
  • 53,940
  • 10
  • 58
  • 91
  • That first code is a complete failure - it lists all cmd.exe processes. The design is fundamentally flawed. – dbenham Apr 01 '15 at 14:29
  • @dbenham yes, it does (like I wrote). Maybe I'd better delete that part. – Stephan Apr 01 '15 at 14:42
  • @dbenham on the other hand: question title says "how to kill all cmd.exe except ..." which I agree is a bad approach. – Stephan Apr 01 '15 at 14:53
  • Edited the title as I realised it was a bad approach indeed. For the record tho, npocmaka's answer did the job. Thanks for the inputs! – MadsTheMan May 21 '15 at 06:46
-2

A couple of lines will help you achieve this:

TITLE exclude
taskkill /IM cmd.exe /FI "WINDOWTITLE ne exclude*"
armatita
  • 12,825
  • 8
  • 48
  • 49
Dhruva
  • 1