diff --git a/rules.action.inc b/rules.action.inc
new file mode 100644
index 0000000..615a442
--- /dev/null
+++ b/rules.action.inc
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Rules 2 actions are not integrated through Drupal actions, so no need
+ * for hook_action_info(). However, VBO insists on calling it for every include,
+ * so in this case it is declared but it returns an empty array.
+ */
+function views_bulk_operations_rules_action_info() {
+  return array();
+}
+
+function views_bulk_operations_rules_action($entity, $context) {
+  // If there was a config form, there's a rules_element.
+  // If not, fallback to the component key.
+  if (isset($context['rules_element'])) {
+    $element = $context['rules_element'];
+  }
+  else {
+   $element = rules_action('component_' . $context['component_key']);
+  }
+  $result = $element->execute($entity);
+}
+
+function views_bulk_operations_rules_action_form($form, &$form_state, $context) {
+  require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'rules') . "/ui/ui.forms.inc";
+
+  $element = rules_action('component_' . $context['action']['key']);
+  $element->form($form, $form_state);
+
+  // Remove the form element for the "entity" param. It will be passed in manually.
+  $entity_key = $context['action']['callback arguments']['entity_key'];
+  unset($form['parameter'][$entity_key]);
+
+  return $form;
+}
+
+function views_bulk_operations_rules_action_validate($form, $form_state) {
+  rules_ui_form_rules_config_validate($form, $form_state);
+}
+
+function views_bulk_operations_rules_action_submit($form, $form_state) {
+  return array(
+    'rules_element' => $form_state['rules_element'],
+  );
+}
diff --git a/ruleset.action.inc b/ruleset.action.inc
deleted file mode 100644
index 52b7434..0000000
--- a/ruleset.action.inc
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-function views_bulk_operations_ruleset_action_info() {
-  if (!module_exists('rules')) return array();
-  $actions = array();
-  /*
-  foreach (rules_get_configured_items('rule_sets') as $ruleset_key => $ruleset) {
-    if (count($ruleset['arguments']) == 1) { // For now, we only accept rulesets with one parameter (taken to be the 'type')
-      $arg = key($ruleset['arguments']);
-      $actions["views_bulk_operations_ruleset_action_{$ruleset_key}"] = array(
-        'type' => $ruleset['arguments'][$arg]['type'],
-        'parameters' => array('ruleset' => $ruleset_key),
-        'label' => $ruleset['label'],
-        'configurable' => FALSE,
-        'rules_ignore' => TRUE,
-      );
-      eval(<<<EOS
-if (!function_exists('views_bulk_operations_ruleset_action_{$ruleset_key}')) {
-  function views_bulk_operations_ruleset_action_{$ruleset_key}(&\$object, \$context) {
-    rules_invoke_rule_set(\$context['ruleset'], \$object);
-  }
-}
-EOS
-      );
-    }
-  }
-  */
-  return $actions;
-}
-
diff --git a/views_bulk_operations.module b/views_bulk_operations.module
index 3f73421..5956f71 100644
--- a/views_bulk_operations.module
+++ b/views_bulk_operations.module
@@ -772,7 +772,10 @@ function _views_bulk_operations_action_do($operation, $entity_id, $entity, $para
       node_save($entity);
     }
   }
