2

if I run networksetup -setairportnetwork en0 myssid

it takes between 10 and 15 seconds.

If I manually click the Wi-Fi icon in the notification bar and select myssid, it's instant.

Alternatively, I'd love to be able to make an applescript that manually clicks to change Wi-Fi networks, but I can't find a good example, everyone just says "use networksetup".

Answer: @CJK's solution works great, I'm using his second script that does the manual click. The first one works in most cases except where I'm trying to connect to an enterprise network that uses a certificate. For some reason connecting this way doesn't work in that case. But in all other cases both scripts work great.

1
  • If just done some testing both clicking on the WiFi icon and using networksetup and have gotten connection times ranging for under 2 seconds to over 11 seconds. I don't believe there is an issue with networksetup per se, it's just how long it takes for the WAP to authenticate and/or the DHCP server to respond.
    – Allan
    Commented Apr 15, 2019 at 17:04

1 Answer 1

3

I note what @Allan has stated in his comment below your question.

Whilst I don't get a delay as long as 10-15 seconds using the commandline, I do find it demonstrably and consistently slower (by a few seconds) than clicking the WiFi icon. I don't know the underlying operations performed by networksetup, but I'm wondering if it performs a network scan each and every time a request is made to join a new network, which may account for the delay. Network scanning times can be influenced by many factors, so this delay would likely vary between users in different environments. This is just hypothesis, though.

I'd love to be able to make an applescript that manually clicks to change Wi-Fi networks

I'll give you two scripts.

1. Service-scripting with AppleScriptObjC

The first script uses AppleScriptObjC to interface with the system directly and control the WiFi interface. A quick summary, which I expand on below:

  • + Robust/reliable
  • + Faster than networksetup
  • + No need to manually enter passwords
  • + No visual disturbances on screen; operates in background
  • Slower than clicking the WiFi icon
  • SSID case-insensitive, whole phrase matching (can be changed)
use framework "CoreWLAN"

property this : a reference to current application
property nil : a reference to missing value
property _1 : a reference to reference

property CWWiFiClient : a reference to CWWiFiClient of this
property NSPredicate : a reference to NSPredicate of this


to joinNetwork given name:ssid as text, password:pw as text : missing value
    local ssid, pw

    set |?| to NSPredicate's predicateWithFormat:("self.ssid ==[c] %@") ¬
        argumentArray:{ssid}

    tell CWWiFiClient's sharedWiFiClient()'s interface()
        its setPower:true |error|:nil

        set networks to {}
        tell cachedScanResults() to if it ≠ missing value then ¬
            set networks to filteredSetUsingPredicate_(|?|)

        if the number of networks = 0 then set networks to ¬
            (its scanForNetworksWithName:ssid |error|:nil)

        set network to (allObjects() in networks)'s firstObject()
        its associateToNetwork:network |password|:pw |error|:_1

        set [success, E] to the result
        if E ≠ missing value then return ¬
            E's localizedDescription() ¬
            as text
        success
    end tell
end joinNetwork

You would then call the joinNetwork handler from within your script like so:

joinNetwork given name:"my network ssid", password:"Passw0rd1"

