确保在任何给定时间只有1个PowerShell脚本实例正在运行

时间:2013-04-12 11:01:56

标签: windows powershell

我正在PowerShell v1中编写一个批处理脚本,它将按计划运行,每分钟说一次。不可避免地,有一段时间工作需要超过1分钟才能完成,现在我们有两个脚本运行实例,然后可能有3个等等......

我希望通过让脚本本身检查是否有自己的实例已经在运行来避免这种情况,如果是,则退出脚本。

我在Linux上用其他语言完成了这项工作但从未在Windows上使用PowerShell完成此操作。

例如在PHP中,我可以执行以下操作:

exec("ps auxwww|grep mybatchscript.php|grep -v grep", $output);
if($output){exit;}

PowerShell v1中有这样的东西吗?我还没有遇到过这样的事情。

在这些常见模式中,哪一个最常用于经常运行的PowerShell脚本?

  1. 锁定文件
  2. OS任务计划程序
  3. 具有睡眠间隔的无限循环

6 个答案:

答案 0 :(得分:8)

如果使用powershell.exe -File开关启动脚本,则可以检测进程命令行属性中存在脚本名称的所有powershell实例:

Get-WmiObject Win32_Process -Filter "Name='powershell.exe' AND CommandLine LIKE '%script.ps1%'"

答案 1 :(得分:5)

这是我的解决方案。它使用命令行和进程ID,因此无需创建和跟踪。它并不关心你是如何启动脚本的任何一个实例的。

以下内容应该按原样运行:

    Function Test-IfAlreadyRunning {
    <#
    .SYNOPSIS
        Kills CURRENT instance if this script already running.
    .DESCRIPTION
        Kills CURRENT instance if this script already running.
        Call this function VERY early in your script.
        If it sees itself already running, it exits.

        Uses WMI because any other methods because we need the commandline 
    .PARAMETER ScriptName
        Name of this script
        Use the following line *OUTSIDE* of this function to get it automatically
        $ScriptName = $MyInvocation.MyCommand.Name
    .EXAMPLE
        $ScriptName = $MyInvocation.MyCommand.Name
        Test-IfAlreadyRunning -ScriptName $ScriptName
    .NOTES
        $PID is a Built-in Variable for the current script''s Process ID number
    .LINK
    #>
        [CmdletBinding()]
        Param (
            [Parameter(Mandatory=$true)]
            [ValidateNotNullorEmpty()]
            [String]$ScriptName
        )
        #Get array of all powershell scripts currently running
        $PsScriptsRunning = get-wmiobject win32_process | where{$_.processname -eq 'powershell.exe'} | select-object commandline,ProcessId

        #Get name of current script
        #$ScriptName = $MyInvocation.MyCommand.Name #NO! This gets name of *THIS FUNCTION*

        #enumerate each element of array and compare
        ForEach ($PsCmdLine in $PsScriptsRunning){
            [Int32]$OtherPID = $PsCmdLine.ProcessId
            [String]$OtherCmdLine = $PsCmdLine.commandline
            #Are other instances of this script already running?
            If (($OtherCmdLine -match $ScriptName) -And ($OtherPID -ne $PID) ){
                Write-host "PID [$OtherPID] is already running this script [$ScriptName]"
                Write-host "Exiting this instance. (PID=[$PID])..."
                Start-Sleep -Second 7
                Exit
            }
        }
    } #Function Test-IfAlreadyRunning


    #Main
    #Get name of current script
    $ScriptName = $MyInvocation.MyCommand.Name 


    Test-IfAlreadyRunning -ScriptName $ScriptName
    write-host "(PID=[$PID]) This is the 1st and only instance allowed to run" #this only shows in one instance
    read-host 'Press ENTER to continue...'  # aka Pause

    #Put the rest of your script here

答案 2 :(得分:4)

加载Powershell的实例并非易事,每分钟都要这样做会给系统带来很多开销。我只是简单地编写一个实例,并编写脚本以在进程 - 睡眠 - 进程循环中运行。通常我会使用秒表计时器,但我不认为他们在V2之前添加了秒表。

$interval = 1
while ($true)
 {
  $now = get-date
  $next = (get-date).AddMinutes($interval)

  do-stuff

  if ((get-date) -lt $next)
    {
     start-sleep -Seconds (($next - (get-date)).Seconds)
    }
  }

答案 3 :(得分:1)

我不知道如何直接做你想做的事。你可以考虑使用外部锁。当脚本启动时,它会更改注册表项,创建文件或更改文件内容或类似内容,当脚本完成后,它会反转锁定。在设置锁定之前,还在脚本的顶部,需要检查以查看锁定的状态。如果它被锁定,脚本将退出。

答案 4 :(得分:1)

让“最高程序”处理这种情况“总是”最好。进程应该在运行第二个实例之前检查它。所以我的建议是使用Task Scheduler为你完成这项工作。这也将消除可能的权限问题(保存没有权限的文件),并保持脚本清洁。

在任务计划程序中配置任务时,您可以在设置下单击选项:

If the task is already running, then the following rule applies: 
Do not start a new instace

答案 5 :(得分:0)

$otherScriptInstances=get-wmiobject win32_process | where{$_.processname -eq 'powershell.exe' -and $_.ProcessId -ne $pid -and $_.commandline -match $($MyInvocation.MyCommand.Path)}
if ($otherScriptInstances -ne $null)
{
    "Already running"
    cmd /c pause
}else
{
    "Not yet running"
    cmd /c pause
}

您可能想要替换

$MyInvocation.MyCommand.Path (FullPathName)

$MyInvocation.MyCommand.Name (Scriptname)