Snippet 0x05: Windows .bat: Checking if process is running by PID file
Batch (.bat) files are an MS-DOS legacy technology and I don’t know a single person who loves writing them — and yet, to this day, many people have to do it. In order to run Java programs, start daemons or check if a process is running, batch files have to be used. For my open source file sync tool Syncany, I had to do just that:
The Syncany daemon runs in the background and is started by a batch script. To check if the daemon is already running, the batch script needs to read a PID file and determine if a process with this PID is running.
Since it took me an enormeous amount of time to figure out how to do that (I am a Linux guy!), I wanted to share that little script in this code snippet.
Content
1. Full code
As always, the full working code (embedded in a my open source file sync software Syncany is available on GitHub. Feel free to use it, or to leave feedback in the comments.
2. Read PID file, and check if process running
Assuming that the application writes a PID file, the batch script has to do two things: First, it needs to check whether that PID file exists. If it doesn’t the application is obviously not running (or the PID file creation failed for some reason).
And second, check if the process ID in the PID file is actually running. If it is, the daemon/process is running. If it is not, the process has probably crashed or has forgotten to clean up after itself. In that case, we can consider the process not running.
Without further ado, here’s the .bat file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
rem (..) set APP_USER_DIR=%AppData%\Syncany set APP_DAEMON_PIDFILE=%APP_USER_DIR%\daemon.pid set APP_DAEMON_PIDFILE_TMP=%APP_USER_DIR%\daemon.pid.tmp rem (..) set RUNNING=0 set APID=-1 if exist %APP_DAEMON_PIDFILE% ( set /P APID=<%APP_DAEMON_PIDFILE% ) if not "%APID%" == "-1" ( wmic process where "ProcessID = %APID%" get processid 2> %APP_DAEMON_PIDFILE_TMP% > NUL set /P STDERR=<%APP_DAEMON_PIDFILE_TMP% for /f %%i in ("%APP_DAEMON_PIDFILE_TMP%") do set size=%%~zi if not defined size set size=0 del %APP_DAEMON_PIDFILE_TMP% ) if not "%APID%" == "-1" ( if "%size%" == "0" set RUNNING=1 ) rem (..) |
By default, the script is assumed to be not running, so only if the PID file exists (if exist ..), the file is read (set /P APID=<...). If this is successful, the APID variable contains the process ID and not -1.
Using the wonderful wmic process command, we can check if that PID is returned in the list of running processes. While a human being can easily determine if that is the case, it’s not so easy with batch files, because the find command is a bit unreliable. I resolved this by simply checking the error output (STDERR) instead. If wmic doesn’t find any process matching the given PID, it writes something to STDERR. If it does, STDERR is empty. To check if STDERR returned something we redirect the error output to a temporary file (2> ...) and check if that file has a size of zero (if not defined size set size=0).
In the last bit, we check if the process ID is not -1, and the size of the temporary error output file is zero (no error); and only then we set RUNNING=1.
3. Issues and justification
I most certainly realize that this solution is not great. I’d even go as far as to say it’s a dirty workaround. However, all the other solutions I’ve tried (tasklist .. | find and checking for the %ERRORLEVEL%) were terribly unreliable. In particular, the find errorlevel/exit code arbitraribly returned 0 (99%), 1 or 9009 — even if the state of the daemon/process did not change. The solution above works just fine, and I hope it’ll help you guys somehow.
A. About this post
I’m trying a new section for my blog. I call it Code Snippets. It’ll be very short, code-focused posts of things I recently discovered or find fascinating or helpful. I hope this helps.
Great!
Thankyou for this share!
You said: “In order to run Java programs, start daemons or check if a process is running, batch files have to be used.”
That’s not true, PowerShell can do all those things in a much more structured way than batch files. I rarely find that I _must_ use batch files. There are times that they are more convenient than PowerShell scripts since they work automatically as drop targets on Windows but even then I often use the batch file to pass the dropped filename to a PowerShell script. I bet I could make ps1 files valid drop targets too but I just haven’t bothered.
That could be done in just a couple relevant lines of PowerShell script like this:
$id = get-content $env:APPDATA\Syncany\daemon.pid -ErrorAction SilentlyContinue
$env:RUNNING = $(get-process -Id $id).Count
You could even replace the $id in the second line with the first line wrapped in $() and make it a single line script. If the process doesn’t exist then you will get an error message at the prompt about $id being null. With a little more scripting that error could be avoided but it’s not harmful to the function of the script.
I know that wasn’t the point of your blog post but PowerShell has been around for quite a while now so I hate to see batch files used when PowerShell would be so much easier.
I’ve never done any PS (not a Windows person). I’ll certainly give it a shot though. Thanks for the comment.
Came across this in a google search. you saved me a day of coding. thanks!