closing VPN dialog with applescript

Hi
New to this forum. Seems like there’s lots of knowledge here :slight_smile:
I have a problem that I would like to get help with.
I have a process going on on a macbook. That process is controlled by an applescript. One of the things that script does is that it, now and then, disconnects and reconnects with a VPN service. I have several VPN services that it randomly chooses between. All of that works well, but occasionally the VPN loses contact and a dialog appears.
What I would like to do is to have a script that detects when this happens, closes the dialog and reconnects with the VPN service.
Is this possible? Can anyone in her help me with this. I would be so grateful. I would like to add that it´s an old macbook with macos 10.6.
Thanks
Heikki

You’ll need a stay-open script that runs on an interval in the background checking for any of the windows informing you that a VPN has disconnected.

I don’t know if it’s one piece of VPN software that connects to multiple VPN endpoints, or different VPN software. But for each possible disconnect window you could get, get the window up on your screen (unplug your ethernet or whatever to force the dialog), then run this script:


delay 5
tell application "System Events"
	set frontProcess to the first application process whose frontmost is true
	tell frontProcess to set UIprops to the properties of every UI element
end tell
return UIprops

Click “run,” then switch to the dialog. The script waits 5 seconds then gathers information on the front application’s UI elements.

Take a look at the information returned and find something you can use to detect the window. If you need help with that, gather the data and post the results here.

Then you make a script that looks like this:

property idleTime : 30 -- how long to wait between checks

on run
	idle
end run

on idle
	tell application "System Events"
		-- check for your windows here
	end tell
	-- if a disconnect window existed in the above check, then run your code that connects to a VPN here
	
	return idleTime
end idle

Once it’s done, save your script as a “Stay Open” application and run it.

For example, in Script Editor, I made this script:

display dialog "Your VPN has disconnected!" with title "Panic!"

I ran it so I had a dialog up, then ran the first script I posted and switched to Script Editor to get the UI information on that dialog. “UIprops” was this:

{{class:window, minimum value:missing value, orientation:missing value, position:{1072, 388}, role description:"dialog", accessibility description:missing value, focused:false, title:"Panic!", size:{420, 120}, value:missing value, help:missing value, enabled:missing value, maximum value:missing value, role:"AXWindow", entire contents:{}, subrole:"AXDialog", selected:missing value, name:"Panic!", description:"dialog"}, {class:window, minimum value:missing value, orientation:missing value, position:{958, 388}, role description:"standard window", accessibility description:missing value, focused:false, title:"Untitled", size:{700, 705}, value:missing value, help:missing value, enabled:missing value, maximum value:missing value, role:"AXWindow", entire contents:{}, subrole:"AXStandardWindow", selected:missing value, name:"Untitled", description:"standard window"}, {class:menu bar, minimum value:missing value, orientation:missing value, position:{0, 0}, role description:"menu bar", accessibility description:missing value, focused:missing value, title:missing value, size:{2560, 22}, value:missing value, help:missing value, enabled:true, maximum value:missing value, role:"AXMenuBar", entire contents:{}, subrole:missing value, selected:missing value, name:missing value, description:"menu bar"}}

So out of the three main UI elements that returned, the first one is the dialog.

So to detect it, I’d script:

tell application "System Events"
	tell application process "Script Editor"
		if exists (first UI element whose description is "dialog") then
			set dialogWindow to the first UI element whose description is "dialog"
			if the title of dialogWindow is "Panic!" then
				set VPNdisconnect to true
				tell dialogWindow
					tell button "OK"
						click it
					end tell
				end tell
			else
				set VPNdisconnect to false
			end if
		else
			set VPNdisconnect to false
		end if
	end tell
end tell

So you’d need code like that to detect each possible window you might get.

**
Edited to also close the VPN disconnected dialog, forgot about that requirement the first time around.

Thanks a lot. I will try this as soon I get home from work :slight_smile:

Ok
Had a little try on the dialog that appears when the VPN is unable to connect (By the way, I use
L2TP protocol and the systems network function)

This is what shows:

{{minimum value:missing value, orientation:missing value, position:{20, 38}, class:window, role description:“standard window”, accessibility description:missing value, focused:false, title:“what process.scpt”, size:{700, 705}, value:missing value, help:missing value, enabled:missing value, maximum value:missing value, role:“AXWindow”, entire contents:{}, subrole:“AXStandardWindow”, selected:missing value, name:“what process.scpt”, description:“standard window”}, {minimum value:missing value, orientation:missing value, position:{20, 38}, class:window, role description:“standard window”, accessibility description:missing value, focused:false, title:“Untitled”, size:{700, 705}, value:missing value, help:missing value, enabled:missing value, maximum value:missing value, role:“AXWindow”, entire contents:{}, subrole:“AXStandardWindow”, selected:missing value, name:“Untitled”, description:“standard window”}, {minimum value:missing value, orientation:missing value, position:{0, 0}, class:menu bar, role description:“menu bar”, accessibility description:missing value, focused:missing value, title:missing value, size:{1280, 22}, value:missing value, help:missing value, enabled:true, maximum value:missing value, role:“AXMenuBar”, entire contents:{}, subrole:missing value, selected:missing value, name:missing value, description:“menu bar”}}

I´m far from an expert, but to me i seems that the script doesn’t see the dialog…

Seeing a window named “what process.scpt” in your results makes me think that you missed the part of the instructions where you have 5 seconds from clicking “Run” on the script to make the VPN disconnect dialog the front window.

I didn’t know the name of the process for your VPN app, so I wrote a script that gets the UI element info for the front process.

Of course, if you’re running it from a script editor, the front process is always the script editor application. So I put in a delay, and am expecting you to run the script, then manually make the disconnect dialog the front window, and wait a few seconds for the script to finish.

Alternately, you could switch in the name of the actual process you want the info from. That would be good to have for writing the final script anyway.

  • Tom.

I think did exactly as you instructed. Pasted the code into my script editor (named it “what process”, not your instructions but I didn’t think that would matter) started it and clicked the dialog to put it in front. And the delay was in the code.
Did I misunderstand anything?

Heikki

From your response, it sounds like you did understand my instructions correctly, but it still looks like something went wrong.

I didn’t see what you did, so I don’t know if you did something wrong. But the script reads the UI elements for the front process at the end of it’s delay, and it found a window called “what process.scpt,” which makes me think that, at the end of the 5 seconds when the script read the UI elements, the front process was Script Editor, not whatever process presents the dialog about the VPN disconnect.

You could try this, which will tell us which process was in front at the end of 5 seconds:

delay 5
tell application "System Events"
	set frontProcess to the first application process whose frontmost is true
	tell frontProcess to set UIprops to the properties of every UI element
end tell
return {frontProcess, UIprops}

and/or run this:

tell application "System Events" to set theProcesses to every application process
return theProcesses

And see if you can find the name of your VPN process in the list.

If you find the process name, you can use:

tell application "System Events"
	tell process "Finder" -- insert your VPN process name here in place of "Finder"
		set frontmost to true
		delay 1
		set UIprops to the properties of every UI element
	end tell
end tell
return UIprops

First of all, I’m very thankful fr your help :slight_smile:
I´m pretty sure I put the dialog in front before five seconds ended, but it seems like the script for some reason is not able to detect it.
Anyway, I tried the next one, that looked for all processes and this is what it showed:

{application process “loginwindow” of application “System Events”, application process “Script Editor” of application “System Events”, application process “Opera” of application “System Events”, application process “cloudphotosd” of application “System Events”, application process “SystemUIServer” of application “System Events”, application process “Dock” of application “System Events”, application process “Spotlight” of application “System Events”, application process “Finder” of application “System Events”, application process “com.apple.dock.extra” of application “System Events”, application process “garcon” of application “System Events”, application process “iTunesHelper” of application “System Events”, application process “NotificationCenter” of application “System Events”, application process “AirPlayUIAgent” of application “System Events”, application process “Dropbox” of application “System Events”, application process “CopyClip” of application “System Events”, application process “Google Drive File Stream” of application “System Events”, application process “LOGINserver” of application “System Events”, application process “USBserver” of application “System Events”, application process “NETserver” of application “System Events”, application process “WiFiAgent” of application “System Events”, application process “FinderSyncExtension” of application “System Events”, application process “Keychain Circle Notification” of application “System Events”, application process “FolderActionsDispatcher” of application “System Events”, application process “DropboxActivityProvider” of application “System Events”, application process “LaterAgent” of application “System Events”, application process “Image Capture Extension” of application “System Events”, application process “universalAccessAuthWarn” of application “System Events”, application process “ViewBridgeAuxiliary” of application “System Events”, application process “CoreServicesUIAgent” of application “System Events”, application process “EscrowSecurityAlert” of application “System Events”, application process “BezelUIServer” of application “System Events”, application process “UserNotificationCenter” of application “System Events”, application process “System Events” of application “System Events”}

Is it some0ne of theese?

I also tried the script without the dialog window up, and the process that was missing then was the one called “UserNotificationCenter”.
So I tried the script that should put a process to the front, and pasted in that name, and the dialog came to the front. So I guess that must be the one :slight_smile:

Now I need to get the next script to work. I´m not really sure how to do that though. Started like this, but I don´t really know how to go on:

tell application "System Events"
	tell application process "UserNotificationCenter"
		if exists then
			set dialogWindow to the first UI element whose description is "dialog"
			if the title of dialogWindow is "VPN Connection" then
				set VPNdisconnect to true
				tell dialogWindow
					tell button "OK"
						click it
					end tell
				end tell
			else
				set VPNdisconnect to false
			end if
		else
			set VPNdisconnect to false
		end if
	end tell
end tell

Good, that’s some progress. Good thinking getting the list twice to see what’s missing.

Can you send the UI information for the dialog now that you have the process name, so I can check on how we’re targeting it?

tell application "System Events"
	tell process "UserNotificationCenter"
		set frontmost to true
		delay 1
		set UIprops to the properties of every UI element
	end tell
end tell
return UIprops

and post the output?

Also, the targeting/clicking of the button to dismiss the dialog may be different than my example. We may need to gather more UI information on the sub-elements of the dialog, but it’s easier for me to provide code to do that after I’m sure we can target the dialog.

I’m concerned there could be dialogs that may appear that are not informing you that your VPN disconnected that might still belong to this process and have the same window name/type, so we want to get all the info we can on this dialog to try to pin it down to ONLY trigger the script if the dialog is actually telling you that your VPN disconnected.

Once we’ve got it targeted, the final script looks something like this:

property idleTime : 30 -- how long to wait between checks

on run
	idle
end run

on idle
	tell application "System Events"
		tell application process "UserNotificationCenter"
			if exists then
				set dialogWindow to the first UI element whose description is "dialog"
				if the title of dialogWindow is "VPN Connection" then
					set VPNdisconnect to true
					tell dialogWindow
						tell button "OK"
							click it
						end tell
					end tell
				else
					set VPNdisconnect to false
				end if
			else
				set VPNdisconnect to false
			end if
		end tell
	end tell
	
	if VPNdisconnect is true then
		-- insert whatever code you're already using in your other Applescript to connect the VPN here
	end if
	
	return idleTime
end idle

This is what shows up when I run the first script.

I tried the second script with what I thought might be the right values. Like this:

property idleTime : 30 -- how long to wait between checks

on run
	idle
end run

on idle
	tell application "System Events"
		tell application process "UserNotificationCenter"
			if exists then
				set dialogWindow to the first UI element whose description is "system dialog"
				if the title of dialogWindow is "VPN Connection" then
					set VPNdisconnect to true
					tell dialogWindow
						tell button "OK"
							click it
						end tell
					end tell
				else
					set VPNdisconnect to false
				end if
			else
				set VPNdisconnect to false
			end if
		end tell
	end tell
	
	if VPNdisconnect is true then
		tell application "System Events"
			tell current location of network preferences
				set myConnection to the service ((random number from 1 to 3) as string)
				
				connect myConnection
				
			end tell
		end tell
	end if
	
	return idleTime
end idle

but ended up with an error message:

I also tried to change the word “description” in the script to “role description”, and ended up with “30”.

I experimented a little bit more, and changed the title from “VPN Connection” to “”. Then it actually closed the dialog and reconnected to the VPN :D. So I went on and saved it as an application that would keep on running.I started it and forced another dialog, and it worked as it should. But now, when it succeeded with the reconnect another dialog showed up:

Can’t get UI element 1 of application process “UserNotificationCenter” whose role description = “system dialog”. Invalid index.

System Events got an error: Can’t get UI element 1 of application process “UserNotificationCenter” whose role description = “system dialog”. Invalid index. (-1719)

With two buttons, one “OK” and one “EDIT”

What to do?

Heikki

At some point you changed the test to see if there was a dialog:

if exists (first UI element whose description is "dialog") then

to just:

 if exists then

which I would have expected would return an error (I would have thought “exists” requires an argument for what it’s checking to see if it exists), but it actually just evaluates to true.

