views:

53

answers:

2
:: dostuff.bat
@echo off
:: insert long-running process call here
: End

What can I add to this batch file to make it terminate if it's already running in another process when it's executed?

+2  A: 

Well, if there can be only exactly one instance ever, then you can probably do this by creating a dummy file somewhere, probably in the temp directory:

copy NUL "%TEMP%\dostuff_is_doing_stuff.tmp"

you then remove it after you're done:

del "%TEMP%\dostuff_is_doing_stuff.tmp"

and at the start of the batch file you can check whether that file exists and exit accordingly:

if exist "%TEMP%\dostuff_is_doing_stuff.tmp" (
    echo DoStuff is already running. Exiting ...
    exit /b 1
)

Similarly, you can do that by changing the window title, which should also be more robust against a Ctrl+C'ed or crashed batch file.

Set the title:

title DoStuff

Re-set it afterwards:

title Not doing stuff

Check for it:

tasklist /FI "WINDOWTITLE eq DoStuff"

However, this has one problem: When cmd runs as administrator you'll get "Administrator: DoStuff" as the title. Which doesn't match anymore with tasklist. You can hack-solve it by also checking for "Administrator: DoStuff" but that might look different depending on the Windows UI language, so it's probably not the best solution.

Joey
The tasklist idea is great. Thank you. How do I write a test against that command's output? That is, when I run tasklist in the batchfile, what's the logic to know if it told me the batch file was already running or not?
lance
Hm, sorry for that tasklist stuff. I thought tasklist would respond with an exit code; you could then use them with `if errorlevel ...` but tasklist always returns 0, regardless of whether a window was found or not. That sucks a bit. You can still capture the output with `for /f` but I'd advise against it since that's inherently brittle (localization and stuff).
Joey
Right. When that didn't work, I fired off my question. I can pipe tasklist's results into findstr, which returns a conditional errorlevel. I don't like it, but I never concern myself with aesthetics when I'm authoring batch files (especially for personal use, as in this case).
lance
Ah, right, `findstr` eluded me at the moment. Yes, that'd be easier still. Still, I don't particularly like relying on the output being in a particular language or format.
Joey
Concur. I'll be looking, simply, for cmd.exe in the output. I wonder if that changes from the locale/region (or version of Windows, even).
lance
Hm, right, one can look for the success message or for the failure message. The success one should be safe, indeed. I can't think straight today, apparently.
Joey
A: 

There is no way to do this in a clean way without using a custom external tool (You want something that creates a mutex AND runs (and waits for) your external command, this way, if the process is killed, the mutex dies with it)

Batch:

@echo off
echo starting long running process
REM calling dir here just to get a long running operation
onecmd.exe cmd.exe /C dir /S /B \*
echo done...bye

C++ helper app:

//Minimal error checking in this sample ;)
#include <Windows.h>
#include <TCHAR.h>

int main()
{
    const TCHAR myguid[]=_T("50D6C9DA-8135-42de-ACFE-EC223C214DD7");
    PROCESS_INFORMATION pi;
    STARTUPINFO si={sizeof(STARTUPINFO)};

    HANDLE mutex=CreateMutex(NULL,true,myguid);
    DWORD gle=GetLastError();
    if (!mutex || gle==ERROR_ALREADY_EXISTS) 
    {
        CloseHandle(mutex);
        return gle;
    }

    TCHAR*p=GetCommandLine();
    if (*p=='"' && *(++p)) {
        while (*p && *p!='"')++p;if (*p)++p;
    }
    else 
        while (*p && *p!=' ')++p;
    while(*p==' ')++p;

    if (CreateProcess(0,p,0,0,false,0,0,0,&si,&pi)) 
    {
        DWORD ec;
        WaitForSingleObject(pi.hProcess,INFINITE);
        GetExitCodeProcess(pi.hProcess,&ec);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        return ec;
    }
    gle=GetLastError();
    CloseHandle(mutex);
    return gle;
}
Anders