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.
Check out the following tech support article from some options:
http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/tsleft.web+WinBatch/Launching~WinBatch~and~Other~Apps+Prevent~more~than~one~WB~Copy~from~Being~Run.txt
I will; thank you.
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?
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.
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. :)
Seems odd that no one has suggested using the AppExist() function, even in the article.
Jim
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.
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.
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.
Sounds like "6 of one/half dozen of the other" (or "Name your poison") to me.
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.
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?
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!
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
;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)))
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?
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
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.
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.
Thanks Tony.
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
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)
Works like a charm; thanks again.