diff --git a/include/class.orm.php b/include/class.orm.php
index 7152745ab521c7b1b578761804402a48e0c6fb0a..5a2d31f115728c22315191e0e7b33ebd4fc4c2b2 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -1349,6 +1349,10 @@ class ModelInstanceManager extends ResultSet {
         unset(self::$objectCache[$key]);
     }
 
+    static function flushCache() {
+        self::$objectCache = array();
+    }
+
     static function checkCache($modelClass, $fields) {
         $key = $modelClass::$meta->model;
         foreach ($modelClass::getMeta('pk') as $f)
diff --git a/include/class.upgrader.php b/include/class.upgrader.php
index aa45d831d3c9b25410ab937ed65cf569c49f1a08..cf75be7847adc7feb56b4218ad27c7a677208e01 100644
--- a/include/class.upgrader.php
+++ b/include/class.upgrader.php
@@ -73,8 +73,8 @@ class Upgrader {
     function setState($state) {
         $this->state = $state;
         if ($state == 'done') {
-            $this->createUpgradedTicket();
             ModelMeta::flushModelCache();
+            $this->createUpgradedTicket();
         }
     }
 
@@ -372,6 +372,10 @@ class StreamUpgrader extends SetupWizard {
         if(!($max_time = ini_get('max_execution_time')))
             $max_time = 30; //Default to 30 sec batches.
 
+        // Drop any model meta cache to ensure model changes do not cause
+        // crashes
+        ModelMeta::flushModelCache();
+
         $task->run($max_time);
         if (!$task->isFinished()) {
             $_SESSION['ost_upgrader']['task'][$this->phash] = $task->sleep();
diff --git a/include/cli/modules/upgrade.php b/include/cli/modules/upgrade.php
new file mode 100644
index 0000000000000000000000000000000000000000..297d6d56f84d36dcdc1ad23c42410a09d483b165
--- /dev/null
+++ b/include/cli/modules/upgrade.php
@@ -0,0 +1,96 @@
+<?php
+require_once INCLUDE_DIR.'class.upgrader.php';
+
+class CliUpgrader extends Module {
+    var $prologue = "Upgrade an osTicket help desk";
+
+    var $options = array(
+        'summary' => array('-s', '--summary',
+            'action' => 'store_true',
+            'help' => 'Print an upgrade summary and exit'),
+    );
+
+    function run($args, $options) {
+        Bootstrap::connect();
+
+        // Pre-checks
+        global $ost;
+        $ost = osTicket::start();
+        $upgrader = $this->getUpgrader();
+
+        if ($options['summary']) {
+            $this->doSummary($upgrader);
+        }
+        else {
+            $this->upgrade($upgrader);
+        }
+    }
+
+    function getUpgrader() {
+        global $ost;
+
+        if (!$ost->isUpgradePending()) {
+            $this->fail('No upgrade is pending for this account');
+        }
+
+        $upgrader = new Upgrader(TABLE_PREFIX, UPGRADE_DIR.'streams/');
+        if (!$upgrader->isUpgradable()) {
+            $this->fail(__('The upgrader does NOT support upgrading from the current vesion!'));
+        }
+        elseif (!$upgrader->check_prereq()) {
+            $this->fail(__('Minimum requirements not met! Refer to Release Notes for more information'));
+        }
+        elseif (!strcasecmp(basename(CONFIG_FILE), 'settings.php')) {
+            $this->fail(__('Config file rename required to continue!'));
+        }
+        return $upgrader;
+    }
+
+    function upgrade($upgrader) {
+        global $ost, $cfg;
+        $cfg = $ost->getConfig();
+
+        while (true) {
+            if ($upgrader->getTask()) {
+                // If there's anythin in the model cache (like a Staff
+                // object or something), ensure that changes to the database
+                // model won't cause crashes
+                ModelInstanceManager::flushCache();
+
+                // More pending tasks - doTasks returns the number of pending tasks
+                $this->stdout->write("... {$upgrader->getNextAction()}\n");
+                $upgrader->doTask();
+            }
+            elseif ($ost->isUpgradePending()) {
+                if ($upgrader->isUpgradable()) {
+                    $this->stdout->write("... {$upgrader->getNextVersion()}\n");
+                    $upgrader->upgrade();
+                    // Reload config to pull schema_signature changes
+                    $cfg->load();
+                }
+                else {
+                    $this->fail(sprintf(
+                        __('Upgrade Failed: Invalid or wrong hash [%s]'),
+                        $ost->getDBSignature()
+                    ));
+                }
+            }
+            elseif (!$ost->isUpgradePending()) {
+                $this->stdout->write("Yay! All finished!\n");
+                break;
+            }
+        }
+    }
+
+    function doSummary($upgrader) {
+        foreach ($upgrader->getPatches() as $p) {
+            $info = $upgrader->readPatchInfo($p);
+            $this->stdout->write(sprintf(
+                "%s :: %s (%s)\n", $info['version'], $info['title'],
+                substr($info['signature'], 0, 8)
+            ));
+        }
+    }
+}
+
+Module::register('upgrade', 'CliUpgrader');