Yet again with the Local MCX

Earlier this week, I outlined some changes to my Local MCX implementation. I moved all of my computer and computergroup records (that existed solely to hold MCX data) from the Default local DS store at /var/db/dslocal/nodes/Default/ to a newly created node at /var/db/dslocal/nodes/MCX/.

In order to make this new local node do anything useful, you have to add it to the authentication search path. In this post I used Directory Utility to perform this task. That’s great when you’re doing your initial testing, but not terribly useful when to need to roll this change out to hundreds of machines (or more!).

You need a way to mass-deploy this change. Fortunately, you have several options. The search path is stored in /Library/Preferences/DirectoryService/SearchNodeConfig.plist. You can configure that on one machine, then distribute it to all your machines via /usr/bin/scp, ARD, radmind, or if you bundle it into an Apple package, via Casper, LANrev, or any other package delivery mechanism.

Another option is to script the change. Apple provides some notes on manipulating the DirectoryService search path from the command line here. Specifically, you could append your new load to the search list like this:


dscl /Search -create / SearchPolicy CSPSearchPath
dscl /Search -append / CSPSearchPath /Local/MCX

You could probably get away with just appending the new /Local/MCX node to the end of the current list, but it feels wrong somehow. I wanted my MCX node to be after the default local nodes, but before any network nodes. Here’s a little more complicated script that accomplishes that result:


#!/bin/sh

# first make sure /Local/MCX node exists
if [ ! -d "/private/var/db/dslocal/nodes/MCX" ] ; then
        echo "Missing /Local/MCX node!"
        exit 0
fi

# now make sure /Local/MCX is in the search path, after /Local/Default /BSD/local
localMCXinSearchPath=`/usr/bin/dscl /Search read / CSPSearchPath | /usr/bin/grep "/Local/MCX"`
if [ "$localMCXinSearchPath" == "" ] ; then
    currentSearchPathContainsBSDlocal=`/usr/bin/dscl /Search read / CSPSearchPath | /usr/bin/grep "/BSD/local"`
    if [ "$currentSearchPathContainsBSDlocal" != "" ] ; then
        currentSearchPathBegin="/Local/Default /BSD/local"
        currentSearchPathEnd=`/usr/bin/dscl /Search read / CSPSearchPath | /usr/bin/cut -d" " -f4-`
    else
        currentSearchPathBegin="/Local/Default"
        currentSearchPathEnd=`/usr/bin/dscl /Search read / CSPSearchPath | /usr/bin/cut -d" " -f3-`
    fi
        /usr/bin/dscl /Search create / SearchPolicy CSPSearchPath
        /usr/bin/dscl /Search create / CSPSearchPath $currentSearchPathBegin /Local/MCX $currentSearchPathEnd
fi

What this script does:

  1. Checks to make sure the /Local/MCX node exists.
  2. Reads the current search path.
  3. If it doesn’t find “/Local/MCX” in the current search path, it parses the search path, splitting it into the default local nodes (usually “/Local/Default /BSD/local”, but can handle it if “BSD/local” is missing), and anything after that, which is usually a network node (or possibly nothing).
  4. Finally, the script writes out the new search path, inserting “/Local/MCX” between the default local node(s) and any pre-existing nodes past the default local nodes.

This script is safe to run multiple times, so you can implement it as a script that runs every startup, which would address the issue where a local admin might use Directory Utility to temporarily remove /Local/MCX from the search path. You’d be assured that this script would re-add it on the next restart.

Yet again with the Local MCX

