Name

sn_appclient.AppUpgrader

Description

This script include is responsible for all the Application and Plugin installation operations.

Script

var AppUpgrader = Class.create();
AppUpgrader.prototype = {

  CANNOT_INSTALL_OR_UPDATE : gs.getMessage("Insufficient privileges to install or update an application package:"),
  DELETE_OLD_APP_PACKAGE : gs.getMessage("Deleting old application package"),
  INSTALL_APP_PACKAGE : gs.getMessage("Installing application package"),
  APP_IS_NOT_AVAILABLE: "Application unavailable for installation on this instance, app id: {0}",
  NO_ASSIGNED_VERSION: "Aborting upgrade of application: {0}, no assigned version specified",
  NO_UPGRADE_NECESSARY: "Application is already on desired version ({0}), no upgrade necessary for {1}",
  CANT_DOWNGRADE: "Application cannot be downgraded: {0}, from version: {1}, to version: {2}",
  PLUGIN_DEPENDENCY_UNAVAILABLE: "Aborting installation of application: {0}, application depends on plugins which are not available on this instance: {1}",
  APP_DEPENDENCY_UNAVAILABLE: "Aborting installation of application: {0}, application depends on other applications which are not accessible at app store: {1}",
  REFRESH_APP_CACHE : gs.getMessage("Refreshing app cache"),

  initialize: function() {
  	this._instanceId = gs.getProperty("instance_id");
  	this._instanceName = gs.getProperty("instance_name");
  	this._autoUpdateEnabled = gs.getProperty('sn_appclient.auto_update', 'true') == 'true';
  	this.versionComparator = new VersionComparator();
  	this.glideAppLoader = new GlideAppLoader();
  	this._PARENT_TRACKER = GlideExecutionTracker.getLastRunning();
  	this._isCustomizationInstall = false;
  	this._isStoreAppInstall = true;
  },

  autoUpdates: function() {
  	var updates = this._getAutoUpdates();
  	for (var i = 0; i < updates.length; i++) {
  		var update = updates[i];
  		this.upgradeProcess(update.source_app_id, update.latest_version, false, true);
  	}
  },

  /***
   * Installs or updates store app for dependent apps or installing an app during the upgrade
   */
  upgrade: /*boolean*/ function(/*source_app_id*/ appID, /*String*/ appVersion, /*String*/ loadDemoData, /*String*/ customizationVersion) {
  	return this.upgradeProcess(appID, appVersion, loadDemoData, false, false, '', customizationVersion);
  },

  /***
   * Installs or updates the root store app from the UI or autoUpdate schedule job
   */
  upgradeProcess: /*boolean*/ function(/*source_app_id*/ appID, /*String*/ appVersion, /*String*/ loadDemoData, obtainMutex,/*boolean*/ isReinstallRequested, /*object*/ additionalArgs, /*String*/ customizationVersion, /*String*/ subscription) {
  	gs.info("upgrade(): appID={0}, version={1}, customizationVersion={2}, loadDemoData={3}, reinstall={4}, subscription={5}", appID, appVersion, customizationVersion, loadDemoData,isReinstallRequested, subscription);

  	this.installationStartTime = new GlideDateTime().getValue();
  	this.customizationVersion = customizationVersion;
  	var customization = this._getCustomization(appID, customizationVersion);
  	var storeApp = this._getOrCreateStoreApp(appID, appVersion);
  	this.activePluginsBefore = new global.AppManagerCacheHelper().getActivePlugins();

  	// ******* VALIDATION ********
  	// if these fail the tracker fails
  	if (!this._shouldInstallOrUpgrade(storeApp, appVersion))
          return false;

  	if (this._isCustomizationInstall && !this._shouldInstallOrUpgrade(customization, customizationVersion))
          return false;

  	this._isStoreAppInstall = this._shouldInstallOrUpgradeStoreApp(storeApp, appVersion, isReinstallRequested);
  	if (this._isStoreAppInstall && !this._checkStoreAppDependencies(storeApp, appVersion))
          return false;

      if (this._isCustomizationInstall)
          this._isCustomizationInstall = this._shouldInstallOrUpgradeCustomization(customization, customizationVersion, isReinstallRequested);

  	if (!this._isStoreAppInstall && !this._isCustomizationInstall) {
          msg = gs.getMessage(this.NO_UPGRADE_NECESSARY, [assignedVersion, app.sys_id]);
          gs.info(msg);
          this._PARENT_TRACKER.success(msg);
          return true;
  	}
  	// ******* VALIDATION END ********

      if (this._isStoreAppInstall) {
          //PRB1320429: Dependent Apps skip loading conditional content of parent - figure out all app dependencies for the app.
          var dependencyObj = this.getAllSNScopeDependencies({sysStoreApp: [], sysRemoteApp: []}, storeApp.scope);

          storeApp.assigned_version = appVersion;
          if (!gs.nil(additionalArgs) && storeApp.isValidField("installation_info"))
              storeApp.installation_info = JSON.stringify(additionalArgs);

          if (subscription) // Only set subscription if one was provided
              storeApp.setValue("license", subscription);

          storeApp.update();
      }

      if (this._isCustomizationInstall) {
  		customization.assigned_version = customizationVersion;
  		customization.update();
      }

  	var installTracker = this._PARENT_TRACKER.createChildIfAbsent(this.INSTALL_APP_PACKAGE);
  	installTracker.run();
  	installTracker.incrementPercentComplete(2);

      var msg = gs.getMessage("Unable to complete install successfully");
      var demoData = (typeof loadDemoData == 'boolean') ? loadDemoData : loadDemoData == 'true';
  	
  	var args = {};
  	// Install Customization if exists
  	if (this._isCustomizationInstall) {
          if (!this._installCustomizationPackage(customization, storeApp, demoData, obtainMutex)) {
              var error = gs.getMessage("Unable to successfully install customizations {0}:{1}", [storeApp.name, appVersion]);
              if (this._failTrackerIfNeeded(installTracker, error))
                 this._PARENT_TRACKER.fail(error);

            return false;
          }
          msg = ("Successfully installed customization version {0} for application {1}:{2}", customization.appVersion, storeApp.name, storeApp.appVersion);
          args = this.getCustomizationsBaseApps(storeApp);
  		
  	} else if (this._isStoreAppInstall) {
          // Install new app package
          if (!this._installApplicationPackage(storeApp, demoData, obtainMutex)) {
              var error = gs.getMessage("Unable to successfully install application package {0}:{1}", [storeApp.name, appVersion]);
              if (this._failTrackerIfNeeded(installTracker, error))
                  this._PARENT_TRACKER.fail(error);

              return false;
  		}

  		msg = ("Successfully installed application {0}:{1}", storeApp.name, storeApp.appVersion);
  		
  		//if we got this far, we can safely remove the old sys_app_version records
  		//any newer versions will be downloaded next time UpdateChecker runs
  		this._removeAppVersions(appID, appVersion);

  		//update installation info for the latest installed apps
  		//this will filter the existing installed apps from the current installed apps and stamp the installation info this is work around for the dependent apps
  		this.updateInstallationInfoOnLatestInstalledApps(additionalArgs);

  		//BEGIN: PRB1320429: Dependent Apps skip loading conditional content of parent
  		if (typeof this.glideAppLoader.loadConditionalContent != "undefined") {
  			for (var i = 0; i < dependencyObj.sysRemoteApp.length; i++) {
  				gs.info("Loading conditional content post install {0}", dependencyObj.sysRemoteApp[i]);
  				this.glideAppLoader.loadConditionalContent(dependencyObj.sysRemoteApp[i], true);
  			}
  		}
  		args = this.getAppsToBeUpdatedInCache();
  	}
  	if(gs.getProperty("sn_appclient.enable_app_manager_cache", "true") == "true")
  		this.updateAppsPluginsCache(args);
  	gs.info(msg);
  	if(gs.getProperty("glide.apps.force_sync_post_install_or_uninstall", "true") == "true")
  		new UpdateChecker().checkAvailableUpdates(false, false, {}, true);
  	installTracker.success();
  	this.captureInstallationAnalytics(storeApp);
  	return true;
  },

  captureInstallationAnalytics: function(storeApp) {
  	var installationStartTime = new GlideDateTime(this.installationStartTime).getNumericValue();
  	var installationEndTime = new GlideDateTime().getNumericValue();
  	var installationTime = (installationEndTime - installationStartTime)/1000;
  	var data = {};
  	data.app_id = storeApp.sys_id.toString();
  	data.time = installationTime.toFixed(2);
  	data.entity_type = storeApp.shared_internally.toString() == "true" ? "Company Application" : "Store Application";
  	data.version = storeApp.assigned_version.toString();
  	new AppClientGCFUtil().recordEvent("app_install", "installation_time", data);
  },

  //this will update cache for installed apps and plugins
  updateAppsPluginsCache: function(args) {
      this.refreshInstalledAppsInCache(args);
      this.refreshActivatedPluginsInCache();
      new sn_appclient.UpdateChecker().updateInstalledAppsInStoreAsync();
  },

  refreshInstalledAppsInCache: function(args) {
      var sysIdList = [];
      args.specificAppsToUpdate.forEach(function(app) {
          sysIdList.push(app.sourceAppId);
      });

      //DEF0334116 : Use GlideRecord instead of GlideRecordSecure
      var useGlideRecord = true;
      
      var apps = [];
      var appsData = new AppsData();
      appsData._addInstalledApps(apps, args.sharedInternally, sysIdList, useGlideRecord);
      appsData._addVersionsToApps(apps);

      args.appsList = apps;

      new global.AppManagerCache().removeAndAppendCorrectAppsInCache(args);
  },

  refreshActivatedPluginsInCache: function() {
      var latestActivatedPlugins = new global.AppManagerCacheHelper().getLatestActivatedPlugins(this.activePluginsBefore);
      var args = {
          specificAppsToUpdate: latestActivatedPlugins,
          sharedInternally: false
      };

      new global.AppManagerCache().updateInstalledPluginsInCache(args);
  },

  getCustomizationsBaseApps: function(storeApp) {
      var specificAppsToUpdate = [];
      specificAppsToUpdate.push({
          sourceAppId: storeApp.sys_id.toString(),
          scope: storeApp.scope.toString(),
          sysCode: storeApp.sys_code.toString()
      });
      var args = {
          specificAppsToUpdate: specificAppsToUpdate,
          sharedInternally: false
      };

      return args;
  },

  getAppsToBeUpdatedInCache: function() {
      var specificAppsToUpdate = [];
      var installedAppsGr = this.getActiveStoreAppsInInstallationWindow();
      var sharedInternally = false;
      while (installedAppsGr.next()) {
          specificAppsToUpdate.push({
              sourceAppId: installedAppsGr.sys_id.toString(),
              scope: installedAppsGr.scope.toString(),
              sysCode: installedAppsGr.sys_code.toString()
          });
          sharedInternally = installedAppsGr.shared_internally.toString() == "true";
      }
      var args = {
          specificAppsToUpdate: specificAppsToUpdate,
          sharedInternally: sharedInternally
      };

      return args;
  },

  getAllSNScopeDependencies: function(obj, scope){
    var dependencyArr = [];
    var grStoreApp = new GlideRecord("sys_store_app");
    grStoreApp.addQuery("scope",scope);
    grStoreApp.query();
    if(grStoreApp.next()){
    	if(grStoreApp.active.toString() === "true")
          obj.sysStoreApp.push(scope);
    	else
    		obj.sysRemoteApp.push(scope);
      dependencyArr = grStoreApp.dependencies.toString().split(",");
    } else {
      var grRemoteApp = new GlideRecord("sys_remote_app");
      grRemoteApp.addQuery("scope",scope);
      grRemoteApp.query();
      if(grRemoteApp.next()){
        obj.sysRemoteApp.push(scope);
        dependencyArr = grRemoteApp.dependencies.toString().split(",");
      }
    }
    for(var i = 0; i < dependencyArr.length; i++) {//go through all dependencies
      var depPair = dependencyArr[i].split(":");
      if(depPair.length == 2
              && depPair[1] != "sys"
              && depPair[0] != "global"
              && obj.sysStoreApp.indexOf(depPair[0]) == -1
              && obj.sysRemoteApp.indexOf(depPair[0]) == -1){//make sure arr doesnt have this scope already. avoiding infinite loop
        obj = this.getAllSNScopeDependencies(obj, depPair[0]);//figure out the dependencies
      }
    }
    return obj;
  },

  _getCustomization: /*GlideRecord sys_app_customization*/ function(/*String*/ appID, /*String*/ customizationVersion) {
  		if (gs.nil(customizationVersion) || customizationVersion == "none") {
  			this._isCustomizationInstall = false;
  			return null;
  		}

  		var customization = new GlideRecord("sys_app_customization");
  		customization.addQuery("vendor_app", appID);
  		customization.query();
  		if (customization.next()) {
  			this._isCustomizationInstall = true;
  			this._checkAndDownloadManifest(appID, customizationVersion);
  			return customization;
  		}
  },
  
 _checkAndDownloadManifest: function(/*String*/vendorAppSourceAppId, /*String*/ customizationVersion) {
  	gs.info("_checkAndDownloadManifest(): vendorSourceAppId: {0} customizationVersion: {1}", [vendorAppSourceAppId, customizationVersion]);
  	var customizationVersions = new GlideRecord("sys_app_customization_version");
  	customizationVersions.addQuery("vendor_app_id", vendorAppSourceAppId);
  	customizationVersions.addQuery("version", customizationVersion);
  	customizationVersions.addNullQuery("manifest");
  	customizationVersions.query();
  	while (customizationVersions.next()) {
  	    var version = customizationVersions.getValue("version");
  	    var app_id = customizationVersions.getValue("app_customization");
  	    var manifest = new ManifestService().getManifestForAppVersion(app_id, version, true, true);
  	    gs.info("Manifest for App customization:  {0} customizationVersion {1} is {2}", [app_id, customizationVersion, JSON.stringify(manifest)]);
  	}	
  },

  _getOrCreateStoreApp: /*GlideRecord sys_store_app*/ function(/*String*/ appID, /*String*/ version) {
  	var storeApp = new GlideRecord("sys_store_app");
  	if (!storeApp.get(appID))
  		storeApp = this._convertToStoreApp(appID, version);
  	if (!gs.isPaused())
  		this._setValuesFromVersion(storeApp, version);
  	return storeApp;
  },

  _convertToStoreApp: /*GlideRecord sys_store_app*/ function (/*String*/ remoteAppId, /*String*/ version) {
        var remoteApp, storeApp;

  	remoteApp = new GlideRecord("sys_remote_app");
  	if (!remoteApp.get(remoteAppId))
  		throw new Error("Unable to find expected remote app: " + remoteAppId);


  	if(remoteApp.getValue("delivery_source") == 'out_of_band' && !gs.isPaused())
  		if(sn_lef.GlideEntitlement.isLicenseCheckRequired(remoteApp.getValue("sys_code")))
  			if(!sn_lef.GlideEntitlement.hasLicenseForApp(remoteApp.getValue("sys_code"))){
  				var msg = gs.getMessage("Aborting installation of application: {0}, application does not have a valid license", [remoteApp.getValue("name")]);
  				gs.error(msg);
  				this._PARENT_TRACKER.fail(msg);
  				throw new Error("license not found for "+remoteApp.getValue("name"));
  			}

  	// create a new sys_store_app record, using sys_remote_app as a copy
  	gs.info("Creating new sys_store_app " + remoteAppId + " from sys_remote_app entry");
  	storeApp = new GlideRecord("sys_store_app");
  	storeApp.initialize();
  	storeApp.setValue("sys_id", remoteAppId);
              storeApp.setValue("licensable", remoteApp.getValue("licensable"));
  	storeApp.setNewGuidValue(remoteAppId);
  	storeApp.setValue("name", remoteApp.getValue("name"));
  	storeApp.setValue("scope", remoteApp.getValue("scope"));
  	storeApp.setValue("latest_version", remoteApp.getValue("latest_version"));
  	storeApp.setValue("assigned_version", version);
  	storeApp.setValue("vendor", remoteApp.getValue("vendor"));
  	storeApp.setValue("vendor_prefix", remoteApp.getValue("vendor_prefix"));
  	storeApp.setValue("short_description", remoteApp.getValue("short_description"));
  	storeApp.setValue("dependencies", remoteApp.getValue("dependencies"));
  	storeApp.setValue("compatibilities", remoteApp.getValue("compatibilities"));
      storeApp.setValue("is_store_app", remoteApp.getValue("is_store_app"));
  	storeApp.setValue("demo_data", remoteApp.getValue("demo_data"));
  	storeApp.setValue("shared_internally", remoteApp.getValue("shared_internally"));
  	storeApp.setValue("auto_update", !!remoteApp.auto_update);
  	storeApp.setValue("active", false); // when installed it'll become active
  	if (storeApp.isValidField("last_installed_from"))
  		storeApp.setValue("last_installed_from",remoteApp.getValue("delivery_source"));
  	storeApp.setValue("upload_info", remoteApp.getValue("upload_info"));
  	storeApp.setValue("products", remoteApp.getValue("products"));

  	/*Get LOB and Price type value for installed version.
  	If no value is available, fallback to the values in sys_remote_app table
  	*/
  	var versionAttributes = this._getVersionAttributes(remoteApp.getValue("source_app_id"), version);

  	var versionLob = versionAttributes ? versionAttributes.lob : remoteApp.getValue("lob"),
  	    versionPrice = versionAttributes ? versionAttributes.price_type : remoteApp.getValue("price_type"),
  	    versionIndicators = versionAttributes ? versionAttributes.indicators : remoteApp.getValue("indicators"),
  	    versionDateMessage = versionAttributes ? versionAttributes.display_message : remoteApp.getValue("display_message"),
  	    versionNeedsAppEngineLicensing = versionAttributes.needs_app_engine_licensing ? versionAttributes.needs_app_engine_licensing : remoteApp.needs_app_engine_licensing,
  	    versionCustomTableCount = versionAttributes.custom_table_count ? versionAttributes.custom_table_count : remoteApp.getValue("custom_table_count");

  	storeApp.setValue("lob", versionLob);
  	storeApp.setValue("price_type", versionPrice);
  	storeApp.setValue("indicators", versionIndicators);
  	storeApp.setValue("display_message", versionDateMessage);
  	storeApp.setValue("uninstall_blocked", remoteApp.uninstall_blocked);
  	storeApp.setValue("hide_on_ui", remoteApp.hide_on_ui.toString() == "true" ? true : false );
  	storeApp.setValue("installed_as_dependency", remoteApp.installed_as_dependency.toString() == "true" ? true : false);
  	storeApp.setValue("needs_app_engine_licensing", versionNeedsAppEngineLicensing);
  	storeApp.setValue("custom_table_count", versionCustomTableCount);
  	storeApp.setValue("logo", remoteApp.getValue("logo"));

  	storeApp.insert();
  	if (!storeApp.isValidRecord())
  		throw new Error("Unable to create new sys_store_app record: " + remoteAppId);

  	// Reap sys_remote_app, since we now have a store app to install
      this.glideAppLoader.reapOldRemoteApp(remoteApp, storeApp);

  	return storeApp;
  },

  _getVersionAttributes: /*Object*/ function (sourceAppId, version) {
  	var appVersion = new GlideRecord("sys_app_version");
  	appVersion.addQuery("version", version);
  	appVersion.addQuery("source_app_id", sourceAppId);
  	appVersion.query();

  	if(appVersion.next()) {
  		return {	"lob": appVersion.getValue("lob"),
  				"price_type": appVersion.getValue("price_type"),
  				"indicators": appVersion.getValue("indicators"),
  				"display_message": appVersion.getValue("display_message"),
  				"needs_app_engine_licensing": appVersion.needs_app_engine_licensing,
  				"custom_table_count": appVersion.getValue("custom_table_count")
  				};
  	} else
  		return "";
  },

  _shouldInstallOrUpgrade: /*boolean*/ function(/*GlideRecord*/ app, /*String*/ version) {
  	var msg;
  	if(!gs.isPaused() && !this._userHasInstallAccess(app)) {
  		msg = gs.getMessage("Insufficient privileges for user: {0} to install or update an application package {1}", [gs.getUserID(), app.sys_id]);
  		gs.error(msg);
  		this._PARENT_TRACKER.fail(msg);
  		return false;
  	}

  	if (!app.isValidRecord()) {
  		msg = gs.getMessage(this.APP_IS_NOT_AVAILABLE, app.sys_id);
  		gs.error(msg);
  		this._PARENT_TRACKER.fail(msg);
  		return false;
  	}

  	var assignedVersion = this._cleanVersion(version);
  	if (gs.nil(assignedVersion)) {
  		msg = gs.getMessage(this.NO_ASSIGNED_VERSION,  app.sys_id);
  		gs.error(msg);
  		this._PARENT_TRACKER.fail(msg);
  		return false;
  	}

  	var installedVersion = this._cleanVersion(app.version);
  	/** installed = from/current, assigned = to/target */
  	if (!gs.nil(installedVersion) && this._isDowngrade(installedVersion, assignedVersion)) {
  		msg = gs.getMessage(this.CANT_DOWNGRADE, [ app.sys_id, installedVersion, assignedVersion]);
  		gs.error(msg);
  		this._PARENT_TRACKER.fail(msg);
  		return false;
  	}
  	return true;
  },

  _shouldInstallOrUpgradeCustomization: /*boolean*/  function(/*GlideRecord*/ app, /*String*/ version, /*boolean */ isReinstall) {
  	var installedVersion = this._cleanVersion(app.version);
  	var assignedVersion = this._cleanVersion(version);
  	if (assignedVersion == installedVersion && !gs.isPaused() && !isReinstall) {
  		msg = gs.getMessage(this.NO_UPGRADE_NECESSARY, [assignedVersion, app.sys_id]);
  		gs.info(msg);
  		return false;
  	}
  	return true;
  },

  _shouldInstallOrUpgradeStoreApp: /*boolean*/ function(/*GlideRecord*/ app, /*String*/ version, /*boolean */ isReinstall) {
  	var installedVersion = this._cleanVersion(app.version);
  	var assignedVersion = this._cleanVersion(version);
  	if (assignedVersion == installedVersion && app.active && !gs.isPaused() && !isReinstall) {
  		msg = gs.getMessage(this.NO_UPGRADE_NECESSARY, [assignedVersion, app.name]);
  		gs.info(msg);
  		return false;
  	}
  	return true;
  },

  _checkStoreAppDependencies: /*boolean*/ function(/*GlideRecord*/ app, /*String*/ version) {
  	this._checkCompatibility(app, version);

  	if (this._unavailableDepPlugins != "") {
  		msg = gs.getMessage(this.PLUGIN_DEPENDENCY_UNAVAILABLE, [app.name, this._unavailableDepPlugins]);
  		gs.error(msg);
  		this._PARENT_TRACKER.fail(msg);
  		return false;
  	}

  	if (this._inaccessibleDepApps != "") {
  		msg = gs.getMessage(this.APP_DEPENDENCY_UNAVAILABLE, [app.name, this._inaccessibleDepApps]);
  		gs.error(msg);
  		this._PARENT_TRACKER.fail(msg);
  		return false;
  	}

  	if(this._depAppsBlockedForInstall != ""){
  		msg = gs.getMessage("Aborting installation of application: {0}, application depends on other applications which are blocked for install: {1}", [app.name, this._depAppsBlockedForInstall]);
  		gs.error(msg);
  		this._PARENT_TRACKER.fail(msg);
  		return false;
  	}
  	return true;
  },

  _userHasInstallAccess : function(/*GlideRecord sys_store_app*/ storeApp) {
  	if (GlidePluginManager.isActive("com.glide.app_api")) {
  		if (typeof sn_app_api.AppStoreAPI.canInstallOrUpgrade != "undefined")
  			return sn_app_api.AppStoreAPI.canInstallOrUpgrade(storeApp);
  	}

  	// admin or app_client_user is allowed to install or upgrade
  	if (gs.hasRole('sn_appclient.app_client_user'))
  		return true;

  	return false;
  },

  _userHasCustomizationInstallAccess : function(/*GlideRecord sys_app_customization*/ customization) {
  	if (GlidePluginManager.isActive("com.glide.app_api")) {
  		if (typeof sn_app_api.AppStoreAPI.canInstallOrUpgrade != "undefined")
  			return sn_app_api.AppStoreAPI.canInstallOrUpgrade(customization);
  	}

  	// admin or app_client_user is allowed to install or upgrade
  	if (gs.hasRole('sn_appclient.app_client_user'))
  		return true;

  	return false;
  },
  
  _setValuesFromVersion : function(/*GlideRecord sys_store_app*/ storeApp, /*String*/ version) {
  	var updateAvailable = false;
  	var customizationExistForApp = false;
  	var customizationVersion = this.customizationVersion;
  	var customizationGR = new GlideRecord("sys_app_customization");
  	customizationGR.addQuery("vendor_app", storeApp.getValue("sys_id"));
  	customizationGR.query();
  	if(customizationGR.next()){
  		customizationExistForApp = true;
  		if(gs.nil(customizationVersion) || !customizationVersion || customizationVersion == 'none' || customizationVersion == "undefined"){
  			customizationVersion = "0.0.0";
  		}
  		if(this._isDowngrade(customizationGR.getValue("latest_version"), customizationVersion))
  			updateAvailable =  true;
  	}
  	
  	if(!updateAvailable && this._isDowngrade(storeApp.getValue("latest_version"), version))
  		updateAvailable = true;
  	storeApp.setValue("customization", (customizationExistForApp ? customizationGR.getValue("sys_id"): null));
  	storeApp.setValue("update_available", updateAvailable);
  	storeApp.update();
  	
  	var versionGr = new GlideRecord('sys_app_version');
  	versionGr.addQuery("source_app_id", storeApp.getValue("sys_id"));
  	versionGr.addQuery("version", version);
  	versionGr.query();

  	if (versionGr.getRowCount() == 1 && versionGr.next()) {
  		//update dependencies, short_description etc
  		storeApp.setValue("auto_update", versionGr.getValue("auto_update"));
  		storeApp.setValue("demo_data", versionGr.getValue("demo_data"));
  		storeApp.setValue("dependencies", versionGr.getValue("dependencies"));
          storeApp.setValue("compatibilities", versionGr.getValue("compatibilities"));
          storeApp.setValue("is_store_app", versionGr.getValue("is_store_app"));
  		storeApp.setValue("name", versionGr.getValue("name"));
  		storeApp.setValue("short_description", versionGr.getValue("short_description"));
  		storeApp.setValue("last_installed_from", versionGr.getValue("delivery_source"));
  		storeApp.setValue("upload_info", versionGr.getValue("upload_info"));
  		storeApp.setValue("uninstall_blocked", versionGr.uninstall_blocked);
  		storeApp.setValue("hide_on_ui", versionGr.hide_on_ui.toString() == "true" ? true : false);
  		storeApp.setValue("installed_as_dependency", versionGr.installed_as_dependency.toString() == "true" ? true : false);
  		storeApp.setValue("needs_app_engine_licensing", versionGr.needs_app_engine_licensing);
  		storeApp.setValue("custom_table_count", versionGr.custom_table_count);

  		this._setValuesFromManifest(storeApp, version);

  	}

  },

  _setValuesFromManifest: function(/*GlideRecord sys_store_app */ storeApp, /*string*/ version) {
  	var manifestService = new ManifestService();
  	var manifest = manifestService.getManifestForAppVersion(storeApp.getValue("sys_id"), version, false, true);
  	var mapRepo, map, source, target, i;
  	var licenseModel, licenseDefinition;

  	if (manifest && manifest.hasOwnProperty("additional_fields")) {
  		mapRepo = new ManifestFieldMap(manifest);
  		for (i = 0; i< manifest.additional_fields.length; i++) {
  			source = manifest.additional_fields[i];
  			map = mapRepo.getMap(source.field_name);
  			target = map(source);

  			gs.debug("Setting field {0} to {1}, from manifest", target.field_name, target.value);
  			if (storeApp.isValidField(target.field_name)) {
  				if (target.field_name == "license_model") {
  					licenseModel = target.value;
  					storeApp.setValue(target.field_name, target.value);
  				} else if (target.field_name == "license_definition")
  					licenseDefinition = target.value;
  				else
  					storeApp.setValue(target.field_name, target.value);
  			}
  		}

  		if (gs.nil(licenseDefinition) ||
  			!(sn_app_api.AppStoreAPI.isISVScope(storeApp.scope)) ||
  			licenseModel != "capacity") {
  			storeApp.setValue("license_definition", "");
  		}
  		else
  			storeApp.setValue("license_definition", licenseDefinition);
  	}
  },

  _cleanVersion: /*String*/ function(/*String*/ version) {
  	if (gs.nil(version))
  		return '';

  	return (version+'').trim();
  },

  /**
   * Analyzes dependencies for the installing app.
   * Produces this._unavailableDepPlugins, this._inaccessibleDepApps, this._depAppsBlockedForInstall CSV lists as result of its execution.
   */
  _checkCompatibility: /*void*/ function(/*sys_store_app*/ storeApp, /*String*/ version) {
  	this._unavailableDepPlugins = ""; // comma-separated list of dependency plugins which are unavailable at this instance
  	this._inaccessibleDepApps = ""; // comman-seprated list of dependency apps which are unavailable at app store
  	this._depAppsBlockedForInstall = "";
  	var dependencies = storeApp.dependencies + "";
  	gs.info("_checkCompatibility: dependencies = " + dependencies);
  	if (gs.nil(dependencies))
  		return;

  // For servicenow Apps we want to auto resolve optional plugin dependency.
  var isServiceNowApp = storeApp.scope.indexOf("sn_") == 0;

  	var depArray = dependencies.split(",");
  	var dependencyApps = "";
  	for (var i = 0; i < depArray.length; i++) {
  		var depPair = depArray[i].split(":"); // pair of dependency id and version
  		if (depPair.length > 0 && gs.nil(depPair[0]))
  			continue;

  		if (depPair.length < 2 || depPair[1] == "sys") { // handle plugin dependencies
  			var name = depPair[0];
  			if (name.startsWith("apps/") || name.startsWith("glidesoft"))
  				continue;

  			gs.info("_checkCompatibility: dependency = " + depPair[0]);
  			var isPluginActive = GlidePluginManager.isActive(depPair[0]);
  			if (!isPluginActive && !isServiceNowApp) {
  				if (this._unavailableDepPlugins != "")
  				 	this._unavailableDepPlugins += ",";
  				 this._unavailableDepPlugins += depPair[0];
  			}
  		} else {
  			if (this._appAlreadyExist(depPair))
  				continue;

  			if (dependencyApps != "")
  				 dependencyApps += ",";

  			dependencyApps += depArray[i];
  		}
  	}

  	gs.info("_checkCompatibility: this._unavailableDepPlugins = " + this._unavailableDepPlugins);
  	gs.info("_checkCompatibility: dependencyApps = " + dependencyApps);
  	// now validate app dependencies at app store only if app is installed from UI which is not offline app and not during zboot or upgrade
  	if (dependencyApps != "" && !gs.isPaused()) {
  		var dependencyProcessorObj = new DependencyProcessor(storeApp.scope);
  		var dependentAppsStatus = dependencyProcessorObj.processDependency(dependencyApps.split(','));
  		dependentAppsStatus.forEach(function(dependentApp) {
  			if(!dependentApp.active) {
  			    if(dependentApp.status == dependencyProcessorObj.knownStatusMap["Installation blocked"]) {
                      this._depAppsBlockedForInstall += (this._depAppsBlockedForInstall == "") ? dependentApp.Id : "," + dependentApp.Id;
                  }else{
                      this._inaccessibleDepApps += (this._inaccessibleDepApps == "") ? (dependentApp.Id + ":" + dependentApp.minVersion) : ("," + dependentApp.Id + ":" + dependentApp.minVersion);
                  }
              }
  		},this);
  	}
  	gs.info("_checkCompatibility: this._inaccessibleDepApps = " + this._inaccessibleDepApps);
  },

  _installCustomizationPackage: /*boolean*/ function(/*sys_app_customization*/ customization,  /*sys_store_app*/ storeApp, /*Boolean*/ loadDemoData, obtainMutex) {
  	gs.info("Installing or upgrading customizations for: " + customization.vendor_app.name);

  	if (this._isStoreAppInstall && !gs.nil(storeApp))
  	    return this.glideAppLoader.installOrUpgradeAppCustomizationAndStoreApp(customization, storeApp, obtainMutex, loadDemoData);

  	return this.glideAppLoader.installOrUpgradeAppCustomization(customization, obtainMutex);
  },


  _installApplicationPackage: /*boolean*/ function(/*sys_store_app*/ storeApp, /*Boolean*/ loadDemoData, obtainMutex) {
  	gs.info("Installing or upgrading application: " + storeApp.name + ", loadDemoData: " + loadDemoData);
  	if (obtainMutex == true) {
  		if (typeof this.glideAppLoader.installOrUpgradeAppWithMutex != "undefined")
  			return this.glideAppLoader.installOrUpgradeAppWithMutex(storeApp.getUniqueValue(), loadDemoData);
  	}

  	return this.glideAppLoader.installOrUpgradeApp(storeApp.getUniqueValue(), /* deprecated */ "", loadDemoData);
  },

  _isDowngrade: /*boolean */ function(/*from version*/ currentVersion, /* to version */ targetVersion) {
  	return this.versionComparator.isDowngrade(currentVersion, targetVersion);
  },

  _isAppCompatible: function(appId, version) {
  	var appVersions = new GlideRecord("sys_app_version");
  	appVersions.addQuery("source_app_id", appId);
  	appVersions.addQuery("version", version);
  	appVersions.addQuery("block_install", false);
  	appVersions.query();

  	return appVersions.getRowCount();
  },

  _getAutoUpdates: /*[{source_app_id,latest_version},...]*/ function() {
  	var updates = [];

  	if (!this._autoUpdateEnabled) {
  		gs.info('Skipping app auto-updates (behavior was disabled via property override)');
  		return updates;
  	}

  	var storeApp = new GlideRecord('sys_store_app');
  	if (!storeApp.isValidField('auto_update')) {
  		gs.info('Skipping app auto-updates (field sys_store_app.auto_update not found)');
  		return updates;
  	}

  	storeApp.addQuery('auto_update', true);
  	storeApp.addNotNullQuery('version');
  	storeApp.addNotNullQuery('latest_version');
  	storeApp.query();
  	gs.info('Got ({0}) candidate records to check for auto-update on', storeApp.getRowCount());
  	while (storeApp.next()) {
  		var installedVersion = this._cleanVersion(storeApp.getValue('version'));
  		var latestVersion = this._cleanVersion(storeApp.getValue('latest_version'));
  		if (gs.nil(latestVersion) || latestVersion == installedVersion)
  			continue;

  		var assignedVersion = this._cleanVersion(storeApp.getValue('assigned_version'));
  		if (latestVersion == assignedVersion)
  			continue; // upgrade in progress

  		var source_app_id = storeApp.getUniqueValue();
  		var scope = storeApp.scope.toString();
  		if(!this._isAppCompatible(source_app_id, latestVersion))
  			continue;

  		gs.info('Auto-update store app {0} ({1}) from {2} to {3}',
  			scope, source_app_id, installedVersion, latestVersion);

  		var update = { source_app_id: source_app_id, scope: scope, latest_version: latestVersion };
  		updates.push(update);
  	}
  	if (storeApp.getRowCount() > 0)
  		gs.info('({0}) apps to auto-update', updates.length);

  	return updates;
  },

  _removeAppVersions : function(/*string*/ source_app_id, version) {
  	//we either installed for the first time, or updated an existing app
  	var appVersions = new GlideRecord("sys_app_version");
  	appVersions.addQuery("source_app_id", source_app_id);
  	appVersions.query();

  	while (appVersions.next()) {
  		gs.info("Comparing {0} to {1}, is {1} an upgrade? {2}", version, appVersions.getValue("version"), this.versionComparator.isUpgrade(version, appVersions.getValue("version")));
  		if (!this.versionComparator.isUpgrade(version, appVersions.getValue("version")))
  			appVersions.deleteRecord();
  		else {
  			appVersions.setValue("remote_app", "");
  			appVersions.setValue("store_app", source_app_id);
  			appVersions.update();
  		}
  	}
  },

  _appAlreadyExist: /*boolean*/ function(/*[]*/ depPair) {

  	// dependency format of scope:version:id, and this might be a global app
  	if (depPair.length < 3)
  		return false;

  	var wantVersion = depPair[1];
  	var appId = depPair[2];
  	var haveVersion;

  	var availablePackage = new GlideRecord("sys_package");
  	availablePackage.addQuery("source", appId);
  	availablePackage.query();
  	if (!availablePackage.next())
  		return false;

  	haveVersion = availablePackage.getValue("version");
  	var comparator = new VersionComparator();

  	//we can allow it if the current version is equal or greater than want version
  	return !comparator.isUpgrade(haveVersion, wantVersion);
  },

  _failTrackerIfNeeded: function (tracker, message) {
  	gs.error(message);
  	var execTracker = new GlideRecord("sys_execution_tracker");
  	execTracker.get(tracker.getSysID());
  	if (!execTracker.isValidRecord())
  		return false;

  	// The tracker was already failed
  	if (execTracker.state == 3)
  		return false;

  	tracker.fail(message);
  	return true;
  },
  getLatestUpgradeHistory: function (appId) {

  	var gr = new GlideRecord('sys_upgrade_history');
  	gr.addQuery('to_version',appId);
  	gr.orderByDesc('sys_created_on');
  	gr.setLimit(1);
  	gr.query();

  	if(gr.next())
  		return gr.getValue('sys_id');
  	return "";
  },
  getLoggedInUserEmail : function(){
  	var user = new GlideRecord("sys_user");
  	user.get(gs.getUserID());
  	return user.getValue("email");
  },
  updateInstallationInfoOnLatestInstalledApps : function(additionalArgs){
  	var storeGr = this.getActiveStoreAppsInInstallationWindow();
  	while(storeGr.next()) {
  		if (!gs.nil(additionalArgs) && storeGr.isValidField("installation_info")) {
  				storeGr.installation_info = JSON.stringify(additionalArgs);
  				storeGr.setWorkflow(false);
  				storeGr.update();
  		}
  	}
  },
  getActiveStoreAppsInInstallationWindow : function (){
  	var installationEndTime = new GlideDateTime().getValue();
  	var storeAppGr = new GlideRecord("sys_store_app");
  	storeAppGr.addQuery("update_date",">=",this.installationStartTime);
  	storeAppGr.addQuery("update_date","<=",installationEndTime);
  	storeAppGr.addActiveQuery();
  	storeAppGr.query();
  	return storeAppGr;
  },
  
  getBatchAppsToBeUpdatedInCache: function(batchInstallPlan) {
  	var specificAppsToUpdate = [];
  	var sharedInternally = false;
  	batchInstallPlan.packages.forEach(function(app) {
  		specificAppsToUpdate.push({
  			sourceAppId: app.id,
  			sysCode: app.id
  		});
  	});

  	var args = {
  		specificAppsToUpdate: specificAppsToUpdate,
  		sharedInternally: sharedInternally
  	};

  	return args;
  },
  
  installBatch : function(batchInstallPlan , isScheduled){
  	this.installationStartTime = new GlideDateTime().getValue();
  	var installedBatch = this.glideAppLoader.installBatch(batchInstallPlan, isScheduled);
  	
  	if(gs.getProperty("sn_appclient.enable_app_manager_cache", "true") == "true") {
  		var args = this.getBatchAppsToBeUpdatedInCache(JSON.parse(batchInstallPlan));
  		this.updateAppsPluginsCache(args);
  	}

  	return installedBatch;
  },

  installProductByPlan: function(installationPlan) {
      var installationResponse = {};
      var batchInfo;
      try {
          gs.info("installPayload" + JSON.stringify(installationPlan));
          if (installationPlan) {
              if (installationPlan && installationPlan.hasOwnProperty('name') && installationPlan.hasOwnProperty('packages') && installationPlan.packages.length > 0) {
                  new AppClientGCFUtil().recordBatchInstallAnalytics(installationPlan);
                  batchInfo = this.installBatch(JSON.stringify(installationPlan), false);
                  if (batchInfo != null)
                      installationResponse.batchInfo = JSON.parse(batchInfo);
                  else
                      installationResponse.error = "failed to install product";
              } else {
                  installationResponse.error = "invalid product payload";
              }
          }
      } catch (e) {
          installationResponse.error = "failed to process product Install" + e;
      }
      return installationResponse;
  },

  installAndUpdateApps: function(params) {
      var appID = params.app_id;
      var version = params.version;
      var customizationVersion = params.customization_version;
      var loadDemoData = params.load_demo_data;
      var isReinstall = params.isReinstall;
      var subscription = params.subscription;

      this.recordAppInstallEvent(params);

      gs.info("Download app: {0}, version: {1}, loadDemoData: {2}, isReinstall: {3}, customizationVersion: {4}, subscription: {5}", appID, version, loadDemoData, isReinstall, customizationVersion, subscription);

      var progress_name = "Install Application";
      var worker = new GlideScriptedHierarchicalWorker();
      worker.setProgressName(progress_name);
      worker.setBackground(true);
      worker.setCannotCancel(true);
      worker.setScriptIncludeName("sn_appclient.AppUpgrader");
      worker.setScriptIncludeMethod("upgradeProcess");
      worker.putMethodArg("appID", appID);
      worker.putMethodArg("version", version);
      worker.putMethodArg("loadDemoData", loadDemoData);
      worker.putMethodArg("obtainMutex", true);
      worker.putMethodArg("isReinstallRequested", isReinstall == "true");


      worker.putMethodArg("additionalArgs", {
          "installed_user": {
              "user_id": gs.getUserName(),
              "user_email": this.getLoggedInUserEmail()
          }
      });

      if (customizationVersion)
          worker.putMethodArg("customizationVersion", customizationVersion);
      else // Explicitly define so that the parameters are spaced property
          worker.putMethodArg("customizationVersion", undefined);

      worker.putMethodArg("subscription", subscription);

      worker.start();

      var trackerId = worker.getProgressID();
      var jsonResponse = {};
      jsonResponse.trackerId = trackerId;

      return jsonResponse;
  },

  recordAppInstallEvent: function(params) {
      var collectionData = {};
      collectionData.app_id = params.app_id;
      collectionData.installation_type = "Standalone Install";
      collectionData.has_customization = params.customization_version ? true : false;
      collectionData.is_repair = params.isReinstall == "true" ? true : false;
      collectionData.is_search_applied = params.is_search_applied;
      collectionData.is_filter_applied = params.is_filter_applied;
      collectionData.entity_type = params.shared_internally == "true" ? "Company Application" : "Store Application";
      new AppClientGCFUtil().recordEvent("install", "app_install", collectionData);
  },

  type: 'AppUpgrader'
};

Sys ID

8b25f312d733210092610eca5e610335

Offical Documentation

Official Docs: