Adobe: scaricare i file di installazione Offline dei prodotti

| |

Ti stai chiedendo il perché? È abbastanza semplice: hai una licenza di un programma che non è più l’ultimo disponibile sulle pagine del produttore, il quale ha magicamente fatto sparire ogni riferimento a quella versione e al suo possibile download. Hai una postazione scollegata dalla rete (incredibile ma vero) e hai bisogno quindi di usare un file di installazione offline che porterai lì con una banale e sorpassata memoria USB. Oppure, diciamocelo francamente, l’agente software di Adobe Creative Cloud ti fa profondamente schifo. Ci possono essere più motivazioni e ognuna di queste ha le sue buone ragioni per essere ritenuta nel tuo caso bloccante. Perché di Adobe non è più così semplice (in alcuni casi è proprio impossibile) reperire i file di installazione passando direttamente dal sito web ufficiale?

MacBook Pro with Adobe Applications
Photo by Mikaela Shannon on Unsplash

Qui non si tratta di software piratato o voli pindarici privi di reale giustificazione, si tratta di voler a tutti i costi imporre agli utenti l’utilizzo di un “ponte” in grado di portare sulla tua macchina un software tra i tanti disponibili sviluppati da chi ha messo al mondo quell’abominio di Flash (pace all’anima sua, tra poco), cercando di approfittare di quella presenza lì per proporre ulteriori prodotti dei quali probabilmente ti importa meno che zero. Ci si nasconde spesso dietro il fatto che così facendo si possano mantenere sempre aggiornati i programmi installati sul PC, poco importa però se poi tu avrai o meno intenzione di passare alla nuova versione pagando la differenza dovuta.

Qualcuno deve non solo averla pensata in questa maniera, ma si è anche dato da fare affinché si potesse in qualche maniera tornare a poter mettere mano sui file di installazione comodamente trasportabili su chiavi USB (hard disk esterni, schede di memoria e così via) o da archiviare in un qualche remoto spazio del NAS per future reinstallazioni. Ho trovato il tutto su GitHub e l’ho personalmente provato sul mio MacBook con macOS Catalina a bordo, tutto funziona a meraviglia e ho generato pacchetti di installazione offline di pressoché qualsiasi prodotto Adobe degli ultimi due anni (corrente compreso).

Tutto parte da qui:

