Pseudo Desktop - Klasse eines störrischen Programmfensters ermitteln

  • Hallo zusammen,

    ich habe hier ja einiges an Unterstützung mitnehmen können :thumbup: und vielleicht hat da jemand was in der Schublade liegen, bevor ich dass auch nocht selber frickeln muss.
    In meinem "Projekt" (hier lang) sollen (Lern-)Programme gestartet werden und vorher überprüft werden, ob ein evtl. Programm schon läuft.

    Eigentlich kein großes Problem - ABER - wir nutzen ein - leider - gut passendes Programm, das sehr "interessant" gestrickt wurde ...
    Aus einem DOS-Programm ist auf (für mich) abenteuerliche Weise ein Win-Programm entstanden. --> Budenberg
    Deswegen kann ich mit dem FensterInfoTool von Autoit nicht die Klasse ermitteln. :thumbdown:
    Hat da jemand vielleicht ein Autoit-Tool vorrätig mit dem man ein Programm startet, und von diesem die benötigten Infos "irgendwo" hinschreiben / ausgeben lässt?
    (Sonst muss ich versuchsweise diese Baustelle selber angehen. || )

    Grüße,
    klaros

  • Ich weiß nicht genau was du vor hast. Aber reicht es nicht mit "ProcessExists" zu prüfen, ob der Prozess existiert, falls nein dann starten, ansonsten eben nichts tun... oder besser: das Programm in den Vordergrund holen.

  • Hallo,

    würde auch mit ProcessExists($iPID) nach dem Prozzes selber suchen.

    Spoiler anzeigen
    Code
    Local $aProcessList = ProcessList("java.exe")
    if $aProcessList[0][0] <> 0 Then
    MsgBox(1,"Beenden","Es sind noch " & $aProcessList[0][0] & " offenen Java Prozess")
    For $i = 1 To $aProcessList[0][0]
    $ret = MsgBox(1,"Beenden","Java Prozess mit PID: " & $aProcessList[0][0] & " beenden?")
    if $ret = 1 Then
    ProcessClose($aProcessList[$i][1]) ;beenden
    EndIf
    Next
    EndIf

    bzw

    Code
    Local $aWinList = WinList("Fenstername")
  • Ich weiß nicht genau was du vor hast. Aber reicht es nicht mit "ProcessExists" zu prüfen, ob der Prozess existiert, falls nein dann starten, ansonsten eben nichts tun... oder besser: das Programm in den Vordergrund holen.

    Wahrscheinlich reicht es nicht über ProcessExists ...
    Das hatte ich nämlich zuerst probiert - aber auf einem Server / (PC ?) laufen gleichnamige Prozesse, die unterschiedlichen Usern gehören (wenn mehrere angemeldet sind, oder, wenn sie sich nur getrennt haben, (noch) weiterlaufen).
    Mit der Klasse werden eindeutig zur Usersitzung Fenster angesprochen.

    Ich überlege aber gerade, ob man eine Kombi aus Prozessname und PID probieren könnte ...

    Höhö, zeitgleich zu GegX ... :)

    Grüße,
    klaros

  • Diese Funktion hier:

    [autoit]


    ;===============================================================================
    ; Function Name: _ProcessListProperties()
    ; Description: Get various properties of a process, or all processes
    ; Call With: _ProcessListProperties( [$Process [, $sComputer]] )
    ; Parameter(s): (optional) $Process - PID or name of a process, default is "" (all)
    ; (optional) $sComputer - remote computer to get list from, default is local
    ; Requirement(s): AutoIt v3.2.4.9+
    ; Return Value(s): On Success - Returns a 2D array of processes, as in ProcessList()
    ; with additional columns added:
    ; [0][0] - Number of processes listed (can be 0 if no matches found)
    ; [1][0] - 1st process name
    ; [1][1] - 1st process PID
    ; [1][2] - 1st process Parent PID
    ; [1][3] - 1st process owner
    ; [1][4] - 1st process priority (0 = low, 31 = high)
    ; [1][5] - 1st process executable path
    ; [1][6] - 1st process CPU usage
    ; [1][7] - 1st process memory usage
    ; [1][8] - 1st process creation date/time = "MM/DD/YYY hh:mm:ss" (hh = 00 to 23)
    ; [1][9] - 1st process command line string
    ; ...
    ; [n][0] thru [n][9] - last process properties
    ; On Failure: Returns array with [0][0] = 0 and sets @Error to non-zero (see code below)
    ; Author(s): PsaltyDS at http://www.autoitscript.com/forum
    ; Date/Version: 12/01/2009 -- v2.0.4
    ; Notes: If an integer PID or string process name is provided and no match is found,
    ; then [0][0] = 0 and @error = 0 (not treated as an error, same as ProcessList)
    ; This function requires admin permissions to the target computer.
    ; All properties come from the Win32_Process class in WMI.
    ; To get time-base properties (CPU and Memory usage), a 100ms SWbemRefresher is used.
    ;===============================================================================
    Func _ProcessListProperties($Process = "", $sComputer = ".")
    Local $sUserName, $sMsg, $sUserDomain, $avProcs, $dtmDate
    Local $avProcs[1][2] = [[0, ""]], $n = 1

    [/autoit][autoit][/autoit][autoit]

    ; Convert PID if passed as string
    If StringIsInt($Process) Then $Process = Int($Process)

    [/autoit][autoit][/autoit][autoit]

    ; Connect to WMI and get process objects
    $oWMI = ObjGet("winmgmts:{impersonationLevel=impersonate,authenticationLevel=pktPrivacy, (Debug)}!\\" & $sComputer & "\root\cimv2")
    If IsObj($oWMI) Then
    ; Get collection processes from Win32_Process
    If $Process == "" Then
    ; Get all
    $colProcs = $oWMI.ExecQuery("select * from win32_process")
    ElseIf IsInt($Process) Then
    ; Get by PID
    $colProcs = $oWMI.ExecQuery("select * from win32_process where ProcessId = " & $Process)
    Else
    ; Get by Name
    $colProcs = $oWMI.ExecQuery("select * from win32_process where Name = '" & $Process & "'")
    EndIf

    [/autoit][autoit][/autoit][autoit]

    If IsObj($colProcs) Then
    ; Return for no matches
    If $colProcs.count = 0 Then Return $avProcs

    [/autoit][autoit][/autoit][autoit]

    ; Size the array
    ReDim $avProcs[$colProcs.count + 1][10]
    $avProcs[0][0] = UBound($avProcs) - 1

    [/autoit][autoit][/autoit][autoit]

    ; For each process...
    For $oProc In $colProcs
    ; [n][0] = Process name
    $avProcs[$n][0] = $oProc.name
    ; [n][1] = Process PID
    $avProcs[$n][1] = $oProc.ProcessId
    ; [n][2] = Parent PID
    $avProcs[$n][2] = $oProc.ParentProcessId
    ; [n][3] = Owner
    If $oProc.GetOwner($sUserName, $sUserDomain) = 0 Then $avProcs[$n][3] = $sUserDomain & "\" & $sUserName
    ; [n][4] = Priority
    $avProcs[$n][4] = $oProc.Priority
    ; [n][5] = Executable path
    $avProcs[$n][5] = $oProc.ExecutablePath
    ; [n][8] = Creation date/time
    $dtmDate = $oProc.CreationDate
    If $dtmDate <> "" Then
    ; Back referencing RegExp pattern from weaponx
    Local $sRegExpPatt = "\A(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(?:.*)"
    $dtmDate = StringRegExpReplace($dtmDate, $sRegExpPatt, "$2/$3/$1 $4:$5:$6")
    EndIf
    $avProcs[$n][8] = $dtmDate
    ; [n][9] = Command line string
    $avProcs[$n][9] = $oProc.CommandLine

    [/autoit][autoit][/autoit][autoit]

    ; increment index
    $n += 1
    Next
    Else
    SetError(2); Error getting process collection from WMI
    EndIf
    ; release the collection object
    $colProcs = 0

    [/autoit][autoit][/autoit][autoit]

    ; Get collection of all processes from Win32_PerfFormattedData_PerfProc_Process
    ; Have to use an SWbemRefresher to pull the collection, or all Perf data will be zeros
    Local $oRefresher = ObjCreate("WbemScripting.SWbemRefresher")
    $colProcs = $oRefresher.AddEnum($oWMI, "Win32_PerfFormattedData_PerfProc_Process" ).objectSet
    $oRefresher.Refresh

    [/autoit][autoit][/autoit][autoit]

    ; Time delay before calling refresher
    Local $iTime = TimerInit()
    Do
    Sleep(20)
    Until TimerDiff($iTime) >= 100
    $oRefresher.Refresh

    [/autoit][autoit][/autoit][autoit]

    ; Get PerfProc data
    For $oProc In $colProcs
    ; Find it in the array
    For $n = 1 To $avProcs[0][0]
    If $avProcs[$n][1] = $oProc.IDProcess Then
    ; [n][6] = CPU usage
    $avProcs[$n][6] = $oProc.PercentProcessorTime
    ; [n][7] = memory usage
    $avProcs[$n][7] = $oProc.WorkingSet
    ExitLoop
    EndIf
    Next
    Next
    Else
    SetError(1); Error connecting to WMI
    EndIf

    [/autoit][autoit][/autoit][autoit]

    ; Return array
    Return $avProcs
    EndFunc ;==>_ProcessListProperties

    [/autoit]

    Rückgabe ist ein Array mit entsprechenden Parametern und den dazugehörigen Benutzer.

    :thumbup:

  • Hi zusammen,

    nachdem bobekos mich dem "129 Zeilen Monster" geschockt hat ;) habe ich noch ein bisschen rumgesucht.
    Und ich habe eine andere UDF gefunden, die ich besser verstehe.
    Damit habe ich mir ein Testscript gebastelt, das ein Programm aufruft und dann schaut wie die Fensterklassen heißen.
    Ich muss noch ein bisschen damit rumspielen, um zu verstehen, welcher der Rückgabe die gesuchte Klasse enthält.

    Ich sitze gerade am "Wohnzimmer-Mac". Morgen stelle ich hier mal rein, was ich gefunden und gemacht habe.

    Grüße,
    klaros

  • hey Klaros

    Vielleicht solltest du dir doch dieses "129 Zeilen Monster" zu Gemüte führen. Davor brauchst du keine Angst haben und musst es nicht mal zwangsläufig verstehen.
    Bei solchen UDFs liest man sich die Beschreibung durch (der grüne Text) um zu erkennen, was damit bezweckt wird und wie es wiedergegeben wird.

    Bei dieser UDF wird klar, dass Infos zu Prozessen in einem 2D Array wiedergegeben werden, die man sogar eingrenzen/filtern kann.
    Um diese UDF (oder besser Funktion, denn es ist ja nur 1 einzige Funktion) zu nutzen, kopierst du diese einfach in dein Script und rufst diese mit einer Variable beginnend auf

    zum Beispiel so
    [autoit]

    #include "array.au3"
    $this = _ProcessListProperties()

    [/autoit] [autoit][/autoit] [autoit]

    _ArrayDisplay($this)
    ;===============================================================================
    ; Function Name: _ProcessListProperties()
    ; Description: Get various properties of a process, or all processes
    ; Call With: _ProcessListProperties( [$Process [, $sComputer]] )
    ; Parameter(s): (optional) $Process - PID or name of a process, default is "" (all)
    ; (optional) $sComputer - remote computer to get list from, default is local
    ; Requirement(s): AutoIt v3.2.4.9+
    ; Return Value(s): On Success - Returns a 2D array of processes, as in ProcessList()
    ; with additional columns added:
    ; [0][0] - Number of processes listed (can be 0 if no matches found)
    ; [1][0] - 1st process name
    ; [1][1] - 1st process PID
    ; [1][2] - 1st process Parent PID
    ; [1][3] - 1st process owner
    ; [1][4] - 1st process priority (0 = low, 31 = high)
    ; [1][5] - 1st process executable path
    ; [1][6] - 1st process CPU usage
    ; [1][7] - 1st process memory usage
    ; [1][8] - 1st process creation date/time = "MM/DD/YYY hh:mm:ss" (hh = 00 to 23)
    ; [1][9] - 1st process command line string
    ; ...
    ; [n][0] thru [n][9] - last process properties
    ; On Failure: Returns array with [0][0] = 0 and sets @Error to non-zero (see code below)
    ; Author(s): PsaltyDS at http://www.autoitscript.com/forum
    ; Date/Version: 12/01/2009 -- v2.0.4
    ; Notes: If an integer PID or string process name is provided and no match is found,
    ; then [0][0] = 0 and @error = 0 (not treated as an error, same as ProcessList)
    ; This function requires admin permissions to the target computer.
    ; All properties come from the Win32_Process class in WMI.
    ; To get time-base properties (CPU and Memory usage), a 100ms SWbemRefresher is used.
    ;===============================================================================
    Func _ProcessListProperties($Process = "", $sComputer = ".")
    Local $sUserName, $sMsg, $sUserDomain, $avProcs, $dtmDate
    Local $avProcs[1][2] = [[0, ""]], $n = 1

    [/autoit] [autoit][/autoit] [autoit]

    ; Convert PID if passed as string
    If StringIsInt($Process) Then $Process = Int($Process)

    [/autoit] [autoit][/autoit] [autoit]

    ; Connect to WMI and get process objects
    $oWMI = ObjGet("winmgmts:{impersonationLevel=impersonate,authenticationLevel=pktPrivacy, (Debug)}!\\" & $sComputer & "\root\cimv2")
    If IsObj($oWMI) Then
    ; Get collection processes from Win32_Process
    If $Process == "" Then
    ; Get all
    $colProcs = $oWMI.ExecQuery("select * from win32_process")
    ElseIf IsInt($Process) Then
    ; Get by PID
    $colProcs = $oWMI.ExecQuery("select * from win32_process where ProcessId = " & $Process)
    Else
    ; Get by Name
    $colProcs = $oWMI.ExecQuery("select * from win32_process where Name = '" & $Process & "'")
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    If IsObj($colProcs) Then
    ; Return for no matches
    If $colProcs.count = 0 Then Return $avProcs

    [/autoit] [autoit][/autoit] [autoit]

    ; Size the array
    ReDim $avProcs[$colProcs.count + 1][10]
    $avProcs[0][0] = UBound($avProcs) - 1

    [/autoit] [autoit][/autoit] [autoit]

    ; For each process...
    For $oProc In $colProcs
    ; [n][0] = Process name
    $avProcs[$n][0] = $oProc.name
    ; [n][1] = Process PID
    $avProcs[$n][1] = $oProc.ProcessId
    ; [n][2] = Parent PID
    $avProcs[$n][2] = $oProc.ParentProcessId
    ; [n][3] = Owner
    If $oProc.GetOwner($sUserName, $sUserDomain) = 0 Then $avProcs[$n][3] = $sUserDomain & "\" & $sUserName
    ; [n][4] = Priority
    $avProcs[$n][4] = $oProc.Priority
    ; [n][5] = Executable path
    $avProcs[$n][5] = $oProc.ExecutablePath
    ; [n][8] = Creation date/time
    $dtmDate = $oProc.CreationDate
    If $dtmDate <> "" Then
    ; Back referencing RegExp pattern from weaponx
    Local $sRegExpPatt = "\A(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(?:.*)"
    $dtmDate = StringRegExpReplace($dtmDate, $sRegExpPatt, "$2/$3/$1 $4:$5:$6")
    EndIf
    $avProcs[$n][8] = $dtmDate
    ; [n][9] = Command line string
    $avProcs[$n][9] = $oProc.CommandLine

    [/autoit] [autoit][/autoit] [autoit]

    ; increment index
    $n += 1
    Next
    Else
    SetError(2); Error getting process collection from WMI
    EndIf
    ; release the collection object
    $colProcs = 0

    [/autoit] [autoit][/autoit] [autoit]

    ; Get collection of all processes from Win32_PerfFormattedData_PerfProc_Process
    ; Have to use an SWbemRefresher to pull the collection, or all Perf data will be zeros
    Local $oRefresher = ObjCreate("WbemScripting.SWbemRefresher")
    $colProcs = $oRefresher.AddEnum($oWMI, "Win32_PerfFormattedData_PerfProc_Process" ).objectSet
    $oRefresher.Refresh

    [/autoit] [autoit][/autoit] [autoit]

    ; Time delay before calling refresher
    Local $iTime = TimerInit()
    Do
    Sleep(20)
    Until TimerDiff($iTime) >= 100
    $oRefresher.Refresh

    [/autoit] [autoit][/autoit] [autoit]

    ; Get PerfProc data
    For $oProc In $colProcs
    ; Find it in the array
    For $n = 1 To $avProcs[0][0]
    If $avProcs[$n][1] = $oProc.IDProcess Then
    ; [n][6] = CPU usage
    $avProcs[$n][6] = $oProc.PercentProcessorTime
    ; [n][7] = memory usage
    $avProcs[$n][7] = $oProc.WorkingSet
    ExitLoop
    EndIf
    Next
    Next
    Else
    SetError(1); Error connecting to WMI
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    ; Return array
    Return $avProcs
    EndFunc ;==>_ProcessListProperties

    [/autoit]


    Mit den zurückgegebenen Daten lässt sich sehr gut arbeiten, also versuchs einfach mal.

    by the way: Diese Funktion kommt auf jeden Fall in meine Sammlung :D

  • Zitat von klaros


    Wahrscheinlich reicht es nicht über ProcessExists ...
    Das hatte ich nämlich zuerst probiert - aber auf einem Server / (PC ?) laufen gleichnamige Prozesse, die unterschiedlichen Usern gehören (wenn mehrere angemeldet sind, oder, wenn sie sich nur getrennt haben, (noch) weiterlaufen).
    Mit der Klasse werden eindeutig zur Usersitzung Fenster angesprochen.

    Ich überlege aber gerade, ob man eine Kombi aus Prozessname und PID probieren könnte ...

    Höhö, zeitgleich zu GegX ... :)

    Grüße,
    klaros

    Also zum einen solltest du als normaler Benutzer ohne Adminberechtigungen ohnehin nur deine eigenen Prozesse sehen können. Somit dürfte es auch kein Problem mit den Prozessen der anderen angemeldeten Benutzer des Servers geben. Aber davon mal ab würde ich mir einfach merken welche Programme ich selber gestartet habe und nur dann einen erneuten Start zulassen wenn die gemerkte eindeutige Prozess ID (PID) nicht mehr existiert.

    Simples Beispiel:

    Spoiler anzeigen
    [autoit]


    #include <Array.au3>
    Global $aProzessHistory[1][2] = [ ["PID","Pfad"] ]

    [/autoit] [autoit][/autoit] [autoit]

    ; Anwendungsbeispiele:

    [/autoit] [autoit][/autoit] [autoit]

    StartProgramm("notepad.exe")
    StartProgramm("mspaint.exe")
    StartProgramm("calc.exe")

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(262144,"debug","Alle Programme wurden gestartet, zur Kontrolle wird das Array jetzt angezeigt. Bitte eines der Programme beenden, im nachfolgenden Schritt sollte dann nur dieses eine Programm neugestartet werden.")
    _ArrayDisplay($aProzessHistory)

    [/autoit] [autoit][/autoit] [autoit]

    $debug = StartProgramm("notepad.exe")
    ConsoleWrite("DEBUG: Rückgabecode --> " & $debug & @CRLF) ; 1 oder -1, jenachdem ob das Programm gestartet wurde oder nicht
    $debug = StartProgramm("mspaint.exe")
    ConsoleWrite("DEBUG: Rückgabecode --> " & $debug & @CRLF) ; 1 oder -1, jenachdem ob das Programm gestartet wurde oder nicht
    $debug = StartProgramm("calc.exe")
    ConsoleWrite("DEBUG: Rückgabecode --> " & $debug & @CRLF) ; 1 oder -1, jenachdem ob das Programm gestartet wurde oder nicht

    [/autoit] [autoit][/autoit] [autoit]

    Sleep(2000)
    _ArrayDisplay($aProzessHistory)

    [/autoit] [autoit][/autoit] [autoit]

    Func StartProgramm($sPath)

    ; Startet beliebige Programme und speichert die PID + Pfad im globalen Array $aProzessHistory.
    ; Wenn der übergebene Pfad bereits im Array vorhanden ist und der zugehörige Prozess noch läuft wird die Funktion abgebrochen und das Programm wird nicht erneut gestartet.

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To UBound($aProzessHistory)-1
    If $aProzessHistory[$i][1] = $sPath Then ; Prozess wurde schonmal gestartet, prüfen ob Prozess beendet wurde oder noch läuft
    If processexists($aProzessHistory[$i][0]) Then
    Return -1 ; Prozess wurde bereits gestartet und läuft auch noch, kein erneuter Start notwendig, daher Abbruch der Funktion
    Else
    _ArrayDelete($aProzessHistory,$i); Eintrag aus dem Array entfernen, da Prozess nicht mehr existiert
    ExitLoop ; Schleife verlassen, da Prüfung erfolgreich abgeschlossen
    EndIf
    EndIf
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; Hier gehts nur weiter wenn der Prozess noch nie gestartet wurde oder nicht mehr läuft...
    Local $sPID = Run($sPath)
    ReDim $aProzessHistory[UBound($aProzessHistory)+1][2] ; erweitert das Array um eine neue Zeile
    $aProzessHistory[Ubound($aProzessHistory)-1][0] = $sPID ; schreibt die PID in die neue letzte Zeile des Arrays
    $aProzessHistory[Ubound($aProzessHistory)-1][1] = $sPath ; schreibt den Programmpfad in die neue letzte Zeile des Arrays

    [/autoit] [autoit][/autoit] [autoit]

    Return 1 ; alles erfolgreich erledigt

    [/autoit] [autoit][/autoit] [autoit]

    EndFunc

    [/autoit]
  • Hallo zusammen,

    erstmal Danke an euch für die Tipps!
    Dann noch ein "entschuldigt bitte" hinterher,
    dafür dass ich mit meinen NebenThreads und dem Link auf den HauptThread noch nicht konsequent genug war ...

    Hier findet ihr das Projekt um das es geht:
    Pseudo Desktop - Konstruktive Kritik :-)

    Die aktuelle Version befindet sich im Moment noch unten im Thread.
    Auf Oscars Rat hin werde ich die Dateien immer oben aktualisiert reinstellen.

    Damit man jetzt nicht zum Link springen muss:
    Es geht um ein Programm, das für Schüler durch Vollbildmodus (evtl. sogar Taskleiste verstecken) den Desktop erstzen soll.
    Dadurch soll dieser einfacher zu bedienen sein (Apps sortiert in farbigen Kacheln, Logoff-Icon statt Start -> Abmelden, usw.)
    Dieser "Pseudo Desktop" soll auf den Clients erscheinen, wenn diese sich auf dem Terminalserver angemeldet haben.
    PD läuft als über Autostart o. ä. auf dem Server.

    Oscar war so freundlich und hat mir einen Vorschlag programmiert, wie es gehen könnte, wenn der Desktop onTop bleibt und immer nur ein Programm gestartet werden kann und dann dieses onTop ist oder über den Klick auf das ProgrammIcon wieder
    "nach vorne" geholt wird.

    Dadurch ergeben sich "Probleme", wie ich es speziell mit Budenberg habe - da greift die Lösung über die Klasse nicht gut.
    (Da Budenberg aber im Vollbildmodus startet, könnte man hier überlegen, das eben brutal einfach abzufangen und nicht zu viel Energie in eine perfekte Lösung zu stecken ...)
    Anderes Problem ist:
    Ich starte den Dateimanager über den Pseudo Desktop (PD) - alles ist gut. Über den Dateimanger starte ich dann z. B. XnView (Bildbetrachter). Dieser Prozess kann über die aktuelle Lösung nicht ganz onTop geholt werden - das Fenster des Dateimanagers wäre immer davor.

    Vielleicht funktioniert das ganze schon besser, wenn ich Oscars Änderung (Vereinfachung) mit aufnehme, dass mehrer Programme gleichzeitig gestartet werden können (gewünschter Effekt für cut&paste usw.).

    Leider habe ich in den letzten Tagen nur Zeit gehabt, mich um "Budenberg zu kümmern", so dass ich an der Stelle noch nicht selber weiter gekommen bin ...

    Eigentlich soll der PD einfach aussehen und für maximal ein Dutzend Programme zuständig sein, wobei meistens tatsächlich nur eins gestartet wird. Leider gibt es jetzt im Nachhinein doch einige Eventualitäten zu beachten, damit der gut gemeinte PD einen User nicht "aussperrt" ...

    misterspeed: Vielleicht ist der Ansatz über PIDs ein gangbarer Weg. Das werde ich mir "in nächster Zeit" mal anschauen.
    (Zu ProcessExists($sProgName): Als angemeldeter Admin habe ich z.B. alle Firefox-Prozesse gesehen. Da habe ich nicht weit genug gedacht und fälschlich angenommen, dass das auch für normale User gilt ... (Dann wäre an dieser Stelle auch noch Potential.).)

    Grüße,
    klaros