Prevent 2nd instance of app from running

Started by stevengraff, December 09, 2013, 09:40:03 AM

Previous topic - Next topic

stevengraff

If myapp.exe is running, but obscured by another window, i.e. a full-screen browser, and I try to launch myapp.exe again, I'd like that to just bring myapp.exe to the foreground, rather than launching another instance of myapp.exe.

Is there a simple way to do this? By the way, in this case, the gui portion of myapp.exe is just a dialog.

Deana

Deana F.
Technical Support
Wilson WindowWare Inc.


stevengraff

Variation on a theme... I'm using:

upAlready = winExist( "My App" )
if upAlready
   WinActivate( "My App" )
   Exit
endIf

Seems too easy, i.e. I must be missing something here. Any obvious "gotcha's" you can see?

Deana

Just make sure your application is given a unique window title. Otherwise if the script is launched and another application has the same window title your script will never run.
Deana F.
Technical Support
Wilson WindowWare Inc.

stevengraff

Quote from: Deana on December 09, 2013, 10:57:58 AM
Just make sure your application is given a unique window title. Otherwise if the script is launched and another application has the same window title your script will never run.
Yes, like if I'm working on the documentation, which happens, ironically enough, to have a title like "My App User Guide" in a program that makes the doc title the Window title? Been there, done that. :)

JTaylor

Seems odd that no one has suggested using the AppExist() function, even in the article.

Jim

ChuckC

AppExist() wouldn't be appropriate in this situation.  It doesn't return a count of how many instances are running, it just returns @TRUE/@FALSE based on whether or not 1 or more instances are running or if no instances are running.  In this situation, a 2nd instance of the application is being launched, and the desire is to detect that another instance is already running and bring it to the foreground and then have the 2nd instance terminate.

snowsnowsnow

Just as an FYI:

I've been thinking about doing something like this - in a generic way.  That is, as a wrapper (script runner) that would do it for any script, rather than have this functionality be coded into the script itself (each and every time, if you see what I mean....

