Jump to content
Sign in to follow this  
Rece

How to manage processes to avoid multiple executions?

Recommended Posts

Rece keep in mind that I new with AutoIt.... :P

Try something like:

AdlibEnable("MonitorPID")
;...
Exit

Func MonitorPID()
   IniReadSection('FileName', 'UserA')
   For loop
      if Not ProccessExist(PID) Or Not ProccessExist('NotePad.exe') Then IniDelete('FileName', 'UserA', 'Notepad.exe')
   Next
EndFunc

AutoIt Scripts:NetPrinter - Network Printer UtilityRobocopyGUI - GUI interface for M$ robocopy command line

Share this post


Link to post
Share on other sites

Thanks, I will try it. But keep in mind that this is a terminal server with lots of users. I think this is not the best solution because of the high CPU load and lots of file reading - IniRead() and ProcessExists() e.g. in every seconds by all sessions!

Share this post


Link to post
Share on other sites

Rece correct me if Im wrong but if all the users are running differents apps from the same terminal server every single apps have they unique PID until that apps is close then another apps or even the same apps can get the PID number that was release. If this is true and keeping in mind what I just mention.

How about adding to the ini file another section and called PID and associate that pid with the user.

[PID]

1234=UserA

5678=UserB

[userA]

1234=Notepad.exe

[userB]

5678=Notepad.exe

Now when the user select an app if a PID numbers repeat at the section [PID] the user associate with that PID already closed that app and you can delete the PID number from the user section and record the PID with the new user.

Before you start the requested apps go to the user section and check that he is not already using the app or if he already closed the apps first instance.

Edited by Danny35d

AutoIt Scripts:NetPrinter - Network Printer UtilityRobocopyGUI - GUI interface for M$ robocopy command line

Share this post


Link to post
Share on other sites

Now when the user select an app if a PID numbers repeat at the section [PID] the user associate with that PID already closed that app and you can delete the PID number from the user section and record the PID with the new user.

Before you start the requested apps go to the user section and check that he is not already using the app or if he already closed the apps first instance.

The only problem is that the PID is given AFTER running an app and the check must be BEFORE it... That's why now I store the PID this way:

[userA]

Notepad.exe=1234

When UserA tries to run Notepad.exe I load the Notepad.exe key from the [userA] section and that gives me the PID of the previously executed Notepad.exe. (Then I check this PID.) When I store the PIDs in a [PID] section like this:

[PID]

1234=UserA

and I load it - I think this will not work! E.g. when UserB starts a program and gets the PID 1234 it will be:

[PID]

1234=UserB

and it works! But if UserA starts a program (not Notepad.exe) it will not work! But if I store this:

[PID]

1234=UserA;Notepad.exe

then it must work because if the PID, the username and the programname is the same then it is the same program... Thanks a lot! I think this is the solution!

So again: when UserA runs Notepad.exe I store this:

[PID]

1234=UserA;Notepad.exe

[userA]

Notepad.exe=1234

And when UserA tries to run Notepad.exe I load the infos from this 2 sections to decide that this is the first attempt or not... If ProcessExists(1234) then StringSplit( IniRead( "inifile", "PID", 1234 ), ";" ) and I get the username and the programname. If both equals then error message otherwise running the program...

Edited by Rece

Share this post


Link to post
Share on other sites

I believe the WMI class Win32_Process might be useful here.

$objWMIService = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")

