diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php
index d496fe7..b184c48 100644
--- a/core/lib/Drupal/Core/Config/ConfigImporter.php
+++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
@@ -85,7 +85,14 @@ class ConfigImporter {
    *
    * @var array
    */
-  protected $processed;
+  protected $processed = array();
+
+  /**
+   * List of changes to process by the import().
+   *
+   * @var array
+   */
+  protected $toProcess = array();
 
   /**
    * Indicates changes to import have been validated.
@@ -115,7 +122,6 @@ public function __construct(StorageComparerInterface $storage_comparer, EventDis
     $this->configFactory = $config_factory;
     $this->entityManager = $entity_manager;
     $this->lock = $lock;
-    $this->processed = $this->storageComparer->getEmptyChangelist();
     // Use an override free context for importing so that overrides to do not
     // pollute the imported data. The context is hard coded to ensure this is
     // the case.
@@ -140,7 +146,8 @@ public function getStorageComparer() {
    */
   public function reset() {
     $this->storageComparer->reset();
-    $this->processed = $this->storageComparer->getEmptyChangelist();
+    $this->toProcess = array();
+    $this->processed = array();
     $this->validated = FALSE;
     return $this;
   }
@@ -148,42 +155,23 @@ public function reset() {
   /**
    * Checks if there are any unprocessed changes.
    *
-   * @param array $ops
-   *   The operations to check for changes. Defaults to all operations, i.e.
-   *   array('delete', 'create', 'update').
-   *
    * @return bool
    *   TRUE if there are changes to process and FALSE if not.
    */
-  public function hasUnprocessedChanges($ops = array('delete', 'create', 'update')) {
-    foreach ($ops as $op) {
-      if (count($this->getUnprocessed($op))) {
-        return TRUE;
-      }
-    }
-    return FALSE;
-  }
-
-  /**
-   * Gets list of processed changes.
-   *
-   * @return array
-   *   An array containing a list of processed changes.
-   */
-  public function getProcessed() {
-    return $this->processed;
+  public function hasUnprocessedChanges() {
+    return count($this->getUnprocessed()) ? TRUE : FALSE;
   }
 
   /**
    * Sets a change as processed.
    *
-   * @param string $op
-   *   The change operation performed, either delete, create or update.
    * @param string $name
    *   The name of the configuration processed.
+   * @param string $op
+   *   The change operation performed, either delete, create or update.
    */
-  protected function setProcessed($op, $name) {
-    $this->processed[$op][] = $name;
+  protected function setProcessed($name, $op) {
+    $this->processed[$name] = $op;
   }
 
   /**
@@ -196,8 +184,11 @@ protected function setProcessed($op, $name) {
    * @return array
    *   An array of configuration names.
    */
-  public function getUnprocessed($op) {
-    return array_diff($this->storageComparer->getChangelist($op), $this->processed[$op]);
+  public function getUnprocessed() {
+    if (empty($this->toProcess)) {
+      $this->toProcess = $this->storageComparer->getChangelistKeyedByName();
+    }
+    return array_diff_key($this->toProcess, $this->processed);
   }
 
   /**
@@ -218,6 +209,8 @@ public function import() {
         // Another process is synchronizing configuration.
         throw new ConfigImporterException(sprintf('%s is already importing', static::ID));
       }
+
+      $this->handleExtensions();
       $this->importInvokeOwner();
       $this->importConfig();
       // Allow modules to react to a import.
@@ -251,19 +244,17 @@ public function validate() {
    * Writes an array of config changes from the source to the target storage.
    */
   protected function importConfig() {
-    foreach (array('delete', 'create', 'update') as $op) {
-      foreach ($this->getUnprocessed($op) as $name) {
-        $config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context);
-        if ($op == 'delete') {
-          $config->delete();
-        }
-        else {
-          $data = $this->storageComparer->getSourceStorage()->read($name);
-          $config->setData($data ? $data : array());
-          $config->save();
-        }
-        $this->setProcessed($op, $name);
+    foreach ($this->getUnprocessed() as $name => $op) {
+      $config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context);
+      if ($op == 'delete') {
+        $config->delete();
+      }
+      else {
+        $data = $this->storageComparer->getSourceStorage()->read($name);
+        $config->setData($data ? $data : array());
+        $config->save();
       }
+      $this->setProcessed($name, $op);
     }
   }
 
@@ -278,34 +269,86 @@ protected function importConfig() {
   protected function importInvokeOwner() {
     // First pass deleted, then new, and lastly changed configuration, in order
     // to handle dependencies correctly.
-    foreach (array('delete', 'create', 'update') as $op) {
-      foreach ($this->getUnprocessed($op) as $name) {
-        // Call to the configuration entity's storage controller to handle the
-        // configuration change.
-        $handled_by_module = FALSE;
-        // Validate the configuration object name before importing it.
-        // Config::validateName($name);
-        if ($entity_type = config_get_entity_type_by_name($name)) {
-          $old_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context);
-          $old_config->load();
-
-          $data = $this->storageComparer->getSourceStorage()->read($name);
-          $new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context);
-          if ($data !== FALSE) {
-            $new_config->setData($data);
-          }
-
-          $method = 'import' . ucfirst($op);
-          $handled_by_module = $this->entityManager->getStorageController($entity_type)->$method($name, $new_config, $old_config);
-        }
-        if (!empty($handled_by_module)) {
-          $this->setProcessed($op, $name);
+    foreach ($this->getUnprocessed() as $name => $op) {
+      // Call to the configuration entity's storage controller to handle the
+      // configuration change.
+      $handled_by_module = FALSE;
+      // Validate the configuration object name before importing it.
+      // Config::validateName($name);
+      if ($entity_type = config_get_entity_type_by_name($name)) {
+        $old_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context);
+        $old_config->load();
+
+        $data = $this->storageComparer->getSourceStorage()->read($name);
+        $new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context);
+        if ($data !== FALSE) {
+          $new_config->setData($data);
         }
+
+        $method = 'import' . ucfirst($op);
+        $handled_by_module = $this->entityManager->getStorageController($entity_type)->$method($name, $new_config, $old_config);
+      }
+      if (!empty($handled_by_module)) {
+        $this->setProcessed($name, $op);
       }
     }
   }
 
   /**
+   * Handle changes to installed modules and themes.
+   */
+  protected function handleExtensions() {
+    $processlist = $this->getUnprocessed();
+    if (isset($processlist['system.module'])) {
+      $this->handleModules();
+    }
+    if (isset($processlist['system.theme'])) {
+      $this->handleThemes();
+    }
+
+    // Recalculate differences as default config could have been imported.
+    $this->storageComparer->reset();
+    $this->toProcess = array();
+    $this->processed = array();
+  }
+  /**
+   * Install, disable or uninstall modules depending on config to import.
+   */
+  protected function handleModules() {
+    $current = $this->storageComparer->getTargetStorage()->read('system.module');
+    $new = $this->storageComparer->getSourceStorage()->read('system.module');
+    if (!\Drupal::moduleHandler()->enable(array_flip(array_diff_key($new['enabled'], $current['enabled'])))) {
+      throw new ConfigImporterException(sprintf('Unable to enable modules'));
+    }
+    $disabled_or_uninstalled = array_flip(array_diff_key($current['enabled'], $new['enabled']));
+    $disabled = array_keys($this->storageComparer->getSourceStorage()->read('system.module.disabled'));
+    // Ensure all modules that should be disabled or uninstalled are disabled.
+    \Drupal::moduleHandler()->disable($disabled_or_uninstalled);
+    // Uninstall any that in the new list of disabled modules.
+    if (!\Drupal::moduleHandler()->uninstall(array_diff($disabled_or_uninstalled, $disabled))) {
+      throw new ConfigImporterException(sprintf('Unable to uninstall modules'));
+    }
+  }
+
+  /**
+   * Enable or disable themes depending on config to import.
+   */
+  protected function handleThemes() {
+    $current = $this->storageComparer->getTargetStorage()->read('system.theme');
+    $new = $this->storageComparer->getSourceStorage()->read('system.theme');
+    if (isset($current['enabled'])) {
+      $current = $current['enabled'];
+    }
+    else {
+      $current = array();
+    }
+    theme_enable(array_flip(array_diff_key($new['enabled'], $current)));
+    if (!empty($current)) {
+      theme_disable(array_flip(array_diff_key($current, $new['enabled'])));
+    }
+  }
+
+  /**
    * Dispatches a config importer event.
    *
    * @param string $event_name
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index 11622b2..63fb57c 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -30,4 +30,12 @@ class ConfigInstaller extends ConfigImporter {
    */
   const ID = 'config.installer';
 
+  /**
+   * Handle changes to installed modules and themes.
+   *
+   * Provide a null implementation so enabling system module during installer
+   * does not call theme_enable() unnecessarily.
+   */
+  protected function handleExtensions() {}
+
 }
diff --git a/core/lib/Drupal/Core/Config/StorageComparer.php b/core/lib/Drupal/Core/Config/StorageComparer.php
index 834f439..6d16e6d 100644
--- a/core/lib/Drupal/Core/Config/StorageComparer.php
+++ b/core/lib/Drupal/Core/Config/StorageComparer.php
@@ -170,6 +170,17 @@ public function hasChanges($ops = array('delete', 'create', 'update')) {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function getChangelistKeyedByName() {
+    $flat_list = array();
+    foreach ($this->changelist as $op => $list) {
+      $flat_list += array_fill_keys($list, $op);
+    }
+    return $flat_list;
+  }
+
+  /**
    * Gets all the configuration names in the source storage.
    *
    * @return array
diff --git a/core/lib/Drupal/Core/Config/StorageComparerInterface.php b/core/lib/Drupal/Core/Config/StorageComparerInterface.php
index 5ca0f3e..fe19642 100644
--- a/core/lib/Drupal/Core/Config/StorageComparerInterface.php
+++ b/core/lib/Drupal/Core/Config/StorageComparerInterface.php
@@ -117,4 +117,12 @@ public function reset();
    */
   public function hasChanges($ops = array('delete', 'create', 'update'));
 
+  /**
+   * Flattens a changelist to an array keyed by name with a value of the operation.
+   *
+   * @return array
+   *   A list of changes keyed by name.
+   */
+  public function getChangelistKeyedByName();
+
 }
diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
index ba54de3..7a214ee 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
@@ -26,10 +26,8 @@ class ConfigImportSubscriber implements EventSubscriberInterface {
    * @throws \Drupal\Core\Config\ConfigNameException
    */
   public function onConfigImporterValidate(ConfigImporterEvent $event) {
-    foreach (array('delete', 'create', 'update') as $op) {
-      foreach ($event->getConfigImporter()->getUnprocessed($op) as $name) {
-        Config::validateName($name);
-      }
+    foreach ($event->getConfigImporter()->getUnprocessed() as $name => $op) {
+      Config::validateName($name);
     }
   }
 
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
index aa133d5..a3d8cc9 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
@@ -228,5 +228,47 @@ function testUpdated() {
     // Verify that there is nothing more to import.
     $this->assertFalse($this->configImporter->hasUnprocessedChanges());
   }
+
+  function testModuleInstallImport() {
+    // Ensure default values of system config.
+    \Drupal::config('system.module')->set('enabled', array())->save();
+    \Drupal::config('system.module.disabled')->save();
+
+    // Set up staging config data.
+    $staging = $this->container->get('config.storage.staging');
+    $data = $staging->read('system.module');
+    $data['enabled']['config_test_module_enable'] = '0';
+    $staging->write('system.module', $data);
+    $staging->write('system.module.disabled', array());
+    $staging->write('config_test_module_enable.system', array(
+      'foo' => 'bar',
+      '404' => 'derp',
+    ));
+    // Import.
+    $this->configImporter->reset()->import();
+
+    $config_enabled_modules = config('system.module')->get('enabled');
+    $this->assertTrue(isset($config_enabled_modules['config_test_module_enable']), 'Enabled module in active configuration.');
+    $this->assertTrue(db_table_exists('config_test_module_enable'), 'Schema created by module exists.');
+    $this->assertEqual(config('config_test_module_enable.system')->get('foo'), 'bar', 'Config for the module has been installed.');
+    $this->assertEqual(config('config_test_module_enable.system')->get('404'), 'derp', 'Config changed from the default supplied by module has been installed.');
+
+    // Test going straight to uninstalled. This will cause the importer to
+    // disable and then uninstall the module because the module is neither in
+    // the list of enabled modules or disabled modules.
+    $data = $staging->read('system.module');
+    unset($data['enabled']['config_test_module_enable']);
+    $staging->write('system.module', $data);
+    $staging->delete('config_test_module_enable.system');
+
+    // Import.
+    $this->configImporter->reset()->import();
+
+    $config_enabled_modules = config('system.module')->get('enabled');
+    $this->assertFalse(isset($config_enabled_modules['config_test_module_enable']), 'Uninstalled module in not active configuration.');
+    $this->assertFalse(db_table_exists('config_test_module_enable'), 'Schema uninstalled after importing.');
+    $this->assertEqual(config('config_test_module_enable.system')->get(), array(), 'Default config for the module has removed after importing.');
+  }
+
 }
 
diff --git a/core/modules/config/tests/config_test_module_enable/config/config_test_module_enable.system.yml b/core/modules/config/tests/config_test_module_enable/config/config_test_module_enable.system.yml
new file mode 100644
index 0000000..c34eddf
--- /dev/null
+++ b/core/modules/config/tests/config_test_module_enable/config/config_test_module_enable.system.yml
@@ -0,0 +1,2 @@
+foo: bar
+404: herp
diff --git a/core/modules/config/tests/config_test_module_enable/config_test_module_enable.info.yml b/core/modules/config/tests/config_test_module_enable/config_test_module_enable.info.yml
new file mode 100644
index 0000000..1e1370e
--- /dev/null
+++ b/core/modules/config/tests/config_test_module_enable/config_test_module_enable.info.yml
@@ -0,0 +1,6 @@
+name: 'Configuration import module enable'
+type: module
+package: 'Testing'
+version: VERSION
+core: 8.x
+hidden: TRUE
diff --git a/core/modules/config/tests/config_test_module_enable/config_test_module_enable.install b/core/modules/config/tests/config_test_module_enable/config_test_module_enable.install
new file mode 100644
index 0000000..fa7e88a
--- /dev/null
+++ b/core/modules/config/tests/config_test_module_enable/config_test_module_enable.install
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the config_module_enable module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function config_test_module_enable_schema() {
+  $schema['config_test_module_enable'] = array(
+    'description' => 'A table to test enabling a module through config_import',
+    'fields' => array(
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'description' => 'The name of the entity type a mapping applies to (node, user, comment, etc.).',
+      ),
+    ),
+    'primary key' => array('type'),
+  );
+
+  return $schema;
+}
diff --git a/core/modules/config/tests/config_test_module_enable/config_test_module_enable.module b/core/modules/config/tests/config_test_module_enable/config_test_module_enable.module
new file mode 100644
index 0000000..91af8ec
--- /dev/null
+++ b/core/modules/config/tests/config_test_module_enable/config_test_module_enable.module
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * Test module containing a schema and config to install during import.
+ */
