Finally moving away from OSX server and network homes

auburnmdm
New Contributor

We are in the process of moving away from OSX server and network homes. I have the os profiles mostly set for testing and now I am on to share. Our needs are as followed but I seem to be at a dead end with this.The machines are bound to AD and when logged in the root of the share drive is mounted:

1) I need the mounted share to be presented to the end user with their share, and not the entire share (even though presently they do not have permission to any other folders in the share but their own).From my understanding (which could be wrong) is that this is how Ad handles this by default.

2) I want my users Desktop, Documents, and Download folders redirected or symlinked to their smb share.

Any guidance or insights into this would be greatly appriciated

Thanks!

9 REPLIES 9

TreviñoL
Contributor

What is Apple Enterprise Connect?
Enterprise Connect is an application that enhances Active Directory integration for OS X systems.
It performs three basic functions:

Kerberos
Enterprise Connect acquires a Kerberos Ticket Granting Ticket (TGT) when it detects your
organization’s network and automatically refreshes it as needed. A Kerberos TGT is useful for
organizations that use Kerberos authentication for resources like web sites or network share
points.

Account Management
Enterprise Connect uses Notification Center to notify a user when their Active Directory
password is nearing expiration. To change their password, a user simply clicks the notification to
change their Active Directory password within Enterprise Connect.

Network Share Management
Enterprise Connect can mount an Active Directory network home directory as well as SMB/AFP
shares defined by the user or administrator. Additionally, if these shares are disconnected, the
shares will be automatically re-mounted when the organizational network comes back online.

Requirements
Enterprise Connect requires the following:
■ OS X Mavericks (10.9) or later
■ An Active Directory domain
■ Connectivity to the network hosting the Active Directory domain
■ An Active Directory account
Enterprise Connect does not require being bound to Active Directory or that the user log into
their Mac with an Active Directory account. Additionally, no server side components are needed.

How does it work?
Enterprise Connect is simply an application. To set it up, the user launches it, enters their Active
Directory credentials and the hostname of their domain, then clicks Sign In. Once this is
complete, Enterprise Connect resides in the user’s menu bar.
When the organizational network is is detected, Enterprise Connect refreshes the user’s Kerberos
TGT, checks their password expiration status and re-mounts any shares that have become
disconnected. From this point onward, the user only needs to interact with Enterprise Connect if
they need to change their Active Directory password or configure the application

Contact Apple Professional Service about Enterprise Connect

auburnmdm
New Contributor

I appreciate the response

I must say this looks interesting, and i have reached out to our apple sales rep, but we are cutting out all OSX hardware on the backend. OSX is only going to be used for end points. If i am not making sense here please let me know

Thanks again!

Emmert
Valued Contributor

This sounds great! Forgive me if this is a dumb question, but how do you set up the client logins? Just have them all auto-login as a local guest-style account?

mm2270
Legendary Contributor III

Enterprise Connect doesn't require any Apple hardware on the backend. Just OS X clients, which you obviously already have. It's software that can be deployed to all your Mac clients that facilitates communication to AD, helps with share mounts, password expiration notices and so on. I would say it's something you should at least look into. It may be something that can help. It's not cheap, but it's a one time cost to purchase. The amount is primarily for professional on site services from Apple to help get it set up as I understand it.

TreviñoL
Contributor

We are thinking of creating local Standard accounts on our Mac's and no longer have them bind to AD. EC will take care of letting staff update their AD password within EC and also we can push configuration profile to EC using JAMF if users need new network shares mounted.

TreviñoL
Contributor

recommend everyone to take a look at Accellion Kiteworks as well. The latest release lets you create a secure container on workstation which is going to allow us to stop mouting network shares. They also release a new iOS app that will let your use with MS Office Apps to open internal network resources within the MS App.

The Accellion product is also a MDM, but only for the Kiteworks app and does not require the devices to be connected to VPN to access your internal resources.

Emmert
Valued Contributor

Apparently, this is in a pilot phase for education right now and there will be more information released at the end of the month.

auburnmdm
New Contributor