23 thoughts on “Yet again with the Local MCX

  1. Tyler Riti says:

    Wouldn’t the following accomplish the same goal?

    dscl /Search -changei / CSPSearchPath 1 /Local/MCX

    In my testing, this appears to work whether or not other non-local nodes are already specified in the custom search path.

  2. It might work – I hadn’t tried it. Based on my reading of the dscl man page, I assumed -changei would _change_ the existing value at index 1, which might not be what you want. You seem to be implying that -changei _inserts_ a new value at index 1.

    I just tested it and it does appear to do what you describe, which does _not_ match the behavior described in the man page:

    changei
    Usage: changei path key index val

    Replaces the value at the given index in the list of values of the given key with
    the new value in the specified record. index is an integer value. An index of 1
    specifies the first value. An index greater than the number of values in the list
    will result in an error.

      1. Tyler Riti says:

        I wonder if it has something to do with /BSD/local being both the 1st node and read-only?

  3. Paul Hildahl says:

    Greg-

    Thank you for the sharing the idea of moving the local MCX information to its own node. However, the cuts in your example script with the whitespace as a delimiter do not work when the computer is bound to an Active Directory. /usr/bin/dscl /Search read / CSPSearchPath will return-
    /Local/Default
    /BSD/local
    /Active Directory/All Domains
    and the cuts will pick up the spaces in /Active Directory/All Domains

    1. Bummer, and another example why shell scripting is not suitable for anything other than the simplest tasks.

      The fix would be to use the -plist options to dscl so you can read and write in list format, which can then be parsed by better tools than /usr/bin/cut.

    2. carl says:

      Use awk (with a modified delimiter) instead of cut:

      – cut -d” ” -f4-

      + awk -F ” */” ‘{ for(i=4; i<=NF; ++i) print "/"$i}'

      1. nick says:

        ok, got around the awk problem by using egrep -iv “local|CSPSearchPath” instead, but I still seem to be running into problems with reinserting the Active Directory entry into the search path from the command line. The path before manipulations has /Active Directory/All domains in the path. if I include that in the command in any way (either from being cut from the dscl output or manually inserting that into the path) I get:
        attribute status: eDSNodeNotFound
        DS Error: -14008 (eDSNodeNotFound)
        Any ideas?

      2. carl says:

        I guess you have to use arrays instead of strings (currentSearchPathBegin, currentSearchPathEnd) for storing search paths (tested using #!/bin/bash).

        Just run the following code in Terminal.app.

        # set the Internal Field Separator of the Bash shell to newline

        IFS=$'\n'

        printf '%q\n' "$IFS"

        # example output of: dscl /Search read / CSPSearchPath

        searchpaths='CSPSearchPath: /Local/Default /BSD/local /Active Directory/All Domains /aaa bbb /ccc ddd/eee'

        # read search paths into an array
        # customize i=4 in awk "for"-loop if necessary
        # note the double space in awk -F " */"

        searchpaths_array=( $( echo "$searchpaths" | /usr/bin/awk -F " */" '{ for(i=4; i<=NF; ++i) print "/"$i}') )

        for ((i=0; i < ${#searchpaths_array[@]}; i+=1)); do
        echo "$i: ${searchpaths_array[i]}"
        done

        # prints
        0: /Active Directory/All Domains
        1: /aaa bbb
        2: /ccc ddd/eee

        # was: /usr/bin/dscl /Search create / CSPSearchPath $currentSearchPathBegin /Local/MCX $currentSearchPathEnd

        currentSearchPathBegin=(
        /Local/Default
        /BSD/local
        )

        # remove "echo" to run command
        # note: create / CSPSearchPath ... '/Active Directory/All Domains' '/aaa bbb' '/ccc ddd/eee'

        set -xv

        echo /usr/bin/dscl /Search create / CSPSearchPath "${currentSearchPathBegin[@]}" /Local/MCX "${searchpaths_array[@]}"

      3. carl says:

        The double space in the awk command above could also be implement in the following way:

        echo 'CSPSearchPath: /Local/Default /BSD/local /Active Directory/All Domains /aaa bbb /ccc ddd/eee' |
        /usr/bin/awk -F "[[:space:]][[:space:]]*/" '{ for(i=4; i<=NF; ++i) print "/"$i}' |
        nl

        # prints
        1 /Active Directory/All Domains
        2 /aaa bbb
        3 /ccc ddd/eee

      4. Tommy says:

        To read the individual nodes of the search path into an array I would recommend using the “-plist” option to the dscl command to get the ouput in XML plist format.

        Note: The following methods assume that the individual nodes of the search path do not contain embedded newline characters (\n).

        # using sed to parse the XML plist output
        dscl -plist /Search read / CSPSearchPath |
        sed -n '/^[[:space:]]*/s/^\([[:space:]]*\)\(.*\)\([[:space:]]*\)$/\2/p'

        # using xmlstarlet to parse the XML plist output
        # see also the xml-coreutils package for further XML tools
        dscl -plist /Search read / CSPSearchPath |
        xmlstarlet sel -T -t -m "//string" -v '.' -n

      5. Tommy says:

        # using sed to parse the XML plist output

        dscl -plist /Search read / CSPSearchPath |
        sed -n '/^[[:space:]]*<string>\(.*\)<\/string>[[:space:]]*$/s//\1/p'

  4. Bryan says:

    There is a problem I’ve been working with for several months, and I’m wondering if you think moving the local record to a new node would help resolve them.

    I deploy MCX to the local node routinely on managed computers. One part of this is restricting applications for a standard user (by folder). The problem is that it seems whenever there is a network change (even DHCP address change), the MCX is re-composited. This seems to result sometimes (too frequently) in all applications being disallowed.

    The current fix for this requires deleting all MCX, reimporting settings, and rebooting – an unsustainable manual process. 10.7 does seem to improve the situation. An “not authorized” dialog will pop up, but after dismissal everything continues to work. Unfortunately this also happens on information kiosks that lack user interaction, so it is still unacceptable.

    Will moving the MCX to a new node as you suggest here possibly fix this problem? It seems to be a caching/recompositing problem, and while I’ve looked at your posts several times over several months, I just noticed the implications for caching.

    1. I don’t know if moving MCX to a non-default node will fix your issue, but if the issue is related to MCX caching, it’s certainly something you should try. It certainly can’t hurt!

  5. Just wanted to confirm, as it’s been a while since we used Local MCX…doesn’t it conflict with any other MCX management (WGM, Casper, etc.) you might have in the environment?

    Don

  6. Of course it would interact with other MCX management. I would not recommend mixing-and-matching unless you really know what you are doing. Pick one place for your MCX (Network DS, Local, or Casper) and use it exclusively.

Comments are closed.