-  else {
+  elseif ($operation['source'] == 'rules_action') {
+    call_user_func_array($operation['callback'], array('entity' => $entity, 'context' => $params));
+  }
+  elseif ($operation['source'] == 'operation') {
     $args = array_merge(array(array($entity_id)), $params);
     call_user_func_array($operation['callback'], $args);
   }
@@ -798,10 +801,11 @@ function _views_bulk_operations_action_aggregate_do($operation, $entities, $para
  * Helper function to verify access permission to execute operation.
  */
 function _views_bulk_operations_action_permission($operation, $account = NULL) {
+  global $user;
+
   if (module_exists('actions_permissions')) {
     $perm = actions_permissions_get_perm($operation['label'], $operation['callback']);
     if (!user_access($perm, $account)) {
-      global $user;
       watchdog('actions permissions', 'An attempt by user %user to !perm was blocked due to insufficient permissions.',
         array('!perm' => $perm, '%user' => isset($account) ? $account->name : $user->name), WATCHDOG_ALERT);
       drupal_access_denied();
@@ -810,11 +814,23 @@ function _views_bulk_operations_action_permission($operation, $account = NULL) {
   }
 
   // Check against additional permissions.
-  if (!empty($operation['permissions'])) foreach ($operation['permissions'] as $perm) {
-    if (!user_access($perm, $account)) {
-      global $user;
-      watchdog('actions permissions', 'An attempt by user %user to !perm was blocked due to insufficient permissions.',
-        array('!perm' => $perm, '%user' => isset($account) ? $account->name : $user->name), WATCHDOG_ALERT);
+  if (!empty($operation['permissions'])) {
+    foreach ($operation['permissions'] as $perm) {
+      if (!user_access($perm, $account)) {
+        watchdog('actions permissions', 'An attempt by user %user to !perm was blocked due to insufficient permissions.',
+          array('!perm' => $perm, '%user' => isset($account) ? $account->name : $user->name), WATCHDOG_ALERT);
+        drupal_access_denied();
+        drupal_exit();
+      }
+    }
+  }
+
+  // If this is a rules action, check its permissions.
+  if ($operation['source'] == 'rules_action') {
+    $component = rules_config_load($operation['key']);
+    if (!$component->access()) {
+      watchdog('actions permissions', 'An attempt by user %user to rules action !key was blocked due to insufficient permissions.',
+        array('!key' => $operation['key'], '%user' => isset($account) ? $account->name : $user->name), WATCHDOG_ALERT);
       drupal_access_denied();
       drupal_exit();
     }
diff --git a/views_bulk_operations_handler_field_operations.inc b/views_bulk_operations_handler_field_operations.inc
index f93b040..e86a392 100644
--- a/views_bulk_operations_handler_field_operations.inc
+++ b/views_bulk_operations_handler_field_operations.inc
@@ -206,7 +206,10 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
 
   function get_selected_operations() {
     $selected = array();
-    foreach (array_filter($this->options['vbo']['selected_operations']) as $key) {
+    $selected_operations = array_filter($this->options['vbo']['selected_operations']);
+    $components = $this->get_rules_components($selected_operations);
+
+    foreach ($selected_operations as $key) {
       if (!isset($this->all_operations[$key])) {
         continue;
       }
@@ -224,11 +227,31 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
           }
         }
       }
+      if ($this->all_operations[$key]['source'] == 'rules_action') {
+        $component = $components[$key];
+        if (!$component->access()) {
+          continue 2;
+        }
+      }
       $selected[$key] = $this->all_operations[$key]['label'];
     }
     return $selected;
   }
 
+  function get_rules_components($selected_operations) {
+    $components = array();
+    $component_keys = array();
+    foreach ($selected_operations as $key) {
+      if ($this->all_operations[$key]['source'] == 'rules_action') {
+        $component_keys[] = $key;
+      }
+    }
+    if ($component_keys) {
+      $components = rules_config_load_multiple($component_keys);
+    }
+    return $components;
+  }
+
   function get_operation_info($key) {
     $operation = $this->all_operations[$key];
     if ($operation['type'] == 'system') { // System actions: morph them into current entity type
@@ -279,6 +302,7 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
       }
     }
 
+    // Merge in Drupal actions, most declared by VBO itself through hook_action_info().
     $action_operations = actions_list() + $this->get_custom_actions();
     foreach ($action_operations as $callback => $operation) {
       $key = isset($operation['key']) ? $operation['key'] : $callback;
@@ -296,6 +320,34 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
       );
     }
 
+    // Merge in actions provided by Rules 2.
+    if (module_exists('rules')) {
+      $entity_info = entity_get_info();
+      $entity_types = array_keys($entity_info);
+      foreach (rules_get_components(FALSE, 'action') as $component_key => $component) {
+        $parameter_info = $component->parameterInfo();
+        $first_parameter = reset($parameter_info);
+        $entity_key = reset(array_keys($parameter_info));
+        // If the first param is not an entity type, skip the component.
+        if (!in_array($first_parameter['type'], $entity_types)) {
+          continue;
+        }
+
+        $operations[$component_key] = array(
+          'key' => $component_key,
+          'label' => $component->label,
+          'callback' => 'views_bulk_operations_rules_action',
+          'callback arguments' => array('component_key' => $component_key, 'entity_key' => $entity_key),
+          'configurable' => count($parameter_info) > 1,
+          'source' => 'rules_action',
+          'type' => $first_parameter['type'],
+          'aggregate' => VBO_AGGREGATE_FORBIDDEN,
+          'access operation' => VBO_ACCESS_OP_UPDATE, // assume edit by default.
+          'permissions' => NULL,
+        );
+      }
+    }
+
     uasort($operations, create_function('$a, $b', 'return strcasecmp($a["label"], $b["label"]);'));
     $this->all_operations = $operations;
   }