So right now, you have the script always expecting to find this dialog, and if it doesn’t, it errors. Instead, it needs to check if there is a dialog.

property idleTime : 30 -- how long to wait between checks

on run
	idle
end run

on idle
	tell application "System Events"
		tell application process "UserNotificationCenter"
			if exists (the first UI element whose description is "system dialog") then
				set dialogWindow to the first UI element whose description is "system dialog"
				if the title of dialogWindow is "" then
					set VPNdisconnect to true
					tell dialogWindow
						tell button "OK"
							click it
						end tell
					end tell
				else
					set VPNdisconnect to false
				end if
			else
				set VPNdisconnect to false
			end if
		end tell
	end tell
	
	if VPNdisconnect is true then
		tell application "System Events"
			tell current location of network preferences
				set myConnection to the service ((random number from 1 to 3) as string)
				
				connect myConnection
				
			end tell
		end tell
	end if
	
	return idleTime
end idle

I think what we have now will probably work, but it would worry me to leave this running all the time.
Originally, I was hoping we could use three points of logic to determine that a VPN disconnect dialog had appeared: the process would be the VPN software, the type of dialog would be some kind of alert/notification, and the title would be something specific. Now none of those are actually doing anything.

I’d assumed the dialog would be presented by the VPN application, but it’s presented by Notification Center. Lot of different dialogs for all sorts of things can be presented through Notification Center, so we have no specificity for this being a VPN Disconnect dialog based on the process.

If it was the VPN application, then presumably most UI elements of the VPN app are not an alert dialog, so that would help narrow it down. But with Notification Center, it’s a “system dialog,” and I’m guessing just about every UI element Notification Center ever presents is also a “system dialog”.

Finally, I was hoping we could use the window’s title, but no luck there. It’s blank.

So my concern here is that, while the script should do what you want, there are dozens of other types of dialogs that could pop up on your system that have nothing to do with your VPN that this script is also going to automatically dismiss, and then try to connect to your VPN again.

So possible solutions to this might be:
1st, let’s keep digging into UI elements here and see what we can find. Maybe we can get to a text field in the dialog with unique text telling us exactly that it’s this dialog.
If not, there might be other things to try - for example, if your non-VPN’d public IP address is always in a certain range, we could use “do shell script” with curl to get your public IP address on every iteration of the loop (with something like https://www.ipify.org/), and if it’s in the range of your non-VPN IP address, then check for the dialog and run the rest of the script. It still wouldn’t be perfect, it could still dismiss some unrelated dialog. But if you know your VPN has disconnected in the last 30 seconds, then there’s a pretty good chance the dialog it finds is about that.

But we probably won’t need to go there. Since the dialog is presented by a recent process written by Apple, it’s probably pure coco UI and we can probably drill down and get the full text of the dialog to pinpoint it.

Get the dialog up and try this:

tell application "System Events"
	tell application process "UserNotificationCenter"
		set frontmost to true
		delay 0.5
		set subElements to every UI element of (the first UI element whose description is "system dialog")
	end tell
end tell
return subElements

If nothing there looks promising to uniquely target the window, you can try going even deeper, getting all the UI Elements of one of those sub-elements. But it’s just an alert dialog, so it probably doesn’t go very deep.

I have no idea when I removed that piece of code, it was not made on purpose :rolleyes:. I put it back now and it works the way it should :).

But, of course, the fact that the script would do this to just any dialog could be problematic. So I tried the scriplet that would look for every subelement on the dialog, but got an error message:

error “System Events got an error: Can’t get UI element 1 of application process "UserNotificationCenter" whose description = "system dialog". Invalid index.” number -1719

Any idea?

I am going to try to run the script as it is though, at least for a test period. The dialogs showing up are basically always VPN related, so I think it will probably work well.

Thank you so much for your help. Not only did I get a script that will probably work, but I also learned a lot.

Heikki

Not sure if that’s failing because there aren’t any sub-UI elements of the dialog, or because it’s not finding the dialog. I would certainly think it would have to have sub-elements.

I assume you had the disconnect alert window up on screen before running it, of course.

Try this, it should at least help pin down the error between getting the UI elements or getting the alert window itself.

tell application "System Events"
	tell application process "UserNotificationCenter"
		set frontmost to true
		delay 0.5
		set dialogWindow to the first UI element whose description is "system dialog"
		set subElements to every UI element of dialogWindow
	end tell
end tell
return subElements