Home Forums Software InstaDMG User Filler Postflight Script

Viewing 12 posts - 1 through 12 (of 12 total)
  • Author
    Posts
  • #380695
    Ben Urban
    Participant

    I’ve been told I would instantly become very popular if I posted this script here. I wish to put that theory to the test. Enjoy.

    This script should be able to handle installing to any volume (and it will figure out and fill the users already present on the target volume, if applicable). It can also compensate for unusual home directory locations (/Volumes/Macintosh HD/var/root comes to mind, where /Volumes/Macintosh HD/var points to /private/var; it can handle this situation correctly, though it skips the root user because its UID is below 500).

    [code]
    #!/usr/bin/python

    # Written by Ben Urban

    “””
    This script clones anything it finds in /private/tmp/fillUsers to all existing
    non-hidden users, as well as all user template localizations. It is meant to be
    used as a postflight script for packages that need this functionality, mimicking
    the functionality available in JAMF’s Casper Suite for DMG packages.

    It requires Python 2.5 or higher (thus, Mac OS X 10.5 or later).
    “””

    import os, subprocess, sys

    fsroot = sys.argv[3] if len(sys.argv) > 3 else ‘/’
    src = os.path.join(fsroot, ‘private/tmp/fillUsers’)

    defaultNode = os.path.join(fsroot, ‘private/var/db/dslocal/nodes/Default’)

    def dscl(cmd, path, *args):
    # Not sure why -f can’t be used directly with any commands…
    path = os.path.join(‘/Local/Target/’, path.lstrip(os.path.sep))
    cmd = ‘ ‘.join((cmd, path) + args)
    result = subprocess.Popen(
    [‘/usr/bin/dscl’, ‘-q’, ‘-f’, defaultNode],
    stdin = subprocess.PIPE,
    stdout = subprocess.PIPE,
    ).communicate(cmd)[0]
    result = result.rstrip(‘\n’).split(‘\n’)
    del result[0] # ‘Entering interactive mode… (Type “help” for commands)’
    return result

    def dsclread(path, prop):
    result = dscl(‘read’, path, prop)
    result = ”.join(result).split(‘%s: ‘ % (prop, ), 1)[1]
    return result

    def makeAbsPath(path):
    path = os.path.normpath(path).lstrip(os.path.sep).split(os.path.sep)
    newpath = []
    for part in path:
    subpath = os.path.join(*([fsroot] + newpath + [part]))
    if os.path.islink(subpath):
    link = os.readlink(subpath)
    if link.startswith(os.path.sep):
    newpath = [os.path.normpath(link.lstrip(os.path.sep))]
    else:
    newpath.append(os.path.normpath(link))
    else:
    newpath.append(part)
    return os.path.normpath(os.path.join(fsroot, *newpath))

    users = []
    for line in dscl(‘ls’, ‘/Users’, ‘UniqueID’):
    user, uid = line.split(‘ ‘, 1)[0], line.rsplit(‘ ‘, 1)[1]
    if int(uid) > 500:
    gid = dsclread(os.path.join(‘/Users’, user), ‘PrimaryGroupID’)
    name = dsclread(os.path.join(‘/Users’, user), ‘RealName’)
    home = dsclread(os.path.join(‘/Users’, user), ‘NFSHomeDirectory’)
    users.append((uid, gid, name, home))

    for lang in os.listdir(os.path.join(fsroot, ‘System/Library/User Template’)):
    users.append((
    ‘0’,
    ‘0’,
    ‘template (%s)’ % (lang, ), # This is cheating, but it works
    os.path.join(‘/System/Library/User Template/’, lang),
    ))

    for uid, gid, name, home in users:
    home = makeAbsPath(home)
    print “Filling %s for user %s” % (home, name)
    subprocess.check_call([‘/usr/sbin/chown’, ‘-R’, uid + ‘:’ + gid, src])
    subprocess.check_call([‘/usr/bin/ditto’, src, home])

    print “Cleaning up %s” % (src, )
    subprocess.check_call([‘/bin/rm’, ‘-rf’, src])
    [/code]

    #380699
    dead2sin
    Participant

    Very Nice! Its always a pain to put stuff into existing user’s folders, but that looks like it’ll do the trick 🙂

    Nate

    #380746
    Chris George
    Participant

    So, if I understand this correctly… say you want something to go into all users /LIbrary/Application Support/blah folder – you create /private/tmp/fillUsers/Library/Application Support/blah/, put everything into that folder, then run this as a postflight?

    #380747
    Ben Urban
    Participant

    Yup!

    I was working on a new version of this that can fill user home folders the next time they log in (using a LaunchAgent), but I haven’t had time to work on it recently. (It’s actually a lot more complicated than you would think to do this – you’ll see why when you see the new version of the script.)

    #380748
    Rusty Myers
    Participant

    Very nice! Thanks for sharing. Do you have your scripts in a version control system we can follow? Githib? Thanks!

    #380749
    Ben Urban
    Participant

    I’m afraid I don’t. I should start using one sometime soon…

    #380750
    Ben Urban
    Participant

    I took another look at the script, and I think I got it working, but I am not in a position to reasonably test it. Anyone care to try?

    Note that currently all it does is output what it would do; it doesn’t actually do anything. You’ll have to edit out the extra return statements to make it actually do stuff.

    I also went nuts with refactoring, partly to allow me to nullify the actual effects in favor of print statements.

    [code]
    #!/usr/bin/python

    # Written by Ben Urban

    “””
    This script clones anything it finds in /tmp/fillUsers to all existing
    non-hidden users, as well as all user template localizations. It also sets up a
    LaunchAgent to automatically extract the data for all users that don’t already
    have it. It is meant to be used as a postflight script for packages that need
    this functionality, mimicking and surpassing the functionality available in
    JAMF’s Casper Suite for DMG packages.

    It requires Python 2.5 or higher (thus, Mac OS X 10.5 or later).
    “””

    import os, subprocess

    srcDir = ‘/tmp/fillUsers’
    defaultNode = ‘/var/db/dslocal/nodes/Default’
    userTemplateRoot = ‘/System/Library/User Template’
    autofillDir = ‘/Library/Autofill’
    dataDir = os.path.join(autofillDir, ‘Data’)
    launchAgentsDir = ‘/Library/LaunchAgents’
    autofillReceiptsDir = ‘Library/Autofill/Receipts’
    autofiller_sh = ”’#!/bin/sh

    for f in “%(dataDir)s”/*
    do
    if ! [ -f “${HOME}/%(autofillReceiptsDir)s/${f%%%%.*}” ]
    then
    /usr/bin/tar -xpf “${f}” -C “${HOME}” –strip-components 1
    fi
    done
    ”’
    autofiller_plist = ”’

    Label
    autofiller
    ProgramArguments

    %(scriptPath)s

    RunAtLoad
    ”’

    def writeFile(path, contents):
    prepDirTree(os.path.dirname(path))
    print ‘%s:’ % dest(path)
    print ‘\t’ + contents.replace(‘\n’, ‘\n\t’)
    return
    f = open(dest(path), ‘w’)
    f.write(contents)
    f.close()

    def prepDirTree(path, mode = 0777):
    print ‘os.mkdirs(%s, 0%o)’ % (dest(path), mode)
    return
    os.makedirs(dest(path), mode)

    def tarCreate(archive, srcFolder):
    print [
    ‘/usr/bin/tar’, ‘-cj’,
    ‘-f’, dest(archive),
    ‘-C’, dest(os.path.dirname(srcFolder)),
    os.path.basename(srcFolder),
    ]
    return
    subprocess.check_call([
    ‘/usr/bin/tar’, ‘-cj’,
    ‘-f’, dest(archive),
    ‘-C’, dest(os.path.dirname(srcFolder)),
    os.path.basename(srcFolder),
    ])

    def tarExtract(archive, dstFolder, uid, gid):
    print [
    ‘/usr/bin/tar’, ‘-x’,
    ‘-f’, dest(archive),
    ‘-C’, dest(dstFolder),
    ‘–strip-components’, ‘1’,
    ], (uid, gid)
    return
    subprocess.check_call(
    [
    ‘/usr/bin/tar’, ‘-x’,
    ‘-f’, dest(archive),
    ‘-C’, dest(dstFolder),
    ‘–strip-components’, ‘1’,
    ],
    preexec_fn = lambda: (os.setuid(uid), os.setgid(gid)),
    )

    def chmod(item, mode):
    print ‘chmod(%s, 0%o)’ % (dest(item), mode)
    return
    os.chmod(dest(item), mode)

    def chown_R(item, uid, gid):
    print [‘/usr/sbin/chown’, ‘-R’, ‘%s:%s’ % (uid, gid), dest(item)]
    return
    subprocess.check_call([‘/usr/sbin/chown’, ‘-R’, ‘%s:%s’ % (uid, gid), dest(item)])

    def rm_rf(item):
    print [‘/bin/rm’, ‘-rf’, dest(item)]
    return
    subprocess.check_call([‘/bin/rm’, ‘-rf’, dest(item)])

    def dest(path):
    “””
    Convert an absolute path into an absolute path within the mountpoint
    specified by destroot. Symlinks in the path are followed properly.

    If you pass a recursive link to this function, enjoy your infinite loop!
    “””
    path = os.path.normpath(path)
    if path.startswith(destroot):
    path = path.split(destroot, 1)[1]
    path = path.lstrip(os.path.sep).split(os.path.sep)
    while True:
    newpath = []
    noLinks = True
    for part in path:
    subpath = os.path.join(*([destroot] + newpath + [part]))
    if os.path.islink(subpath):
    noLinks = False
    link = os.readlink(subpath)
    if link.startswith(os.path.sep):
    newpath = [os.path.normpath(link.lstrip(os.path.sep))]
    else:
    newpath.append(os.path.normpath(link))
    else:
    newpath.append(part)
    if noLinks:
    break
    path = os.path.join(*newpath).lstrip(os.path.sep).split(os.path.sep)
    return os.path.normpath(os.path.join(destroot, *newpath))

    def dscl(cmd, path, *args):
    # Not sure why -f can’t be used directly with any commands…
    path = os.path.join(‘/Local/Target/’, path.lstrip(os.path.sep))
    cmd = ‘ ‘.join((cmd, path) + args)
    result = subprocess.Popen(
    [‘/usr/bin/dscl’, ‘-q’, ‘-f’, defaultNode],
    stdin = subprocess.PIPE,
    stdout = subprocess.PIPE,
    ).communicate(cmd)[0]
    result = result.rstrip(‘\n’).split(‘\n’)
    del result[0] # ‘Entering interactive mode… (Type “help” for commands)’
    return result

    def dsclread(path, prop):
    result = dscl(‘read’, path, prop)
    result = ”.join(result).split(‘%s: ‘ % (prop, ), 1)[1]
    return result

    def collectReachableHomeDirs(uidFilter = lambda uid: uid > 500):
    for line in dscl(‘ls’, ‘/Users’, ‘UniqueID’):
    user, uid = line.split(‘ ‘, 1)[0], int(line.rsplit(‘ ‘, 1)[1])
    if uidFilter(uid):
    gid = int(dsclread(os.path.join(‘/Users’, user), ‘PrimaryGroupID’))
    name = dsclread(os.path.join(‘/Users’, user), ‘RealName’)
    home = dest(dsclread(os.path.join(‘/Users’, user), ‘NFSHomeDirectory’))
    if os.path.exists(home):
    yield (int(uid), int(gid), name, home)

    def collectUserTemplateDirs(languages = None): # None actually means all
    for lang in os.listdir(dest(userTemplateRoot)):
    if languages is None or lang in languages:
    yield (
    0,
    0,
    ‘template (%s)’ % (lang, ), # This is cheating, but it works
    os.path.join(userTemplateRoot, lang),
    )

    def setupAutofiller(launchAgentsDir, autofillDir, autofillReceiptsDir, dataDir):
    print “Setting up LaunchAgent for deferred fill”
    scriptPath = os.path.join(autofillDir, ‘autofiller.sh’)
    writeFile(scriptPath, autofiller_sh % locals())
    chmod(scriptPath, 0755)
    writeFile(os.path.join(launchAgentsDir, ‘autofiller.plist’), autofiller_plist % locals())

    def makeArchive(srcDir, archive):
    print “Archiving autofill data”
    chown_R(srcDir, 0, 0)
    prepDirTree(dataDir)
    tarCreate(archive, srcDir)
    return archive

    def cleanUp(srcDir):
    print “Cleaning up %s” % (dest(srcDir), )
    rm_rf(srcDir)

    def setupDeferredFill(srcDir, pkgid):
    setupAutofiller(launchAgentsDir, autofillDir, autofillReceiptsDir, dataDir)
    writeFile(os.path.join(srcDir, autofillReceiptsDir, pkgid), ”) # Create empty file
    archive = makeArchive(srcDir, os.path.join(dataDir, pkgid + ‘.tar.bz2’))
    cleanUp(srcDir)
    return archive

    def fillDirs(src, dirs):
    for uid, gid, name, home in dirs:
    print “Filling %s for user %s” % (dest(home), name)
    tarExtract(src, home, uid, gid)

    def getPkgID():
    import time, uuid
    return time.strftime(‘%Y-%m-%d %H:%M:%S ‘) + str(uuid.uuid1())

    def main(dest):
    global destroot
    destroot = dest

    pkgid = getPkgID()
    src = setupDeferredFill(srcDir, pkgid)

    dirs = []
    dirs += collectReachableHomeDirs()
    dirs += collectUserTemplateDirs()

    fillDirs(src, dirs)

    if __name__ == ‘__main__’:
    import sys
    main(sys.argv[3] if len(sys.argv) > 3 else ‘/’)
    [/code]

    #380751
    Ben Urban
    Participant

    Here’s a version you can use to test it:

    [code]
    #!/usr/bin/python

    # Written by Ben Urban

    from __future__ import with_statement # in case we’re in Python 2.5

    “””
    This script clones anything it finds in /tmp/fillUsers to all existing
    non-hidden users, as well as all user template localizations. It also sets up a
    LaunchAgent to automatically extract the data for all users that don’t already
    have it. It is meant to be used as a postflight script for packages that need
    this functionality, mimicking and surpassing the functionality available in
    JAMF’s Casper Suite for DMG packages.

    It requires Python 2.5 or higher (thus, Mac OS X 10.5 or later).
    “””

    import os, subprocess

    srcDir = ‘/tmp/fillUsers’
    defaultNode = ‘/var/db/dslocal/nodes/Default’
    userTemplateRoot = ‘/System/Library/User Template’
    autofillDir = ‘/Library/Autofill’
    dataDir = os.path.join(autofillDir, ‘Data’)
    launchAgentsDir = ‘/Library/LaunchAgents’
    autofillReceiptsDir = ‘Library/Autofill/Receipts’
    autofiller_sh = ”’#!/bin/sh

    for f in “%(dataDir)s”/*
    do
    if ! [ -f “${HOME}/%(autofillReceiptsDir)s/${f%%%%.*}” ]
    then
    /usr/bin/tar -xpf “${f}” -C “${HOME}” –strip-components 1
    fi
    done
    ”’
    autofiller_plist = ”’

    Label
    autofiller
    ProgramArguments

    %(scriptPath)s

    RunAtLoad
    ”’

    def writeFile(path, contents):
    path = dest(path)
    os.makedirs(os.path.dirname(path))
    with open(path, ‘w’) as f:
    f.write(contents)

    def tarCreate(archive, srcFolder):
    os.makedirs(dest(os.path.dirname(archive)))
    subprocess.check_call([
    ‘/usr/bin/tar’, ‘-cj’,
    ‘-f’, dest(archive),
    ‘-C’, dest(os.path.dirname(srcFolder)),
    os.path.basename(srcFolder),
    ])

    def tarExtract(archive, dstFolder, uid, gid):
    subprocess.check_call(
    [
    ‘/usr/bin/tar’, ‘-x’,
    ‘-f’, dest(archive),
    ‘-C’, dest(dstFolder),
    ‘–strip-components’, ‘1’,
    ],
    preexec_fn = lambda: (os.setuid(uid), os.setgid(gid)),
    )

    def dest(path):
    “””
    Convert an absolute path into an absolute path within the mountpoint
    specified by destroot. Symlinks in the path are followed properly.

    If you pass a recursive link to this function, enjoy your infinite loop!
    “””
    path = os.path.normpath(path)
    if path.startswith(destroot):
    path = path.split(destroot, 1)[1]
    path = path.lstrip(os.path.sep).split(os.path.sep)
    while True:
    newpath = []
    noLinks = True
    for part in path:
    subpath = os.path.join(*([destroot] + newpath + [part]))
    if os.path.islink(subpath):
    noLinks = False
    link = os.readlink(subpath)
    if link.startswith(os.path.sep):
    newpath = [os.path.normpath(link.lstrip(os.path.sep))]
    else:
    newpath.append(os.path.normpath(link))
    else:
    newpath.append(part)
    if noLinks:
    break
    path = os.path.join(*newpath).lstrip(os.path.sep).split(os.path.sep)
    return os.path.normpath(os.path.join(destroot, *newpath))

    def dscl(cmd, path, *args):
    # Not sure why -f can’t be used directly with any commands…
    path = os.path.join(‘/Local/Target/’, path.lstrip(os.path.sep))
    cmd = ‘ ‘.join((cmd, path) + args)
    result = subprocess.Popen(
    [‘/usr/bin/dscl’, ‘-q’, ‘-f’, defaultNode],
    stdin = subprocess.PIPE,
    stdout = subprocess.PIPE,
    ).communicate(cmd)[0]
    result = result.rstrip(‘\n’).split(‘\n’)
    del result[0] # ‘Entering interactive mode… (Type “help” for commands)’
    return result

    def dsclread(path, prop):
    result = dscl(‘read’, path, prop)
    result = ”.join(result).split(‘%s: ‘ % (prop, ), 1)[1]
    return result

    def collectReachableHomeDirs(uidFilter = lambda uid: uid > 500):
    for line in dscl(‘ls’, ‘/Users’, ‘UniqueID’):
    user, uid = line.split(‘ ‘, 1)[0], int(line.rsplit(‘ ‘, 1)[1])
    if uidFilter(uid):
    gid = int(dsclread(os.path.join(‘/Users’, user), ‘PrimaryGroupID’))
    name = dsclread(os.path.join(‘/Users’, user), ‘RealName’)
    home = dest(dsclread(os.path.join(‘/Users’, user), ‘NFSHomeDirectory’))
    if os.path.exists(home):
    yield (uid, gid, name, home)

    def collectUserTemplateDirs(languages = None): # None actually means all
    for lang in os.listdir(dest(userTemplateRoot)):
    if languages is None or lang in languages:
    yield (
    0,
    0,
    ‘template (%s)’ % (lang, ), # This is cheating, but it works
    dest(os.path.join(userTemplateRoot, lang)),
    )

    def setupAutofiller(**kwargs):
    print “Setting up LaunchAgent for deferred fill”
    scriptPath = os.path.join(kwargs[‘autofillDir’], ‘autofiller.sh’)
    writeFile(kwargs[‘scriptPath’], autofiller_sh % kwargs)
    os.chmod(dest(kwargs[‘scriptPath’]), 0755)
    writeFile(os.path.join(kwargs[‘launchAgentsDir’], ‘autofiller.plist’), autofiller_plist % kwargs)

    def makeArchive(srcDir, archive):
    print “Archiving autofill data”
    subprocess.check_call([‘/usr/sbin/chown’, ‘-R’, ‘0:0’, dest(srcDir)])
    tarCreate(archive, srcDir)
    return archive

    def cleanUp(srcDir):
    srcDir = dest(srcDir)
    print “Cleaning up %s” % (srcDir, )
    subprocess.check_call([‘/bin/rm’, ‘-rf’, srcDir])

    def setupDeferredFill(srcDir, pkgid):
    setupAutofiller(
    launchAgentsDir = launchAgentsDir,
    autofillDir = autoFillDir,
    autofillReceiptsDir = autofillReceiptsDir,
    dataDir = dataDir,
    )
    writeFile(os.path.join(srcDir, autofillReceiptsDir, pkgid), ”) # Create empty file
    archive = makeArchive(srcDir, os.path.join(dataDir, pkgid + ‘.tar.bz2’))
    cleanUp(srcDir)
    return archive

    def fillDirs(src, dirs):
    for uid, gid, name, home in dirs:
    print “Filling %s for user %s” % (dest(home), name)
    tarExtract(src, home, uid, gid)

    def getPkgID():
    import time, uuid
    return time.strftime(‘%Y-%m-%d %H:%M:%S ‘) + str(uuid.uuid1())

    def main(_destroot):
    global destroot
    destroot = _destroot

    pkgid = getPkgID()
    src = setupDeferredFill(srcDir, pkgid)

    dirs = []
    dirs += collectReachableHomeDirs()
    dirs += collectUserTemplateDirs()

    fillDirs(src, dirs)

    if __name__ == ‘__main__’:
    import sys
    main(sys.argv[3] if len(sys.argv) > 3 else ‘/’)
    [/code]

    #380752
    Chris George
    Participant

    Maybe I’m not following, but I don’t get the point of this. If the files get put into all the existing home directories and the user template localizations – what does the launchagent do?

    #380761
    Ben Urban
    Participant

    The LaunchAgent takes care of those home directories that are not reachable when the package is installed. This includes home directories protected by FileVault, as well as network home directories. (Thanks to Greg Neagle for the suggestion, by the way.)

    I expect the LaunchAgent’s impact once the template is already installed (by the script or the LaunchAgent itself) should be negligible. Unfortunately, I can’t test this easily, and I’m not that familiar with writing a LaunchAgent by hand (I copied one that seemed to run with similar frequency). Feedback on that part would be appreciated.

    I’m also not sure this is the best way to generate the package IDs, or to store them (empty files are okay, but there must be some useful information that can go in there…). Any thoughts?

    #380779
    Ben Urban
    Participant

    Hmm, this forum doesn’t allow edits?

    This version fixes a bug that would cause the previous version of the script to consistently crash when it is run. I also renamed autofiller.plist to com.osxdeployment.autofiller.plist (I’m told “Nate won’t mind”), and added a description.

    [code]
    #!/usr/bin/python

    # Written by Ben Urban

    from __future__ import with_statement # in case we’re in Python 2.5

    “””
    This script clones anything it finds in /tmp/fillUsers to all existing
    non-hidden users, as well as all user template localizations. It also sets up a
    LaunchAgent to automatically extract the data for all users that don’t already
    have it. It is meant to be used as a postflight script for packages that need
    this functionality, mimicking and surpassing the functionality available in
    JAMF’s Casper Suite for DMG packages.

    It requires Python 2.5 or higher (thus, Mac OS X 10.5 or later).
    “””

    import os, subprocess

    srcDir = ‘/tmp/fillUsers’
    defaultNode = ‘/var/db/dslocal/nodes/Default’
    userTemplateRoot = ‘/System/Library/User Template’
    autofillDir = ‘/Library/Autofill’
    dataDir = os.path.join(autofillDir, ‘Data’)
    launchAgentsDir = ‘/Library/LaunchAgents’
    autofillReceiptsDir = ‘Library/Autofill/Receipts’
    autofiller_sh = ”’#!/bin/sh

    for f in “%(dataDir)s”/*
    do
    if ! [ -f “${HOME}/%(autofillReceiptsDir)s/${f%%%%.*}” ]
    then
    /usr/bin/tar -xpf “${f}” -C “${HOME}” –strip-components 1
    fi
    done
    ”’
    autofiller_plist = ”’

    Label
    com.osxdeployment.autofiller
    ProgramArguments

    %(scriptPath)s

    RunAtLoad
    ServiceDescription
    Automatically fills the home folder at login, using the tarballs in from %(dataDir).
    ”’

    def writeFile(path, contents):
    path = dest(path)
    os.makedirs(os.path.dirname(path))
    with open(path, ‘w’) as f:
    f.write(contents)

    def tarCreate(archive, srcFolder):
    os.makedirs(dest(os.path.dirname(archive)))
    subprocess.check_call([
    ‘/usr/bin/tar’, ‘-cj’,
    ‘-f’, dest(archive),
    ‘-C’, dest(os.path.dirname(srcFolder)),
    os.path.basename(srcFolder),
    ])

    def tarExtract(archive, dstFolder, uid, gid):
    subprocess.check_call(
    [
    ‘/usr/bin/tar’, ‘-x’,
    ‘-f’, dest(archive),
    ‘-C’, dest(dstFolder),
    ‘–strip-components’, ‘1’,
    ],
    preexec_fn = lambda: (os.setuid(uid), os.setgid(gid)),
    )

    def dest(path):
    “””
    Convert an absolute path into an absolute path within the mountpoint
    specified by destroot. Symlinks in the path are followed properly.

    If you pass a recursive link to this function, enjoy your infinite loop!
    “””
    path = os.path.normpath(path)
    if path.startswith(destroot):
    path = path.split(destroot, 1)[1]
    path = path.lstrip(os.path.sep).split(os.path.sep)
    while True:
    newpath = []
    noLinks = True
    for part in path:
    subpath = os.path.join(*([destroot] + newpath + [part]))
    if os.path.islink(subpath):
    noLinks = False
    link = os.readlink(subpath)
    if link.startswith(os.path.sep):
    newpath = [os.path.normpath(link.lstrip(os.path.sep))]
    else:
    newpath.append(os.path.normpath(link))
    else:
    newpath.append(part)
    if noLinks:
    break
    path = os.path.join(*newpath).lstrip(os.path.sep).split(os.path.sep)
    return os.path.normpath(os.path.join(destroot, *newpath))

    def dscl(cmd, path, *args):
    # Not sure why -f can’t be used directly with any commands…
    path = os.path.join(‘/Local/Target/’, path.lstrip(os.path.sep))
    cmd = ‘ ‘.join((cmd, path) + args)
    result = subprocess.Popen(
    [‘/usr/bin/dscl’, ‘-q’, ‘-f’, defaultNode],
    stdin = subprocess.PIPE,
    stdout = subprocess.PIPE,
    ).communicate(cmd)[0]
    result = result.rstrip(‘\n’).split(‘\n’)
    del result[0] # ‘Entering interactive mode… (Type “help” for commands)’
    return result

    def dsclread(path, prop):
    result = dscl(‘read’, path, prop)
    result = ”.join(result).split(‘%s: ‘ % (prop, ), 1)[1]
    return result

    def collectReachableHomeDirs(uidFilter = lambda uid: uid > 500):
    for line in dscl(‘ls’, ‘/Users’, ‘UniqueID’):
    user, uid = line.split(‘ ‘, 1)[0], int(line.rsplit(‘ ‘, 1)[1])
    if uidFilter(uid):
    gid = int(dsclread(os.path.join(‘/Users’, user), ‘PrimaryGroupID’))
    name = dsclread(os.path.join(‘/Users’, user), ‘RealName’)
    home = dest(dsclread(os.path.join(‘/Users’, user), ‘NFSHomeDirectory’))
    if os.path.exists(home):
    yield (uid, gid, name, home)

    def collectUserTemplateDirs(languages = None): # None actually means all
    for lang in os.listdir(dest(userTemplateRoot)):
    if languages is None or lang in languages:
    yield (
    0,
    0,
    ‘template (%s)’ % (lang, ), # This is cheating, but it works
    dest(os.path.join(userTemplateRoot, lang)),
    )

    def setupAutofiller(**kwargs):
    print “Setting up LaunchAgent for deferred fill”
    kwargs[‘scriptPath’] = os.path.join(kwargs[‘autofillDir’], ‘autofiller.sh’)
    writeFile(kwargs[‘scriptPath’], autofiller_sh % kwargs)
    os.chmod(dest(kwargs[‘scriptPath’]), 0755)
    writeFile(os.path.join(kwargs[‘launchAgentsDir’], ‘com.osxdeployment.autofiller.plist’), autofiller_plist % kwargs)

    def makeArchive(srcDir, archive):
    print “Archiving autofill data”
    subprocess.check_call([‘/usr/sbin/chown’, ‘-R’, ‘0:0’, dest(srcDir)])
    tarCreate(archive, srcDir)
    return archive

    def cleanUp(srcDir):
    srcDir = dest(srcDir)
    print “Cleaning up %s” % (srcDir, )
    subprocess.check_call([‘/bin/rm’, ‘-rf’, srcDir])

    def setupDeferredFill(srcDir, pkgid):
    setupAutofiller(
    launchAgentsDir = launchAgentsDir,
    autofillDir = autoFillDir,
    autofillReceiptsDir = autofillReceiptsDir,
    dataDir = dataDir,
    )
    writeFile(os.path.join(srcDir, autofillReceiptsDir, pkgid), ”) # Create empty file
    archive = makeArchive(srcDir, os.path.join(dataDir, pkgid + ‘.tar.bz2’))
    cleanUp(srcDir)
    return archive

    def fillDirs(src, dirs):
    for uid, gid, name, home in dirs:
    print “Filling %s for user %s” % (dest(home), name)
    tarExtract(src, home, uid, gid)

    def getPkgID():
    import time, uuid
    return time.strftime(‘%Y-%m-%d %H:%M:%S ‘) + str(uuid.uuid1())

    def main(_destroot):
    global destroot
    destroot = _destroot

    pkgid = getPkgID()
    src = setupDeferredFill(srcDir, pkgid)

    dirs = []
    dirs += collectReachableHomeDirs()
    dirs += collectUserTemplateDirs()

    fillDirs(src, dirs)

    if __name__ == ‘__main__’:
    import sys
    main(sys.argv[3] if len(sys.argv) > 3 else ‘/’)
    [/code]

    #381228
    Chris George
    Participant

    Hmm. Old thread bump time!

    I just realized that, unless I’m wrong, this script doesn’t account for network users, such as those from Active Directory/Open Directory, that have local home directories, as it is reading the list of users from dscl on the local machine and only applying the changes to those users. I couldn’t figure out why an installer I made using this postflight didn’t work in our labs, and I think now that’s because we don’t have any local users aside from the admin account.

    Am I wrong?

Viewing 12 posts - 1 through 12 (of 12 total)
  • You must be logged in to reply to this topic.

Comments are closed