7
\$\begingroup\$

There are times when I want to run a one-off executable on an end-user's machine that cleans up after itself, i.e. deletes itself without a trace. An exe can't delete itself because an exe can't be deleted while it's running. However, a batch file can delete itself, presumably because its contents get copied so the file can close before the code gets run.

So what if there was a general-purpose batch file that deletes the calling executable and then deletes itself? I couldn't find such a batch file so I made one, thinking maybe it could be useful to others. And obviously I want to see if it can be improved upon, of course.

This is particularly useful for an NSIS installer because the batch file can be embedded in the exe and then extracted.

Make sure you copy the batch file before you test it because it will delete itself!

@ECHO OFF

SET filename=%~nx1

IF "%filename%"=="" GOTO:EOF

TASKKILL /IM "%filename%" /F

:LOOP
TASKLIST | FIND /I "%filename%" >NUL 2>&1
IF ERRORLEVEL 1 (
  GOTO CONTINUE
) ELSE (
  ECHO %filename% is still running
  PING -n 6 127.0.0.1>NUL
  GOTO LOOP
)

:CONTINUE

DEL /F "%~f1"
DEL /F "%~f0"

I figured the easiest way to get the path of the calling program is to just have it passed in as a parameter. The IF "%filename%"=="" GOTO:EOF is very important because if the first parameter isn't defined then DEL /F "%~f1" will automatically act like a * is there and try to delete the contents of the whole folder.

\$\endgroup\$
5
\$\begingroup\$
TASKKILL /IM "%filename%" /F

It makes sense that you might want to do this, but I expect the file would be more useful if it waited for its calling program to end on its own. That way we can be sure the calling executable is done with everything else, including releasing any resources it's holding. Because you already have looping code checking for the process, this should be as simple as removing the line.


Comments would be great.

For example, I understand that PING -n 6 127.0.0.1>NUL is a fairly common delaying tactic, but it's still downright odd if you haven't seen it before.


Likewise filename manipulations (%~nx1 and such) will always be arcane. A comment saying that it pulls out the name and extension would be appreciated.

There's an alternative here, which is a bit of a cheat but worth mentioning as potentially safer in some cases. Because the use case of this batch file is that it's created by the executable that wants cleaning up, that executable could do the text substitution and put its own filename directly into the relevant code of the batch file. Obviously this depends on the language that the executable is written in having good string manipulation capabilties, but it would help protect against deleting whole folders and other such disasters.


GOTO CONTINUE

Personally, I would not have called it 'CONTINUE' because in many C family languages continue would continue with the next iteration of a loop, rather than break out of it. Obviously it makes no difference to the script, and it's not something that a pure batch programmer would trip over. I just found it slightly jarring, and thought I'd mention it.


) ELSE (

This is not clear cut, but some people find it easier to read if the else is omitted when the if side breaks, returns, or otherwise interupts the control flow. That is

:LOOP
TASKLIST | FIND /I "%filename%" >NUL 2>&1
IF ERRORLEVEL 1 (
  GOTO CONTINUE
)
ECHO %filename% is still running
PING -n 6 127.0.0.1>NUL
GOTO LOOP

Alternatively, you could swap the test around and avoid the goto CONTINUE altogether.

:LOOP
TASKLIST | FIND /I "%filename%" >NUL 2>&1
IF ERRORLEVEL 0 (
  ECHO %filename% is still running
  PING -n 6 127.0.0.1>NUL
  GOTO LOOP
)

I would personally find that easier to read as the batch magic equivalent of a conventional spin lock style construction.

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ Ah yes, I like your loop better. I had taken most of that "wait" code from another post. \$\endgroup\$ – Kyle Delaney May 2 '18 at 20:04
1
\$\begingroup\$

I've applied Josiah's suggestions.

I added a kill switch rather than removing the TASKKILL because there are cases where an application runs a batch file and waits for it to exit so the application wouldn't just end on its own. For example, in NSIS I'm using nsExec::Exec to run the batch file silently.

As for the idea about the calling program inserting its path into the batch file directly, I'll leave that up to whoever writes the calling program. The current parameter implementation can serve as placeholders for that. But note that if the first parameter is omitted the kill switch won't work in the current design, so if direct placement is implemented then the kill switch should be changed as well.

@ECHO OFF

 rem // This is needed for GOTO:EOF or EXIT /B
SETLOCAL EnableExtensions

 rem // Pull out the name/extension from the path provided in the first parameter
 rem // Ex: C:\Folder\file.exe becomes file.exe
SET filename=%~nx1

 rem // Exit if no parameter is provided
IF "%filename%"=="" EXIT /B

 rem // Kill the task if second parameter is /kill (case-insensitive)
IF /I "%~2"=="/kill" TASKKILL /IM "%filename%" /F

:LOOP

 rem // See if TASKLIST result contains the filename, while suppressing output
TASKLIST | FIND /I "%filename%" >NUL 2>&1

 rem // %ERRORLEVEL% will be zero if the filename was found
IF ERRORLEVEL 0 (
  ECHO %filename% is still running

   rem // Wait for 5 seconds
  PING -n 6 127.0.0.1>NUL

  GOTO LOOP
)

 rem // "%~f1" expands to the full path provided in the first parameter
DEL /F "%~f1"

 rem // Delete this batch file
DEL /F "%~f0"
|improve this answer|||||
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.