确保在任何给定时间只有1个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. locking文件
  2. OS任务计划程序
  3. 无限循环与睡眠间隔

Solutions Collecting From Web of "确保在任何给定时间只有1个PowerShell脚本实例正在运行"

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

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

加载一个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) } } 

我不知道有什么方法可以直接做你想做的事情。 你可以考虑使用外部锁而不是。 脚本启动时,它会更改注册表项,创建文件或更改文件内容或其他类似内容,脚本完成后会反转锁定。 在设置锁之前的脚本顶部也需要检查锁的状态。 如果它被锁定,则脚本退出。

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

在“任务计划程序”中配置任务时,在“设置”下有一个选项:

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

这是我的解决方案。 它使用命令行和进程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 
 $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)