diff --git a/includes/install.inc b/includes/install.inc
index c472e1ce199a3ed8aca7ee9ee7d24d74f5673275..164002a9cf1cfffab21414da040a55909a97c83a 100644
--- a/includes/install.inc
+++ b/includes/install.inc
@@ -506,7 +506,10 @@ function drupal_install_modules($module_list = array()) {
   } while ($moved);
   asort($module_list);
   $module_list = array_keys($module_list);
-  array_filter($module_list, '_drupal_install_module');
+  $modules_installed = array_filter($module_list, '_drupal_install_module');
+  if (!empty($modules_installed)) {
+    module_invoke_all('modules_installed', $modules_installed);
+  }
   module_enable($module_list);
 }
 
@@ -574,46 +577,52 @@ function drupal_install_system() {
   module_rebuild_cache();
 }
 
-
 /**
  * Calls the uninstall function and updates the system table for a given module.
  *
- * @param $module
- *   The machine name of the module to uninstall.
- */
-function drupal_uninstall_module($module) {
-  // First, retrieve all the module's menu paths from db.
-  drupal_load('module', $module);
-  $paths = module_invoke($module, 'menu');
-
-  // Uninstall the module(s).
-  module_load_install($module);
-  module_invoke($module, 'uninstall');
-
-  // Now remove the menu links for all paths declared by this module.
-  if (!empty($paths)) {
-    $paths = array_keys($paths);
-    // Clean out the names of load functions.
-    foreach ($paths as $index => $path) {
-      $parts = explode('/', $path, MENU_MAX_PARTS);
-      foreach ($parts as $k => $part) {
-        if (preg_match('/^%[a-z_]*$/', $part)) {
-          $parts[$k] = '%';
+ * @param $module_list
+ *   The modules to uninstall.
+ */
+function drupal_uninstall_modules($module_list = array()) {
+  foreach ($module_list as $module) {
+    // First, retrieve all the module's menu paths from db.
+    drupal_load('module', $module);
+    $paths = module_invoke($module, 'menu');
+
+    // Uninstall the module.
+    module_load_install($module);
+    module_invoke($module, 'uninstall');
+
+    // Now remove the menu links for all paths declared by this module.
+    if (!empty($paths)) {
+      $paths = array_keys($paths);
+      // Clean out the names of load functions.
+      foreach ($paths as $index => $path) {
+        $parts = explode('/', $path, MENU_MAX_PARTS);
+        foreach ($parts as $k => $part) {
+          if (preg_match('/^%[a-z_]*$/', $part)) {
+            $parts[$k] = '%';
+          }
         }
+        $paths[$index] = implode('/', $parts);
       }
-      $paths[$index] = implode('/', $parts);
-    }
-    $placeholders = implode(', ', array_fill(0, count($paths), "'%s'"));
+      $placeholders = implode(', ', array_fill(0, count($paths), "'%s'"));
 
-    $result = db_query('SELECT * FROM {menu_links} WHERE router_path IN (' . $placeholders . ') AND external = 0 ORDER BY depth DESC', $paths);
-    // Remove all such items. Starting from those with the greatest depth will
-    // minimize the amount of re-parenting done by menu_link_delete().
-    while ($item = db_fetch_array($result)) {
-      _menu_delete_item($item, TRUE);
+      $result = db_query('SELECT * FROM {menu_links} WHERE router_path IN (' . $placeholders . ') AND external = 0 ORDER BY depth DESC', $paths);
+      // Remove all such items. Starting from those with the greatest depth will
+      // minimize the amount of re-parenting done by menu_link_delete().
+      while ($item = db_fetch_array($result)) {
+        _menu_delete_item($item, TRUE);
+      }
     }
+
+    drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
   }
 
-  drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
+  if (!empty($module_list)) {
+    // Call hook_module_uninstall to let other modules act
+    module_invoke_all('modules_uninstalled', $module_list);
+  }
 }
 
 /**
diff --git a/includes/module.inc b/includes/module.inc
index 61275488843eafd5083b870915bf98e4f7a8b385..835d77db50905fccd0c04fe3a9137d387ca79eaf 100644
--- a/includes/module.inc
+++ b/includes/module.inc
@@ -297,6 +297,12 @@ function module_enable($module_list) {
       node_access_needs_rebuild(TRUE);
     }
   }
+
+  if (!empty($invoke_modules)) {
+    // Invoke the hook_module_enable after all the modules have been
+    // enabled.
+    module_invoke_all('modules_enabled', $invoke_modules);
+  }
 }
 
 /**
@@ -322,6 +328,9 @@ function module_disable($module_list) {
   }
 
   if (!empty($invoke_modules)) {
+    // Invoke hook_module_disable before disabling modules,
+    // so we can still call module hooks to get information.
+    module_invoke_all('modules_disabled', $invoke_modules);
     // Refresh the module list to exclude the disabled modules.
     module_list(TRUE, FALSE);
     // Force to regenerate the stored list of hook implementations.
diff --git a/modules/simpletest/tests/system_test.module b/modules/simpletest/tests/system_test.module
index d3db9b5870c5a20f2f8f3716dd98b412f5c39407..188facc96e2d130a2834acd1a5a7e8dd8f0ed9a1 100644
--- a/modules/simpletest/tests/system_test.module
+++ b/modules/simpletest/tests/system_test.module
@@ -84,3 +84,39 @@ function system_test_redirect_invalid_scheme() {
 function system_test_destination() {
   return 'The destination: ' . drupal_get_destination();
 }
+
+/**
+ * Implementation of hook_modules_installed().
+ */
+function system_test_modules_installed($modules) {
+  if (in_array('aggregator', $modules)) {
+    drupal_set_message(t('hook_modules_installed fired for aggregator'));
+  }
+}
+
+/**
+ * Implementation of hook_modules_enabled().
+ */
+function system_test_modules_enabled($modules) {
+  if (in_array('aggregator', $modules)) {
+    drupal_set_message(t('hook_modules_enabled fired for aggregator'));
+  }
+}
+
+/**
+ * Implementation of hook_modules_disabled().
+ */
+function system_test_modules_disabled($modules) {
+  if (in_array('aggregator', $modules)) {
+    drupal_set_message(t('hook_modules_disabled fired for aggregator'));
+  }
+}
+
+/**
+ * Implementation of hook_modules_uninstalled().
+ */
+function system_test_modules_uninstalled($modules) {
+  if (in_array('aggregator', $modules)) {
+    drupal_set_message(t('hook_modules_uninstalled fired for aggregator'));
+  }
+}
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc
index ae11a4557b79ea8776474b7ce77b7ed1ffabd3ea..307653bda0661421e9910ac018d42338d3e73e2e 100644
--- a/modules/system/system.admin.inc
+++ b/modules/system/system.admin.inc
@@ -1083,9 +1083,8 @@ function system_modules_uninstall_submit($form, &$form_state) {
 
   if (!empty($form['#confirmed'])) {
     // Call the uninstall routine for each selected module.
-    foreach (array_filter($form_state['values']['uninstall']) as $module => $value) {
-      drupal_uninstall_module($module);
-    }
+    $modules = array_keys($form_state['values']['uninstall']);
+    drupal_uninstall_modules($modules);
     drupal_set_message(t('The selected modules have been uninstalled.'));
 
     unset($form_state['storage']);
diff --git a/modules/system/system.test b/modules/system/system.test
index dbf6c0dbd65f6fdcc9df1157bd067c13e13f2f0f..8934316ac722a8d78ecc4703bcc5235e4298ec9e 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -19,7 +19,7 @@ class EnableDisableCoreTestCase extends DrupalWebTestCase {
    * Implementation of setUp().
    */
   function setUp() {
-    parent::setUp();
+    parent::setUp('system_test');
 
     $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer site configuration'));
     $this->drupalLogin($this->admin_user);
@@ -28,17 +28,22 @@ class EnableDisableCoreTestCase extends DrupalWebTestCase {
   /**
    * Enable a module, check the database for related tables, disable module,
    * check for related tables, unistall module, check for related tables.
+   * Also check for invocation of the hook_module_action hook.
    */
   function testEnableDisable() {
     // Enable aggregator, and check tables.
     $this->assertModules(array('aggregator'), FALSE);
     $this->assertTableCount('aggregator', FALSE);
 
+    // Install (and enable) aggregator module.
     $edit = array();
     $edit['modules[Core][aggregator][enable]'] = 'aggregator';
     $this->drupalPost('admin/build/modules', $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
 
+    // Check that hook_modules_installed and hook_modules_enabled hooks were invoked and check tables.
+    $this->assertText(t('hook_modules_installed fired for aggregator'), t('hook_modules_installed fired.'));
+    $this->assertText(t('hook_modules_enabled fired for aggregator'), t('hook_modules_enabled fired.'));
     $this->assertModules(array('aggregator'), TRUE);
     $this->assertTableCount('aggregator', TRUE);
 
@@ -48,9 +53,12 @@ class EnableDisableCoreTestCase extends DrupalWebTestCase {
     $this->drupalPost('admin/build/modules', $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
 
+    // Check that hook_modules_disabled hook was invoked and check tables.
+    $this->assertText(t('hook_modules_disabled fired for aggregator'), t('hook_modules_disabled fired.'));
     $this->assertModules(array('aggregator'), FALSE);
     $this->assertTableCount('aggregator', TRUE);
 
+    // Uninstall the module.
     $edit = array();
     $edit['uninstall[aggregator]'] = 'aggregator';
     $this->drupalPost('admin/build/modules/uninstall', $edit, t('Uninstall'));
@@ -58,6 +66,8 @@ class EnableDisableCoreTestCase extends DrupalWebTestCase {
     $this->drupalPost(NULL, NULL, t('Uninstall'));
     $this->assertText(t('The selected modules have been uninstalled.'), t('Modules status has been updated.'));
 
+    // Check that hook_modules_uninstalled hook was invoked and check tables.
+    $this->assertText(t('hook_modules_uninstalled fired for aggregator'), t('hook_modules_uninstalled fired.'));
     $this->assertModules(array('aggregator'), FALSE);
     $this->assertTableCount('aggregator', FALSE);
   }