diff --git a/migrate_tools/migrate_tools.drush.inc b/migrate_tools/migrate_tools.drush.inc
index 9d62c4e..1a82b16 100644
--- a/migrate_tools/migrate_tools.drush.inc
+++ b/migrate_tools/migrate_tools.drush.inc
@@ -54,6 +54,25 @@ function migrate_tools_drush_command() {
     'aliases' => array('mi'),
   );
 
+  $items['migrate-rollback'] = array(
+    'description' => 'Rollback one or more migrations.',
+    'options' => array(
+      'all' => 'Process all migrations.',
+      'group' => 'Name of the migration group to rollback',
+      'feedback' => 'Frequency of progress messages, in items processed',
+    ),
+    'arguments' => array(
+      'migration' => 'Name of migration(s) to rollback. Delimit multiple using commas.',
+    ),
+    'examples' => array(
+      'migrate-rollback --all' => 'Perform all migrations',
+      'migrate-rollback --group=beer' => 'Rollback all migrations in the beer group',
+      'migrate-rollback BeerTerm,BeerNode' => 'Rollback imported terms and nodes',
+    ),
+    'drupal dependencies' => array('migrate_tools'),
+    'aliases' => array('mr'),
+  );
+
   $items['migrate-manifest'] = array(
     'description' => 'Execute the migrations as specified in a manifest file.',
     'arguments' => array(
@@ -183,6 +202,38 @@ function drush_migrate_tools_migrate_import($migration_names = '') {
 }
 
 /**
+ * @param string $migration_names
+ */
+function drush_migrate_tools_migrate_rollback($migration_names = '') {
+  $group_name = drush_get_option('group');
+  $all = drush_get_option('all');
+  $options = [];
+  if (!$all && !$group_name && !$migration_names) {
+    drush_set_error('MIGRATE_ERROR', dt('You must specify --all, --group, or one or more migration names separated by commas'));
+    return;
+  }
+
+  if (drush_get_option('feedback')) {
+    $options['feedback'] = drush_get_option('feedback');
+  }
+
+  $log = new DrushLogMigrateMessage();
+
+  $migrations = drush_migrate_tools_migration_list($group_name, $migration_names);
+
+  // Take it one group at a time, rolling back the migrations within each group.
+  foreach ($migrations as $group_id => $migration_list) {
+    // Roll back in reverse order.
+    $migration_list = array_reverse($migration_list);
+    foreach ($migration_list as $migration_id => $migration) {
+      $executable = new MigrateExecutable($migration, $log, $options);
+      // drush_op() provides --simulate support.
+      drush_op(array($executable, 'rollback'));
+    }
+  }
+}
+
+/**
  * Retrieve a list of active migrations.
  *
  * @param string $group_id
diff --git a/migrate_tools/src/MigrateExecutable.php b/migrate_tools/src/MigrateExecutable.php
index 964109d..20c13dd 100644
--- a/migrate_tools/src/MigrateExecutable.php
+++ b/migrate_tools/src/MigrateExecutable.php
@@ -8,6 +8,8 @@
 namespace Drupal\migrate_tools;
 
 use Drupal\migrate\Event\MigratePreRowSaveEvent;
+use Drupal\migrate\Event\MigrateRollbackEvent;
+use Drupal\migrate\Event\MigrateRowDeleteEvent;
 use Drupal\migrate\MigrateExecutable as MigrateExecutableBase;
 use Drupal\migrate\MigrateMessageInterface;
 use Drupal\migrate\Entity\MigrationInterface;
@@ -82,10 +84,14 @@ class MigrateExecutable extends MigrateExecutableBase {
       array($this, 'onMapDelete'));
     \Drupal::service('event_dispatcher')->addListener(MigrateEvents::POST_IMPORT,
       array($this, 'onPostImport'));
+    \Drupal::service('event_dispatcher')->addListener(MigrateEvents::POST_ROLLBACK,
+      array($this, 'onPostRollback'));
     \Drupal::service('event_dispatcher')->addListener(MigrateEvents::PRE_ROW_SAVE,
       array($this, 'onPreRowSave'));
     \Drupal::service('event_dispatcher')->addListener(MigrateEvents::POST_ROW_SAVE,
       array($this, 'onPostRowSave'));
+    \Drupal::service('event_dispatcher')->addListener(MigrateEvents::POST_ROW_DELETE,
+      array($this, 'onPostRowDelete'));
   }
 
   /**
@@ -198,6 +204,16 @@ class MigrateExecutable extends MigrateExecutableBase {
   }
 
   /**
+   * React to rollback completion.
+   *
+   * @param \Drupal\migrate\Event\MigrateRollbackEvent $event
+   *   The map event.
+   */
+  public function onPostRollback(MigrateRollbackEvent $event) {
+    $this->rollbackMessage();
+  }
+
+  /**
    * Emit information on what we've done since the last feedback (or the
    * beginning of this migration).
    *
@@ -224,6 +240,28 @@ class MigrateExecutable extends MigrateExecutableBase {
   }
 
   /**
+   * Emit information on what we've done since the last feedback (or the
+   * beginning of this migration).
+   *
+   * @param bool $done
+   */
+  protected function rollbackMessage($done = TRUE) {
+    $rolled_back = $this->getRollbackCount();
+    if ($done) {
+      $singular_message = "Rolled back 1 item - done with '!name'";
+      $plural_message = "Rolled back !numitems items - done with '!name'";
+    }
+    else {
+      $singular_message = "Rolled back 1 item - continuing with '!name'";
+      $plural_message = "Rolled back !numitems items - continuing with '!name'";
+    }
+    $this->message->display(\Drupal::translation()->formatPlural($rolled_back,
+      $singular_message, $plural_message,
+        array('!numitems' => $rolled_back,
+              '!name' => $this->migration->id())));
+  }
+
+  /**
    * React to an item about to be imported.
    *
    * @param \Drupal\migrate\Event\MigratePreRowSaveEvent $event
@@ -253,4 +291,17 @@ class MigrateExecutable extends MigrateExecutableBase {
     $this->counter++;
   }
 
+  /**
+   * React to item rollback.
+   *
+   * @param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
+   *   The post-save event.
+   */
+  public function onPostRowDelete(MigrateRowDeleteEvent $event) {
+    if ($this->feedback && ($this->deleteCounter) && $this->deleteCounter % $this->feedback == 0) {
+      $this->rollbackMessage(FALSE);
+      $this->resetCounters();
+    }
+  }
+
 }
