diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 9d86cb9..7c7ea6f 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -202,6 +202,8 @@ function install_state_defaults() {
     'profile_info' => array(),
     // An array of available installation profiles.
     'profiles' => array(),
+    // The name of a theme to use during installation.
+    'theme' => 'seven',
     // An array of server variables that will be substituted into the global
     // $_SERVER array via drupal_override_server_variables(). Used by
     // non-interactive installations only.
@@ -499,12 +501,27 @@ function install_begin_request(&$install_state) {
     \Drupal::translation()->setDefaultLangcode($install_state['parameters']['langcode']);
   }
 
+  // Add the list of available profiles to the installation state.
+  $install_state['profiles'] += drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles');
+
+  if ($profile = _install_select_profile($install_state)) {
+    $install_state['parameters']['profile'] = $profile;
+    install_load_profile($install_state);
+    if (isset($install_state['profile_info']['distribution']['install']['theme'])) {
+      // @todo Do we need to validate this?
+      $install_state['theme'] = $install_state['profile_info']['distribution']['install']['theme'];
+    }
+  }
+
   $module_handler = \Drupal::moduleHandler();
   if (!$module_handler->moduleExists('system')) {
     // Override the module list with a minimal set of modules.
     $module_handler->setModuleList(array('system' => 'core/modules/system/system.module'));
   }
-  $module_handler->load('system');
+  if ($profile && !$module_handler->moduleExists($profile)) {
+    $module_handler->setModuleList(array($profile => $install_state['profiles'][$profile]->uri));
+  }
+  $module_handler->loadAll();
 
   // Prepare for themed output. We need to run this at the beginning of the
   // page request to avoid a different theme accidentally getting set. (We also
@@ -538,9 +555,6 @@ function install_begin_request(&$install_state) {
 
   // Modify the installation state as appropriate.
   $install_state['completed_task'] = $task;
-
-  // Add the list of available profiles to the installation state.
-  $install_state['profiles'] += drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles');
 }
 
 /**
@@ -787,10 +801,11 @@ function install_tasks($install_state) {
     'install_download_translation' => array(
       'run' => $needs_download ? INSTALL_TASK_RUN_IF_REACHED : INSTALL_TASK_SKIP,
     ),
-    'install_select_profile' => array(
+    'install_select_profile_form' => array(
+      'type' => 'form',
       'display_name' => t('Choose profile'),
-      'display' => count($install_state['profiles']) != 1,
-      'run' => INSTALL_TASK_RUN_IF_REACHED,
+      'display' => empty($install_state['parameters']['profile']) && count($install_state['profiles']) != 1,
+      'run' => empty($install_state['parameters']['profile']) ? INSTALL_TASK_RUN_IF_REACHED : INSTALL_TASK_SKIP,
     ),
     'install_load_profile' => array(
       'run' => INSTALL_TASK_RUN_IF_REACHED,
@@ -814,7 +829,7 @@ function install_tasks($install_state) {
       'run' => INSTALL_TASK_RUN_IF_REACHED,
     ),
     'install_profile_modules' => array(
-      'display_name' => count($install_state['profiles']) == 1 ? t('Install site') : t('Installation profile'),
+      'display_name' => t('Install site'),
       'type' => 'batch',
     ),
     'install_import_translations' => array(
@@ -1285,88 +1300,42 @@ function install_settings_form_submit($form, &$form_state) {
 }
 
 /**
- * Selects which profile to install.
+ * Determines the installation profile to use in the installer.
  *
- * @param $install_state
- *   An array of information about the current installation state. The chosen
- *   profile will be added here, if it was not already selected previously, as
- *   will a list of all available profiles.
- *
- * @return
- *   For interactive installations, a form allowing the profile to be selected,
- *   if the user has a choice that needs to be made. Otherwise, an exception is
- *   thrown if a profile cannot be chosen automatically.
- */
-function install_select_profile(&$install_state) {
-  if (empty($install_state['parameters']['profile'])) {
-    // Try to find a profile.
-    $profile = _install_select_profile($install_state['profiles']);
-    if (empty($profile)) {
-      // We still don't have a profile, so display a form for selecting one.
-      // Only do this in the case of interactive installations, since this is
-      // not a real form with submit handlers (the database isn't even set up
-      // yet), rather just a convenience method for setting parameters in the
-      // URL.
-      if ($install_state['interactive']) {
-        include_once __DIR__ . '/form.inc';
-        drupal_set_title(t('Select an installation profile'));
-        $form = drupal_get_form('install_select_profile_form', $install_state);
-        return drupal_render($form);
-      }
-      else {
-        throw new Exception(install_no_profile_error());
-      }
-    }
-    else {
-      $install_state['parameters']['profile'] = $profile;
-    }
-  }
-}
-
-/**
- * Selects an installation profile.
+ * A profile will be selected in the following order of conditions:
  *
- * A profile will be selected if:
- * - Only one profile is available,
- * - A profile was submitted through \Drupal::request()->request,
- * - Exactly one of the profiles is marked as "exclusive".
- * If multiple profiles are marked as "exclusive" then no profile will be
- * selected.
+ * 1. Only one profile is available.
+ * 2. A specific profile name is requested in installation parameters:
+ *    - for interactive installations via request query parameters.
+ *    - for non-interactive installations via install_drupal() settings.
+ * 3. A discvered profile that is a distribution.
+ *    If multiple profiles are distributions, then the first discovered profile
+ *    will be selected.
  *
- * @param array $profiles
- *   An associative array of profiles with the machine-readable names as keys.
+ * @param array $install_state
+ *   The current installer state, containing a 'profiles' key, which is an
+ *   associative array of profiles with the machine-readable names as keys.
  *
  * @return
  *   The machine-readable name of the selected profile or NULL if no profile was
  *   selected.
  */
-function _install_select_profile($profiles) {
+function _install_select_profile(&$install_state) {
   // Don't need to choose profile if only one available.
-  $request_params = \Drupal::request()->request;
-  if (count($profiles) == 1) {
-    $profile = array_pop($profiles);
+  if (count($install_state['profiles']) == 1) {
+    $profile = reset($install_state['profiles']);
     return $profile->name;
   }
-  elseif ($request_params->has('profile') && ($profile = $request_params->get('profile')) && isset($profiles[$profile])) {
-    return $profiles[$profile]->name;
+  if (!empty($install_state['parameters']['profile']) && ($profile = $install_state['parameters']['profile']) && isset($install_state['profiles'][$profile])) {
+    return $install_state['profiles'][$profile]->name;
   }
-  // Check for a profile marked as "exclusive" and ensure that only one
-  // profile is marked as such.
-  $exclusive_profile = NULL;
-  foreach ($profiles as $profile) {
+  // Check for a distribution profile.
+  foreach ($install_state['profiles'] as $profile) {
     $profile_info = install_profile_info($profile->name);
-    if (!empty($profile_info['exclusive'])) {
-      if (empty($exclusive_profile)) {
-        $exclusive_profile = $profile->name;
-      }
-      else {
-        // We found a second "exclusive" profile. There's no way to choose
-        // between them, so we ignore the property.
-        return;
-      }
+    if (!empty($profile_info['distribution'])) {
+      return $profile->name;
     }
   }
-  return $exclusive_profile;
 }
 
 /**
@@ -1378,6 +1347,8 @@ function _install_select_profile($profiles) {
  * @ingroup forms
  */
 function install_select_profile_form($form, &$form_state, $install_state) {
+  drupal_set_title(t('Select an installation profile'));
+
   $profiles = array();
   $names = array();
 
@@ -1440,6 +1411,14 @@ function install_select_profile_form($form, &$form_state, $install_state) {
 }
 
 /**
+ * Form submission handler for install_select_profile_form().
+ */
+function install_select_profile_form_submit($form, &$form_state) {
+  global $install_state;
+  $install_state['parameters']['profile'] = $form_state['values']['profile'];
+}
+
+/**
  * Finds all .po files that are useful to the installer.
  *
  * @return
@@ -1895,7 +1874,7 @@ function install_load_profile(&$install_state) {
   $profile_file = $install_state['profiles'][$profile]->uri;
   if (file_exists($profile_file)) {
     include_once DRUPAL_ROOT . '/' . $profile_file;
-    $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], $install_state['parameters']['langcode']);
+    $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], isset($install_state['parameters']['langcode']) ? $install_state['parameters']['langcode'] : 'en');
   }
   else {
     throw new Exception(t('Sorry, the profile you have chosen cannot be loaded.'));
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 8c91dc8..276f08d 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -95,13 +95,13 @@ function drupal_install_profile_distribution_name() {
   // installation state (it might not be saved anywhere yet).
   if (drupal_installation_attempted()) {
     global $install_state;
-    return isset($install_state['profile_info']['distribution_name']) ? $install_state['profile_info']['distribution_name'] : 'Drupal';
+    return isset($install_state['profile_info']['distribution']['name']) ? $install_state['profile_info']['distribution']['name'] : 'Drupal';
   }
   // At all other times, we load the profile via standard methods.
   else {
     $profile = drupal_get_profile();
     $info = system_get_info('module', $profile);
-    return $info['distribution_name'];
+    return $info['distribution']['name'];
   }
 }
 
@@ -577,7 +577,7 @@ function drupal_verify_profile($install_state) {
 
   // The installation profile is also a module, which needs to be installed
   // after all the other dependencies have been installed.
-  $present_modules[] = drupal_get_profile();
+  $present_modules[] = $profile;
 
   // Verify that all of the profile's required modules are present.
   $missing_modules = array_diff($info['dependencies'], $present_modules);
@@ -1036,15 +1036,14 @@ function drupal_check_module($module) {
  * Additional, less commonly-used information that can appear in a
  * profile.info.yml file but not in a normal Drupal module .info.yml file
  * includes:
- * - distribution_name: The name of the Drupal distribution that is being
- *   installed, to be shown throughout the installation process. Defaults to
- *   'Drupal'.
- * - exclusive: If the install profile is intended to be the only eligible
- *   choice in a distribution, setting exclusive = TRUE will auto-select it
- *   during installation, and the install profile selection screen will be
- *   skipped. If more than one profile is found where exclusive = TRUE then
- *   this property will have no effect and the profile selection screen will
- *   be shown as normal with all available profiles shown.
+ * - distribution: Existence of this key denotes that the install profile is
+ *   intended to be the only eligible choice in a distribution and will be
+ *   auto-selected during installation, whereas the installation profile
+ *   selection screen will be skipped. If more than one distribution profile is
+ *   found then the first discovered one will be selected.
+ *   The following subproperties may be set:
+ *   - name: The name of the Drupal distribution that is being installed, to be
+ *     shown throughout the installation process. Defaults to 'Drupal'.
  *
  * Note that this function does an expensive file system scan to get info file
  * information for dependencies. If you only need information from the info
@@ -1074,7 +1073,6 @@ function install_profile_info($profile, $langcode = 'en') {
     $defaults = array(
       'dependencies' => array(),
       'description' => '',
-      'distribution_name' => 'Drupal',
       'version' => NULL,
       'hidden' => FALSE,
       'php' => DRUPAL_MINIMUM_PHP,
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 96a0466..f60ab91 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2556,7 +2556,11 @@ function template_preprocess_install_page(&$variables) {
   template_preprocess_maintenance_page($variables);
   // Override the site name that is displayed on the page, since Drupal is
   // still in the process of being installed.
-  $variables['site_name'] = drupal_install_profile_distribution_name();
+  $distribution_name = String::checkPlain(drupal_install_profile_distribution_name());
+  $variables['site_name'] = $distribution_name;
+  $variables['head_title_array']['name'] = $distribution_name;
+
+  $variables['head_title'] = implode(' | ', $variables['head_title_array']);
 }
 
 /**
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index 8456eee..e8acd6b 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -34,7 +34,12 @@ function _drupal_maintenance_theme() {
 
   // Install and update pages are treated differently to prevent theming overrides.
   if (defined('MAINTENANCE_MODE') && (MAINTENANCE_MODE == 'install' || MAINTENANCE_MODE == 'update')) {
-    $custom_theme = settings()->get('maintenance_theme', 'seven');
+    if (drupal_installation_attempted()) {
+      $custom_theme = $GLOBALS['install_state']['theme'];
+    }
+    else {
+      $custom_theme = settings()->get('maintenance_theme', 'seven');
+    }
   }
   else {
     // The bootstrap was not complete. So we are operating in a crippled
diff --git a/core/lib/Drupal/Core/SystemListingInfo.php b/core/lib/Drupal/Core/SystemListingInfo.php
index d302699..34a8599 100644
--- a/core/lib/Drupal/Core/SystemListingInfo.php
+++ b/core/lib/Drupal/Core/SystemListingInfo.php
@@ -19,6 +19,15 @@ class SystemListingInfo extends SystemListing {
    */
   protected function profiles($directory) {
     $searchdir = array();
+    // If install.php is invoked with a ?profile= query parameter, then
+    // $install_state['parameters']['profile'] will contain that value already,
+    // drupal_get_profile() will return it, and the below calls to
+    // drupal_get_path() will call into drupal_get_filename(), which triggers an
+    // infinite recursion.
+    // @todo Use SystemListing in install_begin_request()?
+    if (drupal_installation_attempted() && empty($GLOBALS['install_state']['profiles'])) {
+      return $searchdir;
+    }
     // The 'core/profiles' directory contains pristine collections of modules
     // and themes as provided by a distribution. It is pristine in the same
     // way that the 'core/modules' directory is pristine for core; users
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index cc647a2..02809e6 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2648,8 +2648,8 @@ function _system_rebuild_module_data() {
     $modules[$profile]->info['required'] = TRUE;
     // Add a default distribution name if the profile did not provide one. This
     // matches the default value used in install_profile_info().
-    if (!isset($modules[$profile]->info['distribution_name'])) {
-      $modules[$profile]->info['distribution_name'] = 'Drupal';
+    if (!isset($modules[$profile]->info['distribution']['name'])) {
+      $modules[$profile]->info['distribution']['name'] = 'Drupal';
     }
   }
 
