SingleInstance Force issue Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

SingleInstance Force issue

17 Jul 2017, 01:04

In the case where a script is already running as admin, and the user manually double-clicks a new instance of the script, the new instance fails to take its place and displays an autohotkey error message. I presume this is because the new instance doesn't have admin privileges and isn't allowed to close the currently running one.

Is there any way to handle this inside the script given the #SingleInstance directive isn't sensitive to If statements?
User avatar
Exaskryz
Posts: 2882
Joined: 17 Oct 2015, 20:28

Re: SingleInstance Force issue

17 Jul 2017, 01:15

I wonder if there is a message that the initial instance of the script receives, which could then be captured by OnMessage(). You may be able to prevent the pop up of the error message and then make the first instance do a Reload (which should retain admin permissions).
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: SingleInstance Force issue  Topic is solved

17 Jul 2017, 03:23

You can handle it in the new instance by using #SingleInstance Off and manually checking for other instances (e.g. with WinGet List). You can identify scripts by %FullPath% ahk_class AutoHotkey with DetectHiddenWindows On. A_ScriptHwnd is the local instance.

#SingleInstance sends a message using the WM_COMMNOTIFY message number (0x44), with a custom message number as a parameter. UIPI (a Windows security feature) blocks these messages by default if they come from a process at a lower integrity level.

You can allow these messages in order to let #SingleInstance close the initial instance. However, a specially-designed process might be able to utilise this to escalate its privileges to those of the running script (i.e. it's a security risk), or to crash the script or possibly do other things.

Code: Select all

if (A_OSVersion ~= "^WIN_VISTA$|^6\.0\.") ; Untested.
    DllCall("ChangeWindowMessageFilter", "uint", 0x44, "uint", 1) ; Untested.
else
    DllCall("ChangeWindowMessageFilterEx", "ptr", A_ScriptHwnd, "uint", 0x44 ; WM_COMMNOTIFY := 0x44
        , "uint", 1, "ptr", 0) ; MSGFLT_ALLOW := 1
A more secure approach would be to use #SingleInstance Off and create your own custom message to be sent by the new instance to the old instance, then use ChangeWindowMessageFilterEx as shown above but replacing 0x44 with your message number. The old instance can do whatever you want in response to the message.
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: SingleInstance Force issue

17 Jul 2017, 09:51

Great answer as always, thanks.

I was going to go the easy route and just ExitApp if there was already another instance running, but I think it would result in a race condition because of:

Code: Select all

If !( A_IsAdmin )
{
	Run , *RunAs "%scriptPath%"    ;if the current instance is still running at the time the new instance is running, the new instance will see an already running instance and close, and then the current instance will close too. 
	ExitApp 
}
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: SingleInstance Force issue

17 Jul 2017, 09:56

Hmm, actually I think the race condition might still be a problem regardless. Because it can't close other instances prior to the above code, because it doesn't yet have admin privileges at that point in the script. So the check for other running instances has to be done AFTER the above lines of code, which means there's a chance it might close the newly spawned one

edit: on second thought it should be fine to do the CloseOtherInstances() before spawning a new RunAsAdmin version, because the former will only work if it's got admin. Otherwise it should just fail silently.

edit3: this should work too

Code: Select all

If !( A_IsAdmin )
{
	Run , *RunAs "%scriptPath%"   
	ExitApp 
}

Else
	CloseOtherInstances()  ;only happens for Admin script instances, which is the only time it's possible to do anyway.
I'd still rather just "do nothing and ExitApp" if there is already a running instance. It seems like a cleaner, less convoluted solution. :think:
Last edited by pneumatic on 17 Jul 2017, 11:45, edited 3 times in total.
User avatar
Exaskryz
Posts: 2882
Joined: 17 Oct 2015, 20:28

Re: SingleInstance Force issue

17 Jul 2017, 11:11

Here's how I interpreted Lexikos's suggestion:

