Assure only 1 instance of PowerShell Script is Running at any given Time

后端 未结 6 1453
小蘑菇
小蘑菇 2021-01-17 12:31

I am writing a batch script in PowerShell v1 that will get scheduled to run let\'s say once every minute. Inevitably, there will come a time when the job needs more than 1 m

相关标签:
6条回答
  • 2021-01-17 13:04

    Loading up an instance of Powershell is not trivial, and doing it every minute is going to impose a lot of overhead on the system. I'd just scedule one instance, and write the script to run in a process-sleep-process loop. Normally I'd uses a stopwatch timer, but I don't think they added those until 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)
        }
      }
    
    0 讨论(0)
  • 2021-01-17 13:08

    If the script was launched using the powershell.exe -File switch, you can detect all powershell instances that have the script name present in the process commandline property:

    Get-WmiObject Win32_Process -Filter "Name='powershell.exe' AND CommandLine LIKE '%script.ps1%'"
    
    0 讨论(0)
  • 2021-01-17 13:11

    I'm not aware of a way to do what you want directly. You could consider using an external lock instead. When the script starts it changes a registry key, creates a file, or changes a file contents, or something similar, when the script is done it reverses the lock. Also at the top of the script before the lock is set there needs to be a check to see the status of the lock. If it is locked, the script exits.

    0 讨论(0)
  • 2021-01-17 13:13

    Here's my solution. It uses the commandline and process ID so there's nothing to create and track. and it doesn't care how you launched either instance of your script.

    The following should just run as-is:

        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
    
    0 讨论(0)
  • 2021-01-17 13:15
    $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
    }
    

    You may want to replace

    $MyInvocation.MyCommand.Path (FullPathName)
    

    with

    $MyInvocation.MyCommand.Name (Scriptname)
    
    0 讨论(0)
  • 2021-01-17 13:25

    It's "always" best to let the "highest process" handle such situations. The process should check this before it runs the second instance. So my advise is to use Task Scheduler to do the job for you. This will also eliminate possible problems with permissions(saving a file without having permissions), and it will keep your script clean.

    When configuring the task in Task Scheduler, you have an option under Settings:

    If the task is already running, then the following rule applies: 
    Do not start a new instace
    
    0 讨论(0)
提交回复
热议问题