【问题标题】:Check if PowerShell script is running using batch script使用批处理脚本检查 PowerShell 脚本是否正在运行
【发布时间】:2019-10-06 12:25:20
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 首先,我建议您将powershell 文件作为-File 运行,而不是作为-Command,例如PowerShell -ExecutionPolicy RemoteSigned -File "U:\...\...\..._PS.ps1"
  • 标记为一个,请先阅读What should I do when someone answers my question?,然后再考虑发布的答案之一是否解决了您发布的问题。谢谢。

标签: powershell batch-file


【解决方案1】:

如果您坚持使用-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 运行它需要不同的转义字符。

【讨论】:

  • 需要使用双反斜杠:-match 'U:\\...\\...\\..._PS.ps1') 否则错误:“无法识别的转义序列”
【解决方案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 替换为您的实际文件路径,重要的是,将2 线上的unknown 替换为_PS.ps1 的实际前缀。请注意,Like 运算符使用下划线作为通配符,因此任何下划线都需要通过将它们括在方括号中来进行转义,(我已经为_PS.ps1 中的已知下划线做到了这一点)。我认为仅检查以 \unknown_PS.ps1" 结尾的命令行就足够了,尽管如果您希望有多个具有相同文件名的 powershell 脚本,您可以将其扩展到完整路径。

【讨论】:

    【解决方案3】:

    AdminOfThings's helpful answerCompo's helpful answer 都提供强大的解决方案

    如果您愿意做出一个假设,这里有一个替代解决方案,它可以工作:

    • 如果您的批处理被命名为foo.cmd,则发现任何执行名为foo.cmd 的批处理文件的cmd.exe 进程确实在执行同一个批处理文件。

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

    • 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
    

    【讨论】:

      【解决方案4】:

      由于您使用的是 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 脚本的内容,那么您应该将此方法直接合并到脚本本身,而不是将脚本包装在另一个代码中

      【讨论】:

        【解决方案5】:

        似乎解决此问题的一种方法是使用 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 中使用相同的机制。

        【讨论】:

        • 您当然不应该在:main 标签内添加PAUSE,而且您似乎没有遵循链接答案中解释的语法。
        • 要添加到@Compo 的注释重新语法:您当前编写的代码执行 PowerShell 命令并将其输出附加到调用批处理文件。结果:命令 (a) 从用户的角度执行没有输出,(b) 调用批处理文件被意外修改,以及 (c) 重复调用被阻止。
        猜你喜欢
        • 2013-07-30
        • 2015-07-06
        • 2010-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-03
        • 1970-01-01
        相关资源
        最近更新 更多