diff --git a/core/includes/module.inc b/core/includes/module.inc
index 49a38db..e827d9f 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -6,7 +6,7 @@
  */
 
 /**
- * Load all the modules that have been enabled in the system table.
+ * Load all installed modules.
  *
  * @param $bootstrap
  *   Whether to load only the reduced set of modules loaded in "bootstrap mode"
@@ -20,7 +20,7 @@ function module_load_all($bootstrap = FALSE) {
   static $has_run = FALSE;
 
   if (isset($bootstrap)) {
-    $type = $bootstrap ? 'bootstrap' : 'module_enabled';
+    $type = $bootstrap ? 'bootstrap' : 'module';
     foreach (module_list($type) as $module) {
       drupal_load('module', $module);
     }
@@ -32,16 +32,17 @@ function module_load_all($bootstrap = FALSE) {
 
 
 /**
- * Returns a list of currently active modules.
+ * Returns a list of modules.
  *
  * Acts as a wrapper around system_list(), returning either a list of all
- * enabled modules, or just modules needed for bootstrap.
+ * modules, all enabled modules, or just modules needed for bootstrap.
  * An optional parameter allows an alternative module list to be provided,
  * which is then stored and used in subsequent calls instead of the one
  * provided by system_list().
  *
  * @param $type
  *   The type of list to return:
+ *   - module: All modules.
  *   - module_enabled: All enabled modules.
  *   - bootstrap: All enabled modules required for bootstrap.
  * @param $fixed_list
@@ -84,12 +85,13 @@ function module_list($type = 'module_enabled', $fixed_list = NULL) {
 }
 
 /**
- * Build a list of bootstrap modules and enabled modules and themes.
+ * Build a list of modules and themes.
  *
  * @param $type
  *   The type of list to return:
- *   - module_enabled: All enabled modules.
  *   - bootstrap: All enabled modules required for bootstrap.
+ *   - module_enabled: All enabled modules.
+ *   - module: All modules
  *   - theme: All themes.
  *
  * @return
@@ -137,20 +139,26 @@ function system_list($type) {
     else {
       $lists = array(
         'module_enabled' => array(),
+        'module' => array(),
         'theme' => array(),
         'filepaths' => array(),
       );
       // The module name (rather than the filename) is used as the fallback
       // weighting in order to guarantee consistent behavior across different
       // Drupal installations, which might have modules installed in different
-      // locations in the file system. The ordering here must also be
-      // consistent with the one used in module_implements().
-      $result = db_query("SELECT * FROM {system} WHERE type = 'theme' OR (type = 'module' AND status = 1) ORDER BY weight ASC, name ASC");
+      // locations in the file system.
+      $result = db_query("SELECT * FROM {system} WHERE type = 'theme' OR type = 'module' ORDER BY weight ASC, name ASC");
       foreach ($result as $record) {
         $record->info = unserialize($record->info);
-        // Build a list of all enabled modules.
         if ($record->type == 'module') {
-          $lists['module_enabled'][$record->name] = $record;
+          // Build a list of all enabled modules.
+          if ($record->status == 1) {
+            $lists['module_enabled'][$record->name] = $record;
+          }
+          // Build a list of installed modules, regardless of their status.
+          if ($record->schema_version > -1) {
+            $lists['module'][$record->name] = $record;
+          }
         }
         // Build a list of themes.
         if ($record->type == 'theme') {
@@ -314,11 +322,11 @@ function module_load_include($type, $module, $name = NULL) {
 }
 
 /**
- * Load an include file for each of the modules that have been enabled in
+ * Load an include file for each of the modules that is present in
  * the system table.
  */
 function module_load_all_includes($type, $name = NULL) {
-  $modules = module_list();
+  $modules = module_list('module');
   foreach ($modules as $module) {
     module_load_include($type, $module, $name);
   }
@@ -427,16 +435,17 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
       system_list_reset();
       module_implements_reset();
       _system_update_bootstrap_status();
-      // Update the registry to include it.
-      registry_update();
-      // Refresh the schema to include it.
-      drupal_get_schema(NULL, TRUE);
-
-      // Allow modules to react prior to the installation of a module.
-      module_invoke_all('modules_preinstall', array($module));
 
       // Now install the module if necessary.
       if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) {
+        // Update the registry to include it.
+        registry_update();
+        // Refresh the schema to include it.
+        drupal_get_schema(NULL, TRUE);
+
+        // Allow modules to react prior to the installation of a module.
+        module_invoke_all('modules_preinstall', array($module));
+
         drupal_install_schema($module);
 
         // Set the schema version to the number of the last update provided
@@ -549,8 +558,6 @@ function module_disable($module_list, $disable_dependents = TRUE) {
     // Invoke hook_modules_disabled before disabling modules,
     // so we can still call module hooks to get information.
     module_invoke_all('modules_disabled', $invoke_modules);
-    // Update the registry to remove the newly-disabled module.
-    registry_update();
     _system_update_bootstrap_status();
   }
 }
@@ -591,17 +598,32 @@ function module_disable($module_list, $disable_dependents = TRUE) {
  *   The name of the hook (e.g. "help" or "menu").
  *
  * @return
- *   TRUE if the module is both installed and enabled, and the hook is
- *   implemented in that module.
+ *   TRUE if the module is installed, and the hook is implemented in
+ *   that module.
  */
 function module_hook($module, $hook) {
   $function = $module . '_' . $hook;
+  $hook_info = module_hook_info();
+  // system_list() can't be used because this function might be invoked
+  // during installation.
+  $modules = module_list('module');
+  $enabled_modules = module_list('module_enabled');
+  $disabled_modules = array_diff($modules, $enabled_modules);
+  // The module is not installed, abort.
+  if (empty($modules[$module])) {
+    return FALSE;
+  }
+  // The module is disabled, and the hook is not in the list of essential hooks
+  // that must always run, abort.
+  if (in_array($module, $disabled_modules) && empty($hook_info[$hook]['essential'])) {
+    return FALSE;
+  }
+
   if (function_exists($function)) {
     return TRUE;
   }
   // If the hook implementation does not exist, check whether it may live in an
   // optional include file registered via hook_hook_info().
-  $hook_info = module_hook_info();
   if (isset($hook_info[$hook]['group'])) {
     module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']);
     if (function_exists($function)) {
@@ -647,13 +669,19 @@ function module_implements($hook) {
     $implementations['#write_cache'] = TRUE;
     $hook_info = module_hook_info();
     $implementations[$hook] = array();
-    $list = module_list();
-    foreach ($list as $module) {
+    $modules = module_list('module');
+    $enabled_modules = module_list('module_enabled');
+    $disabled_modules = array_diff($modules, $enabled_modules);
+    foreach ($modules as $module) {
       $include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']);
       // Since module_hook() may needlessly try to load the include file again,
       // function_exists() is used directly here.
       if (function_exists($module . '_' . $hook)) {
-        $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
+        // The module must be enabled, or the hook must be in the list of
+        // essential hooks.
+        if (!in_array($module, $disabled_modules) || !empty($hook_info[$hook]['essential'])) {
+          $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
+        }
       }
     }
     // Allow modules to change the weight of specific implementations but avoid
@@ -792,7 +820,11 @@ function module_invoke($module, $hook) {
 }
 
 /**
- * Invoke a hook in all enabled modules that implement it.
+ * Invoke a hook in all installed modules that implement it.
+ *
+ * In addition to being invoked in enabled modules, the hook is also invoked
+ * in disabled modules, if it's declared as "essential" in a hook_hook_info()
+ * implementation.
  *
  * @param $hook
  *   The name of the hook to invoke.
diff --git a/core/includes/registry.inc b/core/includes/registry.inc
index 7ac2960..a6949e0 100644
--- a/core/includes/registry.inc
+++ b/core/includes/registry.inc
@@ -40,11 +40,9 @@ function _registry_update() {
     // Store the module directory for use in hook_registry_files_alter().
     $module->dir = $dir;
 
-    if ($module->status) {
-      // Add files for enabled modules to the registry.
-      foreach ($module->info['files'] as $file) {
-        $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
-      }
+    // Add files to the registry.
+    foreach ($module->info['files'] as $file) {
+      $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
     }
   }
   foreach (file_scan_directory('core/includes', '/\.inc$/') as $filename => $file) {
diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module
index fe9a6e5..74bc0c5 100644
--- a/core/modules/entity/entity.module
+++ b/core/modules/entity/entity.module
@@ -19,16 +19,29 @@ function entity_help($path, $arg) {
 }
 
 /**
- * Implements hook_modules_preenable().
+ * Implements hook_hook_info().
  */
-function entity_modules_preenable() {
-  entity_info_cache_clear();
+function entity_hook_info() {
+  $essential_hooks = array(
+    'entity_info',
+    'entity_info_alter',
+    'entity_load',
+    'entity_presave',
+    'entity_insert',
+    'entity_update',
+    'entity_predelete',
+    'entity_delete',
+  );
+  $hooks = array_fill_keys($essential_hooks, array(
+    'essential' => TRUE,
+  ));
+  return $hooks;
 }
 
 /**
- * Implements hook_modules_disabled().
+ * Implements hook_modules_preenable().
  */
-function entity_modules_disabled() {
+function entity_modules_preenable() {
   entity_info_cache_clear();
 }
 
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 1dbdc61..5422fdc 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -313,6 +313,55 @@ function field_help($path, $arg) {
 }
 
 /**
+ * Implements hook_hook_info().
+ */
+function field_hook_info() {
+  $essential_hooks = array(
+    'field_attach_create_bundle',
+    'field_attach_delete',
+    'field_attach_delete_bundle',
+    'field_attach_delete_revision',
+    'field_attach_form',
+    'field_attach_insert',
+    'field_attach_load',
+    'field_attach_presave',
+    'field_attach_purge',
+    'field_attach_rename_bundle',
+    'field_attach_submit',
+    'field_attach_update',
+    'field_attach_validate',
+    'field_info',
+    'field_info_alter',
+    'field_info_max_weight',
+    'field_schema',
+    'field_storage_create_field',
+    'field_storage_delete',
+    'field_storage_delete_field',
+    'field_storage_delete_instance',
+    'field_storage_delete_revision',
+    'field_storage_details',
+    'field_storage_details_alter',
+    'field_storage_info',
+    'field_storage_info_alter',
+    'field_storage_load',
+    'field_storage_pre_insert',
+    'field_storage_pre_load',
+    'field_storage_pre_update',
+    'field_storage_purge',
+    'field_storage_purge_field',
+    'field_storage_purge_field_instance',
+    'field_storage_query',
+    'field_storage_update_field',
+    'field_storage_write',
+    'field_update_forbid',
+  );
+  $hooks = array_fill_keys($essential_hooks, array(
+    'essential' => TRUE,
+  ));
+  return $hooks;
+}
+
+/**
  * Implements hook_theme().
  */
 function field_theme() {
diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module
index b21c64c..29adf97 100644
--- a/core/modules/simpletest/simpletest.module
+++ b/core/modules/simpletest/simpletest.module
@@ -356,29 +356,6 @@ function simpletest_test_get_all() {
 }
 
 /**
- * Implements hook_registry_files_alter().
- *
- * Add the test files for disabled modules so that we get a list containing
- * all the avialable tests.
- */
-function simpletest_registry_files_alter(&$files, $modules) {
-  foreach ($modules as $module) {
-    // Only add test files for disabled modules, as enabled modules should
-    // already include any test files they provide.
-    if (!$module->status) {
-      $dir = $module->dir;
-      if (!empty($module->info['files'])) {
-        foreach ($module->info['files'] as $file) {
-          if (substr($file, -5) == '.test') {
-            $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
-          }
-        }
-      }
-    }
-  }
-}
-
-/**
  * Generate test file.
  */
 function simpletest_generate_file($filename, $width, $lines, $type = 'binary-text') {
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 00c75c7..f8a7ca5 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -14,18 +14,25 @@
  * Defines one or more hooks that are exposed by a module.
  *
  * Normally hooks do not need to be explicitly defined. However, by declaring a
- * hook explicitly, a module may define a "group" for it. Modules that implement
- * a hook may then place their implementation in either $module.module or in
- * $module.$group.inc. If the hook is located in $module.$group.inc, then that
- * file will be automatically loaded when needed.
- * In general, hooks that are rarely invoked and/or are very large should be
- * placed in a separate include file, while hooks that are very short or very
- * frequently called should be left in the main module file so that they are
- * always available.
+ * hook explicitly, a module may define additional information for it.
+ * Supported keys:
+ * - essential: By default, hooks provided by disabled modules do not get
+ *   get executed. However, certain hooks need to run even then, in order to
+ *   ensure that data integrity is maintained.
+ * - group: Modules that implement a hook may then place their implementation
+ *   in either $module.module or in $module.$group.inc.
+ *   If the hook is located in $module.$group.inc, then that
+ *   file will be automatically loaded when needed.
+ *   In general, hooks that are rarely invoked and/or are very large should be
+ *   placed in a separate include file, while hooks that are very short or very
+ *   frequently called should be left in the main module file so that they are
+ *   always available.
  *
  * @return
  *   An associative array whose keys are hook names and whose values are an
  *   associative array containing:
+ *   - essential: A boolean defining whether the hook should be invoked even
+ *     for disabled modules.
  *   - group: A string defining the group to which the hook belongs. The module
  *     system will determine whether a file with the name $module.$group.inc
  *     exists, and automatically load it when required.
@@ -35,6 +42,9 @@
  * @see hook_hook_info_alter().
  */
 function hook_hook_info() {
+  $hooks['entity_info'] = array(
+    'essential' => TRUE,
+  );
   $hooks['token_info'] = array(
     'group' => 'tokens',
   );
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index fd571ce..a023e9b 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -250,6 +250,18 @@ function system_permission() {
  * Implements hook_hook_info().
  */
 function system_hook_info() {
+  $essential_hooks = array(
+    'schema',
+    'modules_enabled',
+    'modules_disabled',
+  );
+  $hooks = array_fill_keys($essential_hooks, array(
+    'essential' => TRUE,
+  ));
+
+  $hooks['schema'] = array(
+    'essential' => TRUE,
+  );
   $hooks['token_info'] = array(
     'group' => 'tokens',
   );
