diff --git a/core/modules/migrate_drupal/src/MigrationCreationTrait.php b/core/modules/migrate_drupal/src/MigrationCreationTrait.php
new file mode 100644
index 0000000..e8777b7
--- /dev/null
+++ b/core/modules/migrate_drupal/src/MigrationCreationTrait.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\MigrationCreationTrait.
+ */
+
+namespace Drupal\migrate_drupal;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Database;
+use Drupal\migrate\Entity\Migration;
+use Drupal\migrate\Exception\RequirementsException;
+use Drupal\migrate\Plugin\RequirementsInterface;
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+
+/**
+ * Trait providing functionality to create the appropriate migrations for
+ * a given source Drupal database. Note the class using the trait must
+ * implement TranslationInterface (i.e., define t()).
+ */
+trait MigrationCreationTrait {
+
+  /**
+   * Set up the relevant migrations for import from the provided database
+   * connection.
+   *
+   * @param \Drupal\Core\Database\Database $database
+   *   Database array representing the source Drupal database.
+   * @param string $source_base_path
+   *   Address of the source Drupal site (e.g., http://example.com/).
+   *
+   * @return array
+   */
+  protected function createMigrations(array $database, $source_base_path) {
+    // Set up the connection.
+    Database::addConnectionInfo('upgrade', 'default', $database);
+    $connection = Database::getConnection('default', 'upgrade');
+
+    if (!$drupal_version = $this->getLegacyDrupalVersion($connection)) {
+      throw new \Exception($this->t('Source database does not contain a recognizable Drupal version.'));
+    }
+
+    $version_tag = 'Drupal ' . $drupal_version;
+
+    $template_storage = \Drupal::service('migrate.template_storage');
+    $migration_templates = $template_storage->findTemplatesByTag($version_tag);
+    foreach ($migration_templates as $id => $template) {
+      // Configure file migrations so they can find the files.
+      if ($template['destination']['plugin'] == 'entity:file') {
+        if ($source_base_path) {
+          // Make sure we have a single trailing slash.
+          $source_base_path = rtrim($source_base_path, '/') . '/';
+          $migration_templates[$id]['destination']['source_base_path'] = $source_base_path;
+        }
+      }
+      // @todo: Use a group to hold the db info, so we don't have to stuff it
+      // into every migration.
+      $migration_templates[$id]['source']['key'] = 'upgrade';
+      $migration_templates[$id]['source']['database'] = $database;
+    }
+
+    // Let the builder service create our migration configuration entities from
+    // the templates, expanding them to multiple entities where necessary.
+    /** @var \Drupal\migrate\MigrationBuilder $builder */
+    $builder = \Drupal::service('migrate.migration_builder');
+    $migrations = $builder->createMigrations($migration_templates);
+    $migration_ids = [];
+    foreach ($migrations as $migration) {
+      try {
+        if ($migration->getSourcePlugin() instanceof RequirementsInterface) {
+          $migration->getSourcePlugin()->checkRequirements();
+        }
+        if ($migration->getDestinationPlugin() instanceof RequirementsInterface) {
+          $migration->getDestinationPlugin()->checkRequirements();
+        }
+        $migration->save();
+        $migration_ids[] = $migration->id();
+      }
+      // Migrations which are not applicable given the source and destination
+      // site configurations (e.g., what modules are enabled) will be silently
+      // ignored.
+      catch (RequirementsException $e) {
+      }
+      catch (PluginNotFoundException $e) {
+      }
+    }
+
+    // loadMultiple will sort the migrations in dependency order.
+    return array_keys(Migration::loadMultiple($migration_ids));
+  }
+
+  /**
+   * Determine what version of Drupal the source database contains.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *
+   * @return int|FALSE
+   */
+  protected function getLegacyDrupalVersion(Connection $connection) {
+    // Don't assume because a table of that name exists, that it has the columns
+    // we're querying. Catch exceptions and report that the source database is
+    // not Drupal.
+
+    // Druppal 5/6/7 can be detected by the schema_version in the system table.
+    if ($connection->schema()->tableExists('system')) {
+      try {
+        $version_string = $connection->query('SELECT schema_version FROM {system} WHERE name = :module', [':module' => 'system'])
+                                     ->fetchField();
+        if ($version_string && $version_string[0] == '1') {
+          if ((int) $version_string >= 1000) {
+            $version_string = '5';
+          }
+          else {
+            $version_string = FALSE;
+          }
+        }
+      }
+      catch (\PDOException $e) {
+        $version_string = FALSE;
+      }
+    }
+    // For Drupal 8 (and we're predicting beyond) the schema version is in the
+    // key_value store.
+    elseif ($connection->schema()->tableExists('key_value')) {
+      $result = $connection->query("SELECT value FROM {key_value} WHERE collection = :system_schema  and name = :module", [':system_schema' => 'system.schema', ':module' => 'system'])->fetchField();
+      $version_string = unserialize($result);
+    }
+    else {
+      $version_string = FALSE;
+    }
+
+    return $version_string ? substr($version_string, 0, 1) : FALSE;
+  }
+
+}
diff --git a/core/modules/migrate_drupal_ui/migrate_drupal_ui.info.yml b/core/modules/migrate_drupal_ui/migrate_drupal_ui.info.yml
new file mode 100644
index 0000000..bc0ddd4
--- /dev/null
+++ b/core/modules/migrate_drupal_ui/migrate_drupal_ui.info.yml
@@ -0,0 +1,10 @@
+name: Migrate Drupal UI
+type: module
+description: 'UI for migration from older Drupal versions.'
+package: Core
+version: VERSION
+core: 8.x
+configure: migrate_drupal_ui.upgrade
+dependencies:
+  - migrate
+  - migrate_drupal
diff --git a/core/modules/migrate_drupal_ui/migrate_drupal_ui.install b/core/modules/migrate_drupal_ui/migrate_drupal_ui.install
new file mode 100644
index 0000000..015e7c1
--- /dev/null
+++ b/core/modules/migrate_drupal_ui/migrate_drupal_ui.install
@@ -0,0 +1,16 @@
+<?php
+
+use Drupal\Core\Url;
+
+/**
+ * @file
+ * Install, update and uninstall functions for the migrate_drupal_ui module.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function migrate_drupal_ui_install() {
+  $url = Url::fromUri('base:upgrade')->toString();
+  drupal_set_message(t("The Migrate Drupal UI module has been enabled. Proceed to the <a href='@url'>upgrade form</a>.", array('@url' => $url)));
+}
diff --git a/core/modules/migrate_drupal_ui/migrate_drupal_ui.module b/core/modules/migrate_drupal_ui/migrate_drupal_ui.module
new file mode 100755
index 0000000..a07f545
--- /dev/null
+++ b/core/modules/migrate_drupal_ui/migrate_drupal_ui.module
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Alert administrators before starting the import process.
+ */
+
+use Drupal\Core\Routing\RouteMatchInterface;
+
+/**
+ * Implements hook_help().
+ */
+function migrate_drupal_ui_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    case 'migrate_drupal_ui.upgrade':
+      $output = '<p>' . t('This form is used for importing configuration and content from a previous version of your Drupal site. Make sure you have backups of your destination site before submitting this form. Also, ensure that the source database and files are accessible to the destination site. For more details, see <a href="!migrate">upgrading from previous versions</a>.', array('!migrate' => 'https://www.drupal.org/upgrade')) . '</p>';
+      return $output;
+  }
+}
diff --git a/core/modules/migrate_drupal_ui/migrate_drupal_ui.routing.yml b/core/modules/migrate_drupal_ui/migrate_drupal_ui.routing.yml
new file mode 100644
index 0000000..b8e522f
--- /dev/null
+++ b/core/modules/migrate_drupal_ui/migrate_drupal_ui.routing.yml
@@ -0,0 +1,18 @@
+migrate_drupal_ui.upgrade:
+  path: '/upgrade'
+  defaults:
+    _form: '\Drupal\migrate_drupal_ui\Form\MigrateUpgradeForm'
+    _title: 'Migration'
+  requirements:
+    _permission: 'administer site configuration'
+  options:
+    _admin_route: TRUE
+
+migrate_drupal_ui.log:
+  path: '/upgrade/log'
+  defaults:
+      _controller: '\Drupal\migrate_drupal_ui\Controller\MigrateController::showLog'
+  requirements:
+    _permission: 'administer site configuration'
+  options:
+    _admin_route: TRUE
diff --git a/core/modules/migrate_drupal_ui/src/Controller/MigrateController.php b/core/modules/migrate_drupal_ui/src/Controller/MigrateController.php
new file mode 100644
index 0000000..254d8cd
--- /dev/null
+++ b/core/modules/migrate_drupal_ui/src/Controller/MigrateController.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\migrate_drupal_ui\Controller\MigrateController.
+ */
+
+namespace Drupal\migrate_drupal_ui\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Controller methods for the migration.
+ */
+class MigrateController extends ControllerBase {
+
+  /**
+   * Sets a log filter and redirects to the log.
+   */
+  public function showLog() {
+    $_SESSION['dblog_overview_filter'] = array();
+    $_SESSION['dblog_overview_filter']['type'] = array('migrate_drupal_ui' => 'migrate_drupal_ui');
+    return $this->redirect('dblog.overview');
+  }
+}
diff --git a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php
new file mode 100644
index 0000000..e736933
--- /dev/null
+++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php
@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal_ui\Form\MigrateUpgradeForm.
+ */
+
+namespace Drupal\migrate_drupal_ui\Form;
+
+use Drupal\Core\Installer\Form\SiteSettingsForm;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\migrate_drupal\MigrationCreationTrait;
+
+/**
+ * Form for performing direct site upgrades. Since we have the same need for
+ * obtaining (source) database credentials on the install process, we build off
+ * its form.
+ */
+class MigrateUpgradeForm extends SiteSettingsForm {
+
+  use MigrationCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'migrate_drupal_ui_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    // Make sure the install API is available.
+    include_once DRUPAL_ROOT . '/core/includes/install.inc';
+
+    $form = parent::buildForm($form, $form_state);
+    $form['#title'] = $this->t('Drupal Upgrade: Source site information');
+
+    $form['database'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Source database'),
+      '#description' => $this->t('Provide credentials for the database of the Drupal site you want to migrate.'),
+      '#open' => TRUE,
+    );
+
+    // Copy the values from the parent form into our structure.
+    $form['database']['driver'] = $form['driver'];
+    $form['database']['settings'] = $form['settings'];
+    $form['database']['settings']['mysql']['host'] = $form['database']['settings']['mysql']['advanced_options']['host'];
+    $form['database']['settings']['mysql']['host']['#title'] = 'Database host';
+    $form['database']['settings']['mysql']['host']['#weight'] = 0;
+
+    // Remove the values from the parent form.
+    unset($form['driver']);
+    unset($form['database']['settings']['mysql']['database']['#default_value']);
+    unset($form['settings']);
+    unset($form['database']['settings']['mysql']['advanced_options']['host']);
+
+    $form['source'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Source site files'),
+      '#open' => TRUE,
+    );
+    $form['source']['source_base_path'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Source file path'),
+      '#default_value' => 'http://',
+      '#description' => $this->t('To import public files from your current Drupal site, enter your site address (e.g. http://example.com), or a local file directory containing your site (e.g. /var/www/docroot).'),
+    );
+
+/*
+    $form['files'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Files'),
+      '#open' => TRUE,
+      '#weight' => 2,
+    );
+    // @todo: Not yet implemented, depends on https://www.drupal.org/node/2547125.
+    $form['files']['private_file_directory'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Private file path'),
+      '#description' => $this->t('To import private files from your current Drupal site, enter a local file directory containing your files (e.g. /var/private_files).'),
+    );
+*/
+
+    // Rename the submit button.
+    $form['actions']['save']['#value'] = $this->t('Perform upgrade');
+
+    // The parent form uses #limit_validation_errors to avoid validating the
+    // unselected database drivers. This makes it difficult for us to handle
+    // database errors in our validation, and does not appear to actually be
+    // necessary with the current implementation, so we remove it.
+    unset($form['actions']['save']['#limit_validation_errors']);
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    // Retrieve the database driver from the form, use reflection to get the
+    // namespace and then construct a valid database array the same as in
+    // settings.php.
+    $driver = $form_state->getValue('driver');
+    $drivers = $this->getDatabaseTypes();
+    $reflection = new \ReflectionClass($drivers[$driver]);
+    $install_namespace = $reflection->getNamespaceName();
+
+    $database = $form_state->getValue($driver);
+    // Cut the trailing \Install from namespace.
+    $database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\'));
+    $database['driver'] = $driver;
+
+    // Validate the driver settings and just end here if we have any issues.
+    if ($errors = $drivers[$driver]->validateDatabaseSettings($database)) {
+      foreach ($errors as $name => $message) {
+        $form_state->setErrorByName($name, $message);
+      }
+      return;
+    }
+
+    try {
+      // Create all the relevant migrations and get their IDs so we can run them.
+      $migration_ids = $this->createMigrations($database, $form_state->getValue('source_base_path'));
+
+      // Store the retrieved migration ids on the form state.
+      $form_state->setValue('migration_ids', $migration_ids);
+    }
+    catch (\Exception $e) {
+      $form_state->setErrorByName(NULL, $this->t($e->getMessage()));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $batch = array(
+      'title' => $this->t('Running migrations'),
+      'progress_message' => '',
+      'operations' => array(
+        array(array('Drupal\migrate_drupal_ui\MigrateUpgradeRunBatch', 'run'), array($form_state->getValue('migration_ids'))),
+      ),
+      'finished' => array('Drupal\migrate_drupal_ui\MigrateUpgradeRunBatch', 'finished'),
+    );
+    batch_set($batch);
+    $form_state->setRedirect('<front>');
+  }
+
+  /**
+   * Returns all supported database driver installer objects.
+   *
+   * @return \Drupal\Core\Database\Install\Tasks[]
+   *   An array of available database driver installer objects.
+   */
+  protected function getDatabaseTypes() {
+    // Make sure the install API is available.
+    include_once DRUPAL_ROOT . '/core/includes/install.core.inc';
+    return drupal_get_database_types();
+  }
+
+}
diff --git a/core/modules/migrate_drupal_ui/src/MigrateMessageCapture.php b/core/modules/migrate_drupal_ui/src/MigrateMessageCapture.php
new file mode 100644
index 0000000..138613f
--- /dev/null
+++ b/core/modules/migrate_drupal_ui/src/MigrateMessageCapture.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal_ui\MigrateMessageCapture.
+ */
+
+namespace Drupal\migrate_drupal_ui;
+
+use Drupal\migrate\MigrateMessageInterface;
+
+/**
+ * Defines a migrate message class for capturing messages rather than
+ * displaying them directly.
+ */
+class MigrateMessageCapture implements MigrateMessageInterface {
+
+  /**
+   * Array of recorded messages.
+   *
+   * @var array
+   */
+  protected $messages = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function display($message, $type = 'status') {
+    $this->messages[] = $message;
+  }
+
+  /**
+   * Clear out any captured messages.
+   */
+  public function clear() {
+    $this->messages = [];
+  }
+
+  /**
+   * Return any captured messages.
+   *
+   * @return array
+   */
+  public function getMessages() {
+    return $this->messages;
+  }
+
+}
diff --git a/core/modules/migrate_drupal_ui/src/MigrateUpgradeRunBatch.php b/core/modules/migrate_drupal_ui/src/MigrateUpgradeRunBatch.php
new file mode 100644
index 0000000..184647b
--- /dev/null
+++ b/core/modules/migrate_drupal_ui/src/MigrateUpgradeRunBatch.php
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal_ui\MigrateUpgradeRunBatch.
+ */
+
+namespace Drupal\migrate_drupal_ui;
+
+use Drupal\migrate\Entity\Migration;
+use Drupal\migrate\Entity\MigrationInterface;
+use Drupal\migrate\MigrateExecutable;
+use Drupal\Core\Url;
+
+class MigrateUpgradeRunBatch {
+
+  /**
+   * Run a single migration batch.
+   *
+   * @param $initial_ids
+   *   The full set of migration IDs to import.
+   * @param $context
+   *   The batch context.
+   */
+  public static function run($initial_ids, &$context) {
+    if (!isset($context['sandbox']['migration_ids'])) {
+      $context['sandbox']['max'] = count($initial_ids);
+      // migration_ids will be the list of IDs remaining to run.
+      $context['sandbox']['migration_ids'] = $initial_ids;
+      $context['sandbox']['messages'] = [];
+      $context['results']['failures'] = 0;
+      $context['results']['successes'] = 0;
+    }
+
+    $migration_id = reset($context['sandbox']['migration_ids']);
+    /** @var \Drupal\migrate\Entity\Migration $migration */
+    $migration = Migration::load($migration_id);
+    if ($migration) {
+      $messages = new MigrateMessageCapture();
+      $executable = new MigrateExecutable($migration, $messages);
+
+      $migration_name = $migration->label() ? $migration->label() : $migration_id;
+      static::logger()->notice('Importing @migration', array('@migration' => $migration_name));
+
+      try  {
+        $migration_status = $executable->import();
+      }
+      catch (\Exception $e) {
+        // PluginNotFoundException is when the D8 module is disabled, maybe that
+        // should be a RequirementsException instead.
+        static::logger()->error($e->getMessage());
+        $migration_status = MigrationInterface::RESULT_FAILED;
+      }
+
+      switch ($migration_status) {
+        case MigrationInterface::RESULT_COMPLETED:
+          $context['sandbox']['messages'][] = t('Imported @migration', array('@migration' => $migration_name));
+          $context['results']['successes']++;
+          static::logger()->notice('Imported @migration', array('@migration' => $migration_name));
+          break;
+
+        case MigrationInterface::RESULT_INCOMPLETE:
+          $context['sandbox']['messages'][] = t('Importing @migration', array('@migration' => $migration_name));
+          break;
+
+        case MigrationInterface::RESULT_STOPPED:
+          $context['sandbox']['messages'][] = t('Import stopped by request');
+          break;
+
+        case MigrationInterface::RESULT_FAILED:
+          $context['sandbox']['messages'][] = t('Import of @migration failed', array('@migration' => $migration_name));
+          $context['results']['failures']++;
+          static::logger()->error('Import of @migration failed', array('@migration' => $migration_name));
+          break;
+
+        case MigrationInterface::RESULT_SKIPPED:
+          $context['sandbox']['messages'][] = t('Import of @migration skipped due to unfulfilled dependencies', array('@migration' => $migration_name));
+          static::logger()->error('Import of @migration skipped due to unfulfilled dependencies', array('@migration' => $migration_name));
+          break;
+
+        case MigrationInterface::RESULT_DISABLED:
+          // Skip silently if disabled.
+          break;
+      }
+
+      // Unless we're continuing on with this migration, take it off the list.
+      if ($migration_status != MigrationInterface::RESULT_INCOMPLETE) {
+        array_shift($context['sandbox']['migration_ids']);
+      }
+
+      // Add any captured messages.
+      foreach ($messages->getMessages() as $message) {
+        $context['sandbox']['messages'][] = $message;
+      }
+
+      // Only display the last 10 messages, in reverse order.
+      $message_count = count($context['sandbox']['messages']);
+      $context['message'] = '';
+      for ($index = max(0, $message_count - 10); $index < $message_count; $index++) {
+        $context['message'] = $context['sandbox']['messages'][$index]. "<br />\n" . $context['message'];
+      }
+      if ($message_count > 10) {
+        // Indicate there are earlier messages not displayed.
+        $context['message'] .= '&hellip;';
+      }
+      // At the top of the list, display the next one (which will be the one
+      // that is running while this message is visible).
+      if (!empty($context['sandbox']['migration_ids'])) {
+        $migration_id = reset($context['sandbox']['migration_ids']);
+        $migration = Migration::load($migration_id);
+        $migration_name = $migration->label() ? $migration->label() : $migration_id;
+        $context['message'] = t('Currently importing @migration', array('@migration' => $migration_name))
+                              . "<br />\n" . $context['message'];
+      }
+    }
+    else {
+      array_shift($context['sandbox']['migration_ids']);
+    }
+
+    $context['finished'] = 1 - count($context['sandbox']['migration_ids']) / $context['sandbox']['max'];
+  }
+
+  /**
+   * A helper method to grab the logger using the migrate_drupal_ui channel.
+   *
+   * @return \Psr\Log\LoggerInterface
+   *   The logger instance.
+   */
+  protected static function logger() {
+    return \Drupal::logger('migrate_drupal_ui');
+  }
+
+  /**
+   * Implementation of the Batch API finished method.
+   */
+  public static function finished($success, $results, $operations, $elapsed) {
+    static::displayResults($results);
+  }
+
+  /**
+   * Display counts of success/failures on the migration upgrade complete page.
+   *
+   * @param $results
+   *   An array of result data built during the batch.
+   */
+  protected static function displayResults($results) {
+    $successes = $results['successes'];
+    $failures = $results['failures'];
+    $translation = \Drupal::translation();
+
+    // If we had any successes lot that for the user.
+    if ($successes > 0) {
+      drupal_set_message(t('Import completed @count successfully.', array('@count' => $translation->formatPlural($successes, '1 migration', '@count migrations'))));
+    }
+
+    // If we had failures, log them and show the migration failed.
+    if ($failures > 0) {
+      drupal_set_message(t('@count failed', array('@count' => $translation->formatPlural($failures, '1 migration', '@count migrations'))), 'error');
+      drupal_set_message(t('Import process not completed'), 'error');
+    }
+    else {
+      // Everything went off without a hitch. We may not have had successes but
+      // we didn't have failures so this is fine.
+      drupal_set_message(t('Congratulations, you upgraded Drupal!'));
+    }
+
+    if (\Drupal::moduleHandler()->moduleExists('dblog')) {
+      $url = Url::fromRoute('migrate_drupal_ui.log');
+      drupal_set_message(\Drupal::l(t('Review the detailed migration log'), $url), $failures ? 'error' : 'status');
+    }
+  }
+
+}