Our rep invited me to a webinar this week, but also said that for k-12 it hasn't been used. And he felt it to be overkill for what we are looking to do. He suggested ABE on the share to help hide all but their "homefolder" on the share. I would be fine with that just as long as we can create that symlink, or at least synced the contents of the folders back to their share without the student needing to interact with it. I realize if it is a moderately sized file and potentially 500+ kids are syncing it might be a long logout process so this would obviously be a concern. Using crashplan pro is not a options due to cost and bandwidth restraints. Given that this is a shared environment the student experience is the sticking point with the redirect issue. Teacher machine (until we can refresh to macbook air) should not be that big of an issue as they all have dedicated machines in their rooms and they can be trained to look at the share drive when not at their machine.

Any thoughts are greatly appreciated and welcomed

Swift
New Contributor II

I have been looking at moving from "network homes" to "forced local homes" myself at some point, and when I do, I also require certain folders to be redirected to their network equivalents. So I knocked this script together.

The script sets itself up as a LaunchAgent that runs when the Finder loads. It finds the user network home, and creates the links.
You should note that the script assumes that by the time you get to the Finder, the network home is mounted.

I hope it is of some use to you

If there is an easier way to do this, I apologise in advance.

#!/bin/bash
# RedirForcedLocalHomes
# Create folder redirections for network accounts with forced local homes
# Version 1.0.1
# Mark J Swift
#
# To customise:
#   look for BEGIN CUSTOMISE in the script
#
# To test:
#   log in as a user, and run the script from a Terminal
#
# To install as a post-login LaunchAgent:
#   log in as an admin and type the following in a terminal:
#     sudo ThisScriptName
#
# Installs into /usr/local/yourdomain/scripts/ and /Library/LaunchAgents/

# -- Get some info about this script

# Get filename of this script
GLB_ThisScriptName="$(basename "${0}")"

# -- Get some info about the workstation

# Get full domain name
AD_DOMAINNAME_DNS=$(echo "show com.apple.opendirectoryd.ActiveDirectory" | scutil | grep "DomainNameDns" | cut -d":" -f 2- | sed "s|^[ ]*||;s|[ ]*$||")

# Initial error check
if test -z "${AD_DOMAINNAME_DNS}"
then
  echo >&2 "Workstation doesn't appear to be part of a domain"
  exit 0
fi

# -- Get some info about the user

# Get user name
GLB_UserName="$(whoami)"

# Get user ID
GLB_UserID="$(id -u ${GLB_UserName})"

# Check if user is a local account (returns "yes" or "no")
GLB_IsLocalAccount=$(dseditgroup -o checkmember -m "${GLB_UserName}" -n . localaccounts | cut -d" " -f1)

# -- Check if we are actually installing the script
if [ "${GLB_UserName}" = "root" ]
then

  # Build a name for the LaunchAgent Plist file
  GLB_LaunchAgentName="$(echo "${AD_DOMAINNAME_DNS}" | tr "." "
" | tail -r | tr "
" "." | sed "s|.$||")"".""${GLB_ThisScriptName}"

  # Path that holds this organisations custom installations
  GLB_CustomInstallPath="/usr/local/${AD_DOMAINNAME_DNS}"

  # Copy this script to this organisations script folder
  mkdir -p "${GLB_CustomInstallPath}/scripts"
  cp -f "${0}" "${GLB_CustomInstallPath}/scripts/"

  # Set permissions
  chown -R root:wheel "${GLB_CustomInstallPath}"
  chmod -R 755 "${GLB_CustomInstallPath}"

  # Create LaunchAgent plist
  cat << HEREDOC > "/Library/LaunchAgents/${GLB_LaunchAgentName}.plist"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>${GLB_LaunchAgentName}</string>
    <key>ProgramArguments</key>
    <array>
        <string>${GLB_CustomInstallPath}/scripts/${GLB_ThisScriptName}</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>LimitLoadToSessionType</key>
    <string>Aqua</string>
</dict>
</plist>
HEREDOC

  # Set permissions on LaunchAgent plist
  chown root:wheel "/library/LaunchAgents/${GLB_LaunchAgentName}.plist"
  chmod 644 "/library/LaunchAgents/${GLB_LaunchAgentName}.plist"

  echo >&2 "Installed LaunchAgent ${GLB_LaunchAgentName}.plist"
  echo >&2 "Now you need to restart the workstation..."
  exit 0
