highspeed1
Goto Top

Script soll sich nur einmal starten lassen

Hallo Forum.

Ich habe ein Script auf einem Server abgelegt. Dieses Script kann von verschiedenen PCs gestartet werden. Allerdings darf es nicht parallel ausführbar sein. Wie kann man so etwas realisieren?

Es sollte nach Möglichkeit so aufgebaut sein das es auch nach einem Softwareabsturz funktionsfähig ist.

Ich freue mich schon auf neuen Input.

Gruß
Christoph

Content-ID: 668397

Url: https://administrator.de/contentid/668397

Ausgedruckt am: 28.09.2024 um 01:09 Uhr

Ted555
Ted555 26.09.2024 aktualisiert um 09:23:04 Uhr
Goto Top
Z B. mittels Status-Datei die man in einem Netzwerk-Pfad ableget und das im Skript-Kopf abfragt, wenn sie existiert, dann Abbruch, wenn nein weiter gehts und Status-Datei anlegen. Am Ende des Skriptes oder bei Fehler log schreiben und Status-Datei löschen.
HighSpeed1
HighSpeed1 26.09.2024 um 09:25:25 Uhr
Goto Top
Hallo Ted555.

Danke für die schnelle Antwort.

Wie kann ich denn sicherstellen das diese Datei bei einem Softwareabsturz wieder zurückgesetzt wird? Kann man in die Datei irgendetwas schreiben das den PC und User identifiziert und man es anhand der Daten prüfen kann ob das Script dort noch läuft?

Gruß
Christoph
Ted555
Ted555 26.09.2024 aktualisiert um 09:42:17 Uhr
Goto Top
Klar kannst du da auch Statusinformationen rein schreiben. Softwarefehler sollte man im Skript auch immer mit Try Catch abfangen. Mit Get-Process bzw. via Win32_Process und WMI kannst du laufende Prozesse auch remote abfragen.
Mit Parametern kannst du auch steuern ob man die Status-Datei forciert löscht wenn abgestürzt. Viele Wege führen nach Rom...
em-pie
em-pie 26.09.2024 um 09:40:30 Uhr
Goto Top
Moin,

lasse das Script mit einem Parameter aufrufen:
myScript.ps1 -Startmode cleanup sorgt beim Start des Rechner dafür, dass die Statusdatei gelöscht wird

wird myScript.ps1 ohne den Parameter aufgerufen (oder mit -Startmode normal), läuft es normal durch.
Wird es weggelassen, wird der Anwender/ Admin vermöbelt, weil er den Parameter nicht mitgegeben hat.
Michi91
Michi91 26.09.2024 um 09:53:56 Uhr
Goto Top
Du kannst auch auch den Zeitstempel der Statusdatei abfragen und "annehmen" dass es zu einem Absturz kam, wenn diese älter als X ist und dann die Datei löschen bzw. neuanlegen und so den Prozess neu starten
emeriks
emeriks 26.09.2024 aktualisiert um 09:56:55 Uhr
Goto Top
Hi,
lass das Script während des Laufs diese Status-Datei immer wieder aktualisieren.
Beim Start des Scripts prüfts Du, ob diese Datei vorhanden ist. Wenn ja, ob der LastWrite-Zeitstempel aktuell ist (max. Alter musst Du Dir festlegen).
Wenn nicht vorhanden oder Zeitstempel zu alt, dann Script fortsetzen, sonst abbrechen.

E.
HighSpeed1
HighSpeed1 26.09.2024 um 11:14:13 Uhr
Goto Top
Hi.

Das mit dem Zeit-Stempel ist eine gute Idee. So ist auf jeden fall gewährleistet dass das Script am nächsten Tag wieder funktioniert.> Zitat von @Ted555:

Klar kannst du da auch Statusinformationen rein schreiben. Softwarefehler sollte man im Skript auch immer mit Try Catch abfangen. Mit Get-Process bzw. via Win32_Process und WMI kannst du laufende Prozesse auch remote abfragen.
Mit Parametern kannst du auch steuern ob man die Status-Datei forciert löscht wenn abgestürzt. Viele Wege führen nach Rom...

@ttd555: Kannst Du hierzu bitte mal ein Beispiel für einstellen? Mit remote habe ich bisher noch nie gearbeitet.

