diff --git site/hosting_site.module site/hosting_site.module
index 4cf9729..97e0c5f 100644
--- site/hosting_site.module
+++ site/hosting_site.module
@@ -721,54 +721,256 @@ function hosting_site_task_status($nid) {
  * @TODO Add paging.
  */
 function hosting_site_list($filter_by = null, $filter_value = null) {
-  $args[] = 'site';
-  $cond = '';
-
-  if ($filter_by && $filter_value) {
-    if ($filter_by == 'status') {
-      $cond = ' AND s.' . $filter_by . ' & %d';
-    } else {
-      $cond = ' AND s.' . $filter_by . ' = %d';
+  return drupal_get_form('hosting_site_list_form', $filter_by, $filter_value);
+}
+
+/**
+ * Create a form for building a list of sites 
+ */
+function hosting_site_list_form($form_state, $filter_by = null, $filter_value = null) {
+  $step = isset($form_state['storage']['step']) ? $form_state['storage']['step'] : 1;
+
+  // Step 1 - select sites
+  if ($step == 1) {
+    $args[] = 'site';
+    $cond = '';
+  
+    if ($filter_by && $filter_value) {
+      if ($filter_by == 'status') {
+        $cond = ' AND s.' . $filter_by . ' & %d';
+      } else {
+        $cond = ' AND s.' . $filter_by . ' = %d';
+      }
+      $args[] = $filter_value;
+    }
+  
+    $header = array(
+      array('data' => t('Site'), 'field' => 'title'),
+      array('data' => t('Profile'), 'field' => 'profile'),
+      array('data' => t('Language'), 'field' => 'site_language'),
+      array('data' => t('Created'), 'field' => 'created', 'sort' => 'desc'),
+    );
+    $platforms = _hosting_get_platforms();
+    if (sizeof($platforms) > 1) {
+      $header[] = array('data' => t('Platform'), 'field' => 'platform');
+    }
+  
+    $sql = "SELECT n.nid, n.title, n.created, s.status as site_status, profile, s.language as site_language, platform, verified FROM {node} n left join {hosting_site} s ON n.vid=s.vid WHERE type='%s' AND s.status != -2 " . $cond;
+    $sql .= tablesort_sql($header);
+  
+    // @TODO hide deleted sites
+    $result = pager_query(db_rewrite_sql($sql), 25, 1, null, $args);
+  
+    $form['options'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Site tasks'),
+      '#prefix' => '<div class="container-inline">',
+      '#suffix' => '</div>',
+    );
+    $options = array();
+  
+    foreach (hosting_available_tasks('site') as $task => $array) {
+      // At this stage we only want to handle simple tasks, the presense of a
+      // specific task form means there are other options for this tasks.
+      $func = 'hosting_task_' . $task . '_form';
+      if (!function_exists($func)) {
+        $options[$task] = $array['title'];
+      }
     }
-    $args[] = $filter_value;
+    $form['options']['task'] = array(
+      '#type' => 'select',
+      '#options' => $options,
+      '#default_value' => 'backup',
+    );
+    $form['options']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Add to queue'),
+      '#submit' => array('hosting_site_list_form_submit'),
+    );
+  
+    $sites = array();
+    while ($node = db_fetch_object($result)) {
+      $sites[$node->nid] = '';
+      $form['site'][$node->nid] = array('#value' => l($node->title, 'node/' . $node->nid));
+      $form['profile'][$node->nid] =  array('#value' => ($node->profile) ? _hosting_node_link($node->profile) : t('n/a'));
+      $form['language'][$node->nid] = array('#value' => ($node->site_language) ? _hosting_language_name($node->site_language) : t('n/a'));
+      $form['created'][$node->nid] =  array('#value' => t("@interval ago", array('@interval' => format_interval(mktime() - $node->created, 1))));
+      if (sizeof($platforms) > 1) {
+        $form['platform'][$node->nid] = array('#value' => ($node->platform) ? _hosting_node_link($node->platform) : t('n/a'));
+      }
+      $form['site_class'][$node->nid] = array('#value' => _hosting_site_list_class($node));
+    }
+    $form['sites'] = array('#type' => 'checkboxes', '#options' => $sites);
+    $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
+    $form['#theme'] = 'hosting_site_list';
+    $form['#action'] = url('hosting/sites');
+    return $form;
+  }
+  elseif ($step == 2) {
+    $task = $form_state['values']['task'];
+    $tasks = hosting_available_tasks('site');
+    $form['help'] = array('#value' => $tasks[$task]['description']); 
+    $form['task'] = array('#value' => $task); 
+
+    $title = array(
+      'passed' => t("The following sites will be processed"),
+      'failed' => t("The following sites failed validation checks and will not be processed")
+    );
+    foreach (array('passed', 'failed') as $type) {
+      if (sizeof($form_state['storage'][$type])) { 
+        foreach ($form_state['storage'][$type] as $site_id => $url) {
+          $form['output'][$type]['title'] = array(
+            '#type' => 'markup',
+            '#value' => '<h2>' . $title[$type] . '</h2>',
+          );
+
+          $form['output'][$type][$site_id] = array(
+            '#type' => 'markup',
+            '#value' => '<div>'. $url .'</div>',
+          );
+        }
+      }
+    }
+ 
+    $question = t("Are you sure you want to @task the following sites?", array('@task' => $task));
+    return confirm_form($form, $question, 'hosting/sites', '', $tasks[$task]['title']);
   }
+}
+/**
+ * Validate hosting_site_list form submissions.
+ */
+function hosting_site_list_form_validate($form, &$form_state) {
+  if (isset($form_state['values']['sites'])) {
+    $sites = array_filter($form_state['values']['sites']);
+    if (count($sites) == 0) {
+      form_set_error('', t('No sites selected.'));
+    }
+  }
+}
 
+/**
+ * Process hosting_site_list form submissions.
+ */
+function hosting_site_list_form_submit($form, &$form_state) {
+  $step = isset($form_state['storage']['step']) ? $form_state['storage']['step'] : 1;
+
+  $task = $form_state['values']['task'];
+  switch ($step) {
+    case 1:
+      $form_state['storage']['task'] = $task;
+      // Verify tasks can be performed on each site.
+      $tasks = hosting_available_tasks('site');
+    
+      // Filter out unchecked sites
+      $sites = array_filter($form_state['values']['sites']);
+    
+      $operations = array();
+      foreach ($sites as $site) {
+        $operations[] = array('_hosting_sites_batch_process',
+          array($site, $task, $form_state));
+      }
+      if (sizeof($operations)) {
+        $batch = array(
+          'operations' => $operations,
+          'finished' => '_hosting_sites_batch_finished',
+          'title' => t('Processing'),
+          'progress_message' => t('Evaluated @current out of @total sites.'),
+          'error_message' => t('The update has encountered an error.'),
+        );
+        batch_set($batch);
+      }
+      break;
+    case 2:
+      $values = $form_state['values'];
+      foreach ($form_state['storage']['passed'] as $site_id => $url) {
+        hosting_add_task($site_id, $form_state['storage']['task'], $values['parameters']);
+      }
+      unset($form_state['storage']);
+      $step = 0;
+      drupal_set_message(t('@task task in task queue for each of the sites.', array('@task' => $form_state['storage']['task'])));
+      break;
+  }
+  $form_state['storage']['step'] = $step + 1;
+}
+
+function _hosting_sites_batch_process($site_id, $task, &$context) {
+  if (!isset($context['sandbox']['progress'])) {
+    $context['sandbox']['progress'] = 0;
+  }
+
+  $site = node_load($site_id);
+  $batch =& batch_get();
+ 
+  if (hosting_task_menu_access($site, $task)) {
+    $batch['form_state']['storage']['passed'][$site->nid] = $site->title;
+  }
+  else {
+    $batch['form_state']['storage']['failed'][$site->nid] = $site->title;
+  }
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function hosting_site_theme() {
+  return array(
+    'hosting_site_list' => array(
+      'arguments' => array('form' => NULL),
+    ),
+  );
+}
+
+/**
+ * Build the site list form.
+ */
+function theme_hosting_site_list($form) {
+  // If there are rows in this form, then $form['site'] contains a list of
+  // the title form elements.
+  $has_posts = isset($form['site']) && is_array($form['site']);
+  $select_header = $has_posts ? theme('table_select_header_cell') : '';
   $header = array(
+    $select_header,
     array('data' => t('Site'), 'field' => 'title'),
     array('data' => t('Profile'), 'field' => 'profile'),
     array('data' => t('Language'), 'field' => 'site_language'),
     array('data' => t('Created'), 'field' => 'created', 'sort' => 'desc'),
   );
-  $platforms = _hosting_get_platforms();
-  if (sizeof($platforms) > 1) {
+  if (isset($form['platform'])) {
     $header[] = array('data' => t('Platform'), 'field' => 'platform');
   }
+  $output = '';
+
+  $output .= drupal_render($form['options']);
+  if ($has_posts) {
+    foreach (element_children($form['site']) as $key) {
+      $row = array();
+      $row[] = drupal_render($form['sites'][$key]);
+      $row[] = array('data' => drupal_render($form['site'][$key]), 'class'=> 'hosting-status');
+      $row[] = drupal_render($form['profile'][$key]);
+      $row[] = drupal_render($form['language'][$key]);
+      $row[] = drupal_render($form['created'][$key]);
+      if (isset($form['platform'])) {
+        $row[] = drupal_render($form['platform'][$key]);
+      }
+      $rows[] = array('data' => $row, 'class' => drupal_render($form['site_class'][$key]));
+    }
 
-  $sql = "SELECT n.nid, n.title, n.created, s.status as site_status, profile, s.language as site_language, platform, verified FROM {node} n left join {hosting_site} s ON n.vid=s.vid WHERE type='%s' AND s.status != -2 " . $cond;
-  $sql .= tablesort_sql($header);
-
-  // @TODO hide deleted sites
-  $result = pager_query(db_rewrite_sql($sql), 25, 1, null, $args);
-
-  if (!$result) {
-    return null;
   }
-  $rows = array();
-  while ($node = db_fetch_object($result)) {
-    $row = array();
-    $row[] =  array('data' => l($node->title, 'node/' . $node->nid), 'class'=> 'hosting-status');
-    $row[] = ($node->profile) ? _hosting_node_link($node->profile) : t('n/a');
-    $row[] = ($node->site_language) ? _hosting_language_name($node->site_language) : t('n/a');
-    $row[] = t("@interval ago", array('@interval' => format_interval(mktime() - $node->created, 1)));
-    if (sizeof($platforms) > 1) {
-      $row[] = ($node->platform) ? _hosting_node_link($node->platform) : t('n/a');
-    }
-    $rows[] = array('data' => $row, 'class' => _hosting_site_list_class($node));
+  else {
+    $rows[] = array(array('data' => t('No sites available.'), 'colspan' => '6'));
   }
-  
-  return theme('table',  $header, $rows, array('class' => 'hosting-table')) . theme('pager', null, 25, 1);
+
+  $output .= theme('table', $header, $rows);
+  if ($form['pager']['#value']) {
+    $output .= drupal_render($form['pager']);
+  }
+
+  $output .= drupal_render($form);
+
+  return $output;
 }
 
+
 function _hosting_site_list_class($node) {
   static $classes = array(
     HOSTING_SITE_QUEUED => "hosting-queue",