#!/bin/bash
CYAN="$(tput bold; tput setaf 6)"
RESET="$(tput sgr0)"
clear
if command -v python3 > /dev/null 2>&1; then
if [ $(python3 -c "print('ye')") = "ye" ]; then
clear
echo "${CYAN}python3 found!${RESET}"
else
clear
echo "python3 found but non-functional" # probably xcode-select stub on Catalina+
echo "${CYAN}If you received a popup asking to install some tools, please accept.${RESET}"
read -n1 -r -p "Press [SPACE] when installation is complete, or any other key to abort." key
echo ""
if [ "$key" != '' ]; then
exit
fi
fi
else
echo "${CYAN}installing python3...${RESET}"
if ! command -v brew > /dev/null 2>&1; then
echo | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
fi
brew install python
fi
python3 -c 'import requests' > /dev/null 2>&1
if [ $? == 0 ]; then
echo "${CYAN}requests found!${RESET}"
else
echo "${CYAN}installing requests...${RESET}"
python3 -m pip install requests --user
fi
clear
echo "${CYAN}starting ccdl${RESET}"
python3 <(curl -s https://gist.githubusercontent.com/ayyybe/a5f01c6f40020f9a7bc4939beeb2df1d/raw/ccdl.py)
view raw ccdl.command hosted with ❤ by GitHub
#!/usr/bin/env python3
# CHANGELOG (0.1.2-hotfix1)
# + updated script to work with new api (newer downloads work now)
# + added workaround for broken installer on big sur
# + made everything even more messy and disgusting
import os
import json
import argparse
import requests
session = requests.Session()
import shutil
from xml.etree import ElementTree as ET
from collections import OrderedDict
from subprocess import Popen, PIPE
VERSION = 4
VERSION_STR = '0.1.2-hotfix1'
CODE_QUALITY = 'FUCKING_AWFUL'
INSTALL_APP_APPLE_SCRIPT = '''
const app = Application.currentApplication()
app.includeStandardAdditions = true
ObjC.import('Cocoa')
ObjC.import('stdio')
ObjC.import('stdlib')
ObjC.registerSubclass({
name: 'HandleDataAction',
methods: {
'outData:': {
types: ['void', ['id']],
implementation: function(sender) {
const data = sender.object.availableData
if (data.length !== '0') {
const output = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding).js
const res = parseOutput(output)
if (res) {
switch (res.type) {
case 'progress':
Progress.additionalDescription = `Progress: ${res.data}%`
Progress.completedUnitCount = res.data
break
case 'exit':
if (res.data === 0) {
$.puts(JSON.stringify({ title: 'Installation succeeded' }))
} else {
$.puts(JSON.stringify({ title: `Failed with error code ${res.data}` }))
}
$.exit(0)
break
}
}
sender.object.waitForDataInBackgroundAndNotify
} else {
$.NSNotificationCenter.defaultCenter.removeObserver(this)
}
}
}
}
})
function parseOutput(output) {
let matches
matches = output.match(/Progress: ([0-9]{1,3})%/)
if (matches) {
return {
type: 'progress',
data: parseInt(matches[1], 10)
}
}
matches = output.match(/Exit Code: ([0-9]{1,3})/)
if (matches) {
return {
type: 'exit',
data: parseInt(matches[1], 10)
}
}
return false
}
function shellescape(a) {
var ret = [];
a.forEach(function(s) {
if (/[^A-Za-z0-9_\\/:=-]/.test(s)) {
s = "'"+s.replace(/'/g,"'\\\\''")+"'";
s = s.replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning
.replace(/\\\\\'''/g, "\\\\'" ); // remove non-escaped single-quote if there are enclosed between 2 escaped
}
ret.push(s);
});
return ret.join(' ');
}
function run() {
const appPath = app.pathTo(this).toString()
//const driverPath = appPath.substring(0, appPath.lastIndexOf('/')) + '/products/driver.xml'
const driverPath = appPath + '/Contents/Resources/products/driver.xml'
const hyperDrivePath = '/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup'
// The JXA Objective-C bridge is completely broken in Big Sur
if (!$.NSProcessInfo && parseFloat(app.doShellScript('sw_vers -productVersion')) >= 11.0) {
app.displayAlert('GUI unavailable in Big Sur', {
message: 'JXA is currently broken in Big Sur.\\nInstall in Terminal instead?',
buttons: ['Cancel', 'Install in Terminal'],
defaultButton: 'Install in Terminal',
cancelButton: 'Cancel'
})
const cmd = shellescape([ 'sudo', hyperDrivePath, '--install=1', '--driverXML=' + driverPath ])
app.displayDialog('Run this command in Terminal to install (press \\'OK\\' to copy to clipboard)', { defaultAnswer: cmd })
app.setTheClipboardTo(cmd)
return
}
const args = $.NSProcessInfo.processInfo.arguments
const argv = []
const argc = args.count
for (var i = 0; i < argc; i++) {
argv.push(ObjC.unwrap(args.objectAtIndex(i)))
}
delete args
const installFlag = argv.indexOf('-y') > -1
if (!installFlag) {
app.displayAlert('Adobe Package Installer', {
message: 'Start installation now?',
buttons: ['Cancel', 'Install'],
defaultButton: 'Install',
cancelButton: 'Cancel'
})
const output = app.doShellScript(`"${appPath}/Contents/MacOS/applet" -y`, { administratorPrivileges: true })
const alert = JSON.parse(output)
alert.params ? app.displayAlert(alert.title, alert.params) : app.displayAlert(alert.title)
return
}
const stdout = $.NSPipe.pipe
const task = $.NSTask.alloc.init
task.executableURL = $.NSURL.alloc.initFileURLWithPath(hyperDrivePath)
task.arguments = $(['--install=1', '--driverXML=' + driverPath])
task.standardOutput = stdout
const dataAction = $.HandleDataAction.alloc.init
$.NSNotificationCenter.defaultCenter.addObserverSelectorNameObject(dataAction, 'outData:', $.NSFileHandleDataAvailableNotification, $.initialOutputFile)
stdout.fileHandleForReading.waitForDataInBackgroundAndNotify
let err = $.NSError.alloc.initWithDomainCodeUserInfo('', 0, '')
const ret = task.launchAndReturnError(err)
if (!ret) {
$.puts(JSON.stringify({
title: 'Error',
params: {
message: 'Failed to launch task: ' + err.localizedDescription.UTF8String
}
}))
$.exit(0)
}
Progress.description = "Installing packages..."
Progress.additionalDescription = "Preparing…"
Progress.totalUnitCount = 100
task.waitUntilExit
}
'''
ADOBE_PRODUCTS_XML_URL = 'https://cdn-ffc.oobesaas.adobe.com/core/v5/products/all?_type=xml&channel=ccm,sti&platform=osx10,osx10-64&productType=Desktop'
ADOBE_APPLICATION_JSON_URL = 'https://cdn-ffc.oobesaas.adobe.com/core/v3/applications'
DRIVER_XML = '''<DriverInfo>
<ProductInfo>
<Name>Adobe {name}</Name>
<SAPCode>{sapCode}</SAPCode>
<CodexVersion>{version}</CodexVersion>
<Platform>osx10-64</Platform>
<EsdDirectory>./{sapCode}</EsdDirectory>
<Dependencies>
{dependencies}
</Dependencies>
</ProductInfo>
<RequestInfo>
<InstallDir>/Applications</InstallDir>
<InstallLanguage>{language}</InstallLanguage>
</RequestInfo>
</DriverInfo>
'''
DRIVER_XML_DEPENDENCY = ''' <Dependency>
<SAPCode>{sapCode}</SAPCode>
<BaseVersion>{version}</BaseVersion>
<EsdDirectory>./{sapCode}</EsdDirectory>
</Dependency>'''
ADOBE_REQ_HEADERS = {
'X-Adobe-App-Id': 'accc-hdcore-desktop',
'User-Agent': 'Adobe Application Manager 2.0',
'X-Api-Key': 'CC_HD_ESD_1_0'
}
def dl(filename, url):
with session.get(url, stream=True, headers=ADOBE_REQ_HEADERS) as r:
with open(filename, 'wb') as f:
shutil.copyfileobj(r.raw, f)
def r(url, headers=ADOBE_REQ_HEADERS):
req = session.get(url, headers=headers)
req.encoding = 'utf-8'
return req.text
def get_products_xml():
return ET.fromstring(r(ADOBE_PRODUCTS_XML_URL))
def parse_products_xml(products_xml):
cdn = products_xml.find('channel/cdn/secure').text
products = {}
parent_map = {c: p for p in products_xml.iter() for c in p}
for p in products_xml.findall('channel/products/product'):
displayName = p.find('displayName').text
sap = p.get('id')
version = p.get('version')
dependencies = list(p.find('platforms/platform/languageSet/dependencies'))
if not products.get(sap):
if p.find('productInfoPage'):
print(p.find('productInfoPage').text)
products[sap] = {
'hidden': p.find('platforms/platform').get('id') != 'osx10-64' or parent_map[parent_map[p]].get('name') != 'ccm',
'displayName': displayName,
'sapCode': sap,
'versions': OrderedDict()
}
products[sap]['versions'][version] = {
'sapCode': sap,
'version': version,
'dependencies': [{
'sapCode': d.find('sapCode').text, 'version': d.find('baseVersion').text
} for d in dependencies],
'buildGuid': p.find('platforms/platform/languageSet').get('buildGuid')
}
return products, cdn
def get_application_json(buildGuid):
headers = ADOBE_REQ_HEADERS.copy()
headers['x-adobe-build-guid'] = buildGuid
return json.loads(r(ADOBE_APPLICATION_JSON_URL, headers))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-l', '--installLanguage', help='Language code (eg. en_US)', action='store')
parser.add_argument('-s', '--sapCode', help='SAP code for desired product (eg. PHSP)', action='store')
parser.add_argument('-v', '--version', help='Version of desired product (eg. 21.0.3)', action='store')
parser.add_argument('-d', '--destination', help='Directory to download installation files to', action='store')
args = parser.parse_args()
ye = int((32 - len(VERSION_STR)) / 2)
print('=================================')
print('= Adobe macOS Package Generator =')
print('{} {} {}\n'.format('='*ye, VERSION_STR, '='*(31-len(VERSION_STR)-ye)))
if (not os.path.isfile('/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup')):
print('Adobe HyperDrive installer not found.\nPlease make sure the Creative Cloud app is installed.')
exit(1)
print('Downloading products.xml\n')
products_xml = get_products_xml()
print('Parsing products.xml')
products, cdn = parse_products_xml(products_xml)
print('CDN: ' + cdn)
print(str(len([p for p in products if not products[p]['hidden']])) + ' products found:')
sapCode = None
if (args.sapCode):
if products.get(args.sapCode):
print('\nUsing provided SAP Code: ' + args.sapCode)
sapCode = args.sapCode
else:
print('\nProvided SAP Code not found in products: ' + args.sapCode)
print('')
if not sapCode:
for p in products.values():
if not p['hidden']:
print('[{}] {}'.format(p['sapCode'], p['displayName']))
while sapCode is None:
val = input('\nPlease enter the SAP Code of the desired product (eg. PHSP for Photoshop): ')
if val == 'APRO':
print('\033[1;31mAcrobat is currently broken, please sit tight while I try to find a solution.\nAll other products are functional.\033[0m')
elif products.get(val):
sapCode = val
else:
print('{} is not a valid SAP Code. Please use a value from the list above.'.format(val))
product = products.get(sapCode)
versions = product['versions']
version = None
if (args.version):
if versions.get(args.version):
print('\nUsing provided version: ' + args.version)
version = args.version
else:
print('\nProvided version not found: ' + args.version)
print('')
if not version:
for v in reversed(versions.values()):
print('{} {}'.format(product['displayName'], v['version']))
while version is None:
val = input('\nPlease enter the desired version (eg. 21.2.3): ')
if versions.get(val):
version = val
else:
print('{} is not a valid version. Please use a value from the list above.'.format(val))
print('')
langs = [ 'en_US', 'en_GB', 'en_IL', 'en_AE', 'es_ES', 'es_MX', 'pt_BR', 'fr_FR', 'fr_CA', 'fr_MA', 'it_IT', 'de_DE', 'nl_NL', 'ru_RU', 'uk_UA', 'zh_TW', 'zh_CN', 'ja_JP', 'ko_KR', 'pl_PL', 'hu_HU', 'cs_CZ', 'tr_TR', 'sv_SE', 'nb_NO', 'fi_FI', 'da_DK' ]
installLanguage = None
if (args.installLanguage):
if (args.installLanguage in langs):
print('\nUsing provided language: ' + args.installLanguage)
installLanguage = args.installLanguage
else:
print('\nProvided language not available: ' + args.installLanguage)
if not installLanguage:
print('Available languages: {}'.format(', '.join(langs)))
while installLanguage is None:
val = input('\nPlease enter the desired install language, or nothing for [en_US]: ') or 'en_US'
if (val in langs):
installLanguage = val
else:
print('{} is not available. Please use a value from the list above.'.format(val))
dest = None
if (args.destination):
print('\nUsing provided destination: ' + args.destination)
dest = args.destination
else:
print('\nPlease navigate to the desired downloads folder, or cancel to abort.')
p = Popen(['/usr/bin/osascript', '-e', 'tell application (path to frontmost application as text)\nset _path to choose folder\nPOSIX path of _path\nend'], stdout=PIPE)
dest = p.communicate()[0].decode('utf-8').strip()
if (p.returncode != 0):
print('Exiting...')
exit()
print('')
install_app_name = 'Install {}_{}-{}.app'.format(sapCode, version, installLanguage)
install_app_path = os.path.join(dest, install_app_name)
print('sapCode: ' + sapCode)
print('version: ' + version)
print('installLanguage: ' + installLanguage)
print('dest: ' + install_app_path)
prodInfo = versions[version]
prods_to_download = [{ 'sapCode': d['sapCode'], 'version': d['version'], 'buildGuid': products[d['sapCode']]['versions'][d['version']]['buildGuid'] } for d in prodInfo['dependencies']]
prods_to_download.insert(0, { 'sapCode': prodInfo['sapCode'], 'version': prodInfo['version'], 'buildGuid': prodInfo['buildGuid'] })
print(prods_to_download)
print('\nCreating {}'.format(install_app_name))
install_app_path = os.path.join(dest, 'Install {}_{}-{}.app'.format(sapCode, version, installLanguage))
with Popen(['/usr/bin/osacompile', '-l', 'JavaScript', '-o', os.path.join(dest, install_app_path)], stdin=PIPE) as p:
p.communicate(INSTALL_APP_APPLE_SCRIPT.encode('utf-8'))
icon_path = '/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Install.app/Contents/Resources/CreativeCloudInstaller.icns'
shutil.copyfile(icon_path, os.path.join(install_app_path, 'Contents', 'Resources', 'applet.icns'))
products_dir = os.path.join(install_app_path, 'Contents', 'Resources', 'products')
print('\nPreparing...\n')
for p in prods_to_download:
s, v = p['sapCode'], p['version']
product_dir = os.path.join(products_dir, s)
app_json_path = os.path.join(product_dir, 'application.json')
print('[{}_{}] Downloading application.json'.format(s, v))
app_json = get_application_json(p['buildGuid'])
p['application_json'] = app_json
print('[{}_{}] Creating folder for product'.format(s, v))
os.makedirs(product_dir, exist_ok=True)
print('[{}_{}] Saving application.json'.format(s, v))
with open(app_json_path, 'w') as file:
json.dump(app_json, file, separators=(',', ':'))
print('')
print ('Downloading...\n')
for p in prods_to_download:
s, v = p['sapCode'], p['version']
app_json = p['application_json']
product_dir = os.path.join(products_dir, s)
print('[{}_{}] Parsing available packages'.format(s, v))
core_pkg_count = 0
noncore_pkg_count = 0
packages = app_json['Packages']['Package']
download_urls = []
for pkg in packages:
if pkg.get('Type') and pkg['Type'] == 'core':
core_pkg_count += 1
download_urls.append(cdn + pkg['Path'])
else:
if ((not pkg.get('Condition')) or installLanguage in pkg['Condition']): # TODO: actually parse `Condition` and check it properly (and maybe look for & add support for conditions other than installLanguage)
noncore_pkg_count += 1
download_urls.append(cdn + pkg['Path'])
print('[{}_{}] Selected {} core packages and {} non-core packages'.format(s, v, core_pkg_count, noncore_pkg_count))
for url in download_urls:
name = url.split('/')[-1].split('?')[0]
print('[{}_{}] Downloading {}'.format(s, v, name))
dl(os.path.join(product_dir, name), url)
print('\nGenerating driver.xml')
driver = DRIVER_XML.format(
name = product['displayName'],
sapCode = prodInfo['sapCode'],
version = prodInfo['version'],
dependencies = '\n'.join([DRIVER_XML_DEPENDENCY.format(
sapCode = d['sapCode'],
version = d['version']
) for d in prodInfo['dependencies']]),
language = installLanguage
)
with open(os.path.join(products_dir, 'driver.xml'), 'w') as f:
f.write(driver)
f.close()
print('\nPackage successfully created. Run {} to install.'.format(install_app_path))
view raw ccdl.py hosted with ❤ by GitHub
#!/bin/bash
CYAN="$(tput bold; tput setaf 6)"
RESET="$(tput sgr0)"
curl https://gist.githubusercontent.com/ayyybe/a5f01c6f40020f9a7bc4939beeb2df1d/raw/ccdl.command -o "/Applications/Adobe Packager.command"
chmod +x "/Applications/Adobe Packager.command"
clear
echo "${CYAN}Done! You can now start /Applications/Adobe Packager.command to begin${RESET}"
exit
view raw install.sh hosted with ❤ by GitHub

Quello che tu puoi fare sulla tua macchina macOS (ma vale anche per Linux e – con qualche ulteriore accorgimento – Windows) è lanciare da Terminale il file bash di installazione e usare poi il tutto ancora da riga di comando. Apri una finestra di Terminale, copia e lancia questo comando:

/bin/bash -c "$(curl -fsSL https://gist.githubusercontent.com/ayyybe/a5f01c6f40020f9a7bc4939beeb2df1d/raw/install.sh)"

Lui penserà al resto collegandosi a GitHub, scaricando e mettendo in piedi il necessario. A questo punto tra le tue applicazioni noterai la novità:

Adobe: creare gli installer Offline dei prodotti 1

Quel file command lo si richiama direttamente da Terminale, questo ti permetterà di accedere ai server Adobe e creare il pacchetto di installazione offline che preferisci, dovrai solo scegliere il programma, la versione e la lingua in cui lo vorrai, quindi la cartella dove scaricarlo. Il gioco è fatto. Ti lascio qualche screenshot giusto per farti dare un’occhiata ai vari passaggi:

Ora puoi prendere quel file di installazione e tenerlo un po’ dove ti pare. Una manciata di giorni fa questo script ha dato il via a un repository ben più ricco e organizzato dedicato al mondo degli installer Offline dei prodotti Adobe, puoi fare un salto su github.com/chriswayg/CC-Offline-Package-Generator per dare un’occhiata tu stesso, c’è chi si è messo a collaborare con l’autore originale dello script per dare una interfaccia grafica più semplice al già ottimo lavoro svolto ma “poco accessibile” per tutte quelle persone che masticano poco il Terminale su macOS e gli equivalenti sugli altri sistemi.

Se hai ulteriori alternative da suggerire ricorda che l’area commenti è a tua totale disposizione, la stessa cosa dicasi per eventuali dubbi o ulteriori informazioni! ?

#StaySafe

Correzioni, suggerimenti? Lascia un commento nell'apposita area qui di seguito o contattami privatamente.
Ti è piaciuto l'articolo? Offrimi un caffè! ☕ :-)

L'articolo potrebbe non essere aggiornato

Questo post è stato scritto più di 5 mesi fa, potrebbe non essere aggiornato. Per qualsiasi dubbio ti invito a lasciare un commento per chiedere ulteriori informazioni! :-)

Pillole

Le pillole sono articoli di veloce lettura dedicati a notizie, script o qualsiasi altra cosa possa essere "divorata e messa in pratica" con poco. Uno spazio del blog riservato agli articoli "a bruciapelo"!
Se vuoi leggere le altre pillole fai clic qui.

Condividi l'articolo con i tuoi contatti:
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Commenti
Oldest
Newest Most Voted
Inline Feedbacks
View all comments