fi

# If we got here, we are running the script - not installing it

# -- Define some functions

f_UndoFolderRedir() # FileRedirectList TargetHomeDir - Undo any existing home folder redirections
{
  local LCL_TargetHomeDir
  local LCL_FileRedirectList
  local LCL_ReqdLinkEntry
  local LCL_ReqdLinkFile

  LCL_FileRedirectList="${1}"
  LCL_TargetHomeDir="${2}"

  if test -f "${LCL_FileRedirectList}"
  then
    # We may have symbolic links that need to be removed

    while read LCL_ReqdLinkEntry
    do
      # Sanitise the Entry
      LCL_ReqdLinkEntry=$(echo "/${LCL_ReqdLinkEntry}" | sed "s|/[/]*|/|g")

      LCL_ReqdLinkFile=$(echo "${LCL_ReqdLinkEntry}" | sed "s|/$||")
      if test -n "${LCL_ReqdLinkFile}"
      then
        if test -L "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}"
        then
          # There is a symbolic link where our file/folder should be

          if [ "${LCL_ReqdLinkEntry}" != "${LCL_ReqdLinkFile}" ]
          then
            # If the link was on a directory, re-create directory
            /bin/rm -f "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}"
            mkdir -p "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}"
          else
            /bin/rm -f "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}"
          fi
        fi
      fi

    done < "${LCL_FileRedirectList}"

  fi
}

f_DoFolderRedir() # FileRedirectList TargetHomeDir SourceHomeDir
{
  local LCL_TargetHomeDir
  local LCL_SourceHomeDir
  local LCL_FileRedirectList
  local LCL_BackupDir
  local LCL_ReqdLinkEntry
  local LCL_ReqdLinkFile
  local LCL_EnclosingDir
  local LCL_TargetIsLocal
  local LCL_StartEpoch

  LCL_FileRedirectList="${1}"
  LCL_TargetHomeDir="${2}"    # Where the link files will be created
  LCL_SourceHomeDir="${3}"    # Where the files/folders are that the links will be pointing to to

  # Take a note when this function started
  LCL_StartEpoch=$(date -u "+%s")

  if test -f "${LCL_FileRedirectList}"
  then

    # Decide whether the target is on the local drive
    if test -n "$(stat -f "%Sd" "${LCL_TargetHomeDir}" | grep "^disk")"
    then
      LCL_TargetIsLocal="yes"
    else
      LCL_TargetIsLocal="no"
    fi

    # Backup location for existing files/folders that might be replaced by a link
    LCL_BackupDir="${LCL_TargetHomeDir}/Backup/"$(date -r ${LCL_StartEpoch} '+%Y-%m-%d_%H-%M-%S')

    # Read entries from file
    while read LCL_ReqdLinkEntry
    do
      # Sanitise the Entry
      LCL_ReqdLinkEntry=$(echo "/${LCL_ReqdLinkEntry}" | sed "s|/[/]*|/|g")

      LCL_ReqdLinkFile=$(echo "${LCL_ReqdLinkEntry}" | sed "s|/$||")
      if test -n "${LCL_ReqdLinkFile}"
      then
        if test -e "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}"
        then
          if test -d "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}"
          then
            # If the existing folder at the target is local, remove any ACLs that might stop us doing our stuff
            if [ "${LCL_TargetIsLocal}" = "yes" ]
            then
              /bin/chmod -RN "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}"
            fi
            # Check if existing folder is empty - and backup if necessary
            if test -z "$(ls -A1 "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}" | grep -Ev "^.DS_Store$|^.localized$")"
            then
              /bin/rm -fR "${LCL_TargetHomeDir}/${LCL_ReqdLinkFile}"
            else
              # Make sure there is a target enclosing folder to backup the folder
              LCL_EnclosingDir="$(dirname "${LCL_BackupDir}${LCL_ReqdLinkFile}")"
              /bin/mkdir -p "${LCL_EnclosingDir}"
              # Move (back up) existing folder at target
              /bin/mv -f "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}" "${LCL_EnclosingDir}/"
            fi
          else
            # If the existing file at the target is local, remove any ACLs that might stop us doing our stuff
            if [ "${LCL_TargetIsLocal}" = "yes" ]
            then
              /bin/chmod -N "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}"
            fi
            # Make sure there is a target enclosing folder to backup the file
            LCL_EnclosingDir="$(dirname "${LCL_BackupDir}${LCL_ReqdLinkFile}")"
            /bin/mkdir -p "${LCL_EnclosingDir}"
            # Move (back up) existing file at target
            /bin/mv -f "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}" "${LCL_EnclosingDir}/"
          fi
        fi

        # Make sure there is a target enclosing folder to store the symbolic link
        LCL_EnclosingDir="$(dirname "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}")"
        /bin/mkdir -p "${LCL_EnclosingDir}"

        # Make sure we have a source folder or source file parent folder
        if [ "${LCL_ReqdLinkEntry}" != "${LCL_ReqdLinkFile}" ]
        then
          # We are linking to a directory - so make sure it exists
          /bin/mkdir -p "${LCL_SourceHomeDir}${LCL_ReqdLinkFile}"
        else
          # We are linking to a file - so make sure the parent folder exists
          LCL_EnclosingDir="$(dirname "${LCL_SourceHomeDir}${LCL_ReqdLinkFile}")"
          /bin/mkdir -p "${LCL_EnclosingDir}"
        fi

        # Create link - source file/folder -to- target link
        /bin/ln -sf "${LCL_SourceHomeDir}${LCL_ReqdLinkFile}" "${LCL_TargetHomeDir}${LCL_ReqdLinkFile}"
      fi
    done < "${LCL_FileRedirectList}"

  fi
}

