Skip to main content

Hello All.



I've been installing a configuration profile that sets up our wifi SSID and associates it with a machine certificate via the AD Certificate section. It associates the cert with the wifi config (so the user doesn’t get prompted) and overall has been working fine for quite some time.



I have one issue though. New networks place the network at the end of the preferred network list. Simple fix: should be to re-install right? Nope. The configuration profile places the network at the end of the Wifi Preferred Networks. I want it to be first.



There doesn’t seem to be a way to re-order it unless I remove the wifi network, then re-add it. That's fairly trivial to script, but that breaks the association with the computer’s certificate and the user now suffers.



Does anyone know how to programmatically re-order SSID preference without breaking the cert association OR how to add wifi and associate it with an existing machine cert so that the user isn't prompted?



Thanks!
Aaron

I opened a case with Apple on this a couple months back. Unfortunately we ran into the same issue as what you are seeing. Below is a suggestion they came back with that we have not tried or tested as of yet.



You’re right that the -addpreferredwirelessnetworkatindex flag only works when adding new SSIDs, not rearranging existing ones.

As for suggestions, I have a few. The SSID name format you’re seeing in the PreferredOrder array is actually the name of the Wi-Fi network converted from ASCII to hexadecimal. There’s two quick ways to turn that into something human readable. First way,
find the “KnowNetworks” key, which contains a dictionary of the hex SSIDs and their individual settings. Within the individual settings, there’s a key called ‘SSIDString', which contains the plaintext version of the name. Example:


<key>wifi.ssid.<464f4f></key>

<dict>

<key>AutoLogin</key>

<false/>

<key>Captive</key>

<false/>

<key>ChannelHistory</key>

<array/>

<key>Closed</key>

<false/>

<key>CollocatedGroup</key>

<array/>

<key>Disabled</key>

<false/>

<key>Passpoint</key>

<false/>

<key>PossiblyHiddenNetwork</key>

<false/>

<key>RoamingProfileType</key>

<string>None</string>

<key>SPRoaming</key>

<false/>

<key>SSID</key>

<data>

Rk9P

</data>

<key>SSIDString</key>

<string>FOO</string>

<key>SecurityType</key>

<string>WPA Personal</string>

<key>SystemMode</key>

<false/>

<key>TemporarilyDisabled</key>

<false/>

</dict>


As you’ll see, this is for my example FOO network from earlier. Everywhere I see wifi.ssid.<464f4f>, it’s referring to this network.


The other and in my opinion much easier way to do this is to find a nice online hex to ascii converter, and paste in the hex version of the name. Here’s one such site: http://www.unit-conversion.info/texttools/hexadecimal/. You can also work it in the
other direction, entering the ASCII name that you know and converting it to the hex name you should be hunting for.


In theory, you could also do the conversion with the command line tool ‘iconv’, but it requires creating input files containing the data to be converted and was a bit convoluted for my taste. ‘man iconv’ if you want to try it out.


Now, after you know the hex name of the network, you should be able to modify the .plist by hand. Just make sure your network is the first item listed in the PreferredOrder array, and that it only appears once in the list.


In my testing, I found that this method worked to reorder the preferred network list, and that my change survived reboots, toggling of the Wi-Fi interface, and many other attempts to test if the system would revert to the original order. The official answer
is still “make the change in the GUI”, so there’s no guarantee this hand editing method will continue to work with later OS releases. As such, if you plan on using this workaround for now, I highly suggest testing that it continues to work when new beta builds
of the OS are made available.

OK. I found the plist and the array PreferredOrder where i see the hex form of my network name. I'll be using plistbuddy to see if I can re-order the items in PreferredOrder by deleting the existing item I want to move, then inserting it at location 0. I just need to verify that it works.



When modifying plists with plistbuddy, is there a preferred method for making sure changes are properly applied and stick? Any cfprefsd killing, or other things to do before, during, after? Target OS's are 10.9 and 10.10.



Thanks!
Aaron


This is a script I made a few weeks ago, It does remove and re-add the SSID from the preferred network list but I've noticed as long as they are currently connected to that SSID they were never kicked off. However we use Configurations to push our networks out. Is it possible for you to add that certificate to be system-based rather than SSID specific?



If so then this script I made could be used:



https://jamfnation.jamfsoftware.com/discussion.html?id=17858


Heh..delete this one. Double posted....