In the auto-execute section (I'd recommend the very first lines), have an instance of a script look for other instances of the script. If it finds a match (i.e. more than one - one being itself, I expect), it should use PostMessage/SendMessage to send a message to the first instance of the script. (This is the only time a message should be sent, so it shouldn't create a race condition, because it's a one-time check.) Upon receiving the message, it'll execute a function per OnMessage() run in the auto-execute and can then close itself.

But now I'm realizing that because the second instance isn't admin, and the first instance is admin, that you would need to include your If !( A_IsAdmin ) check. I don't think it has to be integrated into the idea above, like you talked about in your edit1 and edit2 in the post above. (Or you could just make the script do the Run and ExitApp commands assuming it came back with a positive result on finding other instances, after it would PostMessage them to close.)

---

As to the point of "do nothing and ExitApp", you can use that same check for other scripts as Lexikos mentioned and if it returns true (again, I expect more than one identified window, as it should identify itself), just ExitApp. Make that your first task in the script.
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: SingleInstance Force issue

17 Jul 2017, 11:46

Cheers I'll give it a go.
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: SingleInstance Force issue

19 Jul 2017, 04:04

pneumatic wrote:I was going to go the easy route and just ExitApp if there was already another instance running, but I think it would result in a race condition [...]
I think what you're saying is that the new (admin) instance may exit because it sees the old instance still running. That's easily solved by telling the new instance to automatically close the old instance before executing script, the same way Reload does it.

Code: Select all

Run *RunAs "%A_AhkPath%" /restart "%A_ScriptFullPath%"
Exaskryz wrote:But now I'm realizing that because the second instance isn't admin, and the first instance is admin, that you would need to include your If !( A_IsAdmin ) check.
No, it does not matter whether the second instance is admin or not, if all it needs to do is send a message. As I showed, you can use ChangeWindowMessageFilterEx to allow the admin instance to accept a specific message from any (admin or non-admin) process.
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: SingleInstance Force issue

19 Jul 2017, 08:45

lexikos wrote:
pneumatic wrote:I was going to go the easy route and just ExitApp if there was already another instance running, but I think it would result in a race condition [...]
I think what you're saying is that the new (admin) instance may exit because it sees the old instance still running. That's easily solved by telling the new instance to automatically close the old instance before executing script, the same way Reload does it.

Code: Select all

Run *RunAs "%A_AhkPath%" /restart "%A_ScriptFullPath%"
Great, that saves me a lot of headache :thumbup:

I am always worried about whether UAC would start interfering with what messages are allowed to be sent between windows or which scripts can see which other windows or whether some access rights setting would interfere, so I like to avoid risk as much as possible. I presume if the new instance somehow ran as a regular user and tried to "reload" the already existing admin instance, that it would fail?

Also may I ask, as I am using window messages elsewhere in my script, have you ever come across a scenario where window messages were blocked by access rights? In your experience which is more reliable - detecting windows via WinExist with DetectHiddenWindows On, or sending a discrete message from one to another (eg. the number 1) to tell it whether it's open.

Thanks
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: SingleInstance Force issue

20 Jul 2017, 20:27

WinExist must find the window.

SendMessage must find the window and be able to send a specific message to it.

It should be clear which one would be more reliable.

In general, a process cannot automate windows belonging to a process of a higher integrity level. See "UIPI".
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: SingleInstance Force issue

21 Jul 2017, 00:21

lexikos wrote: SendMessage must find the window and be able to send a specific message to it.
Whoops lol, I forgot that Wintitle must be present in PostMessage :) For some reason I thought the message number was the unique identifier.

In that case I'll just stick with WinExist(ahk_id).

Thanks again.
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: SingleInstance Force issue

17 Aug 2017, 22:36

For anyone who might find this thread through a search in future, the previous code is STILL not good enough (UAC strikes again!). With UAC turned off and "run as administrator" unticked for my script.exe (not compiled) the program for some reason launches in admin mode on the first run which then bypasses the If !(A_isadmin) check, which results in a duplicate instance, so an extra check is still needed with WinExist("Script's main window name") when the script is in admin mode.

Code: Select all

#SingleInstance Off
#DetectHiddenWindows On

RunAsAdmin(){
	
	If !( A_IsAdmin ) 
	{
		Run , *RunAs %A_ScriptDir%\MyScript.exe , ,UseErrorLevel
		ExitApp
		
	}
	
	Else
	{
		If (WinExist("MyScript.ahk - Autohotkey"))
		   ExitApp
	}
	
}
edit: on second thought this is still no good as it's still subject to the race condition if the first ExitApp takes a long time. Bloody hell!
Last edited by pneumatic on 22 Aug 2017, 05:15, edited 1 time in total.
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: SingleInstance Force issue

17 Aug 2017, 23:14

Can anyone advise me on how to do the double reference so the last line works without having to store it in another variable first.

Code: Select all

#SingleInstance Off
#DetectHiddenWindows On

RunAsAdmin(){
	
	If !( A_IsAdmin ) 
	{
		Run , *RunAs %A_ScriptDir%\MyScript.exe, ,UseErrorLevel
		ExitApp
		
	}
	
	Else
	{	
		SetTitleMatchMode 2
		WinGet, Instances, List, MyScript.ahk - AutoHotkey
		Loop %Instances%
		{
			If ( Instances%A_Index% != A_ScriptHwnd )
				WinClose, % "ahk_id " Instances%A_Index% 
		}
	
	}
	
}
Last edited by pneumatic on 22 Aug 2017, 05:16, edited 2 times in total.
User avatar
Exaskryz
Posts: 2882
Joined: 17 Oct 2015, 20:28

Re: SingleInstance Force issue

17 Aug 2017, 23:52

WinClose, % "ahk_id " Instances%A_Index% should work. The space after the d is important.
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: SingleInstance Force issue

18 Aug 2017, 18:21

Exaskryz wrote:WinClose, % "ahk_id " Instances%A_Index% should work. The space after the d is important.
Thanks.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: wilkster and 143 guests