# urldecode function - thanks to https://gist.github.com/cdown/1163649
f_urldecode() {
    # urldecode <string>

    local url_encoded="${1//+/ }"
    printf '%b' "${url_encoded//%/\x}"
}

# -- Get the user home properties

# Get the User Home directory
GLB_UserHomeDir=$(eval echo ~${GLB_UserName})

# Decide whether the user home is on the local drive
if test -n "$(stat -f "%Sd" "${GLB_UserHomeDir}" | grep "^disk")"
then
  GLB_IsLocalHome="yes"
else
  GLB_IsLocalHome="no"
fi

# Get the network defined home directory
if [ "${GLB_IsLocalAccount}" = "no" ]
then
  # - Network user -

  # Get UserHomeNetworkURI 
  # ie: smb://yourserver.com/staff/t/testuser
  # or  smb://yourserver.com/Data/Student%20Homes/Active_Q2/pal/teststudpal

  GLB_UserHomeNetworkURI=$(dscl 2>/dev/null localhost -read /Search/Users/${GLB_UserName} HomeDirectory | sed "s|HomeDirectory: ||;s|<[^>]*>||g;s|/$||;s|^[^:]*:||")
  if test -z "${GLB_UserHomeNetworkURI}"
  then
    GLB_UserHomeNetworkURI=$(dscl 2>/dev/null localhost -read /Search/Users/${GLB_UserName} OriginalHomeDirectory | sed "s|OriginalHomeDirectory: ||;s|<[^>]*>||g;s|/$||")

  fi  

  if test -n "${GLB_UserHomeNetworkURI}"
  then
    # The user home directory has been forced from the network to a local drive

    # Get full path to the network HomeDirectory 
    # ie: /Volumes/staff/t/testuser
    # or  /Volumes/Data/Student Homes/Active_Q2/pal/teststudpal
    while read LCL_MountEntry
    do
      LCL_MountPoint=$(echo ${LCL_MountEntry} | sed -E 's|(^.*) on (.*) ((.*))|2|' | grep -v '^/$')
      LCL_MountShare=$(echo ${LCL_MountEntry} | sed -E 's|(^.*) on (.*) ((.*))|1|' | sed 's|'${GLB_UserName}'@||')
      if test -n "$(echo "${GLB_UserHomeNetworkURI}" | sed "s|^[^:]*:||" | grep -E "^${LCL_MountShare}")"
      then
        GLB_UserHomeNetworkDir=$(f_urldecode "${LCL_MountPoint}$(echo ${GLB_UserHomeNetworkURI} | sed "s|^[^:]*:||;s|^"${LCL_MountShare}"||")")
        break
      fi
    done < <(mount | grep "//${GLB_UserName}@")

  fi