I've been using a python script made by pudquick on GitHub, linked here



I run this to change the order of preferred networks:



#!/usr/bin/python

# As written, this requires the following:
# - OS X 10.6+ (may not work in 10.10, haven't tested)
# - python 2.6 or 2.7 (for collections.namedtuple usage, should be fine as default python in 10.6 is 2.6)
# - pyObjC (as such, recommended to be used with native OS X python install)

# Only tested and confirmed to work against 10.9.5
# Run with root

import objc, ctypes.util, os.path, collections
from Foundation import NSOrderedSet

preferred_SSID = 'yourSSIDhere'
next_to_last_SSID = 'yourSSIDhere'
last_SSID = 'yourSSIDhere'

def load_objc_framework(framework_name):
# Utility function that loads a Framework bundle and creates a namedtuple where the attributes are the loaded classes from the Framework bundle
loaded_classes = dict()
framework_bundle = objc.loadBundle(framework_name, bundle_path=os.path.dirname(ctypes.util.find_library(framework_name)), module_globals=loaded_classes)
return collections.namedtuple('AttributedFramework', loaded_classes.keys())(**loaded_classes)

# Load the CoreWLAN.framework (10.6+)
CoreWLAN = load_objc_framework('CoreWLAN')

# Load all available wifi interfaces
interfaces = dict()
for i in CoreWLAN.CWInterface.interfaceNames():
interfaces[i] = CoreWLAN.CWInterface.interfaceWithName_(i)

# Repeat the configuration with every wifi interface
for i in interfaces.keys():
# Grab a mutable copy of this interface's configuration
configuration_copy = CoreWLAN.CWMutableConfiguration.alloc().initWithConfiguration_(interfaces[i].configuration())
# Find all the preferred/remembered network profiles
profiles = list(configuration_copy.networkProfiles())
# Grab all the SSIDs, in order
SSIDs = [x.ssid() for x in profiles]
# Check to see if our preferred SSID is in the list
if (preferred_SSID in SSIDs):
# Apparently it is, so let's adjust the order
# Profiles with matching SSIDs will move to the front, the rest will remain at the end
# Order is preserved, example where 'ssid3' is preferred:
# Original: [ssid1, ssid2, ssid3, ssid4]
# New order: [ssid3, ssid1, ssid2, ssid4]
profiles.sort(key=lambda x: x.ssid() == preferred_SSID, reverse=True)
# Now we move next_to_last_SSID to the end
profiles.sort(key=lambda x: x.ssid() == next_to_last_SSID, reverse=False)
# Now we move last_SSID to the end (bumping next_to_last_SSID)
profiles.sort(key=lambda x: x.ssid() == last_SSID, reverse=False)
# Now we have to update the mutable configuration
# First convert it back to a NSOrderedSet
profile_set = NSOrderedSet.orderedSetWithArray_(profiles)
# Then set/overwrite the configuration copy's networkProfiles
configuration_copy.setNetworkProfiles_(profile_set)
# Then update the network interface configuration
result = interfaces[i].commitConfiguration_authorization_error_(configuration_copy, None, None)


The problem I was having was with our users computers connecting to our guest wifi over our employee wifi, so I also run a script to disable/re-enable wifi so they actually connect to the preferred network right away:



networksetup -setairportpower en0 off
networksetup -setairportpower en0 on


I just add both of these scripts to a single policy and place it in Self Service.



Edit: Added link to source for pudquick's GitHub


@Anthonyr



That script worked perfectly on my 10.10 system. Thanks for sharing!



Edit For those interested I found the link to the script on pudquick's github.



https://gist.github.com/pudquick/fcbdd3924ee230592ab4



Aaron


@FritzsCorner
No problem! It completely solved our issue as well.
Let Forgor(/pudquick) know you owe him a beer in the MacAdmins Slack channel if you're a member.


This is a lifesaver!!!



Thank you - been looking for a way to do this for some time. I added a sleep 5 to the wireless enable/disable as was not always seeing the power get switched back on to the wireless card after it was switched off. Now it works on all our hardware. Many thanks.


THANK YOU!!!!!!!!!!!!!!!!! I WILL BE IMPLEMENTING TOMORROW!!! I have users that have been driving me mad with OS X's Wifi lists. I can't wait to run it.


12/22/15 at 12:54 PM by Anthonyr
Your solution also worked for me!
Thanks!


This thing worked like a champ on 10.12. Thank you so much!!


Legend! Script works perfectly! Thank you!


This is great! Exactly what I needed! Thanks!


I tweaked pudquick's script so that you just have to edit the array of preferred SSIDs rather than modify the code if you have more than 3 in your environment:



#!/usr/bin/python

# As written, this requires the following:
# - OS X 10.6+ (has been reported working through 10.12.3)
# - python 2.6 or 2.7 (for collections.namedtuple usage, should be fine as default python in 10.6 is 2.6)
# - pyObjC (as such, recommended to be used with native OS X python install)

# Run with root

import objc, ctypes.util, os.path, collections
from Foundation import NSOrderedSet

# List of preferred SSIDs in priority order - edit/add/delete as needed
PreferredSSIDs = ["SSID_1", "SSID_2", "SSID_3"]

def load_objc_framework(framework_name):
# Utility function that loads a Framework bundle and creates a namedtuple where the attributes are the loaded classes from the Framework bundle
loaded_classes = dict()
framework_bundle = objc.loadBundle(framework_name, bundle_path=os.path.dirname(ctypes.util.find_library(framework_name)), module_globals=loaded_classes)
return collections.namedtuple('AttributedFramework', loaded_classes.keys())(**loaded_classes)

# Load the CoreWLAN.framework (10.6+)
CoreWLAN = load_objc_framework('CoreWLAN')

# Load all available wifi interfaces
interfaces = dict()
for i in CoreWLAN.CWInterface.interfaceNames():
interfaces[i] = CoreWLAN.CWInterface.interfaceWithName_(i)

# Repeat the configuration with every wifi interface
for i in interfaces.keys():
# Grab a mutable copy of this interface's configuration
configuration_copy = CoreWLAN.CWMutableConfiguration.alloc().initWithConfiguration_(interfaces[i].configuration())
# Find all the preferred/remembered network profiles
profiles = list(configuration_copy.networkProfiles())
# Grab all the SSIDs, in order
SSIDs = [x.ssid() for x in profiles]
# Loop through PreferredSSIDs list in reverse order sorting each entry to the front of profiles array so it
# ends up sorted with PreferredSSIDs as the first items.
# Order is preserved for other SSIDs, example where PreferredSSIDs is [ssid3, ssid4]:
# Original: [ssid1, ssid2, ssid3, ssid4]
# New order: [ssid3, ssid4, ssid1, ssid2]
for aSSID in reversed(PreferredSSIDs):
profiles.sort(key=lambda x: x.ssid() == aSSID, reverse=True)
# Now we have to update the mutable configuration
# First convert it back to a NSOrderedSet
profile_set = NSOrderedSet.orderedSetWithArray_(profiles)
# Then set/overwrite the configuration copy's networkProfiles
configuration_copy.setNetworkProfiles_(profile_set)
# Then update the network interface configuration
result = interfaces[i].commitConfiguration_authorization_error_(configuration_copy, None, None)

This script it great :)



I'd like to be able to set the SSID in JSS. I know that this can be done by using $4, $5, $6 etc but what else is required in the script to get this to work?



# List of preferred SSIDs in priority order - edit/add/delete as needed
PreferredSSIDs = ["$4", "$5", "$6"]

@MatG Sorry, didn't notice your question earlier. The $4, $5, and $6 parameters are directly accessible as variables in a shell bash script, but in Python you access them by using sys.argv[parameter#]. See this post for more info: Adding a Python Script to JSS


This is great! Just tested this and confirmed on a 10.12.5 machine that our school's Wi-Fi network successfully is moved to the top while the rest of the Wi-Fi network order remains the same.


Hi all,



I'm getting this error when its ran on some machines, but not all:



[STEP 1 of 4]
Executing Policy Fix Wi-Fi network order
[STEP 2 of 4]
Running script Set_Wifi_Order.sh...
Script exit code: 1
Script result: Traceback (most recent call last):
File "/Library/Application Support/JAMF/tmp/Set_Wifi_Order.sh", line 35, in
profiles = list(configuration_copy.networkProfiles())
TypeError: 'NoneType' object is not iterable
Error running script: return code was 1.
[STEP 3 of 4]
[STEP 4 of 4]


Any ideas? I only have one SSID listed:



#!/usr/bin/python

# As written, this requires the following:
# - OS X 10.6+ (has been reported working through 10.12.3)
# - python 2.6 or 2.7 (for collections.namedtuple usage, should be fine as default python in 10.6 is 2.6)
# - pyObjC (as such, recommended to be used with native OS X python install)

# Run with root

import objc, ctypes.util, os.path, collections
from Foundation import NSOrderedSet

# List of preferred SSIDs in priority order - edit/add/delete as needed
PreferredSSIDs = ["ILAnetwork"]

def load_objc_framework(framework_name):
# Utility function that loads a Framework bundle and creates a namedtuple where the attributes are the loaded classes from the Framework bundle
loaded_classes = dict()
framework_bundle = objc.loadBundle(framework_name, bundle_path=os.path.dirname(ctypes.util.find_library(framework_name)), module_globals=loaded_classes)
return collections.namedtuple('AttributedFramework', loaded_classes.keys())(**loaded_classes)

# Load the CoreWLAN.framework (10.6+)
CoreWLAN = load_objc_framework('CoreWLAN')

# Load all available wifi interfaces
interfaces = dict()
for i in CoreWLAN.CWInterface.interfaceNames():
interfaces[i] = CoreWLAN.CWInterface.interfaceWithName_(i)

# Repeat the configuration with every wifi interface
for i in interfaces.keys():
# Grab a mutable copy of this interface's configuration
configuration_copy = CoreWLAN.CWMutableConfiguration.alloc().initWithConfiguration_(interfaces[i].configuration())
# Find all the preferred/remembered network profiles
profiles = list(configuration_copy.networkProfiles())
# Grab all the SSIDs, in order
SSIDs = [x.ssid() for x in profiles]
# Loop through PreferredSSIDs list in reverse order sorting each entry to the front of profiles array so it
# ends up sorted with PreferredSSIDs as the first items.
# Order is preserved for other SSIDs, example where PreferredSSIDs is [ssid3, ssid4]:
# Original: [ssid1, ssid2, ssid3, ssid4]
# New order: [ssid3, ssid4, ssid1, ssid2]
for aSSID in reversed(PreferredSSIDs):
profiles.sort(key=lambda x: x.ssid() == aSSID, reverse=True)
# Now we have to update the mutable configuration
# First convert it back to a NSOrderedSet
profile_set = NSOrderedSet.orderedSetWithArray_(profiles)
# Then set/overwrite the configuration copy's networkProfiles
configuration_copy.setNetworkProfiles_(profile_set)
# Then update the network interface configuration
result = interfaces[i].commitConfiguration_authorization_error_(configuration_copy, None, None)

@rqomsiya Sounds to me like those computers either don't have the SSID (profile) configured or don't have a WIFI interface.


sdagley's version of the script works perfectly on OSX 11.x


FYI: In Big Sur, Apple moved the known networks from /Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist to /Library/Preferences/com.apple.wifi.known-networks.plist file.


Anyone still using a script to manage order of preferred wireless networks in MacOS 11 or above?  If so is it working?


#!/bin/bash
##############################################################
# Script : Orders Mac Wi-Fi
##############################################################

# To order we remove and replace. Replace is always top of the list
SSID=$4
Security_Type=$5

networksetup -removepreferredwirelessnetwork en0 "$SSID"
networksetup -addpreferredwirelessnetworkatindex en0 "$SSID" 0 $Security_Type

We use this script and set the S4 & S5 values in the Script Parameters in the Script Payload of the policy.
The script removes and adds and when adding the network goes to the top of the list, even if you are connected to the Wi-Fi network being removed and added we see no loss of connection.


#!/bin/bash
##############################################################
# Script : Orders Mac Wi-Fi
##############################################################

# To order we remove and replace. Replace is always top of the list
SSID=$4
Security_Type=$5

networksetup -removepreferredwirelessnetwork en0 "$SSID"
networksetup -addpreferredwirelessnetworkatindex en0 "$SSID" 0 $Security_Type

We use this script and set the S4 & S5 values in the Script Parameters in the Script Payload of the policy.
The script removes and adds and when adding the network goes to the top of the list, even if you are connected to the Wi-Fi network being removed and added we see no loss of connection.


@MatG That will break any 802.1x configuration that was in effect for the SSID.


@MatG That will break any 802.1x configuration that was in effect for the SSID.


@sdagley We have it in place on our Monterey devices and its not causing any issues and was thoroughly tested with numerous devices. We are not getting any reports of loss of connection.

 


Reply