Gruß
Christoph
Ted555
Lösung Ted555 26.09.2024 aktualisiert um 11:26:07 Uhr
Goto Top
Kannst Du hierzu bitte mal ein Beispiel für einstellen? Mit remote habe ich bisher noch nie gearbeitet.
Get-CimInstance Win32_Process -ComputerName XXXXXX -Filter "Name = 'blablub.exe'"  

Würde es aber persönlich ohne das Remote-Gedöns so abfackeln

# status file
$statusfile = "\\server.domain.tld\share\status.tok"  
# maximum time a process is allowed to run
$maxwaitseconds = 1800

$erroractionpreference = 'Stop'  
# check if file exists
if (Test-Path $statusfile -PathType leaf){
  # if file last modified older than x seconds, delete file
  if ((Get-Item $statusfile).LastWriteTime -lt (get-date).AddSeconds(-$maxwaitseconds)){
      Remove-Item $statusfile -force
  }else{
     # other script still running, exit script
     exit
  }
}

# set statusfile
"User '$env:USERNAME' running process on machine '$env:COMPUTERNAME'" | set-content $statusfile  

try{
    # run actions here
    $cmd = start-process "D:\Pfad\zum\Programm.exe" -PassThru  
    # check process continously while running
    $start = get-date
    while(!$cmd.HasExited){
        write-host "Waiting for process to finish ..."  
        # if process runs longer than x seconds, forcefully end process and throw error
        if(((get-date)-$start).TotalSeconds -ge $maxwaitseconds -or !$cmd.Responding){
            $cmd | stop-process -Force
            throw  "Process did not finish in time or did not respond, process killed!"  
        }
        sleep 1
    }
    write-host "Worker finished."  
}catch{
    # write error
    write-host $_.Exception.Message -F Red
}finally{
    # remove status file
    Remove-Item $statusfile -force
}
emeriks
emeriks 26.09.2024 um 11:29:51 Uhr
Goto Top
Mit Get-Process bzw. via Win32_Process und WMI kannst du laufende Prozesse auch remote abfragen.
Das macht aber auch nur Sinn, wenn man alle in Frage kommende Computer abfragt und dann auch noch für all diese die entsprechenden Berechtigungen hat. Also wohl eher nicht praktikabel.
Wenn man über Freigabe ein Script startet, dann läuft dieses doch nicht auf dem Server sondern auf dem Client, wo man es startet.

Was auch nich eine Variante wäre:

Das Script läuft nur auf genau einem Computer. Und der Anwender löst den Start des Scripts auf diesem Client aus. Das Script kann jetzt lokal die laufenden Prozesse prüfen, und wenn es schon läuft, dann wieder abbrechen.
Auch dafür gäbe es dann auch verschiedene Möglichkeiten der Umsetzung.
Eine wäre z.B. ein Sheduled Task auf diesem einen ausführenden Server. Der Anwender muss dann in der lage sein, diesen Scheduled Task remote auszulösen. Am Task könnte man einstellen, dass dieser nur in einer Instanz laufen darf. Als Trigger für diesen Task könnte z.B. das Erstellen einer definierten Datei sein. Wenn man NTFS überwacht, dann würde das einen Eventlog-Eintrag auf diesem Computer auslösen. Und an diesen Eventlog-Eintrag könnte man den Task anpflanschen.

Viele Wege führen nach Rumänien.
emeriks
emeriks 26.09.2024 aktualisiert um 11:34:46 Uhr
Goto Top
Oder ein Scheduled Task auf diesem einen Computer mit diesem Script. Auslösung jede Minute einmal. Nur eine Instanz zulassen.
Das Script prüft Existenz einer Datei, welche vom Anwender erstellt werden kann. Wenn Datei nicht vorhanden, dann Script beenden. Wenn vorhanden, dann Script fortsetzen. Dabei als erstes diese Datei löschen.
Selbst wenn jetzt während des Laufs jemand anderer diese Datei wieder erstellt, würde das Script doch frühestens dann erneut gestartet werden, wenn es gerade nicht läuft und der Task zum nächsten Intervall wieder gestartet wird.
HighSpeed1
HighSpeed1 26.09.2024 aktualisiert um 11:40:38 Uhr
Goto Top
Zitat von @emeriks:

Mit Get-Process bzw. via Win32_Process und WMI kannst du laufende Prozesse auch remote abfragen.
Das macht aber auch nur Sinn, wenn man alle in Frage kommende Computer abfragt und dann auch noch für all diese die entsprechenden Berechtigungen hat. Also wohl eher nicht praktikabel.

Den infrage kommenden PC könnte man ja beim Start des Programms in eine Datei schreiben lassen.

Gruß
Christoph
Crusher79
Crusher79 26.09.2024 um 11:38:02 Uhr
Goto Top
Hallo,

z.B. auch so. Hängt alles, bzw. wird die Lck nciht gelöscht, so wird nach einer gewissen Zeit der Lock übergangen und die Datei gelöscht und neu angelegt.

Die Methode von @em-pie kommt mir noch eleganter vor. Hat auch was face-wink


Function CreateLckFile() { 
            $null = New-Item -ItemType "file" -Path $lckFile;   
            # Fix NTFS Tunnellig
            $(Get-Item -Force $lckFile).CreationTime = $(Get-Item -Force $lckFile).LastWriteTime; 
            }

Function LckTimeDiff() {
            $lckFileCreationTime = $((Get-Item $lckFile).CreationTime)
            $Diffobj = $(Get-Date) - $lckFileCreationTime
            [int]$Script:DiffInMin = [int]$Diffobj.TotalMinutes
            }

Function LckAndStart() {  
    if (! (Test-path -path $lckFile)) {
        CreateLckFile
        ............   
        Remove-Item $lckFile -Force
    } else { 
        LckTimeDiff                            
        IF ($DiffInMin -gt 10 ) {                            
            WriteLog "Alte Lck vorhanden"  
            Remove-Item $lckFile -Force   
            CreateLckFile
            ........
            } 

    }
ThePinky777
ThePinky777 27.09.2024 aktualisiert um 14:46:17 Uhr
Goto Top
per VBS Script hab ich solche checks schon gemacht, z.B. ob ein MSI Install File gerade noch aktiv ist dann warten im loop

'################### Check if MSI is running START###################  
Dim ActiveInstall, strComputer, ProcessCommandLine, Answer, WshShell

ActiveInstall = 1
Set WshShell = WScript.CreateObject("WScript.Shell")  
do while ActiveInstall = 1
  
	strComputer = "."  
	Set objWMIService = GetObject("winmgmts:" _  
	& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")  

	Set colProcessList = objWMIService.ExecQuery _
	("Select * from Win32_Process")  

	ActiveInstall = 0

	For Each objProcess in colProcessList
		'wscript.echo objProcess.CommandLine  
		ProcessCommandLine = objProcess.CommandLine

		If Instr(1, ProcessCommandLine, "msiexec.exe") > 0 Then  
			If NOT Instr(1, ProcessCommandLine, "/V") > 0 Then  
				'wscript.echo "Prozess gefunden! " & ProcessCommandLine  
				ActiveInstall = 1

				'Answer = WshShell.Popup("MSIEXEC.exe in use, waiting 10 Secounds before trying to continue....", 10, "Info", 0 + 64)  
				Wscript.echo (Time) & " MSI Found - waiting 5 Secounds"  
				Wscript.sleep 5000 

			End If
		End If
	Next
loop
'###################### Check if MSI is running END################  

Das kann man natürlich anpassen stat msiexec.exe ne andere exe checken

If Instr.... ist quasi if Like
If Not Instr... ist if not like

hier im beispiel doppel IF abfrage weil eben msiexec auch als dienst läuft mit /V als Parameter und die interessiert hier nicht bei der abfrage. bedeutet bei dir reicht nur ein IF exe am laufen quasi...

und mit

If Instr(1, objProcess.CommandLine, "Gesuchte.exe") > 0 Then  
wscript.echo "Prozess gefunden! " & objProcess.CommandLine  
objProcess.Terminate()
End If

Kann man den Prozess auch killen
HighSpeed1
HighSpeed1 27.09.2024 um 14:59:05 Uhr
Goto Top
Danke für den zusätzlichen Input.

Schönes Wochenende.
Christoph