While 1
    $msg = GUIGetMsg()
    Select
        Case $msg = $btnStartExcel
            _StartProcess("Microsoft Excel", "EXCEL.EXE", "C:\Program Files\Microsoft Office\Office10\")
        Case $msg = $btnStartIE
            _StartProcess("Internet Explorer", "IEXPLORE.EXE", "C:\Program Files\Internet Explorer\")
        Case $msg = $btnStartWord
            _StartProcess("Microsoft Word", "WINWORD.EXE", "C:\Program Files\Microsoft Office\Office10\")
        Case $msg = $btnLogoff
            Shutdown(0+1); Forced Logoff
    EndSelect
WEnd

Func _StartProcess($strAppTitle, $strExeName, $strExePath)
    $colProcesses = $objWMIService.ExecQuery( _
        "Select * from Win32_Process Where " & _
        "ExecutablePath = '" & $strExePath & $strExeName & "'")
    For Each $objProcess in $colProcesses
        If $objProcess.GetOwner(@UserName) Then
            MsgBox(16, "Warning!", "You can only run that program once!")
            WinActivate($strAppTitle)
            Return 1
        Else
            Run($strExeName)
            WinWait($strAppTitle)
            Return 0
        EndIf
    Next
EndFunc

This way you don't have to keep track of anything in INI files, since your script checks for the existance of a process that is owned by the user.

Please let me know if I've missed the mark or anything obvious in the code.

Share this post


Link to post
Share on other sites

This is a very good idea but my users should be able to run multiple programs simultaneously...

And every user runs the same programs with the same window titles...

What? I must not have communicated very well.

The WMI solution that c0deWorm mentioned seems the best solution - provided that it allows each of your "menu scripts" to talk to each other... but I still think you will find that the methodology I mentioned will:

...allow User A to open Notepad, Word, Excel...

(all at the same time - within one terminal session)

...detect when User A has closed any of those apps

...prevent User A from opening Notepad within a second terminal session

(if it is still open in another terminal session)

User A and User B can both have Word open at the same time.

[With a slight change to the method, User A can be allowed to have several Word windows open at one time within one terminal session. It can still detect when the last Word window was closed.]

If you answered my question about a WinWait line of code in a script running in a terminal session, then I missed it.

Let me restate that question this way.

User A - one terminal session, start one test script with one line of code:

WinWait("Untitled - Notepad")

User B - one terminal session, start notepad.exe...

Does the test script for User A close?

...or are the terminal session windows isolated?

I apologize if I have misunderstood your needs. There is no need to respond to this if you still feel that my method will not meet your needs... but feel free to respond if you need more info about what I meant. [but WMI is lookin' pretty good right now.]

It might help if you would summarize your needs again and watch your terms like "program" vs. "script". I'm not sure that I understand what you want to allow and disallow. Perhaps some statements like this:

A Windows 2000 server is "offering" terminal sessions to several users.

With each terminal session, one copy of your "menu script" runs on the server.

[This is different from running from the server. If you look at the process list on the server, you should see many copies of your "menu script" running on the server - and this is okay - hence no FAQ14 or Singleton solution needed.]

All of the commercial applications made available to each user will run on the W2k server not on the computer that the user is sitting at. If you look at the process list on the server, you could see many copies of MS Word running. One copy of Word might belong to User A, one to User B, ...

Again, you do not care how many copies of your "menu script" is running on the W2k server. You do want to prevent User A from opening what? ...multiple copies of Word from multiple terminal sessions?

Stated another way.

User A is allowed:

As many terminal sessions on as many computers (or on the same computer) as desired.

(each terminal session will start your configurable "menu script")

User A is not allowed:

What?

Is User A allowed to have several copies of Word open within one terminal session, but not open Word from another terminal session until all copies are closed in that other terminal session? [i would think that this is what you really want. User A should be able to open two Word docs within one terminal session.]

One reason why I'm confused - in your first post you said:

"...(users can run the same program only once at a time)..."

[i clarified with you that "programs" = commercial apps like MS Word.]

In the post that I quoted above, you state:

"...users should be able to run multiple programs simultaneously..."

[Are these different users? User A, User B, User C...]

later....


[size="1"][font="Arial"].[u].[/u][/font][/size]

Share this post


Link to post
Share on other sites

I try to explain my needs again. Every user is allowed to have several terminal sessions and run several different programs in one or more terminal sessions. But one user is allowed to run only one copy of every program. Program means commercial apps like Word. "Menu script" means my AutoIt EXE which starts when you log in to the server and shows the buttons of the executable programs and starts them. Users can't access the Windows desktop or the Start menu, the only way of executing programs is clicking on this buttons... That's why my script can't wait that the program is closed because the user should be able to start another program from the menu...

An example:

If UserA runs Word in terminal session 1 then

- UserA is allowed to run e.g. Notepad in terminal session 1 or terminal session N

- UserA is not allowed to run another copy of Word in terminal session 1 or terminal session N

- UserB (a different user) is allowed to run one copy of Word...

Without reference to the number of terminal sessions only one copy of every program is allowed to run for every user but every user is allowed to run several different programs at the same time. (Program means commercial apps.)

I hope it is now clear for everybody.

cOdeWorm: Thanks! I tried your code but I've got a "Variable used without being declared" error message for the line

$colProcesses = $objWMIService.ExecQuery( ... ) What's the problem?

Edited by Rece

Share this post


Link to post
Share on other sites

Rece looking at cOdeWorm script he used this line outside of the function _StartProcess:

$objWMIService = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
You have two choices either declare as a Global at the begining of the line or move the entire line at the begining of the function. Edited by Danny35d

AutoIt Scripts:NetPrinter - Network Printer UtilityRobocopyGUI - GUI interface for M$ robocopy command line

Share this post


Link to post
Share on other sites

Thanks, it "works"! But I get another error message because "For Each ..." is not an AutoIt statement I think. ("For statement is badly formatted"...) If I correct it to "For $objProcess in $colProcesses..." then it's OK but it does nothing...

Edited by Rece

Share this post


Link to post
Share on other sites

Try this...

While 1
    $msg = GUIGetMsg()
    Select
        Case $msg = $btnStartExcel
            _StartProcess("Microsoft Excel", "EXCEL.EXE", "C:\Program Files\Microsoft Office\Office10\")
        Case $msg = $btnStartIE
            _StartProcess("Internet Explorer", "IEXPLORE.EXE", "C:\Program Files\Internet Explorer\")
        Case $msg = $btnStartWord
            _StartProcess("Microsoft Word", "WINWORD.EXE", "C:\Program Files\Microsoft Office\Office10\")
        Case $msg = $btnLogoff
            Shutdown(0+1); Forced Logoff
    EndSelect
WEnd

Func _StartProcess($strAppTitle, $strExeName, $strExePath)
    $objWMIService = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
    $colProcesses = $objWMIService.ExecQuery( _
        "Select * from Win32_Process Where " & _
        "ExecutablePath = '" & $strExePath & $strExeName & "'")
    For $objProcess in $colProcesses
        If $objProcess.GetOwner(@UserName) Then
            MsgBox(16, "Warning!", "You can only run that program once!")
            WinActivate($strAppTitle)
            Return 1
        Else
            Run($strExeName)
            WinWait($strAppTitle)
            Return 0
        EndIf
    Next
EndFunc

I moved the $objWMIService inside the function because that's really its scope in this script. I originally left it in the global realm so it could be used by multiple functions. I also removed the "each" from the "for each" statement. You will need the current beta to use this type of for loop I believe.

Also, to clarify, I have not tested this script with terminal services, but as long as you have not denied the users the ability to see all processes (not just their own) it will keep user A from logging into two sessions and running Word in both. My script looks at all processes on the machine with the given name (i.e. WINWORD.EXE) that are owned by the current username, not just the user in "this" session.

*EDIT* Improper code formatting.

Edited by c0deWorm

Share this post


Link to post
Share on other sites

I corrected the code like this but it does nothing. The For loop doesn't run. I've tried it on my client PC... Please try it on your computer...

Share this post


Link to post
Share on other sites

I corrected the code like this but it does nothing. The For loop doesn't run. I've tried it on my client PC... Please try it on your computer...

What errors are you receiving? Are you using the latest beta?

Here's my actual test and it works. Two sessions logged on, Session A is running a command prompt, session B cannot start one using the button.

NOTE: I did change a couple things. For one, the GetOwner actually returns zero for successful, not one.

#include <GUIConstants.au3>
#include <Array.au3>
#include <Misc.au3>

Opt("WinTitleMatchMode", 2)

$GUI = GUICreate("test gui", 120, 40)
$btnStartCMD = GUICtrlCreateButton("Command Prompt", 10, 10, 100, 20)

GUISetState()

While 1
    $msg = GUIGetMsg()
    Select
        Case $msg = $btnStartCMD
            _StartProcess("Command Prompt", "cmd.exe", "C:\WINNT\System32\")
        Case $msg = $GUI_EVENT_CLOSE
            ExitLoop
    EndSelect
WEnd

Func _StartProcess($strAppTitle, $strExeName, $strExePath)
    $objWMIService = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
    $colProcesses = $objWMIService.ExecQuery( _
        "Select * from Win32_Process Where " & _
        "Name = '" & $strExeName & "'")

    For $objProcess in $colProcesses
        If $objProcess.GetOwner(@UserName) = 0 Then; Zero is TRUE, Non-Zero is FALSE
            MsgBox(16, "Warning!", "You can only run that program once!")
            WinActivate($strAppTitle)
            Return 1
        Else
            TrayTip("", "Starting " & $strAppTitle, 3)
            Run($strExePath & $strExeName)
            WinWait($strAppTitle, 3)
            TrayTip("","",0)
            Return 0
        EndIf
    Next
EndFunc

Share this post


Link to post
Share on other sites

What errors are you receiving? Are you using the latest beta?

Here's my actual test and it works. Two sessions logged on, Session A is running a command prompt, session B cannot start one using the button.

I use the latest beta but it doesn't work for me. The "For $objProcess in $colProcesses" loop doesn't run...

Share this post


Link to post
Share on other sites

Oh it works here! (The previous was another PC with XP.) But unfortunately only with Name string not with ExecutablePath. Have you any idea why? ExecutablePath would be better for me...

I've found a string called Handle. Does anybody know what is it? Is it a unique (not recurring) identifier not like PID?

class Win32_Process : CIM_Process
{
  string Caption;
  string CommandLine;
  string CreationClassName;
  datetime CreationDate;
  string CSCreationClassName;
  string CSName;
  string Description;
  string ExecutablePath;
  uint16 ExecutionState;
  string Handle;
  uint32 HandleCount;
  datetime InstallDate;
  uint64 KernelModeTime;
  uint32 MaximumWorkingSetSize;
  uint32 MinimumWorkingSetSize;
  string Name;
  string OSCreationClassName;
  string OSName;
  uint64 OtherOperationCount;
  uint64 OtherTransferCount;
  uint32 PageFaults;
  uint32 PageFileUsage;
  uint32 ParentProcessId;
  uint32 PeakPageFileUsage;
  uint64 PeakVirtualSize;
  uint32 PeakWorkingSetSize;
  uint32 Priority;
  uint64 PrivatePageCount;
  uint32 ProcessId;
  uint32 QuotaNonPagedPoolUsage;
  uint32 QuotaPagedPoolUsage;
  uint32 QuotaPeakNonPagedPoolUsage;
  uint32 QuotaPeakPagedPoolUsage;
  uint64 ReadOperationCount;
  uint64 ReadTransferCount;
  uint32 SessionId;
  string Status;
  datetime TerminationDate;
  uint32 ThreadCount;
  uint64 UserModeTime;
  uint64 VirtualSize;
  string WindowsVersion;
  uint64 WorkingSetSize;
  uint64 WriteOperationCount;
  uint64 WriteTransferCount;
};

Share this post


Link to post
Share on other sites

Oh it works here! (The previous was another PC with XP.) But unfortunately only with Name string not with ExecutablePath. Have you any idea why? ExecutablePath would be better for me...

One thing I noticed in a test of ExecutablePath (and why I decided just to use Name) is that it uses the 8.3 filenames. For instance, when testing for WINWORD.EXE, it retrieves a path of "C:\PROGRA~1\MICROS~1\OFFICE10\WINWORD.EXE" not "C:\Program Files\Microsoft Office\Office10\WINWORD.EXE".

That can be corrected fairly easily, but I figured since your users can only execute programs from your menu you know what paths they will have and will adjust so that WINWORD.EXE is only run from one path, therefore you don't need to check the path, only the executable name.

However, if you decided to use two programs with the same executable name and different paths you would have to figure out the 8.3 equivalent of that path and use it in the parameter you pass to the function. If you do this, just change the "Name" to "ExecutablePath" in the function and it should work.

Share this post


Link to post
Share on other sites

There is a main environment and there is a testing environment too. Both uses the same EXE but from different folders. That's why I need the path too...

I've tried "C:\1.exe" but ExecutablePath didn't work... Very interesting because if I use Name and then I write out the ExecutablePath it's correct but if I use ExecutablePath in the Select it isn't... I can solve this by checking the ExecutablePath inside the If... but I don't understand it...

Edited by Rece

Share this post


Link to post
Share on other sites

Try this one.

  • I combined the path with the executable (so you're specifying the exact ExecutablePath you want)
  • I added FileGetShortName to get the short path for you
  • I changed the query to use ExecutablePath.
#include <GUIConstants.au3>
#include <Array.au3>
#include <Misc.au3>

Opt("WinTitleMatchMode", 2)

$GUI = GUICreate("test gui", 120, 40)
$btnStartCMD = GUICtrlCreateButton("Command Prompt", 10, 10, 100, 20)

GUISetState()

While 1
    $msg = GUIGetMsg()
    Select
        Case $msg = $btnStartCMD
            _StartProcess("Command Prompt", "C:\WINNT\System32\cmd.exe")
        Case $msg = $GUI_EVENT_CLOSE
            ExitLoop
    EndSelect
WEnd

Func _StartProcess($strAppTitle, $strAppPath)
    $objWMIService = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
    $colProcesses = $objWMIService.ExecQuery( _
        "Select * from Win32_Process Where " & _
        "ExecutablePath = '" & FileGetShortName($strAppPath) & "'")

    For $objProcess in $colProcesses
        If $objProcess.GetOwner(@UserName) = 0 Then; Zero is TRUE, Non-Zero is FALSE
            MsgBox(16, "Warning!", "You can only run that program once!")
            WinActivate($strAppTitle)
            Return 1
        Else
            TrayTip("", "Starting " & $strAppTitle, 3)
            Run($strAppPath)
            WinWait($strAppTitle, 3)
            TrayTip("","",0)
            Return 0
        EndIf
    Next
EndFunc

Share this post


Link to post
Share on other sites

Thanks but it doesn't work for me.

This is my code and it works as I need:

Func _StartProcess( $strExeName, $strExePath )
    $objWMIService = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
    $run = 1
    $colProcesses = $objWMIService.ExecQuery( _
        "Select * from Win32_Process Where " & _
        "Name = '" & $strExeName & "'")
    For $objProcess in $colProcesses
        If ( $objProcess.GetOwner(@UserName) = 0 ) And _
        ( $objProcess.ExecutablePath = $strExePath & $strExeName ) Then
            $run = 0
            ExitLoop
        EndIf
    Next
    If $run = 0 Then
        MsgBox(16, "Warning!", "You can only run that program once!")
    Else
        Run( $strExePath & $strExeName )
    EndIf
    Return $run
EndFunc

I have to use the variabe $run because if no copy of the program is running then "For $objProcess in $colProcesses..." doesn't run and the program will not be executed...

I only need a function witch splits the path and the name but it's easy...

Thank you very much your help!

Edited by Rece

Share this post


Link to post
Share on other sites

Fighting a somewhat similar problem on a Citrix/Terminal Server box, and I found the code snip does not work for admins. The following does.

Global $User, $Domain

_StartProcess("cmd.exe", "c:\windows\system32\")

Func _StartProcess( $strExeName, $strExePath )
    $objWMIService = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
    $run = 1
    $colProcesses = $objWMIService.ExecQuery("Select * from Win32_Process Where " & "Name = '" & $strExeName & "'")

   For $objProcess in $colProcesses
        $objProcess.GetOwner($User, $Domain)
        If ( $objProcess.ExecutablePath = $strExePath & $strExeName ) And $User = @UserName Then
            $run = 0
            ExitLoop
        EndIf
    Next
    If $run = 0 Then
        MsgBox(16, "Warning!", "You can only run that program once!")
    Else
        Run( $strExePath & $strExeName )
    EndIf
    Return $run
EndFunc

Notice the change to get the real user name...

For $objProcess in $colProcesses

$objProcess.GetOwner($User, $Domain)

If ( $objProcess.ExecutablePath = $strExePath & $strExeName ) And $User = @UserName Then

BTW, thanks to everyone in this thread for the earlier code snips. Saved me tons of time. :think:

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×
×
  • Create New...