I was thinking about using shared memory blocks {*} rather than Window Titles as the markers, since (in the general case), programs should be free to manipulate their Window Title, without it interfering with the "run me only once" functionality.  The shared memory block could contain a Window Handle (or something similar; I haven't quite worked out all the details yet), which would allow for easy switching to the existing window (if that is the desired effect).

I haven't gotten around to coding this up, yet.  I'm thinking of calling it "RunOnce.wbt", or something like that.

{*} Using the shared memory routines found in the Tech DB.

DAG_P6

Mutexes are probably a lot simpler to implement. The required Windows API calls are quite straightforward. I've used them in a few scripts, for which I created a small library of User Defined Functions to further simplify their use. I've attached the library, MutexLib_WW.WIL, and MutexLib_CONSTANTS_WW.WIL, an inline include that defines constants for use with the library. To use, include both into the top of your script, and call the functions as needed.
David A. Gray
You are more important than any technology.

snowsnowsnow

Sounds like "6 of one/half dozen of the other" (or "Name your poison") to me.

DAG_P6

Maybe so, but the last time I studied shared memory, it looked like they required lots of API calls and C style structures.  A mutex requires neither, unless you want to attach an access control list to it, the latter of which is nontrivial in WinBatch, though they are a snap in C.
David A. Gray
You are more important than any technology.

stevengraff

My preferred method, because it's so easy, and works nearly always is:

upAlready = winExist( "My App" )
if upAlready
   WinActivate( "My App" )
   Exit
endIf

However, one serious limitation is when My App is running in the system tray. In this case, My App has no window name. So, what I've been doing is using tListProc to get a list of all running processes, and immediately exit if discovering a second myapp.exe on the list. I hate doing it this way... while it sure is reliable, it takes several seconds to execute.

Is there anything like a tListProc("myapp.exe"), i.e. something that wouldn't waste time finding processes that are not myapp.exe?

Or is there a way I could "trick" the system by applying a window name to My App in the tray?

Or is there a way I could rapidly return the number of myapp.exe instances running?

stevengraff

Re-reading Deana's reply (with the link), I tried this:

; This checks to see if already running, and exits if so.
objWMIService = GetObject("WinMgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
colItems = objWMIService.ExecQuery("Select * from Win32_Process where Caption = 'myapp.exe'")
count = colItems.count
if count > 1 then Exit                                    ; Another instance of AppName exists ââ,¬â€œ terminate this instance
colItems = ""
objWMIService = ""

Works great!

stanl

There was a similar discussion about this a couple of years ago. I tested WMI for Office Applications. Only difference is I looked for the count >0 rather than >1

Code (WINBATCH) Select

;Winbatch - is office app already loaded
;           uses WMI query for app
;Based on suggestion from Deana
;
;Stan Littlefield,  January 2, 2012
;/////////////////////////////////////////////////////////////////////////////////////////////////////

;tested two functions to see if any case sensitivity involved
#DefineFunction isLoaded(cApp)
retval=0
oWMI = GetObject("WinMgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
oItems = oWMI.ExecQuery("Select * from Win32_Process where Caption = '%cApp%'")
count = oItems.count
if count > 0 then retval=1     
oItems = ""
oWMI = ""
Return(retval)
#EndFunction

#DefineFunction isLoaded1(cApp)
retval=0
oWMI = GetObject("WinMgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
oItems = oWMI.ExecQuery("Select * from Win32_Process")
ForEach item In oItems
   If StrTrim(StrUpper(item.Caption))==cApp
      retval=1
      Break
   Endif
Next
 
oItems = ""
oWMI = ""
Return(retval)
#EndFunction

cApp="excel.exe"

Message("Is %cApp% Loaded?",isLoaded(cApp))

Message("Is %cApp% Loaded?",isLoaded1(StrUpper(cApp)))

stevengraff

In the line

GetObject("WinMgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")

I suppose I should just test this, but, will the impersonationLevel clause mean that my end-users will need certain minimum rights, i.e. Local Admin?

Deana

Quote from: stevengraff on July 07, 2014, 05:53:02 AM
In the line

GetObject("WinMgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")

I suppose I should just test this, but, will the impersonationLevel clause mean that my end-users will need certain minimum rights, i.e. Local Admin?

The ImpersonationLevel property is an integer that defines the COM impersonation level that is assigned to this object. This setting determines if processes owned by Windows Management Instrumentation (WMI) can detect or use your security credentials when making calls to other processes.

Please read:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms681722(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/aa393852(v=vs.85).aspx
Deana F.
Technical Support
Wilson WindowWare Inc.

stevengraff

Is there a way, in a Terminal Server environment, to have this mean "in the current session?" It's triggering "success" if the exe is being run by another user.

td

The  Win32_Process class has a 'SessionId' property.  You could modify your query to specify the current session id.  The 'trick' is to get the current 'SessionId'.  WMI provides a couple of classes that look useful but it may be a bit of a challenge to distinguished the current logged in user from the console user or other remote user sessions in a terminal server environment.

If someone doesn't post an example first, I will try to put something together later in the day.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade


td

Found this scrap on my hard drive.  It is the poster child for mashups without even using web based technology but it is relatively simple and does appear to work in limited testing.  It also needs proper error detection but at least it is something

Code (winbatch) Select

AddExtender( "WWWTS44I.DLL" , 0, "WWWTS64I.DLL" )

; Get the logon session id from the running script.
CurrentPID = DllCall('KERNEL32.DLL',long:'GetCurrentProcessId')
SessionId = wtsProcIdToSessId(CurrentPID)

; Query for all cmd.exe processes with the current user's session id.
strComputer = "."
objWMIService = GetObject( "winmgmts:\\" : strComputer : "\root\cimv2")
WQL = "Select * From Win32_Process  where Caption = 'cmd.exe' and SessionId = '%SessionId%'"
colProcesses = objWMIService.ExecQuery(WQL)

nCount = 0
ForEach objProcess in colProcesses
   nCount += 1
Next

Message("Command Shell Processes", "# of command shell process = ":nCount)
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stevengraff