fi

# -- Now do some error checking

if ! test -e "${GLB_UserHomeDir}"
then
  echo >&2 "User home doesn't exist"
  exit 0
fi

if [ "${GLB_IsLocalHome}" = "no" ]
then
  echo >&2 "User home isn't on a local drive"
  exit 0
fi

# -- Now we can do the file/folder linking

# Move any existing link list
touch "${GLB_UserHomeDir}/.FolderRedir.txt"
mv -f "${GLB_UserHomeDir}/.FolderRedir.txt" "${GLB_UserHomeDir}/.FolderRedir-Old.txt"

# Create a new empty link list
touch "${GLB_UserHomeDir}/.FolderRedir.txt"

# If a network home exists, populate the empty link list
if test -n "${GLB_UserHomeNetworkDir}"
then

  # -- BEGIN CUSTOMISE --

  cat << HEREDOC >> "${GLB_UserHomeDir}/.FolderRedir.txt"
/Desktop/
/Documents/
/Library/Group Containers/
/Movies/
/Music/
/Pictures/
HEREDOC

  # -- END CUSTOMISE --

fi

# Find out which folders we need to check for links/unlinks
echo "$(cat "${GLB_UserHomeDir}/.FolderRedir.txt" ; cat "${GLB_UserHomeDir}/.FolderRedir-Old.txt" )" | sort -u > "${GLB_UserHomeDir}/.FolderRedir-Check.txt"

# Find out which folders we need to unlink (if we ever change the link list)
echo "$(cat "${GLB_UserHomeDir}/.FolderRedir.txt" ; cat "${GLB_UserHomeDir}/.FolderRedir-Check.txt" )" | sort | uniq -u > "${GLB_UserHomeDir}/.FolderRedir-Unlink.txt"

# Find out which folders we need to link
echo "$(cat "${GLB_UserHomeDir}/.FolderRedir-Old.txt" ; cat "${GLB_UserHomeDir}/.FolderRedir-Check.txt" )" | sort | uniq -u > "${GLB_UserHomeDir}/.FolderRedir-Link.txt"

# Link files
f_DoFolderRedir  "${GLB_UserHomeDir}/.FolderRedir-Link.txt" "${GLB_UserHomeDir}" "${GLB_UserHomeNetworkDir}"

# Unlink files
f_UndoFolderRedir "${GLB_UserHomeDir}/.FolderRedir-Unlink.txt" "${GLB_UserHomeDir}"

# Check if the Desktop was linked/unlinked
if test -n "$(printf "$(cat "${GLB_UserHomeDir}/.FolderRedir-Unlink.txt";cat "${GLB_UserHomeDir}/.FolderRedir-Link.txt")" | grep -E "^/Desktop/$")"
then
  # Kill the Finder (Refreshes Desktop)
  pkill -U ${GLB_UserID} Finder
fi

# Delete temporary files
rm -f "${GLB_UserHomeDir}/.FolderRedir-Old.txt"
rm -f "${GLB_UserHomeDir}/.FolderRedir-Check.txt"
rm -f "${GLB_UserHomeDir}/.FolderRedir-Unlink.txt"
rm -f "${GLB_UserHomeDir}/.FolderRedir-Link.txt"

# Delete the link file if it is empty
if ! test -s "${GLB_UserHomeDir}/.FolderRedir.txt"
then
  rm -f "${GLB_UserHomeDir}/.FolderRedir.txt"
fi

# ---

Redirecting local folders to point to network folders can be more of a chore than you may expect.

If you are not in control of your network shares, you can never be sure of how home directories will be mounted. You could be presented with the root of the share, or the root of the user home. This is uncertain until the user home is actually mounted.

Also, with "Force local home directory on startup disk" enabled, the user home is not actually mounted until after login.

For these reasons, the path to the network home is uncertain until immediately after login and shouldn't really be assumed to follow any sort of pattern.