diff --git a/commands/core/drupal/batch.inc b/commands/core/drupal/batch.inc
index 41dee24..1c11b24 100644
--- a/commands/core/drupal/batch.inc
+++ b/commands/core/drupal/batch.inc
@@ -14,8 +14,7 @@
  *    The command to call to process the batch.
  *
  */
-function _drush_backend_batch_process($command = 'batch-process', $args) {
-  global $user;
+function _drush_backend_batch_process($command = 'batch-process', $args, $options) {
   $batch =& batch_get();
 
   if (isset($batch)) {
@@ -31,6 +30,7 @@ function _drush_backend_batch_process($command = 'batch-process', $args) {
     // Assign an arbitrary id: don't rely on a serial column in the 'batch'
     // table, since non-progressive batches skip database storage completely.
     $batch['id'] = db_next_id();
+    $args[] = $batch['id'];
 
     $batch['progressive'] = TRUE;
 
@@ -141,6 +141,9 @@ function _drush_batch_worker() {
         'finished' => &$finished,
         'message'  => &$task_message,
       );
+      // Magic wrap to catch changes to 'message' key.
+      // TODO: DrushBatchContext seems to work pretty well for some things, but it breaks updatedb in site-upgrade (and therefore may be a problem for some pm-updatecode + updatedb as well?)
+      $batch_context = new DrushBatchContext($batch_context);
       call_user_func_array($function, array_merge($args, array(&$batch_context)));
 
       if ($finished == 1) {
diff --git a/commands/core/drupal/batch_6.inc b/commands/core/drupal/batch_6.inc
index 3396253..cebfeb4 100644
--- a/commands/core/drupal/batch_6.inc
+++ b/commands/core/drupal/batch_6.inc
@@ -14,8 +14,7 @@
  *    The command to call to process the batch.
  *
  */
-function _drush_backend_batch_process($command = 'batch-process', $args) {
-  global $user;
+function _drush_backend_batch_process($command = 'batch-process', $args, $options) {
   $batch =& batch_get();
 
   if (isset($batch)) {
@@ -28,6 +27,7 @@ function _drush_backend_batch_process($command = 'batch-process', $args) {
     // at least an empty string for the (not null) 'token' column.
     db_query("INSERT INTO {batch} (token, timestamp) VALUES ('', %d)", time());
     $batch['id'] = db_last_insert_id('batch', 'bid');
+    $args[] = $batch['id'];
 
     // Actually store the batch data and the token generated form the batch id.
     db_query("UPDATE {batch} SET token = '%s', batch = '%s' WHERE bid = %d", drupal_get_token($batch['id']), serialize($batch), $batch['id']);
@@ -35,13 +35,7 @@ function _drush_backend_batch_process($command = 'batch-process', $args) {
     $finished = FALSE;
 
     while (!$finished) {
-      if ($user->uid) {
-        $data = drush_invoke_process('@self', $command, $args, array($batch['id'], '-u', $user->uid));
-      }
-      else {
-        $data = drush_invoke_process('@self', $command, $args, array($batch['id']));
-      }
-
+      $data = drush_invoke_process('@self', $command, $args, $options);
       $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE);
     }
   }
@@ -105,6 +99,8 @@ function _drush_batch_worker() {
       // Build the 'context' array, execute the function call,
       // and retrieve the user message.
       $batch_context = array('sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message);
+      // Magic wrap to catch changes to 'message' key.
+      $batch_context = new DrushBatchContext($batch_context);
       // Process the current operation.
       call_user_func_array($function, array_merge($args, array(&$batch_context)));
     }
diff --git a/commands/core/drupal/update.inc b/commands/core/drupal/update.inc
index 640e7f2..996530a 100644
--- a/commands/core/drupal/update.inc
+++ b/commands/core/drupal/update.inc
@@ -144,7 +144,6 @@ function update_main_prepare() {
   // Our custom error handler is not yet installed, so we just suppress them.
   drush_errors_off();
 
-
   // We prepare a minimal bootstrap for the update requirements check to avoid
   // reaching the PHP memory limit.
   require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
diff --git a/includes/backend.inc b/includes/backend.inc
index f3b09e4..f2c455b 100644
--- a/includes/backend.inc
+++ b/includes/backend.inc
@@ -541,7 +541,7 @@ function drush_backend_invoke_sitealias_command($site_record, $command, $args, $
   $drush_command_path = drush_build_drush_command($drush_path, array_key_exists('php', $command_options) ? $command_options['php'] : NULL, drush_os($site_record), array_key_exists('remote-host', $site_record));
 
   // Insure that contexts such as DRUSH_SIMULATE and NO_COLOR are included.
-  $command_options += _drush_backend_get_global_contexts();
+  $command_options += _drush_backend_get_global_contexts($site_record);
 
   list($post_options, $commandline_options, $drush_global_options) = _drush_backend_classify_options($command_options, $backend_options);
   $cmd = _drush_backend_generate_command($site_record, $drush_command_path . " " . _drush_backend_argument_string($drush_global_options, $os) . " " . $command, $args, $commandline_options, $backend_options) . ' 2>&1';
@@ -552,14 +552,18 @@ function drush_backend_invoke_sitealias_command($site_record, $command, $args, $
  * Find all of the drush contexts that are used to cache global values and
  * return them in an associative array.
  */
-function _drush_backend_get_global_contexts() {
+function _drush_backend_get_global_contexts($site_record) {
   $result = array();
   $global_option_list = drush_get_global_options(FALSE);
   foreach ($global_option_list as $global_key => $global_metadata) {
     if ((is_array($global_metadata)) && (array_key_exists('context', $global_metadata))) {
-      $value = drush_get_context($global_metadata['context'], array());
-      if (!empty($value)) {
-        $result[$global_key] = $value;
+      // If the context is declared to be a 'local-context-only',
+      // then only put it in if this is a local dispatch.
+      if (!array_key_exists('local-context-only', $global_metadata) || !array_key_exists('remot-host', $site_record)) {
+        $value = drush_get_context($global_metadata['context'], array());
+        if (!empty($value)) {
+          $result[$global_key] = $value;
+        }
       }
     }
   }
diff --git a/includes/batch.inc b/includes/batch.inc
index 5260046..2d09bde 100644
--- a/includes/batch.inc
+++ b/includes/batch.inc
@@ -22,9 +22,28 @@
  * Each major release of Drupal has also had slightly different implementations
  * of the batch API, and this provides a uniform interface to all of these
  * implementations.
- *
  */
 
+/**
+ * Class extending ArrayObject to allow the batch API to perform logging when
+ * some keys of the array change.
+ *
+ * It is used to wrap batch's $context array and set log messages when values
+ * are assigned to keys 'message' or 'error_message'.
+ *
+ * @see _drush_batch_worker().
+ */
+class DrushBatchContext extends ArrayObject {
+  function offsetSet($name, $value) {
+    if ($name == 'message') {
+      drush_log($value, 'ok');
+    }
+    elseif ($name == 'error_message') {
+      drush_log($value, 'error');
+    }
+    parent::offsetSet($name, $value);
+  }
+}
 
 /**
  * Process a Drupal batch by spawning multiple Drush processes.
@@ -48,9 +67,15 @@
  *   use their own command.
  *
  */
-function drush_backend_batch_process($command = 'batch-process', $args = array()) {
+function drush_backend_batch_process($command = 'batch-process', $args = array(), $options = array()) {
+  // Command line options to pass to the command.
+  global $user;
+  if ($user->uid) {
+    $options['u'] = $user->uid;
+  }
+
   drush_include_engine('drupal', 'batch', drush_drupal_major_version());
-  _drush_backend_batch_process($command, $args);
+  _drush_backend_batch_process($command, $args, $options);
 }
 
 /**
diff --git a/includes/command.inc b/includes/command.inc
index e919a23..ec5c3a5 100644
--- a/includes/command.inc
+++ b/includes/command.inc
@@ -1003,6 +1003,8 @@ function _drush_find_commandfiles($phase, $phase_max = FALSE) {
 
       // User commands, specified by 'include' option
       if ($include = drush_get_option(array('i', 'include'), FALSE)) {
+        // We set the DRUSH_INCLUDE context here for the benefit of backend_invoke
+        drush_set_context('DRUSH_INCLUDE', $include);
         foreach (explode(PATH_SEPARATOR, $include) as $path) {
           $searchpath[] = $path;
         }
diff --git a/includes/drush.inc b/includes/drush.inc
index fb1d2e8..d70df32 100644
--- a/includes/drush.inc
+++ b/includes/drush.inc
@@ -321,8 +321,8 @@ function _convert_csv_to_array($args) {
  *     - description: The help text for this item. displayed by `drush help`.
  */
 function drush_get_global_options($brief = FALSE) {
-  $options['root']               = array('short-form' => 'r', 'context' => 'DRUSH_DRUPAL_ROOT', 'never-post' => TRUE, 'description' => "Drupal root directory to use (default: current directory).", 'example-value' => '<path>');
-  $options['uri']                = array('short-form' => 'l', 'context' => 'DRUSH_URI', 'never-post' => TRUE, 'description' => 'URI of the drupal site to use (only needed in multisite environments or when running on an alternate port).', 'example-value' => 'http://example.com:8888');
+  $options['root']               = array('short-form' => 'r', 'never-post' => TRUE, 'description' => "Drupal root directory to use (default: current directory).", 'example-value' => '<path>');
+  $options['uri']                = array('short-form' => 'l', 'never-post' => TRUE, 'description' => 'URI of the drupal site to use (only needed in multisite environments or when running on an alternate port).', 'example-value' => 'http://example.com:8888');
   $options['verbose']            = array('short-form' => 'v', 'context' => 'DRUSH_VERBOSE', 'description' => 'Display extra information about the command.');
   $options['debug']              = array('short-form' => 'd', 'context' => 'DRUSH_DEBUG', 'description' => 'Display even more information, including internal messages.');
   $options['yes']                = array('short-form' => 'y', 'context' => 'DRUSH_AFFIRMATIVE', 'description' => "Assume 'yes' as answer to all prompts.");
@@ -336,7 +336,7 @@ function drush_get_global_options($brief = FALSE) {
 
   if (!$brief) {
     $options['quiet']               = array('short-form' => 'q', 'description' => 'Suppress non-error messages.');
-    $options['include']             = array('short-form' => 'i', 'description' => "A list of additional directory paths to search for drush commands.");
+    $options['include']             = array('short-form' => 'i', 'context' => 'DRUSH_INCLUDE', 'local-context-only' => TRUE, 'description' => "A list of additional directory paths to search for drush commands.");
     $options['config']              = array('short-form' => 'c', 'description' => "Specify an additional config file to load. See example.drushrc.php.");
     $options['user']                = array('short-form' => 'u', 'description' => "Specify a Drupal user to login with. May be a name or a number.");
     $options['cache']               = array('description' => 'Keep a local cache of downloaded files.');
diff --git a/tests/backendTest.php b/tests/backendTest.php
index f3c5e9a..bb6fab7 100644
--- a/tests/backendTest.php
+++ b/tests/backendTest.php
@@ -14,7 +14,6 @@
 */
 
 class backendCase extends Drush_CommandTestCase {
-  const DRUSH_BACKEND_OUTPUT_DELIMITER = 'DRUSH_BACKEND_OUTPUT_START>>>%s<<<DRUSH_BACKEND_OUTPUT_END';
 
   /*
    * Covers the following origin responsibilities.
@@ -43,7 +42,7 @@ class backendCase extends Drush_CommandTestCase {
     $stdin = json_encode(array('filter'=>'sql'));
     $exec = sprintf('echo %s | %s help --backend 2>/dev/null', self::escapeshellarg($stdin), self::escapeshellarg(UNISH_DRUSH));
     $this->execute($exec);
-    $parsed = $this->parse($this->getOutput());
+    $parsed = parse_backend_output($this->getOutput());
     $this->assertTrue((bool) $parsed, 'Successfully parsed backend output');
     $this->assertArrayHasKey('log', $parsed);
     $this->assertArrayHasKey('output', $parsed);
@@ -56,7 +55,7 @@ class backendCase extends Drush_CommandTestCase {
     // Check error propogation by requesting an invalid command (missing Drupal site).
     $exec = sprintf('%s core-cron --backend 2>/dev/null', self::escapeshellarg(UNISH_DRUSH));
     $this->execute($exec, self::EXIT_ERROR);
-    $parsed = $this->parse($this->getOutput());
+    $parsed = parse_backend_output($this->getOutput());
     $this->assertEquals(1, $parsed['error_status']);
     $this->assertArrayHasKey('DRUSH_NO_DRUPAL_ROOT', $parsed['error_log']);
   }
@@ -79,26 +78,4 @@ class backendCase extends Drush_CommandTestCase {
     $this->assertTrue($backend_output_offset !== FALSE, "Drush backend output marker appears in output.");
     $this->assertTrue($drush_version_offset < $backend_output_offset, "Drush version string appears in output before the backend output marker.");
   }
-
-  /*
-   * A slightly less functional copy of drush_backend_parse_output().
-   */
-  function parse($string) {
-    $regex = sprintf(self::DRUSH_BACKEND_OUTPUT_DELIMITER, '(.*)');
-    preg_match("/$regex/s", $string, $match);
-    if ($match[1]) {
-      // we have our JSON encoded string
-      $output = $match[1];
-      // remove the match we just made and any non printing characters
-      $string = trim(str_replace(sprintf(self::DRUSH_BACKEND_OUTPUT_DELIMITER, $match[1]), '', $string));
-    }
-
-    if ($output) {
-      $data = json_decode($output, TRUE);
-      if (is_array($data)) {
-        return $data;
-      }
-    }
-    return $string;
-  }
 }
diff --git a/tests/completeTest.php b/tests/completeTest.php
index 57c29d1..f43c2a0 100644
--- a/tests/completeTest.php
+++ b/tests/completeTest.php
@@ -25,17 +25,17 @@ class completeCase extends Drush_CommandTestCase {
     $this->drush('php-eval', array('drush_complete_cache_clear();'));
     // Confirm we get cache rebuilds for runs both in and out of a site
     // which is expected since these should resolve to separate cache IDs.
-    $this->verifyComplete('@dev uni', 'uninstall', 'unit-invoke', FALSE);
+    $this->verifyComplete('@dev uni', 'uninstall', 'unit-batch', FALSE);
     $this->verifyComplete('uni', 'uninstall', 'uninstall', FALSE);
     // Next, rerun and check results to confirm cache IDs are generated
     // correctly on our fast bootstrap when returning the cached result.
-    $this->verifyComplete('@dev uni', 'uninstall', 'unit-invoke');
+    $this->verifyComplete('@dev uni', 'uninstall', 'unit-batch');
     $this->verifyComplete('uni', 'uninstall', 'uninstall');
 
     // Test cache clearing for a completion type, which should be effective only
     // for current environment - i.e. a specific site should not be effected.
     $this->drush('php-eval', array('drush_complete_cache_clear("command-names");'));
-    $this->verifyComplete('@dev uni', 'uninstall', 'unit-invoke');
+    $this->verifyComplete('@dev uni', 'uninstall', 'unit-batch');
     $this->verifyComplete('uni', 'uninstall', 'uninstall', FALSE);
 
     // Test cache clearing for a command specific completion type, which should
@@ -62,7 +62,7 @@ class completeCase extends Drush_CommandTestCase {
     // Commands that start the same as another command (i.e. unit is a valid
     // command, but we should still list unit-eval and unit-invoke when
     // completing on "unit").
-    $this->verifyComplete('@dev unit', 'unit', 'unit-invoke');
+    $this->verifyComplete('@dev unit', 'unit', 'unit-batch');
     // Global option alone.
     $this->verifyComplete('--n', '--no', '--nocolor');
     // Site alias + command.
@@ -108,8 +108,10 @@ class completeCase extends Drush_CommandTestCase {
     $exec = sprintf('%s --early=includes/complete.inc --complete-debug %s %s 2> %s', UNISH_DRUSH, UNISH_DRUSH, $command, $debug_file);
     $this->execute($exec);
     $result = $this->getOutputAsList();
-    $this->assertEquals($first, reset($result));
-    $this->assertEquals($last, end($result));
+    $expected = reset($result);
+    $this->assertEquals("$command: $first", "$command: $expected");
+    $expected = end($result);
+    $this->assertEquals("$command: $last", "$command: $expected");
     // If checking for HIT, we ensure no MISS exists, if checking for MISS we
     // ensure no HIT exists. However, we exclude the first cache report, since
     // it is expected that the command-names cache (loaded when matching
@@ -124,4 +126,4 @@ class completeCase extends Drush_CommandTestCase {
     $this->assertFalse(strpos($contents, 'Cache ' . $check_not_exist . ' cid', $first_cache_pos));
     unlink($debug_file);
   }
-}
\ No newline at end of file
+}
diff --git a/tests/drush_testcase.inc b/tests/drush_testcase.inc
index a731b89..1edf705 100644
--- a/tests/drush_testcase.inc
+++ b/tests/drush_testcase.inc
@@ -33,7 +33,7 @@ abstract class Drush_TestCase extends PHPUnit_Framework_TestCase {
    */
   public static function tearDownAfterClass() {
     if (file_exists(UNISH_SANDBOX)) {
-      unish_file_delete_recursive(UNISH_SANDBOX);
+      #unish_file_delete_recursive(UNISH_SANDBOX);
     }
   }
 
@@ -424,6 +424,29 @@ function unish_init() {
    return rmdir($dir);
  }
 
+/**
+ * A slightly less functional copy of drush_backend_parse_output().
+ */
+define('DRUSH_BACKEND_OUTPUT_DELIMITER', 'DRUSH_BACKEND_OUTPUT_START>>>%s<<<DRUSH_BACKEND_OUTPUT_END');
+function parse_backend_output($string) {
+  $regex = sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, '(.*)');
+  preg_match("/$regex/s", $string, $match);
+  if ($match[1]) {
+    // we have our JSON encoded string
+    $output = $match[1];
+    // remove the match we just made and any non printing characters
+    $string = trim(str_replace(sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, $match[1]), '', $string));
+  }
+
+  if ($output) {
+    $data = json_decode($output, TRUE);
+    if (is_array($data)) {
+      return $data;
+    }
+  }
+  return $string;
+}
+
  // This code is in global scope.
  // TODO: I would rather this code at top of file, but I get Fatal error: Class 'Drush_TestCase' not found
  unish_init();
diff --git a/tests/unit.drush.inc b/tests/unit.drush.inc
index 1ca371d..83508fb 100644
--- a/tests/unit.drush.inc
+++ b/tests/unit.drush.inc
@@ -23,6 +23,10 @@ function unit_drush_command() {
     'description' => 'Return an array indicating which invoke hooks got called.',
     'bootstrap' => DRUSH_BOOTSTRAP_NONE,
   );
+  $items['unit-batch'] = array(
+    'description' => 'Run a batch process.',
+    'bootstrap' => DRUSH_BOOTSTRAP_MAX,
+  );
   return $items;
 }
 
@@ -69,3 +73,40 @@ function unit_invoke_log($function = NULL) {
     return $called;
   }
 }
+
+/**
+ * Command callback.
+ */
+function drush_unit_batch() {
+  // Reduce php memory/time limits to test backend respawn.
+  // TODO.
+
+  $batch = array(
+    'operations' => array(
+       array('_drush_unit_batch_operation', array()),
+    ),
+    'finished' => '_drush_unit_batch_finished',
+    // 'file' => Doesn't work for us. Drupal 7 enforces this path
+    // to be relative to DRUPAL_ROOT.
+    // @see _batch_process().
+  );
+  batch_set($batch);
+  drush_backend_batch_process();
+
+  // Print the batch output.
+  drush_backend_output();
+}
+
+function _drush_unit_batch_operation(&$context) {
+  $context['message'] = "!!! ArrayObject does its job.";
+
+  for ($i = 0; $i < 5; $i++) {
+    drush_print("Iteration $i");
+  }
+  $context['finished'] = 1;
+}
+
+function _drush_unit_batch_finished() {
+  // Restore php limits.
+  // TODO.
+}
