Adobe Creative Cloud Deployment – Pushing Installers with Munki

munki_transparentWe previously covered a few aspects of Adobe Creative Cloud from the perspective of deploying it to OS X clients. We spent the whole time dealing with the licensing aspects but never talked about the actual installers and updates.

Adobe installers are spoiled

There is a single option available to you for getting the installers: you must use the Adobe Creative Cloud Packager application (CCP for short) to fetch and build OS X installer packages. Because Adobe has reinvented the wheel and opted to use their own custom installer framework, the installer packages that CCP outputs do not use any of OS X’s native installer features – instead the packages simply provide just enough of a mechanism to bundle up Adobe’s own installer tooling (which have actually grown substantially in size in proportion to the actual applications they install) and run them as “preinstall” scripts.

The advantage of having the installers in an installer package format is that they can be deployed using the multitude of tools which can install packages: the OS X GUI, the installer command, Remote Desktop, DeployStudio, and management platforms such as Munki. The disadvantages of this wolf-in-sheep’s clothing packaging system are numerous, but this is the only option we have to deploy Adobe applications efficiently.

Adobe support in Munki

Luckily, Greg Neagle, author of Munki, has done us the service of providing great support in Munki for these spoiled application installers – because historically these packages have needed “help” installing in different contexts. After CCP builds a new package, it outputs both an installer and uninstaller package to our output directory, and we import these packages like any other using munkiimport.

I was going to go into more detail on this whole process, but Nick McSpadden has already done just that in an Adobe CC guide on the Munki Wiki.

This boils down to using Munki’s admin tools to import the base installer package, and then using aamporter to automatically fetch and import all the applicable updates. Nick also details some of the less-standard applications that need some more massaging once in the repository.

Because the process (which creates compressed disk images from the CCP bundle-style packages) is lengthy and you may have many packages to do, I wrote a very simple helper Python script that helps batch these initial imports. Nick has covered this in the wiki article as well, but for reference it’s here.

As far as updates go, one way in which Munki beats all other software management platforms is that it can apply Adobe’s “patch updates” natively without any repackaging necessary. Adobe has for a long time offered the ability to generate packages from their updates, but that requires not only manual work to build them, but a lot of manual bookkeeping to keep track of them. aamporter has been able to leverage this support by grabbing the updates directly from the in-application updater mechanisms and importing these items directly. This also makes it possible to keep CCP installer packages “simple” but omitting the optional updates, and minimizing the need to interact with CCP.

Creative Cloud Desktop app

If you opt to deploy Named licenses, one important thing that distinguishes those installers from device- or serial-number-licensed packages is that when you build the package from CCP, the Creative Cloud Desktop Application (or CCDA) cannot be deselected from the package configuration. With device- or serial-number-licensed packages it can be omitted, with Named licenses it cannot. The “Applications & Updates via the Apps Panel” can be, which might be useful to disable if your users do not have administrative rights or you’d prefer they install apps via your own in-house systems.

CCDA cannot be disabled with Named Licenses, although the "updates via the Apps Panel" can be.

CCDA cannot be disabled with Named Licenses, although the “updates via the Apps Panel” can be.

Alongside the CCDA, the installer package output by CCP will include a LaunchAgent that opens it at login for all users. Users will see it in the menu bar and by default it will show a big login window prompt when it’s first opened.

This is by design, because Named licenses include “cloud services” like storage and collaboration tools. However, if you are just deploying device licenses on top of Named installers (as I covered earlier) and don’t have license agreements that include these features (or your users have no use for them, or are using them in labs), you may want to ensure that this application doesn’t constantly pop up.

This LaunchAgent is located at /Library/LaunchAgents/com.adobe.AdobeCreativeCloud.plist, and so you may wish to remove this in a postinstall script or via some other mechanism. If you have many Named installers, just be aware that the installation of each Named installer will put this LaunchAgent back. I’ve opted to run a script that removes this plist on every machine boot (using a system like outset), a script that I’ve installed as an update for my device license package (built using the method described in this earlier post). Since I intend to keep the Named installers useful for users who actually have Named licenses and could potentially use the features offered via the CDDA, I did not want to automatically remove the LaunchAgent for any installation, only those for which I’ve installed a device license activation.

Because this is a LaunchAgent and not a LaunchDaemon we cannot simply run launchctl unload -w to disable it system-wide – it would need to be disabled and overridden per user. So, if you intend to remove this LaunchAgent to prevent it from auto-launching, more drastic measures are required.

Wrapping up

Are we done here? This is about as much information as I’ve needed to absorb in this process of getting our CC deployment prepared. I have some general thoughts about this whole process that may show up in a later post, and hopefully there aren’t any other major issues that crop up with the approaches I’ve outlined in the previous posts in this series.

Tagged , , | 3 Responses

Adobe Creative Cloud Licensing and Deployment – Managing Licenses with Munki

munki_transparentPreviously we covered some boring details about Adobe Creative Cloud licensing and how this impacts deploying it to managed clients. We also covered a process and script I came up with that makes it slightly less painful to package up device and serial licenses for distribution to clients. Now, how to we manage these in a software management system? Since I use Munki, I’ll use that as a model for how you might manage this license from an administrative standpoint. This is Munki-specific, but the principles should apply elsewhere.

Munki

With Munki we use “manifests” as our instructions for what clients should do with the software in our repository. We can list items that will be installed or uninstalled, or items that the user can install themselves via a self service app,”Managed Software Center.” We can also use “conditional items,” which let us restrict these rules within conditions that need to be satisfied based on certain criteria scavenged from the client (which themselves can be extended by the admin). Any manifest may represent a single machine or it might be shared across a group of machines. For more details on these concepts in Munki, here are links to manifests and conditional items on the Munki wiki.

Note that I’ll be using the word “pool” a lot here, but those with enterprise license agreement types should be able to substitute “pool” with “serial number.”

Manifest/pkginfo structure

Here are a couple simple ways that Munki could handle managing the apps and licenses: we could add device file installers as separate items and add these to a manifest alongside the actual applications included in (or a subset of) that device file’s corresponding pool. Here’s an example of a Munki manifest with some CC 2014 applications, and notice in the last item I’ve added the licensing package to be installed independently of the applications. These application items are all packages built as Named licensed packages, which means they are effectively unlicensed. See our first post for more brain-meltingly boring background information on this.

<plist version="1.0">
<dict>
    <key>catalogs</key>
    <array>
        <string>production</string>
    </array>
    <key>managed_installs</key>
    <array>
        <string>AdobePhotoshopCC2014</string>
        <string>AdobePremiereProCC2014</string>
        <string>AdobeSpeedGradeCC2014</string>
        <string>AdobeCC_License_Complete</string>
    </array>
</dict>
</plist>

Were we to leave out the AdobeCC_License_Complete item above, these applications would successfully install but a user would need to first sign in to use the software. In this way we can think of the installs as “unlicensed by default” and then we add a device/serial license on top if it’s appropriate. Alternatively users could install the device licenses themselves as optional software, if that would make sense in your environment. In this example above, the Munki admin or help desk would be manually adding the license for a given manifest. If one later wanted to remove and deactivate the license, one would just move the item to a managed_uninstalls array in the same manifest.

Here’s an abbreviated pkginfo for AdobeCC_License_Complete, just to show some important bits.

<plist version="1.0">
<dict>
    [...]
    <key>catalogs</key>
    <array>
        <string>production</string>
    </array>
    [...]
    <key>name</key>
    <string>AdobeCC_License_Complete</string>
    <key>version</key>
    <string>2015.05.19</string>
</dict>
</plist>

The manifest we saw above looks at a single catalog: production, and the license pkginfo belongs to only this catalog. This is a simple example.

For a slightly more sophisticated example, this license package could instead be set as an update for any products we have that would be “licensable” using that license, and be limited to a specific catalog. Our manifest, slightly modified, looks like:

<plist version="1.0">
<dict>
    <key>catalogs</key>
    <array>
        <string>adobe-cc-license-pool-complete</string>
        <string>production</string>
    </array>
    <key>managed_installs</key>
    <array>
        <string>AdobePhotoshopCC2014</string>
        <string>AdobePremiereProCC2014</string>
        <string>AdobeSpeedGradeCC2014</string>
    </array>
</dict>
</plist>

Notice we’ve taken the license as a separate installer “item” out of the manifest’s installs list, and instead just made this manifest look at another catalog named after this pool. Our pkginfo for this license installer is part of this same special catalog, and is an update_for all items we have in our system that are part of the “Complete” pool:

<plist version="1.0">
<dict>
    [...]
    <key>catalogs</key>
    <array>
        <string>adobe-cc-license-pool-complete</string>
    </array>
    [...]
    <key>name</key>
    <string>AdobeCC_License_Complete</string>
    <key>update_for</key>
    <array>
        <string>AdobeAuditionCC2014</string>
        <string>AdobeIllustratorCC2014</string>
        <string>AdobePhotoshopCC2014</string>
        <string>AdobePremiereProCC2014</string>
        <string>AdobeSpeedGradeCC2014</string>
    </array>
    <key>version</key>
    <string>2015.05.19</string>
</dict>
</plist>

Note how we have other items in our update_for list like Audition CC, which could be referenced in other manifests. In this specific example I’m only deploying up to five different apps, but since this device license package belongs to a “Complete” pool, any Adobe app I package later could then be added as an update_for this Complete device license package.

The elegance of the second approach is that Munki can actually handle automatic activation and deactivation for us. If I would decide later to move SpeedGrade to the manifests’s managed_uninstalls list, Munki would check if in addition it can also remove the AdobeCC_License_Complete item, but determine that it cannot because other items in our managed_installs list are still installed. If we were to remove all the applications for which this license is an update, Munki would then go and remove the license, deactivating the machine and freeing up a license.

You might think, That’s fine for this example with a Complete pool, but what if we also purchase a pool that’s just Photoshop? You don’t want to consume a Complete pool license if someone only has Photoshop – but this is why each license installer would be limited to a special-purpose Munki catalog. If we had a “Video apps” pool, for example, we’d just add another catalog named adobe-cc-license-pool-video and make it an update for all the apps we have that could be in that pool.

I’ve rarely used catalogs as a mechanism to separate out licenses for individual business units, but it seems like this could be a use case where it might provide a useful layer of abstraction. On the other hand, managing the license as a separate line item also makes it easier to “convert” a machine back to a Named license model, if manifests are per-machine and the machine changes users, or as budget dictates the allocation of ongoing subscription licenses. In the second example above (using catalogs), if we later remove the special license catalog from a manifest’s catalogs array, this does not mean that Munki will then automatically remove the license pkg from the client. It’s only by actually placing a license in a managed_uninstalls array that Munki actively goes out to ensure the item is removed. So, there are pros and cons to both approaches.

License and application usage tracking

So, those are a couple examples of how you one might approach managing these licenses in your Munki repo and among your clients. Another approach that might be worth considering is to have a more intelligent license-tracking mechanism help manage this for you. Luckily, Munki even has one built in! At this time of writing, MunkiWebAdmin is the only public Munki web-based reporting application that has support for tracking the licenses, but the client uses a simple enough mechanism to determine based on data it receives from a server whether or not it will offer a given item to a user.

Recently Greg Batye gave a talk at the monthly Macbrained meetup, covering how Facebook uses Munki. One interesting thing he covered was how they use a combination of Crankd and Munki conditional items to keep a list of applications that haven’t recently been used, and remove these automatically. This allows them to offer expensive software to a much wider user audience because in many cases the software will be automatically uninstalled after it’s not in use for some length of time. See a video recording of the talk starting around 34:10 minutes in, and the recap with a link to slides.

Next

We’re still not done! We haven’t detailed anything about the actual installation process, we’ve only covered licensing in three dense posts. If you’re not bored yet, stay tuned for some odds and ends about importing these applications and updates using Munki and aamporter.

Tagged , | Leave a comment

Adobe Creative Cloud Deployment: Packaging a License File

CCP_Pkg_128.pngIn the previous post, we covered the scenarios in which you might want to deploy a Creative Cloud device license or serial number separate from the actual applications, as a “License File Package”. Although the Creative Cloud Packager app supports this as a workflow, the problem is that it doesn’t help you out much with regards to the files it outputs.

ccp_create_license_file

Adobe has had the APTEE tool around for a while, as a command-line interface to the Creative Suite licensing tools, to aid with deployment automation – it’s a single executable which confusingly does not include “APTEE” anywhere in the name of the binary: adobe_prtk.

This tool is still around, and has been updated for Creative Cloud. It’s also claimed to be installed as part of Creative Cloud Packager, which is true, but its location is not documented anywhere I could find, so I’ll save you the trouble looking for it: /Applications/Utilities/Adobe Application Manager/CCP/utilities/APTEE/adobe_prtk.

According to the official documentation for the “Create License File” option in CCP, that outputs four files:

  • AdobeSerialization
  • RemoveVolumeSerial
  • helper.bin
  • prov.xml

..there’s no adobe_prtk among those. But it turns out, if we take a look at the strings of AdobeSerialization – which the docs say we can run with “admin privileges” to license the software – some of the first strings found in the binary look an awful lot like flags to adobe_prtk:

com.apple.PackageMaker
3.0.3
AdobeSerialization
AdobeSerialization.log
CreativeCloudPackager
Utilities
##################################################
Launching the AdobeSerialization in elevated mode ...
helper.bin
prov.xml
/Provisioning/EnigmaData
type
DBCS
--tool=GetPackagePools
--tool=VolumeSerialize
--stream
--provfile=

AdobeSerialization seems to be a purpose-built version of adobe_prtk with options baked in. This tool loads your authenticated data and license details stored in an opaque format from the prov.xml file to perform a transaction with Adobe’s licensing servers and commit the results to the local machine’s Adobe licensing database.

Along with AdobeSerialization there’s the RemoveVolumeSerial tool. Unfortunately, as mentioned previously and in Adobe’s official CCP documentation this tool is supported for “Enterprise and EEA customers only” – which means it can’t be used to deactivate a machine that is using a Device license in a Teams-based agreement. In fact, it has an LEID baked in along with the adobe_prtk options: V7{}CreativeCloudEnt-1.0-Mac-GM. (For reference, the current LEID for the Creative Cloud Teams “Complete” product is V6{}CreativeCloudTeam-1.0-Mac-GM.)

We’ve got enough hints in these two binaries to figure out that we can pass flags to adobe_prtk. From my examination, these roughly boil down to using the --tool=GetPackagePools flag for a device (Teams) license (see references to “DBCS” throughout the code, ~/Library/Logs/oobelib.log file and the prov.xml file), and --tool=VolumeSerialize for a serial number (Enterprise) license.

Using the adobe_prtk tool and knowing the LEID of the product we want to deactivate, we can also do what the RemoveVolumeSerial tool cannot do: deactivate a teams-based device. The tool options don’t seem to be different depending on a device or serial license, the issue is simply that RemoveVolumeSerial has a hardcoded LEID, whereas we can know ours by looking up the list, or even better, retrieving this automatically from the prov.xml file.

Based on this examination, it looks like adobe_prtk can perform a superset of the functions these two special binaries output from CCP can do, using a single binary. So in order to build a “licensing package” that can be installed as a native OS X installer package (and deployed with Munki, Imagr, DeployStudio, Casper, etc.) we have our necessary ingredients: we need the adobe_prtk (or “APTEE”) tool, the prov.xml file corresponding to our license, and we know the commands to install and remove the license. Still, we need to know which command flags go with which license type, and we need to set the correct LEID if we want to ever be able to deactivate the license. Why not instead use the binaries that are output by CCP? As I described above, the removal tool will not work for all license agreements. I’d rather not have to keep track of multiple different binaries if one can do all the work.

Since investigating all this I decided this would be useful to encapsulate into a script that removes the guesswork from this, and so it has been put on GitHub here: make-adobe-cc-license-pkg.

It only requires a copy of adobe_prtk, which will be discovered automatically if you’ve already installed CCP on the system running the script, and your prov.xml file output from your “Create License File” workflow. Everything else should be figured out for you, and a package will be output given the package parameters you specify:

$ ./make-adobe-cc-license-pkg --name AdobeCC_Complete --reverse-domain ca.macops prov.xml

** Found DBCS (device) license type in prov.xml
** Found LEID 'V6{}CreativeCloudTeam-1.0-Mac-GM' in prov.xml
** Extracted version 8.0.0.160 from adobe_prtk Info.plist section
** Wrote uninstall script to /Users/tsutton/AdobeCC_Complete-2015.05.27.uninstall
pkgbuild: Inferring bundle components from contents of /var/folders/8t/5trmslfj2cnd5gxkbmkbn5fj38qb2l/T/tmprvCGEI
pkgbuild: Adding top-level postinstall script
pkgbuild: Wrote package to /Users/tsutton/AdobeCC_Complete-2015.05.27.pkg
** Built package at /Users/tsutton/AdobeCC_Complete-2015.05.27.pkg
** Done.

Since I use Munki, and this package can only really be properly “removed” using an uninstall script, this tool can also import the resultant package into Munki and set the appropriate uninstall_script key with an uninstall script that will be populated with the appropriate LEID. Either way the uninstall script will be saved to the package’s output directory for your own use in other systems.

See the repo’s GitHub page for more details and documentation about how the package is built.

One of the Mac sysadmin community’s biggest peeves with Adobe’s AAM installer framework is that when failures occur (and they happen a lot), useful error codes are rarely printed in the context in which one normally monitors package installations (for example /var/log/install.log). Adobe documents their error codes on their website, and so the install/uninstall scripts generated by this package actually report this info to standard output/error so you can at least immediately get a short description of why any failures might have occurred. There will always be full debug output in the various AAM logs, but the locations of these files are rarely easily discoverable or well-named (for example, ~/Library/Logs/oobelib.log).

This tool hasn’t been widely tested (thanks to Patrick Fergus again for his help testing the functionality with an Enterprise license), and it will probably be getting some tweaks or fixes over time.

Moving on, if you were using Munki or some other software management system (and hopefully you are using one of these), how would you “scope” how these licenses get deployed to machines? We’ll look at a short example using Munki in the next post.

Tagged , , , | 5 Responses