使用批处理脚本检查PowerShell脚本是否正在运行

时间:2019-10-06 12:25:20

标签: powershell batch-file process ps1

我正在使用这样的批处理文件运行Powershell脚本:

auto identity = [](int n) constexpr
{
    return n;
};

constexpr auto add = [](int x, int y)
{
    auto L = [=] // what is = [=]?
    {
        return x;
    };
    auto R = [=]
    {
        return y;
    };
    return [=] // what return [=] means here?
    {
        return L() + R();
    };
};

void ConstexprLambda()
{
    static_assert(identity(123) == 123);
    static_assert(add(1, 2)() == 3); // why can't we just add(1,2) like above?
}

此.ps1脚本可能需要15分钟才能运行。在这段时间内,我想避免再次运行此脚本。如果用户运行.bat文件,我希望控制台打印出类似“ Powershell脚本已在运行,请等到结束”之类的内容。

从各种示例中,我已经看到了这段代码:

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

它有2个问题: 1.它仅适用于.exe文件,例如记事本,powershell等...我具有.ps1的路径 2.它杀死了任务,我只需要显示一条消息

5 个答案:

答案 0 :(得分:3)

如果坚持使用-Command参数,则可以执行以下操作:

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'}"

想法是检查当前正在运行的Win32_Process项,这些项的命令行包含您的..._PS.ps1文件。由于检查命令将在检查条件之前将自身添加到堆栈中,因此您必须检查1次以上的事件。由于.使用正则表达式,因此在使用-match运算符时,所有文字-match字符都需要反斜杠转义。请注意内部双引号的反斜杠转义,因为您是从CMD Shell运行的。从PowerShell运行此命令将需要不同的转义符。

答案 1 :(得分:2)

您可能可以使用来检查powershell.exe进程,并以脚本名结尾的关联CommandLine

@(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"

由于您没有提供完全合格的实际文件路径和名称,因此需要自行调整此脚本。在5行中,将U:\...\...\unknown_PS.ps1替换为您的实际文件路径,并在unknown行中将2替换为_PS.ps1的实际前缀。请注意,Like运算符使用下划线作为通配符,因此任何下划线都需要通过将其括在方括号中来进行转义,(对于_PS.ps1中已知的下划线,我已经这样做了)。我认为仅检查以\unknown_PS.ps1"结尾的命令行就足够了,尽管如果您希望具有相同文件名的多个powershell脚本可以将其扩展到完整路径。

答案 2 :(得分:0)

AdminOfThings's helpful answerCompo's helpful answer均提供可靠的解决方案

这是一个替代解决方案,如果您愿意做一个假设

  • 如果您的批次名为foo.cmd,则发现执行名为cmd.exe的批次文件的任何foo.cmd进程确实在执行相同的批次文件。

该解决方案利用了两个事实:

  • cmd.exe控制台窗口将名称/路径(取决于调用细节)附加到窗口标题;例如,如果您要从当前目录调用foo.cmd,则窗口标题将更改为Command Prompt - foo.cmd

  • tasklist.exe /v还会输出匹配进程的主窗口标题。

@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

您可以更加努力地使解决方案更强大:

  • 为当前控制台窗口分配唯一的自定义窗口标题

  • 首先检查具有该标题的cmd.exe进程:如果找到,请退出;如果没有,请设置唯一的窗口标题并启动PowerShell脚本。

  • 注意事项:您必须在完成时重置窗口标题,否则它将超出批处理文件的执行范围:

    • 面临的挑战是,如果有人使用 Ctrl + C 终止您的批处理文件,您将没有机会重置标题。

    • 但是,如果您的批处理文件是在控制台窗口中启动的,那么该批处理文件将在终止时自动关闭,例如从桌面或文件资源管理器启动,那么这将不是问题。

@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

答案 3 :(得分:0)

由于使用的是PowerShell,因此还可以在PowerShell中创建系统范围的互斥锁,以防止同时执行脚本的多个实例。

@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}"

如果您可以控制PowerShell脚本的内容,则应将此方法直接合并到脚本本身中,而不是将脚本包装在另一个代码中

答案 4 :(得分:-1)

解决此问题的一种方法似乎是使用dbenham编写的lock file mechanism,该代码适合您的代码,

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

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

EXIT /B

然后,如果另一个进程尝试执行相同的batch-file,它将失败。但是Powershell可以执行.ps1。因此,在这种情况下,最佳解决方案可能是在.ps1内部使用相同的机制。

相关问题