Hi
New to this forum. Seems like there’s lots of knowledge here
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
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
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
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