diff --git dialog.batch.inc dialog.batch.inc
new file mode 100644
index 0000000..38bcac6
--- /dev/null
+++ dialog.batch.inc
@@ -0,0 +1,236 @@
+<?php
+// $Id$
+/**
+ * Support for batch processing in dialogs.
+ */
+
+/**
+ * Batch processing inside a dialog.
+ *
+ * Very similar to core batch processing without any drupal_goto() calls.
+ * @see batch_process()
+ */
+function dialog_batch_process($redirect = NULL, $url = NULL) {
+  $batch = & batch_get();
+
+  if (isset($batch)) {
+    // Add process information
+    $process_info = array(
+      'current_set' => 0,
+      'progressive' => TRUE,
+      'url' => isset($url) ? $url : 'dialog/batch/ajax',
+      'source_page' => $_GET['q'],
+      'redirect' => $redirect,
+    );
+    $batch += $process_info;
+
+    if ($batch['progressive']) {
+      // Clear the way for the drupal_goto redirection to the batch processing
+      // page, by saving and unsetting the 'destination' if any, on both places
+      // drupal_goto looks for it.
+      if (isset($_REQUEST['destination'])) {
+        $batch['destination'] = $_REQUEST['destination'];
+        unset($_REQUEST['destination']);
+      }
+      elseif (isset($_REQUEST['edit']['destination'])) {
+        $batch['destination'] = $_REQUEST['edit']['destination'];
+        unset($_REQUEST['edit']['destination']);
+      }
+
+      // Initiate db storage in order to get a batch id. We have to provide
+      // 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');
+
+      // Now that we have a batch id, we can generate the redirection link in
+      // the generic error message.
+      $t = get_t();
+      $batch['error_message'] = $t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
+
+      // 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']);
+
+      // Setup batch $_REQUEST variables.
+      $_REQUEST['op'] = 'start';
+      $_REQUEST['id'] = $batch['id'];
+
+      // User must have_js by now.
+      $_COOKIE['has_js'] = TRUE;
+
+      return dialog_batch_page();
+    }
+    else {
+      // @todo Handle non-progressive batches.
+      // Non-progressive execution: bypass the whole progressbar workflow
+      // and execute the batch in one pass.
+      // require_once './includes/batch.inc';
+      // _batch_process();
+    }
+  }
+}
+
+/**
+ * Page callback for dialog batch processing.
+ */
+function dialog_batch_page() {
+  require_once './includes/batch.inc';
+  ctools_include('ajax');
+
+  // We must override _batch_finished because it calls drupal_goto().
+  if ($_REQUEST['op'] == 'finished') {
+    $output = dialog_batch_finished();
+  }
+  else {
+    // Get batch page output.
+    $output = _batch_page();
+    if ($output === FALSE) {
+      // @todo Handle no output.
+      dpm('No Output');
+    }
+
+    // Page output is already json.
+    if ($_REQUEST['op'] == 'do') {
+      exit();
+    }
+  }
+
+  // Render ajax commands.
+  $batch = batch_get();
+  $title = $batch['original_title'] . t(': Processing...');
+  $commands[] = dialog_command_display($title, $output);
+  ctools_ajax_render($commands);
+}
+
+/**
+ * Dialog replacement for _batch_finished().
+ *
+ * Very similar without any drupal_goto() calls.
+ * @see batch_process()
+ */
+function dialog_batch_finished() {
+  $batch = & batch_get();
+
+  // Retrieve the current state of batch from db.
+  if (isset($_REQUEST['id']) && $data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d AND token = '%s'", $_REQUEST['id'], drupal_get_token($_REQUEST['id'])))) {
+    $batch = unserialize($data);
+  }
+  else {
+    return FALSE;
+  }
+
+  // Register database update for end of processing.
+  register_shutdown_function('_batch_shutdown');
+
+  // Add our saved messages back to the SESSION.
+  if (isset($batch['dialog_status_messages']) && is_array($batch['dialog_status_messages'])) {
+    foreach ($batch['dialog_status_messages'] as $message) {
+      drupal_set_message($message);
+    }
+  }
+
+  // Execute the 'finished' callbacks for each batch set.
+  foreach ($batch['sets'] as $key => $batch_set) {
+    if (isset($batch_set['finished'])) {
+      // Check if the set requires an additional file for functions definitions.
+      if (isset($batch_set['file']) && is_file($batch_set['file'])) {
+        include_once($batch_set['file']);
+      }
+      if (function_exists($batch_set['finished'])) {
+        $batch_set['finished']($batch_set['success'], $batch_set['results'], $batch_set['operations']);
+      }
+    }
+  }
+
+  // Cleanup the batch table and unset the global $batch variable.
+  if ($batch['progressive']) {
+    db_query("DELETE FROM {batch} WHERE bid = %d", $batch['id']);
+  }
+  $_batch = $batch;
+  $batch = NULL;
+
+  // Redirect if needed.
+  if ($_batch['progressive']) {
+    // Put back the 'destination' that was saved in batch_process().
+    if (isset($_batch['destination'])) {
+      $_REQUEST['destination'] = $_batch['destination'];
+    }
+
+    // Use $_batch['form_state']['redirect'], or $_batch['redirect'],
+    // or $_batch['source_page'].
+    if (isset($_batch['form_state']['redirect'])) {
+      $redirect = $_batch['form_state']['redirect'];
+    }
+    elseif (isset($_batch['redirect'])) {
+      $redirect = $_batch['redirect'];
+    }
+    else {
+      $redirect = $_batch['source_page'];
+    }
+
+    // Setup a title.
+    $title = (isset($_batch['original_title'])) ? $_batch['original_title'] : 'Finished Processing';
+
+    // Let drupal_redirect_form handle redirection logic.
+    $form = isset($batch['form']) ? $batch['form'] : array();
+    if (empty($_batch['form_state']['rebuild']) && empty($_batch['form_state']['storage'])) {
+      dialog_redirect_form($form, $redirect, $title);
+    }
+
+    // We get here if $form['#redirect'] was FALSE, or if the form is a
+    // multi-step form. We save the final $form_state value to be retrieved
+    // by drupal_get_form, and we redirect to the originating page.
+    $_SESSION['batch_form_state'] = $_batch['form_state'];
+    dialog_redirect_form(NULL, $_batch['source_page'], $title);
+  }
+
+  return '';
+}
+
+/**
+ * Dialog specific replacement for drupal_redirect_form().
+ *
+ * @see drupal_redirect_form()
+ */
+function dialog_redirect_form($form, $redirect, $title = 'Finished') {
+  $goto = NULL;
+  $options = array();
+  if (isset($redirect)) {
+    $goto = $redirect;
+  }
+  if ($goto !== FALSE && isset($form['#redirect'])) {
+    $goto = $form['#redirect'];
+  }
+  if (!isset($goto) || ($goto !== FALSE)) {
+    if (isset($goto)) {
+      if (is_array($goto)) {
+        $url = array_shift($goto);
+        if ($query = array_shift($goto)) {
+          $_REQUEST += $query;
+          $_GET += $query;
+          $options['query'] = $query;
+        }
+        $goto = $url;
+      }
+    }
+    else {
+      $goto = $_GET['q'];
+    }
+  }
+
+  ctools_include('ajax');
+  $commands = array();
+  $output = '';
+  // Most likely the redirect is to an ajax path.
+  if (strpos($goto, 'ajax') !== FALSE) {
+    $output = menu_execute_active_handler($goto);
+  }
+  else {
+    $commands[] = ctools_ajax_command_redirect($goto, 0, $options);
+  }
+  $commands[] = dialog_command_display($title, $output);
+  ctools_ajax_render($commands);
+}
+
+
+
+
diff --git dialog.batch.js dialog.batch.js
new file mode 100644
index 0000000..60749dd
--- /dev/null
+++ dialog.batch.js
@@ -0,0 +1,56 @@
+// $Id$
+
+/**
+ * Attaches the batch behavior to progress bars (without a redirect).
+ */
+Drupal.behaviors.dialog_batch = function (context) {
+  // This behavior attaches by ID, so is only valid once on a page.
+  if ($('#progress.dialog-batch-processed').size()) {
+    return;
+  }
+  $('#progress', context).addClass('dialog-batch-processed').each(function () {
+    var holder = this;
+    var uri = Drupal.settings.batch.uri;
+    var initMessage = Drupal.settings.batch.initMessage;
+    var errorMessage = Drupal.settings.batch.errorMessage;
+
+    // Success: redirect to the summary.
+    var updateCallback = function (progress, status, pb) {
+      if (progress == 100) {
+        pb.stopMonitoring();
+        var url = uri+'&op=finished';
+        try {
+          url = Drupal.CTools.AJAX.urlReplaceNojs(url);
+          $.ajax({
+            type: "POST",
+            url: url,
+            data: { 'js': 1, 'ctools_ajax': 1},
+            global: true,
+            success: Drupal.CTools.AJAX.respond,
+            error: function(xhr) {
+              Drupal.CTools.AJAX.handleErrors(xhr, url);
+            },
+            dataType: 'json'
+          });
+        }
+        catch (err) {
+          alert("An error occurred while attempting to process " + url);
+          return false;
+        }
+      }
+    };
+
+    var errorCallback = function (pb) {
+      var div = document.createElement('p');
+      div.className = 'error';
+      $(div).html(errorMessage);
+      $(holder).prepend(div);
+      $('#wait').hide();
+    };
+
+    var progress = new Drupal.progressBar('updateprogress', updateCallback, "POST", errorCallback);
+    progress.setProgress(-1, initMessage);
+    $(holder).append(progress.element);
+    progress.startMonitoring(uri+'&op=do', 10);
+  });
+};
diff --git dialog.module dialog.module
index 451a27f..4f8b5e8 100644
--- dialog.module
+++ dialog.module
@@ -28,6 +28,14 @@ function dialog_menu() {
     'file'             => 'dialog.admin.inc',
   );
 
