2013-04-12 7 views
5

Ich schreibe ein Batch-Skript in PowerShell v1, das für die Ausführung geplant ist, sagen wir einmal pro Minute. Zwangsläufig wird es eine Zeit kommen, wenn der Job mehr als 1 Minute benötigt, um zu vervollständigen, und jetzt haben wir zwei Instanzen des Skripts ausgeführt, und dann möglicherweise 3, etc ...Sicherstellen, dass nur 1 Instanz des PowerShell-Skripts zu einer bestimmten Zeit ausgeführt wird

Ich möchte dies vermeiden, indem Sie das Skript haben Überprüfen Sie selbst, ob bereits eine Instanz von sich selbst ausgeführt wird, und wenn dies der Fall ist, wird das Skript beendet.

Ich habe dies in anderen Sprachen unter Linux getan, aber nie unter Windows mit PowerShell.

Zum Beispiel in PHP kann ich so etwas wie:

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

Gibt es etwas wie dies in Powershell v1? So etwas bin ich noch nicht begegnet.

Von diesen allgemeinen Mustern, die am meisten Sinn macht, wenn ein PowerShell-Skript häufig ausgeführt wird?

  1. Sperrdatei
  2. OS-Taskplaner
  3. Endlosschleife mit einem Schlafintervall
+1

Wie planen Sie es ersetzen? Wenn Sie den Taskplaner verwenden, haben Sie eine Option unter Einstellungen: "Wenn der Task bereits ausgeführt wird, gilt die folgende Regel: Starten Sie kein neues Instace". –

+0

Nun, das ist einfach. Ich kann Taskplaner verwenden oder eine Sperrdatei erstellen, die völlig unnötig erscheint, wenn das Betriebssystem damit umgehen kann. – Slinky

Antwort

7

Wenn das Skript gestartet wurde die powershell.exe -File Schalter verwenden, können Sie alle Powershell Fällen erkennen, die den Namen des Skripts in dem Prozess Commandline-Eigenschaft haben:

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

Hat das nicht eine Wettlaufsituation? Beide Skripts könnten starten, ihre Argumente parsen und dann diesen Befehl ausführen, bevor der andere beendet wird. Daher finden beide Skripts, dass ein anderer ausgeführt wird und dann beendet wird. Es ist wahrscheinlich ein sehr kleines Risiko, aber gut, wenn es so ist. – jpmc26

+0

Verwenden Sie ein Launch-/Wrapperscript, um den "Get-Wmiobject" -Test durchzuführen, um eine Race-Bedingung zu vermeiden –

1

ich nicht bewusst, so bin zu tun, was Sie direkt möchten. Sie können stattdessen eine externe Sperre verwenden. Wenn das Skript startet, ändert es einen Registrierungsschlüssel, erstellt eine Datei oder ändert den Inhalt einer Datei oder etwas Ähnliches. Wenn das Skript fertig ist, kehrt es die Sperre um. Auch oben im Skript, bevor die Sperre gesetzt wird, muss überprüft werden, um den Status der Sperre zu sehen. Wenn es gesperrt ist, wird das Skript beendet.

4

Das Laden einer Powershell-Instanz ist nicht trivial, und jede Minute wird dem System viel Overhead auferlegen. Ich würde nur eine Instanz planen und schreiben Sie das Skript in einer Prozess-Sleep-Prozess-Schleife ausgeführt werden. Normalerweise verwende ich einen Stoppuhr-Timer, aber ich glaube nicht, dass sie diese bis V2 hinzugefügt haben.

$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

Endlosschleife scheint wie eine gute Lösung, auch – Slinky

+0

Edit: nur bemerken, ich hatte die Operanden auf die Sleep-Timer-Berechnung umgekehrt. Fest. – mjolinor

1

Es ist „immer“ Am besten lassen Sie den "höchsten Prozess" mit solchen Situationen umgehen. Der Prozess sollte dies überprüfen, bevor es die zweite Instanz ausführt. Also ist es ratsam, den Taskplaner zu verwenden, um die Aufgabe für Sie zu erledigen. Dies wird auch mögliche Probleme mit Berechtigungen (Speichern einer Datei ohne Berechtigungen) beseitigen, und es wird Ihr Skript sauber halten.

Wenn die Aufgabe in Taskplaner konfigurieren, müssen Sie eine Option unter Einstellungen:

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

Hier ist meine Lösung. Es verwendet die Befehlszeile und die Prozess-ID, sodass nichts erstellt und verfolgt werden kann. und es ist mir egal, wie Sie beide Instanzen Ihres Skripts gestartet haben.

Folgendes sollte nur als Service-Leistung laufen:

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 
} 

Sie möchten

$MyInvocation.MyCommand.Path (FullPathName) 

mit

$MyInvocation.MyCommand.Name (Scriptname) 
Verwandte Themen