1

I'm running a powershell script using batch file like this :

@ECHO OFF
PowerShell.exe -Command "& 'U:\...\...\..._PS.ps1'"
PAUSE

This .ps1 script might take 15 minutes to run. During this time I want to avoid running this script once again. If a user runs the .bat file I want the console to print out something like "Powershell script is already running, wait until it's over"

From various examples I've seen this code :

echo off
tasklist /fi "imagename eq powershell.exe" |find ":" > nul
if errorlevel 1 taskkill /f /im "powershell.exe"
exit

There are 2 problems with it : 1.It only works with .exe files such as notepad,powershell etc... I have a path with .ps1 2.It kills the task, I need only to show a message

1
  • 5
    The first thing, I'd suggest is that you run your powershell file as a -File, not as a -Command, e.g. PowerShell -ExecutionPolicy RemoteSigned -File "U:\...\...\..._PS.ps1" Commented Oct 6, 2019 at 12:37

5 Answers 5

3

You could do something like the following if you are sticking with the -Command parameter:

powershell.exe -Command "if (((Get-CimInstance Win32_Process -Filter \"name = 'powershell.exe'\").CommandLine -match '..._PS\.ps1').Count -gt 1) {'Powershell is already running.'} else {& 'U:\...\...\..._PS.ps1'}"

The idea is to check the Win32_Process items currently running that have a command line that includes your ..._PS.ps1 file. Since the command that checks will add itself to the stack before condition is checked, you must check for more than 1 occurrence. Any literal . characters will need to be backslash escaped when using the -match operator since -match uses regex. Note the backslash escape of inner double quotes because you are running this from the CMD shell. Running this from PowerShell will require different escape characters.

Sign up to request clarification or add additional context in comments.

1 Comment

need to use double backslash : -match 'U:\\...\\...\\..._PS.ps1') otherwise error : "Unrecognized escape sequence"
2

You could probably use to check for a powershell.exe process with an associated CommandLine ending with the scripts name:

@(For /F "Delims==" %%A In (
    'WMIC Process Where "Name='powershell.exe' And CommandLine Like '%%\\unknown[_]PS.ps1\"'" Get Name /Value'
)Do @If "%%A"=="Name" (
    Echo "Powershell script is already running, wait until it's over"&Timeout 10 /NoBreak>NUL&Exit /B)) 2>NUL
@Powershell -ExecutionPolicy RemoteSigned -File "U:\...\...\unknown_PS.ps1"

Because you did not provide the fully qualified actual file path and name, you'll need to adjust this script yourself. On line 5 replace U:\...\...\unknown_PS.ps1 with your actual file path and importantly replace unknown on line 2 with the actual prefix to _PS.ps1. Please note that the Like operator uses an underscore as a wildcard character, so any underscores require escaping by surrounding them in square brackets, (I've already done that for the known underscore in _PS.ps1). I think that it's sufficient to check only for a command line ending with \unknown_PS.ps1" although you could extend it to the full path if you expect to have more than one powershell script with the same file name.

Comments

0

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

Comments

0

Since you are using PowerShell, you could also create a system-wide mutex in PowerShell to prevent multiple instances of the script to be executed at the same time.

@echo off
setlocal

:: This should be a predefined static unique value.
:: This is an example, you should generate your own GUID
set "MutexName=MyScript.PS1:{72a7654d-aa01-4e7e-a985-89add8524590}"
set "PSMsg=Powershell script is already running, wait until it''s over"

set "PSCode=[bool]$isOwner = $false; $mutex=[System.Threading.Mutex]::new($true , '%MutexName%', [ref]$isOwner)"
set "PSCode=%PSCode%; if($isOwner) {& '.\MyScript.ps1'} else {Write-Output '%PSMsg%'}; $mutex.close()"

PowerShell.exe -Command "iex ${env:PSCode}"

If you have control over the contents of the PowerShell script, then you should incorporate this method directly in to script itself instead of wrapping the script inside another code

Comments

-1

It seems that one solution to this problem is to use a lock file mechanism, by dbenham, that adapted to your code might look like this,

@ECHO OFF
CALL :main >>"%~f0"
EXIT /B

:main
PowerShell.exe -Command "& 'U:\...\...\..._PS.ps1'"

EXIT /B

Then if another process tries to execute this same batch-file it will fail. However a Powershell can execute .ps1. Therefore, the optimal solution, in this case, will be probably using the same mechanism inside this .ps1.

2 Comments

You certainly shouldn't be adding a PAUSE inside the :main label and you don't seem to have followed the syntax explained in the linked answer.
To add to @Compo's comment re syntax: Your code as currently written executes the PowerShell command and appends its output to the calling batch file. The upshot: The command (a) executes without output from the user's perspective, (b) the calling batch file is unexpectedly modified, and (c) repeat invocations are not prevented.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.