AdminOfThings's helpful answer and Compo's helpful answer both provide robust solutions.
Here's an alternative solution that works if you're willing to make one assumption:
- If your batch is named, say,
foo.cmd, finding any cmd.exe process executing a batch file named foo.cmd is indeed executing that same batch file.
The solution takes advantage of two facts:
A cmd.exe console window appends the name / path (depending on invocation specifics) to the window title; for instance, if you're invoking foo.cmd from the current directory, the window title changes to Command Prompt - foo.cmd.
tasklist.exe /v also outputs the main-window titles of matching processes.
@echo off
setlocal
rem # Count the cmd.exe processes that have the name of this batch file
rem # in their window title.
for /f %%c in ('tasklist /v /fi "imagename eq cmd.exe" ^| find /C "%~nx0"') do (
REM # If more than 1 process matches, another instance of this batch file
REM # must already be running, so issue a warning and exit.
if %%c GTR 1 echo Powershell script already running, wait for it to finish. >&2 & exit /b 1
)
echo Running PowerShell script...
rem # Run the PowerShell script.
rem # As @Compo advises, it's better to use -File to invoke scripts
rem # and to use -ExecutionPolicy Bypass if needed (and you trust the script).
powershell.exe -file U:\path\to\your\PowerShellScript.ps1
pause
With a more effort you can make the solution more robust:
Assign a unique custom window title to the current console window.
Check for cmd.exe processes with that title first: if found, exit; if not, set the unique window title and launch the PowerShell script.
Caveat: You must reset the window title on completion, otherwise it will linger beyond the execution of the batch file:
The challenge there is that if someone terminates your batch file with Ctrl+C, you won't get a chance to reset the title.
However, this won't be an issue if your batch file was launched in a console window that automatically closes when the batch file terminates - such as if it was launched from the desktop or File Explorer.
@echo off
setlocal
rem # Get and save the current window title.
rem # Note: This - complex and somewhat slow - solution is necessary,
rem # because the `title` only supports *setting* a window title,
rem # not *getting it*.
for /f "usebackq delims=" %%t in (`powershell -noprofile -c "[Console]::Title.Replace(' - '+[Environment]::CommandLine,'') -replace '(.+) - .+', '$1'"`) do set origTitle=%%t
rem # Choose a unique window title.
set "titleWhileRunning=Some.ps1 is running..."
rem # If a cmd.exe process with the same window title already exists
rem # we issue a warning and exit
tasklist /v /fi "imagename eq cmd.exe" /fi "windowtitle eq %titleWhileRunning%" | find "%titleWhileRunning%" >NUL
if %ERRORLEVEL% EQU 0 echo Powershell script already running, wait for it to finish. >&2 & exit /b 1
rem # Set the new title that indicates that the script is running.
title %titleWhileRunning%
echo Running PowerShell script...
rem # Run the PowerShell script.
rem # As @Compo advises, it's better to use -File to invoke scripts
rem # and to use -ExecutionPolicy Bypass if needed (and you trust the script).
powershell.exe -file U:\path\to\your\PowerShellScript.ps1
rem # Restore the original window title.
title %origTitle%
pause
-File, not as a-Command, e.g.PowerShell -ExecutionPolicy RemoteSigned -File "U:\...\...\..._PS.ps1"