+  // Batches in dialogs.
+  $items['dialog/batch'] = array(
+    'title'           => 'Dialog Batch Processing',
+    'page callback'   => 'dialog_batch_page',
+    'access callback' => TRUE,
+    'file'            => 'dialog.batch.inc',
+  );
+
   return $items;
 }
 
@@ -50,7 +58,7 @@ function dialog_add_js() {
       'width' => variable_get('dialog_default_width', '600px'),
     )
   );
-  
+
   drupal_add_js($settings, 'setting');
   drupal_add_js('misc/jquery.form.js');
   ctools_add_js('ajax-responder');
@@ -138,6 +146,7 @@ function dialog_form_wrapper($form_id, &$form_state) {
   $form_state += array(
     're_render' => FALSE,
     'no_redirect' => !empty($form_state['ajax']),
+    'in_dialog' => TRUE,
   );
 
   $output = ctools_build_form($form_id, $form_state);
@@ -172,13 +181,13 @@ function dialog_form_render($form_state, $output) {
 /**
  * Generic dialog replacement for drupal_get_form().  Suitable for use as the
  * page callback in a menu item.
- * 
- * This function introduces a new form callback function to handle the 
+ *
+ * This function introduces a new form callback function to handle the
  * post-submit dialog commands, in the ajax context.  This function takes the
- * form of form_id_dialog_success.  If this function is found, it will be 
+ * form of form_id_dialog_success.  If this function is found, it will be
  * automatically called after a valid submission of the form has been detected.
- * If the function does not exist, a redirect will be issued based on the 
- * redirect value in the form_state array.  As the final fallback, if the 
+ * If the function does not exist, a redirect will be issued based on the
+ * redirect value in the form_state array.  As the final fallback, if the
  * redirect value is missing or empty, a client-side reload command is issued.
  *
  * @param $id
@@ -225,6 +234,80 @@ function dialog_get_form($form_id, $js) {
  * Process variables for dialog-content.tpl.php.
  */
 function template_preprocess_dialog_content(&$variables) {
-  $variables['help'] = theme('help');  
+  $variables['help'] = theme('help');
   $variables['messages'] = theme('status_messages');
 }
+
+/**
+ * Implement hook_form_alter().
+ */
+function dialog_form_alter(&$form, &$form_state) {
+  // We add an after build to maximize the chance our submit handler runs last.
+  $form['#after_build'][] = 'dialog_process_ajax_form';
+}
+
+/**
+ * Process an ajax form.
+ */
+function dialog_process_ajax_form($form, &$form_state) {
+  if (isset($form_state['in_dialog']) && $form_state['in_dialog'] && $form_state['submitted']) {
+    // Our submit handler will replace all others.
+    // It will execute the other handlers and deal with any batches before
+    // returning execution to the regular form processing.
+    if (isset($form_state['submit_handlers'])) {
+      $form_state['dialog_submit_handlers'] = $form_state['submit_handlers'];
+    }
+    elseif (isset($form['#submit'])) {
+      $form_state['dialog_submit_handlers'] = $form['#submit'];
+    }
+    else {
+      $form_state['dialog_submit_handlers'] = array();
+    }
+    unset($form['#submit']);
+    $form_state['submit_handlers'] = array('dialog_submit_ajax_form');
+  }
+  return $form;
+}
+
+/**
+ * Submit handler for ajax forms.
+ */
+function dialog_submit_ajax_form(&$form, &$form_state) {
+  // Call all the overridden submit handlers.
+  if (!empty($form_state['dialog_submit_handlers'])) {
+    $form_state['submit_handlers'] = $form_state['dialog_submit_handlers'];
+    form_execute_handlers('submit', $form, $form_state);
+  }
+
+  // Override the normal FAPI batch processing.
+  if ($batch = &batch_get() && !isset($batch['current_set'])) {
+    // Process the batch
+    module_load_include('inc', 'dialog', 'dialog.batch');
+    $batch['form'] = $form;
+    $batch['form_state'] = $form_state;
+    $batch['progressive'] = !$form['#programmed'];
+    global $dialog_batch;
+    $dialog_batch = TRUE;
+    if ($messages = drupal_get_messages('status')) {
+      $batch['dialog_status_messages'] = $messages['status'];
+    }
+    $batch['original_title'] = drupal_get_title();
+    dialog_batch_process();
+    unset($batch);
+  }
+}
+
+/**
+ * Implement hook_js_replacements().
+ *
+ * Replace batch.js if the current request is a dialog batch.
+ */
+function dialog_js_replacements() {
+  global $dialog_batch;
+  $replacements = array();
+  if ($dialog_batch) {
+    $replacements['core']['misc/batch.js'] = drupal_get_path('module', 'dialog') . '/dialog.batch.js';
+  }
+  return $replacements;
+}
+
