2013-04-12 11 views
5

Sto scrivendo uno script batch in PowerShell v1 che verrà pianificato per l'esecuzione diciamo una volta ogni minuto. Inevitabilmente, arriverà un momento in cui il lavoro ha bisogno di più di 1 minuto per essere completato e ora abbiamo due istanze dello script in esecuzione, e quindi forse 3, ecc ...Assicurare solo 1 istanza di PowerShell Script è in esecuzione in un dato momento

Voglio evitare questo avendo lo script controlla se c'è già un'istanza di se stesso in esecuzione e, in tal caso, lo script termina.

Ho fatto questo in altre lingue su Linux ma non l'ho mai fatto su Windows con PowerShell.

Per esempio in PHP che posso fare qualcosa di simile:

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

C'è qualcosa di simile in PowerShell v1? Non ho ancora incontrato nulla di simile.

Di questi modelli comuni, quale ha più senso con uno script PowerShell eseguito frequentemente?

  1. File Lock
  2. OS Utilità di pianificazione
  3. ciclo infinito con un intervallo di sonno
+1

Come stai pianificando esso? Se stai utilizzando l'Utilità di pianificazione, hai un'opzione sotto Impostazioni: "Se l'attività è già in esecuzione, si applica la seguente regola: Non avviare una nuova istanza". –

+0

Bene, è facile. Posso usare l'Utilità di pianificazione o creare un file di blocco, che sembra del tutto inutile se il sistema operativo è in grado di gestirlo. – Slinky

risposta

7

Se lo script è stato avviato con l'interruttore -File powershell.exe, è possibile rilevare tutte le istanze PowerShell che hanno il nome dello script presente nella proprietà processo di comando:

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

Questo non ha una condizione di gara? Entrambi gli script potrebbero iniziare, analizzare i loro argomenti e quindi eseguire questo comando prima che l'altro termini l'uscita. Quindi entrambi gli script ne trovano un altro in esecuzione e quindi escono. Probabilmente è un rischio molto piccolo, ma è bello esserne consapevoli se è così. – jpmc26

+0

usa un launch-/wrapperscript per eseguire il test 'Get-Wmiobject' per evitare una condizione di competizione –

1

Io non sono a conoscenza di un modo per fare quello che vuoi direttamente. Potresti prendere in considerazione l'utilizzo di un lucchetto esterno. Quando lo script inizia, cambia una chiave di registro, crea un file, o cambia il contenuto di un file, o qualcosa di simile, quando lo script termina, inverte il blocco. Inoltre, nella parte superiore dello script, prima che il blocco sia impostato, è necessario un controllo per vedere lo stato del blocco. Se è bloccato, lo script termina.

4

Il caricamento di un'istanza di Powershell non è banale, e farlo ogni minuto imporrà un sacco di spese generali sul sistema. Vorrei solo analizzare un'istanza e scrivere lo script per l'esecuzione in un ciclo di processo-processo del sonno. Normalmente userei un cronometro, ma non credo che li abbiano aggiunti fino alla 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

Il ciclo infinito sembra una buona soluzione, anche – Slinky

+0

Modifica: basta notare che gli operandi sono stati invertiti nel calcolo dello sleep timer. Fisso. – mjolinor

1

E ' "sempre" meglio lasciare che il "processo più alto" gestisca tali situazioni. Il processo dovrebbe controllarlo prima di eseguire la seconda istanza. Quindi il mio consiglio è di usare l'Utilità di pianificazione per fare il lavoro per te. Questo eliminerà anche possibili problemi con le autorizzazioni (salvando un file senza autorizzazioni) e manterrà pulito il tuo script.

Quando si configura il compito in Utilità di pianificazione, avete un'opzione in Impostazioni:

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

Ecco la mia soluzione. Usa la riga di comando e l'ID di processo in modo che non ci sia nulla da creare e tracciare. e non importa come hai lanciato l'istanza del tuo script.

Di seguito dovrebbe solo correre così com'è:

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
$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 
} 

si consiglia di sostituire

$MyInvocation.MyCommand.Path (FullPathName) 

con

$MyInvocation.MyCommand.Name (Scriptname) 
Problemi correlati