This script will be robust and reliable (barring any human error on my part, which I will correct if you find a bug I've overlooked during testing). It preferentially uses cached scan results to connect to networks; if none are available, or the specified SSID didn't appear when the scan results were last cached, it will perform a fresh network scan. Therefore, if my hypothesis about networksetup is a contributing factor, this script will be faster than the commandline on the majority of runs, but may take the same amount of time on the occasional run when forced to do a scan.

One benefit is that, because you can specify a network password when calling the joinNetwork handler, you won't need to enter it manually when joining a new network.

The SSID—specified by the name parameter—is case-insensitive in both scripts; the password, obviously, is not. The script above has elected to perform an entire phrase match on the SSID, i.e. "my net" will not ever join a network called "my network"; the script below has elected to do a fuzzy matching, i.e. "net" will potentially join "my net" or "my network".

2. UI Scripting

This second script is actually the one you requested: it interfaces with the UI to click the WiFi menu icon and then select the network (if it appears in the list). A summary:

  • + Despite being a UI script, it ought not to suffer from the same weaknesses inherent to most UI scripts; so hopefully reliable
  • + Fastest implementation—networks joined immediately
  • Visually jarring when menu appears; also, user must not interact with system until the script has exited (the nature of any UI script)
  • Must manually enter passwords if not already saved
  • SSID case-insensitive, fuzzy matching (can be changed)
    use application "System Events"

    property process : a reference to application process "SystemUIServer"
    property menu bar : a reference to menu bar 1 of my process
    property menu bar item : a reference to (menu bar items of my menu bar ¬
        where the description contains "Wi-Fi")
    property menu : a reference to menu 1 of my menu bar item
    property menu item : a reference to menu items of my menu


    to joinNetwork given name:ssid as text
        local ssid

        if not (my menu bar item exists) then return false
        click my menu bar item

        repeat until my menu exists
            delay 0.5
        end repeat

        set M to a reference to (my menu item where the name contains ssid)

        repeat 20 times --> 10 seconds @ 0.5s delay
            if M exists then exit repeat
            delay 0.5
        end repeat
        click M
    end joinNetwork


    joinNetwork given name:"my network ssid"

Scripts that try to control the UI are using a method called UI or GUI scripting. These are popular amongst and popularised by novice scripters, simply because these are the most commonly-found scripts on the internet. I won't go into it here too much other than to caution you against using scripts that rely on clicks and keystrokes—they're inherently fragile and unreliable even when well written, but most UI scripts (written by novices) are terribly written, which compounds the problem. There's almost always a better way to achieve the desired outcome.

That said, this is one of the few sorts of UI scripts that are perhaps an exception to my warning, and in my view, a pretty acceptable implementation. This is because it's targeting an aspect of the UI (a menu bar icon owned by the system) that is virtually guaranteed to always be accessible regardless of what window or desktop has focus, etc. The one situation where the script of this nature would potentially break is if the menu bar icon wasn't there (i.e. the user has specifically set a preference not to have the WiFi icon available); however, I've put a line in the script that checks for this, and performs a graceful exit in that situation.

This script appears to be the speediest implementation, which doesn't surprise me as it will be equal in speed to clicking the icon manually; but, it impresses me in that I can't think of many UI scripts that would end up out-performing an application- or service-targetted script. The network is joined almost instantly, provided the password is already saved in your keychain. On occasion, the network you'd expect to see in the menu list might not be there until the background scanning refreshes and the list updates. Therefore, I built in a 10-second period within which the script checks every half second to see if the menu item has appeared; if it doesn't, the script exits and you'll have to try again.

The drawback is the absence of a keychain password for a network will necessitate you needing to manually enter the password when asked for it. It is possible to script this using UI techniques as well, but this immediately makes the script highly susceptible to the issues I mentioned and then I, personally, wouldn't use it.


System info: AppleScript version: 2.7 System version: 10.13.6

5
  • WOW. Thank you for the amazing work. I have one issue, not with your script, but I'm wondering if you know what's going on. I can join any open Wi-Fi network with your first script and it's FAST, but with my corporate network, the wifi icon simply displays a ! symbol, and it won't connect unless I do it manually. I had this same issue with the script I tried from the terminal. I assume there's some kind of setup needed for this type of network, which connects using a certificate I have installed in the keychain. Is there a way to specify the certificate, or make it work in my case?? Commented Apr 17, 2019 at 13:36
  • Is your corporate WiFi an Enterprise network? If you give me details about the type of network it is, and what sort of authentication process you go through manually when first connecting, I’ll see what I recognise, if I do. Really glad the service-led script is performant, as it’s the one I will be maintaining for the future. Your corporate network isn’t an ad-hoc network, by any chance? Tell me also what commands you used from Terminal, as this gives me indications of how it might translate into Objective-C.
    – CJK
    Commented Apr 17, 2019 at 14:14
  • It's an Enterprise network. When I connected via the terminal I just did a simple connect with no password or anything, and it failed the same way. So, I'm thinking there's some magic with regard to clicking that isn't present in the terminal command. There is a certificate in the keychain that must be installed in order to connect to this network. Once it's installed, I can connect all day and it's just like a regular network, unless I use a terminal command, or your first script. I hope that's enough info, let me know if I can give any more. Commented Apr 17, 2019 at 14:59
  • I don't know if this matters, but in my network preferences the 802.1X: line lists the enterprise network SSID, regardless of what network I'm currently connected to. I don't think it's Ad-Hoc, we have routers hanging from the ceiling. The security is listed as "WPA2 Enterprise" Commented Apr 17, 2019 at 15:03
  • Having had a play around with the Enterprise network methods, there's a lot of new territory for me involving retrieving security certificates from the keychain, which takes up the bulk of the code for the script. As I can't test any of it, I don't think it's possible for me to draft a workable script. I would suggest posting a new question specifically asking how to script connecting to an Enterprise network, and include tags like AppleScript, JavaScript, JXA, AppleScriptObjC, bash and terminal. Anyone with knowledge scripting Apple's Security framework will know how to do it.
    – CJK
    Commented Apr 23, 2019 at 3:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .