├── .gitignore ├── Adobe ├── CFBundleVersionSmartGroupTemplate.xml ├── CreativeCloudApp.jss.recipe ├── CreativeCloudApp.munki.recipe ├── CreativeCloudApp.pkg.recipe ├── CreativeCloudAppNoUninstall.munki.recipe ├── CreativeCloudAppSerialized.munki.recipe ├── CreativeCloudAppSerialized.pkg.recipe ├── CreativeCloudBuildModifier.py ├── CreativeCloudFeed.py ├── CreativeCloudPackager.py ├── CreativeCloudVersioner.py ├── PolicyTemplate.xml ├── SmartGroupTemplate.xml ├── UninstallPolicyTemplate.xml ├── UninstallSmartGroupTemplate.xml └── examples │ ├── AcrobatDC17.jss.recipe │ ├── AcrobatDC17.munki.recipe │ ├── AcrobatDCSmartGroupTemplate.xml │ ├── AdobeAuditionCC2017.jss.recipe │ ├── AdobePhotoshopCC2017.munki.recipe │ ├── AdobePhotoshopCC2017.pkg.recipe │ ├── PolicyTemplate.xml │ ├── README.md │ └── SmartGroupTemplate.xml ├── LICENSE ├── README.md ├── list_ccp_feed └── whats_my_org.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /Adobe/CFBundleVersionSmartGroupTemplate.xml: -------------------------------------------------------------------------------- 1 | 2 | %group_name% 3 | true 4 | 5 | 6 | Application Title 7 | 0 8 | and 9 | is 10 | %JSS_INVENTORY_NAME% 11 | 12 | 13 | %NAME%Version 14 | 1 15 | and 16 | is not 17 | %VERSION% 18 | 19 | 20 | %NAME%Version 21 | 2 22 | and 23 | is not 24 | 25 | 26 | 27 | Computer Group 28 | 3 29 | and 30 | member of 31 | Testing 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Adobe/CreativeCloudApp.jss.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Description 6 | Build and import a Creative Cloud Application into JSS 7 | Identifier 8 | com.github.mosen.jss.Adobe.CreativeCloudApp 9 | Input 10 | 11 | CATEGORY 12 | Productivity 13 | GROUP_NAME 14 | %NAME%-update-smart 15 | GROUP_TEMPLATE 16 | SmartGroupTemplate.xml 17 | POLICY_CATEGORY 18 | Testing 19 | POLICY_TEMPLATE 20 | PolicyTemplate.xml 21 | UNINSTALL_CATEGORY 22 | Uninstallers 23 | UNINSTALL_GROUP_NAME 24 | %NAME%-uninstall 25 | UNINSTALL_GROUP_TEMPLATE 26 | UninstallSmartGroupTemplate.xml 27 | UNINSTALL_POLICY_CATEGORY 28 | Testing 29 | UNINSTALL_POLICY_TEMPLATE 30 | UninstallPolicyTemplate.xml 31 | SELF_SERVICE_ICON 32 | 33 | 34 | MinimumVersion 35 | 1.0.0 36 | ParentRecipe 37 | com.github.mosen.pkg.Adobe.CreativeCloudApp 38 | Process 39 | 40 | 41 | Processor 42 | Copier 43 | Arguments 44 | 45 | source_path 46 | %pkg_path% 47 | destination_path 48 | %RECIPE_CACHE_DIR%/%NAME%_Install-%version%.pkg 49 | 50 | 51 | 52 | Arguments 53 | 54 | category 55 | %CATEGORY% 56 | groups 57 | 58 | 59 | name 60 | %GROUP_NAME% 61 | smart 62 | 63 | template_path 64 | %GROUP_TEMPLATE% 65 | 66 | 67 | jss_inventory_name 68 | %jss_inventory_name% 69 | pkg_path 70 | %destination_path% 71 | policy_category 72 | %POLICY_CATEGORY% 73 | policy_template 74 | %POLICY_TEMPLATE% 75 | prod_name 76 | %NAME% 77 | self_service_description 78 | %release_notes% 79 | self_service_icon 80 | %icon_path% 81 | 82 | Processor 83 | JSSImporter 84 | 85 | 86 | Processor 87 | PathDeleter 88 | Arguments 89 | 90 | path_list 91 | %destination_path% 92 | 93 | 94 | 95 | Processor 96 | Copier 97 | Arguments 98 | 99 | source_path 100 | %uninstaller_pkg_path% 101 | destination_path 102 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%version%.pkg 103 | 104 | 105 | 106 | Arguments 107 | 108 | category 109 | %UNINSTALL_CATEGORY% 110 | groups 111 | 112 | 113 | name 114 | %UNINSTALL_GROUP_NAME% 115 | smart 116 | 117 | template_path 118 | %UNINSTALL_GROUP_TEMPLATE% 119 | 120 | 121 | jss_inventory_name 122 | %jss_inventory_name% 123 | pkg_path 124 | %destination_path% 125 | policy_category 126 | %UNINSTALL_POLICY_CATEGORY% 127 | policy_template 128 | %UNINSTALL_POLICY_TEMPLATE% 129 | prod_name 130 | %NAME% 131 | self_service_icon 132 | %icon_path% 133 | 134 | Processor 135 | JSSImporter 136 | 137 | 138 | Processor 139 | PathDeleter 140 | Arguments 141 | 142 | path_list 143 | %destination_path% 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /Adobe/CreativeCloudApp.munki.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Description 6 | Build and import a Creative Cloud Application into munki 7 | Identifier 8 | com.github.mosen.munki.Adobe.CreativeCloudApp 9 | Input 10 | 11 | MUNKI_REPO_SUBDIR 12 | apps/AdobeCC 13 | 14 | FETCH_ICON 15 | false 16 | 17 | FETCH_RELEASE_NOTES 18 | false 19 | 25 | pkginfo 26 | 27 | catalogs 28 | 29 | testing 30 | 31 | developer 32 | Adobe 33 | name 34 | %NAME% 35 | 36 | 37 | MinimumVersion 38 | 1.0.0 39 | ParentRecipe 40 | com.github.mosen.pkg.Adobe.CreativeCloudApp 41 | Process 42 | 43 | 46 | 47 | Processor 48 | DmgCreator 49 | Arguments 50 | 51 | dmg_root 52 | %pkg_path% 53 | dmg_path 54 | %RECIPE_CACHE_DIR%/%NAME%_Install-%user_facing_version%.dmg 55 | 56 | 57 | 58 | Processor 59 | DmgCreator 60 | Arguments 61 | 62 | dmg_root 63 | %uninstaller_pkg_path% 64 | dmg_path 65 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%user_facing_version%.dmg 66 | 67 | 68 | 69 | 70 | Processor 71 | MunkiPkginfoMerger 72 | 73 | 74 | 75 | Processor 76 | MunkiPkginfoMerger 77 | Arguments 78 | 79 | additional_pkginfo 80 | 81 | version 82 | %user_facing_version% 83 | description 84 | %release_notes% 85 | 86 | 87 | 88 | 89 | Arguments 90 | 91 | pkg_path 92 | %RECIPE_CACHE_DIR%/%NAME%_Install-%user_facing_version%.dmg 93 | uninstaller_pkg_path 94 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%user_facing_version%.dmg 95 | repo_subdirectory 96 | %MUNKI_REPO_SUBDIR% 97 | 98 | Processor 99 | MunkiImporter 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Adobe/CreativeCloudApp.pkg.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Description 6 | Use Creative Cloud Packager to configure and build a package for a creative cloud 7 | product which uses the 'HyperDrive' installer engine. This recipe must be overridden 8 | and have at least NAME, PRODUCT_ID, BASE_VERSION and ORG_NAME defined for a given 9 | product. 10 | 11 | A 'product' is a unique combination of a PRODUCT_ID and a BASE_VERSION. For example, 12 | Photoshop CC 2017 would be PRODUCT_ID 'PHSP' and BASE_VERSION '18.0'. 13 | 14 | Internally Adobe calls these PRODUCT_IDs "SAP Codes", and these can be found here: 15 | https://helpx.adobe.com/creative-cloud/packager/apps-deployed-without-their-base-versions.html 16 | 17 | There a script in this recipe's directory, 'listfeed.py', which when run will query 18 | the product feed and display all known versions, their SAP codes and base versions. 19 | Only products with base versions defined can be used with this recipe. 20 | 21 | VERSION defaults to 'latest', which will retrieve the latest version for that product. 22 | A previous version can be specified. 23 | 24 | Other Input values here correspond to options provided in the CCP UI. 25 | Identifier 26 | com.github.mosen.pkg.Adobe.CreativeCloudApp 27 | Input 28 | 29 | NAME 30 | CreativeCloudApp 31 | FETCH_ICON 32 | false 33 | FETCH_RELEASE_NOTES 34 | false 35 | ccpinfo 36 | 37 | 38 | 39 | 40 | matchOSLanguage 41 | 42 | 43 | 44 | rumEnabled 45 | 46 | 47 | 48 | updatesEnabled 49 | 50 | 51 | 52 | appsPanelEnabled 53 | 54 | 55 | 56 | adminPrivilegesEnabled 57 | 58 | 59 | 60 | organizationName 61 | ADMIN_PLEASE_CHANGE 62 | 63 | 64 | customerType 65 | team 66 | Language 67 | en_US 68 | 69 | 70 | Products 71 | 72 | 73 | sapCode 74 | 75 | baseVersion 76 | 77 | version 78 | latest 79 | 80 | 81 | 82 | 83 | MinimumVersion 84 | 0.4.0 85 | Process 86 | 87 | 88 | Processor 89 | CreativeCloudFeed 90 | Arguments 91 | 92 | fetch_icon 93 | %FETCH_ICON% 94 | fetch_release_notes 95 | %FETCH_RELEASE_NOTES% 96 | 97 | 98 | 99 | Processor 100 | CreativeCloudPackager 101 | Arguments 102 | 103 | package_name 104 | %NAME% 105 | 106 | 107 | 108 | Processor 109 | EndOfCheckPhase 110 | 111 | 112 | Processor 113 | CreativeCloudVersioner 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /Adobe/CreativeCloudAppNoUninstall.munki.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Description 6 | Build and import a Creative Cloud Application into munki (for applications that do not have an uninstaller) 7 | Identifier 8 | com.github.mosen.munki.Adobe.CreativeCloudAppNoUninstall 9 | Input 10 | 11 | MUNKI_REPO_SUBDIR 12 | apps/AdobeCC 13 | pkginfo 14 | 15 | 16 | 17 | 18 | MinimumVersion 19 | 1.0.0 20 | ParentRecipe 21 | com.github.mosen.pkg.Adobe.CreativeCloudApp 22 | Process 23 | 24 | 27 | 28 | Processor 29 | DmgCreator 30 | Arguments 31 | 32 | dmg_root 33 | %RECIPE_CACHE_DIR%/%NAME% 34 | dmg_path 35 | %RECIPE_CACHE_DIR%/%NAME%_Install-%version%.dmg 36 | 37 | 38 | 39 | Arguments 40 | 41 | additional_pkginfo 42 | 43 | version 44 | %version% 45 | minimum_os_version 46 | %minimum_os_version% 47 | display_name 48 | %display_name% 49 | 50 | 51 | Processor 52 | MunkiPkginfoMerger 53 | 54 | 55 | Arguments 56 | 57 | pkg_path 58 | %RECIPE_CACHE_DIR%/%NAME%_Install-%version%.dmg 59 | uninstaller_pkg_path 60 | 61 | repo_subdirectory 62 | %MUNKI_REPO_SUBDIR% 63 | 64 | Processor 65 | MunkiImporter 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /Adobe/CreativeCloudAppSerialized.munki.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Description 6 | Build and import a Serialized Creative Cloud Application (without CCDA) into munki 7 | Identifier 8 | com.github.mosen.munki.Adobe.CreativeCloudAppSerialized 9 | Input 10 | 11 | MUNKI_REPO_SUBDIR 12 | apps/AdobeCC 13 | 19 | pkginfo 20 | 21 | catalogs 22 | 23 | testing 24 | 25 | developer 26 | Adobe 27 | name 28 | %NAME% 29 | 30 | 31 | MinimumVersion 32 | 1.0.0 33 | ParentRecipe 34 | com.github.mosen.pkg.Adobe.CreativeCloudApp 35 | Process 36 | 37 | 40 | 41 | Processor 42 | CreativeCloudBuildModifier 43 | Arguments 44 | 45 | suppress_ccda 46 | 47 | 48 | 49 | 50 | Processor 51 | DmgCreator 52 | Arguments 53 | 54 | dmg_root 55 | %pkg_path% 56 | dmg_path 57 | %RECIPE_CACHE_DIR%/%NAME%_Install-%user_facing_version%.dmg 58 | 59 | 60 | 61 | Processor 62 | DmgCreator 63 | Arguments 64 | 65 | dmg_root 66 | %uninstaller_pkg_path% 67 | dmg_path 68 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%user_facing_version%.dmg 69 | 70 | 71 | 72 | 73 | Processor 74 | MunkiPkginfoMerger 75 | 76 | 77 | 78 | Processor 79 | MunkiPkginfoMerger 80 | Arguments 81 | 82 | additional_pkginfo 83 | 84 | version 85 | %user_facing_version% 86 | 87 | 88 | 89 | 90 | Arguments 91 | 92 | pkg_path 93 | %RECIPE_CACHE_DIR%/%NAME%_Install-%user_facing_version%.dmg 94 | uninstaller_pkg_path 95 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%user_facing_version%.dmg 96 | repo_subdirectory 97 | %MUNKI_REPO_SUBDIR% 98 | 99 | Processor 100 | MunkiImporter 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /Adobe/CreativeCloudAppSerialized.pkg.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Description 6 | Build and import a Serialized Creative Cloud Application (without CCDA) into munki 7 | Identifier 8 | com.github.mosen.pkg.Adobe.CreativeCloudAppSerialized 9 | Input 10 | 11 | 12 | MinimumVersion 13 | 1.0.0 14 | ParentRecipe 15 | com.github.mosen.pkg.Adobe.CreativeCloudApp 16 | Process 17 | 18 | 21 | 22 | Processor 23 | CreativeCloudBuildModifier 24 | Arguments 25 | 26 | suppress_ccda 27 | 28 | 29 | 30 | 31 | Processor 32 | DmgCreator 33 | Arguments 34 | 35 | dmg_root 36 | %pkg_path% 37 | dmg_path 38 | %RECIPE_CACHE_DIR%/%NAME%_Install-%user_facing_version%.dmg 39 | 40 | 41 | 42 | Processor 43 | DmgCreator 44 | Arguments 45 | 46 | dmg_root 47 | %uninstaller_pkg_path% 48 | dmg_path 49 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%user_facing_version%.dmg 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Adobe/CreativeCloudBuildModifier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2017 Mosen/Tim Sutton 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # for debugging 18 | from pprint import pprint 19 | import os.path 20 | from autopkglib import Processor, ProcessorError 21 | from xml.etree import ElementTree 22 | 23 | __all__ = ["CreativeCloudBuildModifier"] 24 | 25 | ACC_PACKAGE_SETS = { 26 | 'AAM': [ 27 | 'UWA', 28 | 'PDApp', 29 | 'D6', 30 | 'DECore', 31 | 'DWA', 32 | 'P6', 33 | 'LWA', 34 | 'CCM', 35 | 'P7', 36 | 'AdobeGCClient', 37 | 'IPC' 38 | ], 39 | 'ADC': [ 40 | 'Runtime', 41 | 'Core', 42 | 'HEX', 43 | 'CEF', 44 | 'CoreExt', 45 | 'ElevationManager', 46 | 'TCC', 47 | 'Notifications', 48 | 'SignInApp' 49 | ], 50 | 'ACC': [ 51 | 'HDCore', 52 | 'AppsPanel' 53 | ] 54 | } 55 | 56 | 57 | class CreativeCloudBuildModifier(Processor): 58 | """This processor parses the output of the CCP build process and makes modifications.""" 59 | description = __doc__ 60 | input_variables = { 61 | "pkg_path": { 62 | "required": True, 63 | "description": "The package produced by Creative Cloud Packager", 64 | }, 65 | "suppress_ccda": { 66 | "description": "Suppress the installation of Creative Cloud Desktop Application.", 67 | "required": False, 68 | "default": True 69 | } 70 | } 71 | output_variables = {} 72 | 73 | def _addPackage(self, parent, name): 74 | """Add a package element w/name to a set""" 75 | pkg = ElementTree.SubElement(parent, 'package') 76 | name_el = ElementTree.SubElement(pkg, 'name') 77 | name_el.text = name 78 | 79 | def _addPackageSet(self, parent, set_name, package_names): 80 | """Add a package set given a name and list of package names""" 81 | pkg_set = ElementTree.SubElement(parent, 'packageSet') 82 | pkg_set_name = ElementTree.SubElement(pkg_set, 'name') 83 | pkg_set_name.text = set_name 84 | 85 | pkgs = ElementTree.SubElement(pkg_set, 'packages') 86 | for pkg_name in package_names: 87 | self._addPackage(pkgs, pkg_name) 88 | 89 | def _addOverrides(self, aam_info): 90 | """Add the overrideXML parts for disabling CCDA""" 91 | overridexml = ElementTree.SubElement(aam_info, 'overrideXML') 92 | override_app = ElementTree.SubElement(overridexml, 'application') 93 | package_sets = ElementTree.SubElement(override_app, 'packageSets') 94 | 95 | for sap, packages in ACC_PACKAGE_SETS.items(): 96 | self._addPackageSet(package_sets, sap, packages) 97 | 98 | def _removeASUPackages(self): 99 | """Remove ASU packages""" 100 | asu_appinfo_path = os.path.join(self.env['pkg_path'], 'Contents', 'Resources', 'ASU', 'packages', 101 | 'ApplicationInfo.xml') 102 | asu_appinfo = ElementTree.parse(asu_appinfo_path) 103 | asu_appinfo_root = asu_appinfo.getroot() 104 | 105 | acc_packageset = asu_appinfo_root.find(".//packageSet[name='ACC']/packages") 106 | if acc_packageset is None: 107 | raise ProcessorError('Tried to modify ACC installation, but no packageSet element was found. This should' + 108 | 'never happen') 109 | 110 | adc_packageset = asu_appinfo_root.find(".//packageSet[name='ADC']/packages") 111 | if adc_packageset is None: 112 | raise ProcessorError('Tried to modify ACC(ADC) installation, but no packageSet element was found. This should' + 113 | 'never happen') 114 | 115 | packages_to_remove = [ 116 | 'ACCC', 117 | 'Utils', 118 | 'CoreSync', 119 | 'CoreSyncExtension', 120 | 'LiveType', 121 | 'ExchangePlugin', 122 | 'DesignLibraryPlugin', 123 | 'SynKit', 124 | 'CCSyncPlugin', 125 | 'CCLibrary', 126 | 'HomePanel', 127 | 'AssetsPanel', 128 | 'FilesPanel', 129 | 'FontsPanel', 130 | 'MarketPanel', 131 | 'BehancePanel', 132 | 'SPanel', 133 | 'CCXProcess' 134 | ] 135 | 136 | # also remove package 'ADC' from 'ADC' set 137 | 138 | for to_remove in packages_to_remove: 139 | remove_pkg = acc_packageset.find(".//package[name='{}']".format(to_remove)) 140 | if remove_pkg is not None: 141 | self.output('Removing package {}'.format(to_remove)) 142 | self.output(remove_pkg) 143 | acc_packageset.remove(remove_pkg) 144 | else: 145 | self.output('Could not find package "{}" to remove.'.format(to_remove)) 146 | 147 | with open(asu_appinfo_path, 'wb') as fd: 148 | fd.write(ElementTree.tostring(asu_appinfo_root)) 149 | 150 | # 151 | # 152 | # 153 | # AppsPanel 154 | # false 155 | # 156 | # 157 | # SelfServeInstalls 158 | # false 159 | # 160 | # 161 | # 162 | def _addPanelMasking(self, root): 163 | """Disable the apps and updates panels. 164 | 165 | AppsPanel = false 166 | SelfServeInstalls = false 167 | """ 168 | panels = root.findall('.//Configurations/ACCPanelMaskingConfig/config') 169 | 170 | for panel_or_feature in panels: 171 | self.output(panel_or_feature) 172 | return root 173 | 174 | 175 | def _suppressCcda(self, root): 176 | """Suppress the CCDA from being installed.""" 177 | acc = root.find('.//Configurations/SuppressOptions/ACC') 178 | if acc is None: 179 | raise ProcessorError('Expected to find element .//Configurations/SuppressOptions/ACC') 180 | 181 | acc_suppressed = acc.get('suppress') 182 | self.output('Creative Cloud Desktop Application (ACC) suppressed? {}'.format(acc_suppressed)) 183 | 184 | if acc_suppressed == 'true': 185 | self.output('Already suppressed, no changes required.') 186 | else: 187 | self.output('Setting ACC suppress to True') 188 | acc.set('suppress', 'true') 189 | 190 | update = root.find('.//Configurations/SuppressOptions/Update') 191 | if update is None: 192 | raise ProcessorError('Expected to find element .//Configurations/SuppressOptions/Update') 193 | 194 | update_suppressed = update.get('isEnabled') 195 | self.output('User initiated updates suppressed? {}'.format(update_suppressed)) 196 | 197 | if update_suppressed == '1': 198 | self.output('Already suppressed, no changes required.') 199 | else: 200 | self.output('Setting Update isEnabled to 0') 201 | update.set('isEnabled', '0') 202 | 203 | aam_info = root.find('.//AAMInfo') 204 | 205 | package_sets = root.find('.//AAMInfo/overrideXML/application/packageSets') 206 | if package_sets is None: 207 | self.output('Need to add overrides for ACC') 208 | self._addOverrides(aam_info) 209 | 210 | return root 211 | 212 | def main(self): 213 | if not os.path.exists(self.env['pkg_path']): 214 | raise ProcessorError('The specified package does not exist: {}'.format(self.env['pkg_path'])) 215 | 216 | option_xml_path = os.path.join(self.env['pkg_path'], 'Contents', 'Resources', 'optionXML.xml') 217 | option_xml = ElementTree.parse(option_xml_path) 218 | root = option_xml.getroot() 219 | 220 | if self.env.get('suppress_ccda', False): 221 | modified_root = self._suppressCcda(root) 222 | modified_root = self._addPanelMasking(modified_root) 223 | 224 | self._removeASUPackages() 225 | 226 | with open(option_xml_path, 'wb') as fd: 227 | fd.write(ElementTree.tostring(modified_root)) 228 | 229 | self.output('OptionXML modified') 230 | 231 | 232 | if __name__ == "__main__": 233 | processor = CreativeCloudBuildModifier() 234 | processor.execute_shell() 235 | -------------------------------------------------------------------------------- /Adobe/CreativeCloudFeed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2016-2017 Mosen/Tim Sutton 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import sys 18 | import os.path 19 | import string 20 | import json 21 | import urllib2 22 | from tempfile import mkdtemp 23 | from urllib import urlencode 24 | from distutils.version import LooseVersion as LV 25 | from xml.etree import ElementTree 26 | 27 | # for debugging 28 | from pprint import pprint 29 | 30 | from autopkglib import Processor, ProcessorError 31 | 32 | __all__ = ["CreativeCloudFeed"] 33 | 34 | AAMEE_URL = 'https://prod-rel-ffc.oobesaas.adobe.com/adobe-ffc-external/aamee/v2/products/all' 35 | BASE_URL = 'https://prod-rel-ffc-ccm.oobesaas.adobe.com/adobe-ffc-external/core/v4/products/all' 36 | CDN_SECURE_URL = 'https://ccmdls.adobe.com' 37 | UPDATE_DESC_URL = 'https://prod-rel-ffc.oobesaas.adobe.com/adobe-ffc-external/core/v1/update/description' 38 | UPDATE_FEED_URL_MAC = 'https://swupmf.adobe.com/webfeed/oobe/aam20/mac/updaterfeed.xml' 39 | HEADERS = {'User-Agent': 'Creative Cloud', 'x-adobe-app-id': 'AUSST_4_0'} 40 | 41 | 42 | class CreativeCloudFeed(Processor): 43 | """Fetch information about product(s) from the Creative Cloud products feed.""" 44 | description = __doc__ 45 | input_variables = { 46 | "ccpinfo": { 47 | "required": True, 48 | "description": "Creative Cloud Packager Product(s) Information", 49 | }, 50 | "channels": { 51 | "required": False, 52 | "default": "ccp_hd_2,sti", 53 | "description": "The update feed channel(s), comma separated. (default is the ccp_hd_2 and sti channels). \ 54 | The first channel will be used to fetch application info", 55 | }, 56 | "platforms": { 57 | "required": False, 58 | "default": "osx10,osx10-64", 59 | "description": "The deployment platform(s), comma separated. (default is osx10,osx10-64)", 60 | }, 61 | "parse_proxy_xml": { 62 | "required": False, 63 | "default": False, 64 | "description": "Fetch and parse the product proxy XML which will set proxy_version in the output" 65 | }, 66 | "fetch_release_notes": { 67 | "required": False, 68 | "default": False, 69 | "description": "Fetch the update release notes in the current language" 70 | }, 71 | "fetch_icon": { 72 | "required": False, 73 | "default": False, 74 | "description": "Fetch the product icon to the cache directory" 75 | }, 76 | "write_product_json": { 77 | "required": False, 78 | "default": True, 79 | "description": "Write a product.json file to the cache directory from the selected product fragment" 80 | } 81 | } 82 | 83 | output_variables = { 84 | "product_info_url": { 85 | "description": "Product main information URL" 86 | }, 87 | "icon_url": { 88 | "description": "Icon download URL for the highest resolution available, normally 96x96." 89 | }, 90 | "version": { 91 | "description": "The full length version" 92 | }, 93 | "display_name": { 94 | "description": "The product full name and major version" 95 | }, 96 | "manifest_url": { 97 | "description": "The URL to the product manifest" 98 | }, 99 | "family": { 100 | "description": "The product family" 101 | }, 102 | "minimum_os_version": { 103 | "description": "The minimum operating system version required to install this package" 104 | }, 105 | "release_notes": { 106 | "description": "The update release notes if fetch_release_notes was true, otherwise empty string" 107 | }, 108 | "icon_path": { 109 | "description": "Path to the downloaded icon, if fetch_icon was true." 110 | }, 111 | "proxy_version": { 112 | "description": "The product version listed in the proxy file, which usually has more digits" 113 | } 114 | } 115 | 116 | def feed_url(self, channels, platforms): 117 | """Build the GET query parameters for the product feed.""" 118 | params = [ 119 | ('payload', 'true'), 120 | ('productType', 'Desktop'), 121 | ('_type', 'json') 122 | ] 123 | for ch in channels: 124 | params.append(('channel', ch)) 125 | 126 | for pl in platforms: 127 | params.append(('platform', pl)) 128 | 129 | return BASE_URL + '?' + urlencode(params) 130 | 131 | def desc_url(self, sapcode, version, platform, language): 132 | """Build the query for fetching an update description""" 133 | params = [ 134 | ('name', sapcode), 135 | ('version', version), 136 | ('platform', platform), 137 | ('language', language) 138 | ] 139 | 140 | return UPDATE_DESC_URL + '?' + urlencode(params) 141 | 142 | def fetch_proxy_data(self, proxy_data_url): 143 | """Fetch the proxy data to get additional information about the product.""" 144 | self.output('Fetching proxy data from {}'.format(proxy_data_url)) 145 | req = urllib2.Request(proxy_data_url, headers=HEADERS) 146 | content = urllib2.urlopen(req).read() 147 | 148 | # Write out the proxy for debugging purposes 149 | with open('{}/proxy.xml'.format(self.env['RECIPE_CACHE_DIR']), 'w+') as fd: 150 | fd.write(content) 151 | 152 | proxy_data = ElementTree.fromstring(content) 153 | return proxy_data 154 | 155 | def fetch_manifest(self, manifest_url): 156 | """Fetch the manifest.xml at manifest_url which contains asset download and proxy data information. 157 | Not all products have a proxy_data element 158 | 159 | :returns A tuple of (manifest, proxy) ElementTree objects 160 | """ 161 | self.output('Fetching manifest.xml from {}'.format(manifest_url)) 162 | req = urllib2.Request(manifest_url, headers=HEADERS) 163 | content = urllib2.urlopen(req).read() 164 | 165 | # Write out the manifest for debugging purposes 166 | with open('{}/manifest.xml'.format(self.env['RECIPE_CACHE_DIR']), 'w+') as fd: 167 | fd.write(content) 168 | 169 | manifest = ElementTree.fromstring(content) 170 | 171 | proxy_data_url_el = manifest.find('asset_list/asset/proxy_data') 172 | if proxy_data_url_el is None: 173 | raise ProcessorError('Could not find proxy data URL in manifest, aborting since your package requires it.') 174 | 175 | proxy_data = self.fetch_proxy_data(proxy_data_url_el.text) 176 | 177 | return manifest, proxy_data 178 | 179 | def fetch_release_notes(self, sapcode, version, platform, language): 180 | """Fetch the update description (release notes). 181 | 182 | This returns with an xml response in the form:: 183 | 184 | en_US 185 | ... 186 | 187 | 188 | It usually contains the specific bugs and features introduced in this version, not a general overview of the 189 | product. 190 | """ 191 | url = self.desc_url(sapcode, version, platform, language) 192 | self.output('Fetching release notes from: {}'.format(url)) 193 | req = urllib2.Request(url, headers=HEADERS) 194 | raw_data = urllib2.urlopen(req).read() 195 | 196 | return raw_data 197 | 198 | def fetch(self, channels, platforms): 199 | """Download the main feed""" 200 | url = self.feed_url(channels, platforms) 201 | self.output('Fetching from feed URL: {}'.format(url)) 202 | 203 | req = urllib2.Request(url, headers=HEADERS) 204 | data = json.loads(urllib2.urlopen(req).read()) 205 | 206 | return data 207 | 208 | def filter_product(self, data, sap_code, base_version, version='latest'): 209 | """Find product information from a feed dump given a single sap_code, base version and optional version.""" 210 | product = {'version': '0.0.1'} 211 | channels = string.split(self.env.get('channels'), ',') 212 | 213 | #12 inputs to ccpinfo dict testing w BridgeCC 214 | for channel in data['channel']: 215 | if channel['name'] not in channels: 216 | continue 217 | 218 | for prod in channel['products']['product']: 219 | if prod['id'] != sap_code: 220 | continue 221 | 222 | if base_version and prod['platforms']['platform'][0]['languageSet'][0].get('baseVersion') != base_version: 223 | continue 224 | 225 | if 'version' not in prod: 226 | self.output('product has no version: {}'.format(prod['displayName'])) 227 | continue 228 | 229 | if version == "latest": 230 | if LV(prod['version']) > LV(product['version']): 231 | product = prod 232 | else: 233 | if prod['version'] == version: 234 | product = prod 235 | 236 | if 'platforms' not in product: 237 | return None 238 | 239 | return product 240 | 241 | def fetch_extended_product_info(self, product, platform, cdn): 242 | """Fetch extended information about a product such as: manifest, 243 | proxy (if available), release notes, and icon""" 244 | extended_info = {} 245 | channels = string.split(self.env.get('channels'), ',') 246 | 247 | # Fetch Icon 248 | if 'productIcons' in product: 249 | largest_width = 0 250 | largest_icon_url = None 251 | for icon in product['productIcons'].get('icon', []): 252 | self.output('Considering icon: {}'.format(icon)) 253 | w, h = icon.get('size', '0x0').split('x', 2) 254 | if int(w) > largest_width: 255 | largest_width = int(w) 256 | largest_icon_url = icon.get('value') 257 | 258 | self.env['icon_url'] = largest_icon_url 259 | 260 | if 'icon_url' in self.env and self.env.get('fetch_icon', 'false').lower() == 'true': 261 | self.output('Fetching icon from {}'.format(self.env['icon_url'])) 262 | req = urllib2.Request(self.env['icon_url'], headers=HEADERS) 263 | content = urllib2.urlopen(req).read() 264 | 265 | with open('{}/Icon.png'.format(self.env['RECIPE_CACHE_DIR']), 'w+') as fd: 266 | fd.write(content) 267 | 268 | extended_info['icon_path'] = '{}/Icon.png'.format(self.env['RECIPE_CACHE_DIR']) 269 | else: 270 | self.output('An icon was not requested or the url did not exist.') 271 | extended_info['icon_path'] = '' 272 | 273 | # Fetch Manifest + Proxy 274 | if 'urls' in platform['languageSet'][0] and 'manifestURL' in platform['languageSet'][0]['urls']: 275 | extended_info['manifest_url'] = '{}{}'.format( 276 | cdn[channels[0]]['secure'], 277 | platform['languageSet'][0]['urls'].get('manifestURL') 278 | ) 279 | 280 | if self.env.get('parse_proxy_xml', False): 281 | self.output('Processor will fetch manifest and proxy xml') 282 | manifest, proxy = self.fetch_manifest(extended_info['manifest_url']) 283 | product_version_el = proxy.find('InstallerProperties/Property[@name="ProductVersion"]') 284 | if product_version_el is None: 285 | raise ProcessorError('Could not find ProductVersion in proxy data, aborting.') 286 | else: 287 | self.output('Found version in proxy.xml: {}'.format(product_version_el.text)) 288 | extended_info['proxy_version'] = product_version_el.text 289 | else: 290 | extended_info['proxy_version'] = '' 291 | else: 292 | self.output('Did not find a manifest.xml in the product json data') 293 | 294 | # Fetch Release Notes 295 | if self.env.get('fetch_release_notes', 'false').lower() == 'true': 296 | self.output('Processor will fetch update release notes') 297 | desc = self.fetch_release_notes(product['id'], product['version'], 'osx10-64', 'en_US') 298 | rn_etree = ElementTree.fromstring(desc) 299 | release_notes_el = rn_etree.find('UpdateDescription') 300 | 301 | if release_notes_el is None: 302 | raise ProcessorError('Could not find UpdateDescription in release notes') 303 | 304 | extended_info['release_notes'] = release_notes_el.text 305 | else: 306 | extended_info['release_notes'] = '' 307 | 308 | return extended_info 309 | 310 | def cache_product_info(self, input_product, output_product): 311 | """Cache the feed result (outputProduct) based on parameters specified in inputProduct.""" 312 | self.env['download_changed'] = True 313 | cache_json_path = '{0}/{1}_{2}_{3}.json'.format( 314 | self.env['RECIPE_CACHE_DIR'], 315 | input_product['sapCode'], 316 | input_product.get('baseVersion', ''), 317 | input_product.get('version', 'latest') 318 | ) 319 | 320 | # Check against last result if available 321 | if os.path.exists(cache_json_path): 322 | with open(cache_json_path, 'r') as fd: 323 | content = fd.read() 324 | data = json.loads(content) 325 | if data.get('version', '') == output_product.get('version'): 326 | self.output('The feed version matches the last fetched version, no download is required') 327 | self.env['download_changed'] = False 328 | else: 329 | self.output('The feed version has changed from the last fetch, download is required') 330 | 331 | # Feed processor uses this to detect whether there is a newer feed version. 332 | if self.env.get('write_product_json', True): 333 | self.output('Caching product information to {}'.format(cache_json_path)) 334 | with open(cache_json_path, 'w+') as fd: 335 | fd.write(json.dumps(output_product)) 336 | 337 | def validate_input(self): 338 | """Validate processor inputs""" 339 | if 'ccpinfo' not in self.env: 340 | raise ProcessorError('No ccpinfo dict supplied to CreativeCloudFeed. Please check your recipe.') 341 | 342 | ccpinfo = self.env['ccpinfo'] 343 | if 'Products' not in ccpinfo or len(ccpinfo['Products']) == 0: 344 | raise ProcessorError('ccpinfo does not specify any products. Please check your recipe.') 345 | 346 | for prod in ccpinfo['Products']: 347 | if 'sapCode' not in prod: 348 | raise ProcessorError('ccpinfo product did not contain a SAP Code') 349 | 350 | 351 | def main(self): 352 | ccpinfo = self.env['ccpinfo'] 353 | channels = string.split(self.env.get('channels'), ',') 354 | platforms = string.split(self.env.get('platforms'), ',') 355 | 356 | data = self.fetch(channels, platforms) 357 | self.validate_input() 358 | 359 | channel_cdn = {} 360 | for channel in data['channel']: 361 | if channel['name'] in channels: 362 | channel_cdn[channel['name']] = channel.get('cdn') 363 | 364 | # Resolve actual build versions from the feed 365 | products = [] 366 | for product_info in ccpinfo['Products']: 367 | sapcode = product_info['sapCode'] 368 | baseversion = product_info.get('baseVersion', '') 369 | version = product_info.get('version', 'latest') 370 | 371 | product = self.filter_product(data, sapcode, baseversion, version) 372 | if product is None: 373 | raise ProcessorError( 374 | 'No package matched the SAP code ({0}), base version ({1}), ' 375 | 'and version ({2}) combination you specified.'.format(sapcode, baseversion, version) 376 | ) 377 | 378 | self.output('Found matching product {}, version: {}'.format(product.get('displayName'), 379 | product.get('version'))) 380 | 381 | product_info['version'] = product['version'] 382 | product_info['requestedVersion'] = version 383 | products.append(product) 384 | self.cache_product_info(product_info, product) 385 | 386 | if len(products) == 1: # Single product, will use normal version, notes, icon 387 | product = products[0] 388 | 389 | first_platform = {} 390 | for platform in product['platforms']['platform']: 391 | if platform['id'] in platforms: 392 | first_platform = platform 393 | break 394 | 395 | if first_platform.get('packageType') == 'RIBS': 396 | raise ProcessorError('This process does not support RIBS style packages.') 397 | 398 | if len(first_platform['systemCompatibility']['operatingSystem']['range']) > 0: 399 | compatibility_range = first_platform['systemCompatibility']['operatingSystem']['range'][0] 400 | # systemCompatibility currently has values like: 401 | # 10.x.0- 402 | # 10.10- (no minor version specified) 403 | # (empty array) 404 | self.env['minimum_os_version'] = compatibility_range.split('-')[0] 405 | else: 406 | # hacky workaround to avoid packager bailing when there is no minimum os version 407 | self.env['minimum_os_version'] = '' 408 | 409 | # output variable naming has been kept as close to pkginfo names as possible in order to feed munkiimport 410 | self.env['product_info_url'] = product.get('productInfoPage') 411 | self.env['version'] = product.get('version') 412 | self.env['display_name'] = product.get('displayName') 413 | 414 | extended_info = self.fetch_extended_product_info(product, first_platform, channel_cdn) 415 | for k, v in extended_info.items(): 416 | self.env[k] = v 417 | 418 | else: # more than one product: indeterminate version, concatenated notes, no icon 419 | raise ProcessorError('Multi product packages not yet supported') 420 | 421 | 422 | if __name__ == "__main__": 423 | processor = CreativeCloudFeed() 424 | processor.execute_shell() 425 | -------------------------------------------------------------------------------- /Adobe/CreativeCloudPackager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2016-2017 Mosen/Tim Sutton 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # pylint: disable=locally-disabled, import-error, invalid-name, no-member 18 | 19 | # for now 20 | # pylint: disable=line-too-long 21 | 22 | import os 23 | import shutil 24 | import subprocess 25 | import uuid 26 | import FoundationPlist 27 | 28 | from xml.etree import ElementTree 29 | from Foundation import CFPreferencesCopyAppValue, CFPreferencesSetAppValue 30 | 31 | from autopkglib import Processor, ProcessorError 32 | 33 | __all__ = ["CreativeCloudPackager"] 34 | 35 | # https://helpx.adobe.com/creative-cloud/packager/ccp-automation.html 36 | # 37 | # https://github.com/timsutton/adobe-ccp-automation/blob/master/ccp_auto 38 | 39 | CUSTOMER_TYPES = ["enterprise", "team"] 40 | CCP_PREFS_FILE = os.path.expanduser( 41 | "~/Library/Application Support/Adobe/CCP/CCPPreferences.xml") 42 | 43 | CCP_ERROR_MSGS = { 44 | "CustomerTypeMismatchError": \ 45 | ("Please check that your organization is of the correct " 46 | "type, one of ({}).".format(', '.join(CUSTOMER_TYPES))), 47 | "TronWelcomeInputValidationError": \ 48 | ("Please check that your organizationName matches one to which your " 49 | "CCP-signed-in user ""belongs."), 50 | "TronSerialNumberValidationError": \ 51 | ("Serial number validation failed."), 52 | } 53 | 54 | # Note: TronWelcomeInputValidationError can also happen if a file or folder 55 | # already exists at the package path given. Sample result output in that case: 56 | # 57 | # 58 | # 2 59 | # false 60 | # TronWelcomeInputValidationError 61 | # 62 | # 63 | 64 | # Note: if the user supplies an incorrect SAP Code or a product that cannot be packaged individually (like AAM) 65 | # you will receive this error: 66 | # 67 | # 68 | # 3 69 | # false 70 | # productNotFound 71 | # 72 | # 73 | 74 | class CreativeCloudPackager(Processor): 75 | """Create and execute a CCP automation file. The package output will always be the autopkg cache directory""" 76 | description = "Runs the CCP packager." 77 | input_variables = { 78 | "ccpinfo": { 79 | "required": True, 80 | "description": "Creative Cloud Packager Product(s) Information", 81 | }, 82 | "package_name": { 83 | "required": True, 84 | "description": "The output package name", 85 | }, 86 | } 87 | 88 | output_variables = { 89 | "pkg_path": { 90 | "description": "Path to the built bundle-style CCP installer pkg.", 91 | }, 92 | "uninstaller_pkg_path": { 93 | "description": "Path to the built bundle-style CCP uninstaller pkg.", 94 | }, 95 | "ccp_path": { 96 | "description": "Path to the .ccp file output from the build process." 97 | }, 98 | "package_info_text": { 99 | "description": "Text notes about which packages and updates are included in the pkg." 100 | }, 101 | "ccp_version": { 102 | "description": "Version of CCP tools used to build the package." 103 | }, 104 | "creative_cloud_packager_summary_result": { 105 | "description": "Description of interesting results." 106 | }, 107 | } 108 | 109 | def ccp_preferences(self): 110 | """Get information about the currently signed-in CCP user, if available.""" 111 | prefs_path = os.path.expanduser(CCP_PREFS_FILE) 112 | prefs_elem = ElementTree.parse(prefs_path).getroot() 113 | 114 | prefs = {} 115 | user_type_elem = prefs_elem.find('AAMEEPreferences/Preference/Screen/userType') 116 | if user_type_elem is not None: 117 | # convert 'FOO_CUSTOMER_TYPE' into 'foo' 118 | prefs["customer_type"] = user_type_elem.text.lower().split('_')[0] 119 | return prefs 120 | 121 | def automation_xml(self): 122 | """Returns the complete pretty-formatted XML string for a CCP automation 123 | session.""" 124 | # params = self.automation_manifest_from_ccpinfo() 125 | params = dict(self.env['ccpinfo']) 126 | 127 | # 1. If you specified IncludeUpdates then that value will override everything. 128 | # 2. If any specified product has version 'latest' then IncludeUpdates is true, and CCP will fetch the latest 129 | # available product update. 130 | # 3. Otherwise, You only get the baseVersion you entered. 131 | if 'IncludeUpdates' not in params: 132 | if any([product.get('requestedVersion', None) == 'latest' for product in params['Products']]): 133 | self.output("At least one product has requested version 'latest'. This means we are enabling IncludeUpdates for CCP") 134 | params['IncludeUpdates'] = True 135 | else: 136 | params['IncludeUpdates'] = False 137 | 138 | # add additional parameters for which there's no need for the user to 139 | # supply in the 'ccpinfo' input 140 | params.update({ 141 | 'packageName': self.env['package_name'], 142 | 'outputLocation': self.env['RECIPE_CACHE_DIR'], 143 | 'packaging_job_id': str(uuid.uuid4()), 144 | 'is64Bit': True, 145 | }) 146 | 147 | # if params.get('serial_number') and scrub_serial: 148 | # params['serial_number'] = 'REDACTED' 149 | 150 | # Begin assembling XML Element 151 | pkg_elem = ElementTree.Element('CreatePackage') 152 | # add some hardcoded elements 153 | category = ElementTree.Element('ProductCategory') 154 | category.text = 'Custom' 155 | pkg_elem.append(category) 156 | # language - must be included even if matchOSLanguage is true 157 | 158 | lang = ElementTree.Element('Language') 159 | lang.append(ElementTree.Element('id')) 160 | lang.find('id').text = params['Language'] 161 | pkg_elem.append(lang) 162 | del params['Language'] 163 | 164 | # Input keys now match the target XML to avoid transforming 165 | for param, value in params.iteritems(): 166 | if param == 'Products': 167 | continue 168 | 169 | elem = ElementTree.Element(param) 170 | if isinstance(value, bool): 171 | value = str(value).lower() 172 | elem.text = value 173 | 174 | pkg_elem.append(elem) 175 | 176 | # Products 177 | products = ElementTree.Element('Products') 178 | for prod in params['Products']: 179 | product = ElementTree.Element('Product') 180 | sap = ElementTree.Element('sapCode') 181 | sap.text = prod['sapCode'] 182 | ver = ElementTree.Element('version') 183 | ver.text = prod['baseVersion'] 184 | product.append(sap) 185 | product.append(ver) 186 | products.append(product) 187 | pkg_elem.append(products) 188 | 189 | # # serial 190 | # if params.get('serialNumber'): 191 | # self.output('Adding serial number to ccp_automation xml') 192 | # serial = ElementTree.Element('serialNumber') 193 | # serial.text = params['serial_number'] 194 | # pkg_elem.append(serial) 195 | 196 | xml_root = ElementTree.Element('CCPPackage') 197 | xml_root.append(pkg_elem) 198 | 199 | # run it through `xmllint --format` just to save it pretty :/ 200 | xml_string = ElementTree.tostring(xml_root, encoding='utf8', method='xml') 201 | proc = subprocess.Popen(['/usr/bin/xmllint', '--format', '-'], 202 | stdin=subprocess.PIPE, 203 | stdout=subprocess.PIPE) 204 | out, err = proc.communicate(xml_string) 205 | if proc.returncode: 206 | raise ProcessorError("Unexpected error from running XML output through " 207 | "xmllint. Stderr output:\n%s" % err) 208 | return out 209 | 210 | def set_customer_type(self, ccpinfo): 211 | # Set the customer type, using CCP's preferences if none provided 212 | if not ccpinfo.get("customerType"): 213 | ccp_prefs = self.ccp_preferences() 214 | self.env['customer_type'] = ccp_prefs.get("customer_type") 215 | if not self.env.get("customer_type"): 216 | raise ProcessorError( 217 | "No customer_type input provided and unable to read one " 218 | "from %s" % CCP_PREFS_FILE) 219 | self.output("Using customer type '%s' found in CCPPreferences: %s'" 220 | % (self.env['customer_type'], CCP_PREFS_FILE)) 221 | 222 | def is_ccp_running(self): 223 | """Determine whether CCP is already running. This would prevent us from actually running the automation XML.""" 224 | status = subprocess.call(['/usr/bin/pgrep', '-q', 'PDApp']) 225 | return status == 0 226 | 227 | def check_and_disable_appnap_for_pdapp(self): 228 | """Log a warning if AppNap isn't disabled on the system.""" 229 | appnap_disabled = CFPreferencesCopyAppValue( 230 | 'NSAppSleepDisabled', 231 | 'com.adobe.PDApp') 232 | if not appnap_disabled: 233 | self.output("WARNING: A bug in Creative Cloud Packager makes " 234 | "it likely to stall indefinitely whenever the app " 235 | "window is hidden or obscured due to App Nap. To " 236 | "prevent this, we're setting a user preference to " 237 | "disable App Nap for just the " 238 | "Adobe PDApp application. This can be undone using " 239 | "this command: 'defaults delete com.adobe.PDApp " 240 | "NSAppSleepDisabled") 241 | CFPreferencesSetAppValue( 242 | 'NSAppSleepDisabled', 243 | True, 244 | 'com.adobe.PDApp') 245 | 246 | def validate_input(self): 247 | """Validate input variables will produce something meaningful.""" 248 | ccpinfo = self.env['ccpinfo'] 249 | 250 | if 'Products' not in ccpinfo or len(ccpinfo['Products']) == 0: 251 | raise ProcessorError('ccpinfo does not specify any products. Please check your recipe.') 252 | 253 | for prod in ccpinfo['Products']: 254 | if 'sapCode' not in prod: 255 | raise ProcessorError('ccpinfo product did not contain a SAP Code') 256 | 257 | if 'organizationName' not in ccpinfo or ccpinfo['organizationName'] == 'ADMIN_PLEASE_CHANGE': 258 | raise ProcessorError('No organization name specified in recipe.') 259 | 260 | if ccpinfo['customerType'] not in CUSTOMER_TYPES: 261 | raise ProcessorError( 262 | "customerType input variable must be one of : %s" % 263 | ", ".join(CUSTOMER_TYPES)) 264 | 265 | if ccpinfo['customerType'] != 'enterprise' and ccpinfo.get('serialNumber'): 266 | raise ProcessorError( 267 | ("Serial number was given, but serial numbers are only for " 268 | "use with 'enterprise' customer types.")) 269 | 270 | def check_ccda_installed(self): 271 | """Check and raise a ProcessorError if the CCDA is installed, as CCP should 272 | never build packages on a system with the CCDA installed""" 273 | ccda_path = '/Applications/Utilities/Adobe Creative Cloud/ACC/Creative Cloud.app' 274 | if os.path.isdir(ccda_path): 275 | raise ProcessorError( 276 | ("The Adobe Creative Cloud Desktop App was detected at '%s'. " 277 | "This recipe will only run on systems without it installed, " 278 | "as it can otherwise cause major issues with built packages. " 279 | "It can be uninstalled using the Uninstaller located at " 280 | "'/Applications/Utilities/Adobe Creative Cloud'.") % ccda_path) 281 | 282 | def main(self): 283 | if self.env.get("ALLOW_CCDA_INSTALLED", False): 284 | self.check_ccda_installed() 285 | 286 | # establish some of our expected build paths 287 | expected_output_root = os.path.join(self.env["RECIPE_CACHE_DIR"], self.env["package_name"]) 288 | self.env["pkg_path"] = os.path.join(expected_output_root, "Build/%s_Install.pkg" % self.env["package_name"]) 289 | self.env["uninstaller_pkg_path"] = os.path.join(expected_output_root, 290 | "Build/%s_Uninstall.pkg" % self.env["package_name"]) 291 | 292 | saved_automation_xml_path = os.path.join(expected_output_root, 293 | '.ccp_automation_input.xml') 294 | automation_manifest_plist_path = os.path.join(expected_output_root, 295 | '.autopkg_manifest.plist') 296 | self.set_customer_type(self.env['ccpinfo']) 297 | 298 | # ccpinfo Needs pre-processing before comparison to old run 299 | new_manifest = self.automation_xml() 300 | 301 | # Handle any pre-existing package at the expected location, and end early if it matches our 302 | # input manifest 303 | if os.path.exists(automation_manifest_plist_path): 304 | existing_manifest = FoundationPlist.readPlist(automation_manifest_plist_path) 305 | self.output("Found existing CCP package build automation info, comparing") 306 | self.output("existing build: %s" % existing_manifest) 307 | self.output("current build: %s" % new_manifest) 308 | if new_manifest == existing_manifest: 309 | self.output("Returning early because we have an existing package " 310 | "with the same parameters.") 311 | return 312 | 313 | # Going forward with building, set up or clear needed directories 314 | xml_workdir = os.path.join(self.env["RECIPE_CACHE_DIR"], 'automation_xml') 315 | if not os.path.exists(xml_workdir): 316 | os.mkdir(xml_workdir) 317 | if os.path.isdir(expected_output_root): 318 | shutil.rmtree(expected_output_root) 319 | 320 | # using .xml as a suffix because CCP's automation mode creates a '_results.xml' file with the assumption 321 | # that the input ends in '.xml' 322 | xml_path = os.path.join(xml_workdir, 'ccp_automation_%s.xml' % self.env['package_name']) 323 | with open(xml_path, 'w') as fd: 324 | fd.write(new_manifest) 325 | 326 | if self.is_ccp_running(): 327 | raise ProcessorError( 328 | "You cannot start a Creative Cloud Packager automation workflow " + 329 | "if Creative Cloud Packager is already running. Please quit CCP and start this recipe again." 330 | ) 331 | 332 | self.check_and_disable_appnap_for_pdapp() 333 | 334 | cmd = [ 335 | '/Applications/Utilities/Adobe Application Manager/core/Adobe Application Manager.app/Contents/MacOS/PDApp', 336 | '--appletID=CCP_UI', 337 | '--appletVersion=1.0', 338 | '--workflow=ccp', 339 | '--automationMode=ccp_automation', 340 | '--pkgConfigFile=%s' % xml_path] 341 | self.output("Executing CCP build command: %s" % " ".join(cmd)) 342 | proc = subprocess.Popen(cmd, 343 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 344 | out, _ = proc.communicate() 345 | if out: 346 | self.output("CCP Output: %s" % out) 347 | exitcode = proc.returncode 348 | self.output("CCP Exited with status {}".format(exitcode)) 349 | 350 | results_file = os.path.join(os.path.dirname(xml_path), os.path.splitext(xml_path)[0] + '_result.xml') 351 | results_elem = ElementTree.parse(results_file).getroot() 352 | if results_elem.find('error') is not None: 353 | # Build an AutoPkg error message with help to diagnose 354 | # possible build failures 355 | autopkg_error_msg = "CCP package build reported failure.\n" 356 | err_msg_type = results_elem.find('error/errorMessage') 357 | if err_msg_type is not None: 358 | autopkg_error_msg += "Error type: '%s' - " % err_msg_type.text 359 | if err_msg_type.text in CCP_ERROR_MSGS: 360 | autopkg_error_msg += CCP_ERROR_MSGS[err_msg_type.text] + "\n" 361 | autopkg_error_msg += ( 362 | "Please inspect the PDApp log file at: %s. 'results' XML file " 363 | "contents follow: \n%s" % ( 364 | os.path.expanduser("~/Library/Logs/PDApp.log"), 365 | open(results_file, 'r').read())) 366 | 367 | raise ProcessorError(autopkg_error_msg) 368 | 369 | if results_elem.find('success') is None: 370 | raise ProcessorError( 371 | "Unexpected result from CCP, 'results' XML file contents follow: \n{}".format( 372 | open(results_file, 'r').read() 373 | ) 374 | ) 375 | 376 | # Sanity-check that we really do have our install package! 377 | if not os.path.exists(self.env["pkg_path"]): 378 | raise ProcessorError( 379 | "CCP exited successfully, but no expected installer package " 380 | "at %s exists." % self.env["pkg_path"]) 381 | 382 | # Save both the automation XML for posterity and our manifest plist for 383 | # later comparison 384 | shutil.copy(xml_path, saved_automation_xml_path) 385 | # TODO: we aren't scrubbing the automation XML file at all 386 | FoundationPlist.writePlist( 387 | self.env['ccpinfo'], 388 | automation_manifest_plist_path) 389 | 390 | # Save PackageInfo.txt 391 | packageinfo = os.path.join(expected_output_root, "PackageInfo.txt") 392 | if os.path.exists(packageinfo): 393 | self.env["package_info_text"] = open(packageinfo, 'r').read() 394 | 395 | ccp_path = os.path.join(expected_output_root, 'Build/{}.ccp'.format(self.env["package_name"])) 396 | if os.path.exists(ccp_path): 397 | self.env["ccp_path"] = ccp_path 398 | 399 | option_xml_root = ElementTree.parse(os.path.join( 400 | self.env["pkg_path"], 'Contents/Resources/optionXML.xml')).getroot() 401 | 402 | # Save the CCP build version 403 | self.env["ccp_version"] = "" 404 | ccp_version = option_xml_root.find("prodVersion") 405 | if ccp_version is None: 406 | self.output( 407 | "WARNING: Didn't find expected 'prodVersion' key (CCP " 408 | "version) in optionXML.xml") 409 | self.env["ccp_version"] = ccp_version.text 410 | 411 | if len(self.env['ccpinfo']['Products']) == 1: 412 | built_products = self.env['ccpinfo']['Products'][0]['sapCode'] 413 | else: 414 | built_products = '(multiple)' 415 | self.env["creative_cloud_packager_summary_result"] = { 416 | 'summary_text': 'The following CCP packages were built:', 417 | 'report_fields': ['display_name', 'product_id', 'version', 'pkg_path'], 418 | 'data': { 419 | 'display_name': self.env['display_name'], 420 | 'product_id': built_products, 421 | 'version': self.env['version'], 422 | 'pkg_path': self.env['pkg_path'], 423 | } 424 | } 425 | 426 | if __name__ == "__main__": 427 | processor = CreativeCloudPackager() 428 | processor.execute_shell() 429 | -------------------------------------------------------------------------------- /Adobe/CreativeCloudVersioner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2017 Mosen/Tim Sutton/Macmule 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # pylint: disable=locally-disabled, import-error, invalid-name, no-member 18 | 19 | # for now 20 | # pylint: disable=line-too-long 21 | 22 | import json 23 | import os 24 | import re 25 | import zipfile 26 | 27 | from xml.etree import ElementTree 28 | 29 | import FoundationPlist 30 | from autopkglib import Processor, ProcessorError 31 | 32 | __all__ = ["CreativeCloudVersioner"] 33 | 34 | 35 | class CreativeCloudVersioner(Processor): 36 | """Parses generated CCP installers for detailed application path and bundle 37 | version info, for use in Munki installs info and JSS application inventory 38 | info for Smart Group templates. 'version' is used to store the bundle version 39 | because the JSS recipe uses app inventory version info for the Smart Group 40 | criteria""" 41 | description = __doc__ 42 | input_variables = { 43 | 44 | } 45 | 46 | output_variables = { 47 | "additional_pkginfo": { 48 | "description": 49 | "Some pkginfo fields extracted from the Adobe metadata.", 50 | }, 51 | "jss_inventory_name": { 52 | "description": "Application title for jamf pro smart group criteria.", 53 | }, 54 | "user_facing_version": { 55 | "description": ("The version which would be seen in the application's About window, " 56 | "and which is referred to in documentation and marketing materials."), 57 | }, 58 | "version": { 59 | "description": ("The value of CFBundleShortVersionString for the app bundle. " 60 | "This may match user_facing_version, but it may also be more " 61 | "specific and add another version component."), 62 | }, 63 | } 64 | 65 | def main(self): 66 | """ 67 | Determine a pkginfo, version and jss inventory name from the created package. 68 | 69 | Inputs: 70 | ccpinfo: The CCPInfo dict which was included in the original recipe. 71 | version: The desired version 72 | Outputs: 73 | user_facing_version: The version which would be seen in the application's About window. 74 | prod: The CCPInfo products dictionary 75 | sapCode: The SAP Code of the product 76 | ccpVersion: 77 | app_json: The path of the Application.json file that CCP produced as part of the build process 78 | 79 | """ 80 | ccpinfo = self.env["ccpinfo"] 81 | # 'version' contains that which was extracted from the feed, which is 82 | # actually what we want as a user-facing version, so just grab it 83 | # immediately 84 | self.env["user_facing_version"] = self.env["version"] 85 | self.env["prod"] = ccpinfo["Products"] 86 | self.env["sapCode"] = self.env["prod"][0]["sapCode"] 87 | self.output("sapCode: %s" % self.env["sapCode"]) 88 | self.env["ccpVersion"] = self.env["prod"][0]["version"] 89 | self.output("ccpVersion: %s" % self.env["ccpVersion"]) 90 | self.env["app_json"] = os.path.join(self.env["pkg_path"], "Contents/Resources/HD", self.env["sapCode"] + self.env["ccpVersion"], "Application.json") 91 | # If Application.json exists, we"re looking at a HD installer 92 | if os.path.exists(self.env["app_json"]): 93 | self.output("Installer is HyperDrive") 94 | self.output("app_json: %s" % self.env["app_json"]) 95 | self.process_hd_installer() 96 | else: 97 | self.output("Installer is RIBS, since path does not exist: {}".format(self.env["app_json"])) 98 | # If not a HD installer 99 | # Legacy Installers: PKG"s but for old titles 100 | # RIBS: SPGD, LTRM, FLBR, KETK 101 | # If the above get moved to HD installs, won"t hit this. 102 | # Acrobat is a "current" title with a PKG installer we can extract needed 103 | # metadata from 104 | if self.env["sapCode"] != "APRO": 105 | self.process_ribs_installer(self.env['pkg_path'], sap_code_hint=self.env['sapCode']) 106 | else: 107 | self.env["proxy_xml"] = os.path.join(self.env["pkg_path"], "Contents/Resources/Setup", self.env["sapCode"] + self.env["ccpVersion"], "proxy.xml") 108 | if not os.path.exists(self.env["proxy_xml"]): 109 | raise ProcessorError("APRO selected, proxy.xml not found at %s" % self.env["proxy_xml"]) 110 | else: 111 | self.process_apro_installer() 112 | 113 | def process_apro_installer(self): 114 | """ Process APRO installer """ 115 | self.output("Processing Acrobat installer") 116 | self.output("proxy_xml: %s" % self.env["proxy_xml"]) 117 | tree = ElementTree.parse(self.env["proxy_xml"]) 118 | root = tree.getroot() 119 | 120 | app_bundle_text = root.findtext("./ThirdPartyComponent/Metadata/Properties/Property[@name='path']") 121 | app_bundle = app_bundle_text.split('/')[1] 122 | self.output("app_bundle: %s" % app_bundle) 123 | 124 | app_path_text = root.findtext('./InstallDir/Platform') 125 | self.output(app_path_text) 126 | app_path = app_path_text.split('/')[1] 127 | self.output("app_path: %s" % app_path) 128 | 129 | installed_path = os.path.join("/Applications", app_path, app_bundle) 130 | self.output("installed_path: %s" % installed_path) 131 | 132 | app_version = root.findtext('./InstallerProperties/Property[@name="ProductVersion"]') 133 | self.output("app_version: %s" % app_version) 134 | 135 | # Now we have the deets, let"s use them 136 | self.create_pkginfo(app_bundle, app_version, installed_path) 137 | 138 | def process_hd_installer(self): 139 | """Process HD installer 140 | 141 | Inputs: 142 | app_json: Path to the Application JSON that was extracted from the feed. 143 | """ 144 | self.output("Processing HD installer") 145 | with open(self.env["app_json"]) as json_file: 146 | load_json = json.load(json_file) 147 | 148 | # AppLaunch is not always in the same format, but is splittable 149 | if 'AppLaunch' in load_json: # Bridge CC is HD but does not have AppLaunch 150 | app_launch = load_json["AppLaunch"] 151 | self.output("app_launch: %s" % app_launch) 152 | app_details = list(re.split("/", app_launch)) 153 | if app_details[2].endswith(".app"): 154 | app_bundle = app_details[2] 155 | app_path = app_details[1] 156 | else: 157 | app_bundle = app_details[1] 158 | app_path = list(re.split("/", (load_json["InstallDir"]["value"])))[1] 159 | self.output("app_bundle: %s" % app_bundle) 160 | self.output("app_path: %s" % app_path) 161 | 162 | installed_path = os.path.join("/Applications", app_path, app_bundle) 163 | self.output("installed_path: %s" % installed_path) 164 | 165 | zip_file = load_json["Packages"]["Package"][0]["PackageName"] 166 | self.output("zip_file: %s" % zip_file) 167 | 168 | zip_path = os.path.join(self.env["pkg_path"], "Contents/Resources/HD", self.env["sapCode"] + self.env["ccpVersion"], zip_file + ".zip") 169 | self.output("zip_path: %s" % zip_path) 170 | with zipfile.ZipFile(zip_path, mode="r") as myzip: 171 | with myzip.open(zip_file + ".pimx") as mytxt: 172 | txt = mytxt.read() 173 | tree = ElementTree.fromstring(txt) 174 | # Loop through .pmx's Assets, look for target=[INSTALLDIR], then grab Assets Source. 175 | # Break when found .app/Contents/Info.plist 176 | for elem in tree.findall("Assets"): 177 | for i in elem.getchildren(): 178 | if i.attrib["target"].upper().startswith("[INSTALLDIR]"): 179 | bundle_location = i.attrib["source"] 180 | else: 181 | continue 182 | if not bundle_location.startswith("[StagingFolder]"): 183 | continue 184 | else: 185 | bundle_location = bundle_location[16:] 186 | if bundle_location.endswith(".app"): 187 | zip_bundle = os.path.join("1", bundle_location, "Contents/Info.plist") 188 | else: 189 | zip_bundle = os.path.join("1", bundle_location, app_bundle, "Contents/Info.plist") 190 | try: 191 | with myzip.open(zip_bundle) as myplist: 192 | plist = myplist.read() 193 | data = FoundationPlist.readPlistFromString(plist) 194 | app_version = data["CFBundleShortVersionString"] 195 | #app_identifier = data["CFBundleIdentifier"] 196 | self.output("staging_folder: %s" % bundle_location) 197 | self.output("staging_folder_path: %s" % zip_bundle) 198 | self.output("app_version: %s" % app_version) 199 | self.output("app_bundle: %s" % app_bundle) 200 | #self.output("app_identifier: %s" % app_identifier) 201 | break 202 | except: 203 | continue 204 | 205 | # Now we have the deets, let's use them 206 | self.create_pkginfo(app_bundle, app_version, installed_path) 207 | 208 | def process_ribs_installer(self, pkg_path, sap_code_hint=None): 209 | """Extract version number of RIBS based package. 210 | 211 | Args: 212 | pkg_path (str): Path to the package that was produced 213 | sap_code_hint (str): hint the sap code of the "main" package, to extract the version from. 214 | """ 215 | option_xml_path = os.path.join(pkg_path, 'Contents', 'Resources', 'optionXML.xml') 216 | # ribs_root = os.path.join(pkg_path, 'Contents', 'Resources', 'Setup') 217 | 218 | option_xml = ElementTree.parse(option_xml_path) 219 | main_media = None 220 | for media in option_xml.findall('.//Medias/Media'): # Media refers to RIBS media only. HD is in HDMedia 221 | if media.findtext('SAPCode') == sap_code_hint: 222 | main_media = media 223 | break 224 | 225 | if main_media is None: 226 | raise ProcessorError('Could not find main RIBS package indicated by SAP Code {}'.format(sap_code_hint)) 227 | 228 | # media_path = os.path.join(ribs_root, main_media.findtext('TargetFolderName')) 229 | # if not os.path.exists(media_path): 230 | # raise ProcessorError('Could not find Media for RIBS package in path: {}'.format(media_path)) 231 | 232 | self.create_pkginfo('NOT_SUPPORTED', main_media.findtext('prodVersion'), '') 233 | 234 | def create_pkginfo(self, app_bundle, app_version, installed_path): 235 | """Create pkginfo with found details 236 | 237 | Args: 238 | app_bundle (str): Bundle name 239 | app_version (str): Bundle version 240 | installed_path (str): The path where the installed item will be installed. 241 | """ 242 | self.env["version"] = app_version 243 | self.env["jss_inventory_name"] = app_bundle 244 | 245 | pkginfo = { 246 | 'display_name': self.env["display_name"], 247 | 'minimum_os_version': self.env["minimum_os_version"] 248 | } 249 | 250 | # Allow the user to provide an installs array that prevents CreativeCloudVersioner from overriding it. 251 | if 'pkginfo' not in self.env or 'installs' not in self.env['pkginfo']: 252 | pkginfo['installs'] = [{ 253 | 'CFBundleShortVersionString': self.env['version'], 254 | 'path': installed_path, 255 | 'type': 'application', 256 | 'version_comparison_key': 'CFBundleShortVersionString', 257 | }] 258 | 259 | self.env["additional_pkginfo"] = pkginfo 260 | self.output("additional_pkginfo: %s" % self.env["additional_pkginfo"]) 261 | 262 | 263 | if __name__ == "__main__": 264 | processor = CreativeCloudVersioner() 265 | -------------------------------------------------------------------------------- /Adobe/PolicyTemplate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Install Latest %PROD_NAME% 4 | true 5 | Ongoing 6 | 7 | %POLICY_CATEGORY% 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | true 21 | Install %VERSION% 22 | %SELF_SERVICE_DESCRIPTION% 23 | 24 | 25 | true 26 | 27 | 28 | -------------------------------------------------------------------------------- /Adobe/SmartGroupTemplate.xml: -------------------------------------------------------------------------------- 1 | 2 | %group_name% 3 | true 4 | 5 | 6 | Application Title 7 | 0 8 | and 9 | is 10 | %JSS_INVENTORY_NAME% 11 | 12 | 13 | Application Version 14 | 1 15 | and 16 | is not 17 | %VERSION% 18 | 19 | 20 | Computer Group 21 | 2 22 | and 23 | member of 24 | Testing 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Adobe/UninstallPolicyTemplate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Uninstall Latest %PROD_NAME% 4 | true 5 | Ongoing 6 | 7 | %POLICY_CATEGORY% 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | true 21 | Uninstall %VERSION% 22 | %SELF_SERVICE_DESCRIPTION% 23 | 24 | 25 | true 26 | 27 | 28 | -------------------------------------------------------------------------------- /Adobe/UninstallSmartGroupTemplate.xml: -------------------------------------------------------------------------------- 1 | 2 | %group_name% 3 | true 4 | 5 | 6 | Application Title 7 | 0 8 | and 9 | is 10 | %JSS_INVENTORY_NAME% 11 | 12 | 13 | Application Version 14 | 1 15 | and 16 | is 17 | %VERSION% 18 | 19 | 20 | Computer Group 21 | 2 22 | and 23 | member of 24 | Testing 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Adobe/examples/AcrobatDC17.jss.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Identifier 6 | local.jss.Adobe.AcrobatDC 7 | Input 8 | 9 | NAME 10 | AdobeAcrobatDC 11 | ORG_NAME 12 | ADMIN_PLEASE_CHANGE 13 | ccpinfo 14 | 15 | matchOSLanguage 16 | 17 | rumEnabled 18 | 19 | updatesEnabled 20 | 21 | appsPanelEnabled 22 | 23 | adminPrivilegesEnabled 24 | 25 | IncludeUpdates 26 | 27 | is64Bit 28 | 29 | organizationName 30 | ADMIN_PLEASE_CHANGE 31 | customerType 32 | team 33 | Language 34 | en_US 35 | Products 36 | 37 | 38 | sapCode 39 | APRO 40 | baseVersion 41 | 42 | version 43 | 15.0 44 | 45 | 46 | 47 | 48 | parse_proxy_xml 49 | 50 | fetch_description 51 | 52 | fetch_icon 53 | 54 | 55 | CATEGORY 56 | Productivity 57 | GROUP_NAME 58 | %NAME%-update-smart 59 | GROUP_TEMPLATE 60 | AcrobatDCSmartGroupTemplate.xml 61 | POLICY_CATEGORY 62 | Testing 63 | POLICY_TEMPLATE 64 | PolicyTemplate.xml 65 | 66 | ParentRecipe 67 | com.github.mosen.jss.Adobe.CreativeCloudApp 68 | 69 | 70 | -------------------------------------------------------------------------------- /Adobe/examples/AcrobatDC17.munki.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Identifier 6 | local.munki.Adobe.AcrobatDC 7 | Input 8 | 9 | NAME 10 | AdobeAcrobatDC 11 | ccpinfo 12 | 13 | matchOSLanguage 14 | 15 | rumEnabled 16 | 17 | updatesEnabled 18 | 19 | appsPanelEnabled 20 | 21 | adminPrivilegesEnabled 22 | 23 | organizationName 24 | ADMIN_PLEASE_CHANGE 25 | customerType 26 | team 27 | Language 28 | en_US 29 | Products 30 | 31 | 32 | sapCode 33 | APRO 34 | baseVersion 35 | 36 | version 37 | 17.0 38 | 39 | 40 | 41 | MUNKI_REPO_SUBDIR 42 | apps/Adobe 43 | pkginfo 44 | 45 | blocking_applications 46 | 47 | Adobe Acrobat 48 | Acrobat Distiller 49 | 50 | catalogs 51 | 52 | testing 53 | 54 | name 55 | %NAME% 56 | developer 57 | Adobe 58 | unattended_install 59 | 60 | 61 | 62 | ParentRecipe 63 | com.github.mosen.munki.Adobe.CreativeCloudApp 64 | 65 | 66 | -------------------------------------------------------------------------------- /Adobe/examples/AcrobatDCSmartGroupTemplate.xml: -------------------------------------------------------------------------------- 1 | 2 | %GROUP_NAME% 3 | true 4 | 5 | 6 | Application Title 7 | 0 8 | and 9 | is 10 | Adobe Acrobat.app 11 | 12 | 13 | Application Version 14 | 1 15 | and 16 | like 17 | 15. 18 | 19 | 20 | Application Version 21 | 2 22 | and 23 | is not 24 | %proxy_version% 25 | 26 | 27 | Computer Group 28 | 3 29 | and 30 | member of 31 | Testing 32 | 33 | 34 | -------------------------------------------------------------------------------- /Adobe/examples/AdobeAuditionCC2017.jss.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Identifier 6 | local.jss.AdobeAuditionCC2017 7 | Input 8 | 9 | 10 | NAME 11 | AdobeAuditionCC2017 12 | ccpinfo 13 | 14 | matchOSLanguage 15 | 16 | rumEnabled 17 | 18 | updatesEnabled 19 | 20 | appsPanelEnabled 21 | 22 | adminPrivilegesEnabled 23 | 24 | organizationName 25 | ADMIN_PLEASE_CHANGE 26 | customerType 27 | team 28 | Language 29 | en_US 30 | Products 31 | 32 | 33 | sapCode 34 | AUDT 35 | baseVersion 36 | 10.0.0 37 | version 38 | latest 39 | 40 | 41 | 42 | 43 | fetch_description 44 | 45 | fetch_icon 46 | 47 | 48 | 49 | CATEGORY 50 | Sound 51 | GROUP_NAME 52 | %NAME%-update-smart 53 | GROUP_TEMPLATE 54 | SmartGroupTemplate.xml 55 | POLICY_CATEGORY 56 | Testing 57 | POLICY_TEMPLATE 58 | PolicyTemplate.xml 59 | 60 | ParentRecipe 61 | com.github.mosen.jss.Adobe.CreativeCloudApp 62 | 63 | 64 | -------------------------------------------------------------------------------- /Adobe/examples/AdobePhotoshopCC2017.munki.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Identifier 6 | local.munki.AdobePhotoshopCC2017 7 | Input 8 | 9 | MUNKI_REPO_SUBDIR 10 | apps/Adobe/CC2017 11 | NAME 12 | AdobePhotoshopCC2017 13 | ccpinfo 14 | 15 | matchOSLanguage 16 | 17 | rumEnabled 18 | 19 | updatesEnabled 20 | 21 | appsPanelEnabled 22 | 23 | adminPrivilegesEnabled 24 | 25 | organizationName 26 | ADMIN_PLEASE_CHANGE 27 | customerType 28 | team 29 | Language 30 | en_US 31 | Products 32 | 33 | 34 | sapCode 35 | PHSP 36 | baseVersion 37 | 18.0 38 | version 39 | latest 40 | 41 | 42 | 43 | pkginfo 44 | 45 | blocking_applications 46 | 47 | 48 | Creative Cloud 49 | Adobe Application Manager 50 | AAM Updates Notifier 51 | Adobe Application Manager (Updater) 52 | 53 | Adobe Photoshop CC 2017 54 | 55 | catalogs 56 | 57 | testing 58 | 59 | 60 | 61 | ParentRecipe 62 | com.github.mosen.munki.Adobe.CreativeCloudApp 63 | 64 | 65 | -------------------------------------------------------------------------------- /Adobe/examples/AdobePhotoshopCC2017.pkg.recipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Identifier 6 | local.pkg.AdobePhotoshopCC2017 7 | Input 8 | 9 | NAME 10 | AdobePhotoshopCC2017 11 | ccpinfo 12 | 13 | matchOSLanguage 14 | 15 | rumEnabled 16 | 17 | updatesEnabled 18 | 19 | appsPanelEnabled 20 | 21 | adminPrivilegesEnabled 22 | 23 | organizationName 24 | ADMIN_PLEASE_CHANGE 25 | customerType 26 | enterprise 27 | Language 28 | en_US 29 | Products 30 | 31 | 32 | sapCode 33 | PHSP 34 | baseVersion 35 | 18.0 36 | version 37 | latest 38 | 39 | 40 | 41 | 42 | ParentRecipe 43 | com.github.mosen.pkg.Adobe.CreativeCloudApp 44 | 45 | 46 | -------------------------------------------------------------------------------- /Adobe/examples/PolicyTemplate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Install Latest %PROD_NAME% 4 | true 5 | Ongoing 6 | 7 | %POLICY_CATEGORY% 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | true 21 | Install %VERSION% 22 | %release_notes% 23 | 24 | 25 | true 26 | 27 | -------------------------------------------------------------------------------- /Adobe/examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains some example overrides. Since it's possible these may drift over time, these should be considered only as illustrative - creating overrides should always be done using `autopkg make-override --name ` 4 | 5 | ### Known issues in JSS recipes 6 | 7 | - No SS Icons 8 | - No SS Description 9 | - Camera Raw Version Smart Group does not match EA version. 10 | - Acrobat DC Smart Group Version does not match Installed Application version. 11 | - Version string comparison might end up scoping computers for a downgrade if two package smartgroups exist. 12 | - Error in local.jss.Adobe.IllustratorCC: Processor: JSSImporter: Error: Central directory offset would require ZIP64 extensions 13 | -------------------------------------------------------------------------------- /Adobe/examples/SmartGroupTemplate.xml: -------------------------------------------------------------------------------- 1 | 2 | %group_name% 3 | true 4 | 5 | 6 | Application Title 7 | 0 8 | and 9 | is 10 | %JSS_INVENTORY_NAME% 11 | 12 | 13 | Application Version 14 | 1 15 | and 16 | is not 17 | %VERSION% 18 | 19 | 20 | Computer Group 21 | 2 22 | and 23 | member of 24 | Testing 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ccp-recipes 2 | 3 | AutoPkg recipes for Creative Cloud Packager workflows. 4 | This repo has now been moved to [autopkg/adobe-ccp-recipes](https://github.com/autopkg/adobe-ccp-recipes), 5 | so any further development will happen there. 6 | 7 | Recipe identifiers have not changed, so if you were already tracking this repo with Git, 8 | one simple way to migrate your repo to the new remote location would be to go to your AutoPkg 9 | recipe repo dir (most likely `~/Library/AutoPkg/RecipeRepos/com.github.mosen.ccp-recipes`) and modify 10 | the `.git/config` to use the new repo location of https://github.com/autopkg/adobe-ccp-recipes. 11 | 12 | ## New behaviour with CCP v1.14.0.97 (March 2018) 13 | 14 | In v1.14.0.97 the ability to specify a custom package by its exact version was broken (either intentionally 15 | or unintentionally) by Adobe. The only remaining options for us are the base version or "latest" version of 16 | the product. 17 | 18 | The following changes were made as a result: 19 | 20 | - If you specify version "latest", it will automatically include any updates to the main product and bundled 21 | products at the time you generate the package. This means that recipes are non-deterministic. 22 | - If you don't specify a version you get the base version only. 23 | - You may use "IncludeUpdates" in place of specifying a version to mean "include all updates at the time of 24 | packaging", this is the same as "latest". 25 | 26 | -------------------------------------------------------------------------------- /list_ccp_feed: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2016 Mosen/Tim Sutton 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import os 18 | import sys 19 | import json 20 | import unicodedata 21 | import urllib2 22 | from urllib import urlencode 23 | 24 | CCM_URL = 'https://prod-rel-ffc-ccm.oobesaas.adobe.com/adobe-ffc-external/core/v4/products/all' 25 | BASE_URL = 'https://prod-rel-ffc.oobesaas.adobe.com/adobe-ffc-external/aamee/v2/products/all' 26 | 27 | def add_product(products, product): 28 | if product['id'] not in products: 29 | products[product['id']] = [] 30 | 31 | products[product['id']].append(product) 32 | 33 | 34 | def feed_url(channels, platforms): 35 | """Build the GET query parameters for the product feed.""" 36 | params = [ 37 | ('payload', 'true'), 38 | ('productType', 'Desktop'), 39 | ('_type', 'json') 40 | ] 41 | for ch in channels: 42 | params.append(('channel', ch)) 43 | 44 | for pl in platforms: 45 | params.append(('platform', pl)) 46 | 47 | return BASE_URL + '?' + urlencode(params) 48 | 49 | def fetch(channels, platforms): 50 | """Fetch the feed contents.""" 51 | url = feed_url(channels, platforms) 52 | print('Fetching from feed URL: {}'.format(url)) 53 | 54 | req = urllib2.Request(url, headers={ 55 | 'User-Agent': 'Creative Cloud', 56 | 'x-adobe-app-id': 'AUSST_4_0', 57 | }) 58 | data = json.loads(urllib2.urlopen(req).read()) 59 | 60 | return data 61 | 62 | def dump(channels, platforms): 63 | """Save feed contents to feed.json file""" 64 | url = feed_url(channels, platforms) 65 | print('Fetching from feed URL: {}'.format(url)) 66 | 67 | req = urllib2.Request(url, headers={ 68 | 'User-Agent': 'Creative Cloud', 69 | 'x-adobe-app-id': 'AUSST_4_0', 70 | }) 71 | data = urllib2.urlopen(req).read() 72 | with open(os.path.join(os.path.dirname(__file__), 'feed.json'), 'w+') as feed_fd: 73 | feed_fd.write(data) 74 | print('Wrote output to feed.json') 75 | 76 | 77 | if __name__ == "__main__": 78 | if len(sys.argv) > 1 and sys.argv[1] == 'dump': 79 | dump(['ccp_hd_2', 'sti'], ['osx10', 'osx10-64']) 80 | else: 81 | data = fetch(['ccp_hd_2', 'sti'], ['osx10', 'osx10-64']) 82 | products = {} 83 | for channel in data['channel']: 84 | for product in channel['products']['product']: 85 | add_product(products, product) 86 | 87 | for sapcode, productVersions in products.iteritems(): 88 | print("SAP Code: {}".format(sapcode)) 89 | 90 | for product in productVersions: 91 | base_version = product['platforms']['platform'][0]['languageSet'][0].get('baseVersion') 92 | if not base_version: 93 | base_version = "N/A" 94 | 95 | name = unicodedata.normalize("NFKD", product['displayName']) 96 | print("\t{0: <60}\tBaseVersion: {1: <14}\tVersion: {2: <14}".format( 97 | name, 98 | base_version, 99 | product['version'] 100 | )) 101 | print("") 102 | -------------------------------------------------------------------------------- /whats_my_org.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Find out your Adobe organization name by scraping the PDApp log 3 | 4 | output=$(mktemp) 5 | grep 'OrgDetails return status is (0) server-response' ~/Library/Logs/PDApp.log | tail -n 1 | cut -d\( -f3 | cut -d\) -f1 |plutil -convert xml1 - -o - > "${output}" 6 | /usr/libexec/PlistBuddy -c 'Print 0:orgName' "${output}" 7 | --------------------------------------------------------------------------------