diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
index 45e956d08c6c5d89f5c2f08c93d4e94154cefb2c..45eabb88d322b4f97812facf1c9bb075c2eca80d 100644
--- a/plugins/module_utils/ansible_freeipa_module.py
+++ b/plugins/module_utils/ansible_freeipa_module.py
@@ -45,6 +45,7 @@ else:
     import gssapi
     from datetime import datetime
     from contextlib import contextmanager
+    import inspect
 
     # ansible-freeipa requires locale to be C, IPA requires utf-8.
     os.environ["LANGUAGE"] = "C"
@@ -716,8 +717,21 @@ else:
             """
             return api_check_ipa_version(oper, requested_version)
 
-        def execute_ipa_commands(self, commands, handle_result=None,
-                                 **handle_result_user_args):
+        # pylint: disable=unused-argument
+        @staticmethod
+        def member_error_handler(module, result, command, name, args, errors):
+            # Get all errors
+            for failed_item in result.get("failed", []):
+                failed = result["failed"][failed_item]
+                for member_type in failed:
+                    for member, failure in failed[member_type]:
+                        errors.append("%s: %s %s: %s" % (
+                            command, member_type, member, failure))
+
+        def execute_ipa_commands(self, commands, result_handler=None,
+                                 exception_handler=None,
+                                 fail_on_member_errors=False,
+                                 **handlers_user_args):
             """
             Execute IPA API commands from command list.
 
@@ -727,30 +741,56 @@ else:
                 The list of commands in the form (name, command and args)
                 For commands that do not require a 'name', None needs be
                 used.
-            handle_result: function
+            result_handler: function
                 The user function to handle results of the single commands
-            handle_result_user_args: dict (user args mapping)
-                The user args to pass to handle_result function
+            exception_handler: function
+                The user function to handle exceptions of the single commands
+                Returns True to continue to next command, else False
+            fail_on_member_errors: bool
+                Use default member error handler handler member_error_handler
+            handlers_user_args: dict (user args mapping)
+                The user args to pass to result_handler and exception_handler
+                functions
 
             Example (ipauser module):
 
-            def handle_result(module, result, command, name, args, exit_args):
+            def result_handler(module, result, command, name, args, exit_args,
+                              one_name):
                 if "random" in args and command in ["user_add", "user_mod"] \
                    and "randompassword" in result["result"]:
-                    exit_args.setdefault(name, {})["randompassword"] = \
-                        result["result"]["randompassword"]
+                    if one_name:
+                        exit_args["randompassword"] = \
+                            result["result"]["randompassword"]
+                    else:
+                        exit_args.setdefault(name, {})["randompassword"] = \
+                            result["result"]["randompassword"]
+
+            def exception_handler(module, ex, exit_args, one_name):
+                if ex.exception == ipalib_errors.EmptyModlist:
+                    result = {}
+                return False
 
             exit_args = {}
-            changed = module.execute_ipa_commands(commands, handle_result,
-                                                  exit_args=exit_args)
+            changed = module.execute_ipa_commands(commands, result_handler,
+                                                  exception_handler,
+                                                  exit_args=exit_args,
+                                                  one_name=len(names)==1)
 
-            if len(names) == 1:
-                ansible_module.exit_json(changed=changed,
-                                         user=exit_args[names[0]])
-            else:
-                ansible_module.exit_json(changed=changed, user=exit_args)
+            ansible_module.exit_json(changed=changed, user=exit_args)
 
             """
+            # Fail on given handlers_user_args without result or exception
+            # handler
+            if result_handler is None and exception_handler is None and \
+               len(handlers_user_args) > 0:
+                self.fail_json(msg="Args without result and exception hander: "
+                               "%s" % repr(handlers_user_args))
+
+            # Fail on given result_handler and fail_on_member_errors
+            if result_handler is not None and fail_on_member_errors:
+                self.fail_json(
+                    msg="result_handler given and fail_on_member_errors set")
+
             # No commands, report no changes
             if commands is None:
                 return False
@@ -759,6 +799,24 @@ else:
             if self.check_mode:
                 return len(commands) > 0
 
+            # Error list for result_handler and error_handler
+            _errors = []
+
+            # Handle fail_on_member_errors, set result_handler to
+            # member_error_handler
+            # Add internal _errors for result_hendler if the module is not
+            # adding it. This also activates the final fail_json if
+            # errors are found.
+            if fail_on_member_errors:
+                result_handler = IPAAnsibleModule.member_error_handler
+                handlers_user_args["errors"] = _errors
+            elif result_handler is not None:
+                if "errors" not in handlers_user_args:
+                    # pylint: disable=deprecated-method
+                    argspec = inspect.getargspec(result_handler)
+                    if "errors" in argspec.args:
+                        handlers_user_args["errors"] = _errors
+
             changed = False
             for name, command, args in commands:
                 try:
@@ -773,13 +831,22 @@ else:
                     else:
                         changed = True
 
-                    if handle_result is not None:
-                        handle_result(self, result, command, name, args,
-                                      **handle_result_user_args)
+                    # If result_handler is not None, call it with user args
+                    # defined in **handlers_user_args
+                    if result_handler is not None:
+                        result_handler(self, result, command, name, args,
+                                       **handlers_user_args)
 
                 except Exception as e:
+                    if exception_handler is not None and \
+                       exception_handler(self, e, **handlers_user_args):
+                        continue
                     self.fail_json(msg="%s: %s: %s" % (command, name, str(e)))
 
+            # Fail on errors from result_handler and exception_handler
+            if len(_errors) > 0:
+                self.fail_json(msg=", ".join(_errors))
+
             return changed
 
     class FreeIPABaseModule(IPAAnsibleModule):
diff --git a/plugins/modules/ipaautomember.py b/plugins/modules/ipaautomember.py
index 49c8d9a28170db8360296645740c1bef4056eca6..9a93cd23c1d8b662daa79d615b010ef35fad5fc0 100644
--- a/plugins/modules/ipaautomember.py
+++ b/plugins/modules/ipaautomember.py
@@ -390,32 +390,16 @@ def main():
                     commands.append([None, 'automember_rebuild',
                                     {"hosts": rebuild_hosts}])
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
-        for name, command, args in commands:
-            try:
-                if name is None:
-                    result = ansible_module.ipa_command_no_name(command, args)
-                else:
-                    result = ansible_module.ipa_command(command, name, args)
+        # Execute commands
 
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as ex:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(ex)))
-
-            # result["failed"] is used only for INCLUDE_RE, EXCLUDE_RE
-            # if entries could not be added that are already there and
-            # it entries could not be removed that are not there.
-            # All other issues like invalid attributes etc. are handled
-            # as exceptions. Therefore the error section is not here as
-            # in other modules.
+        changed = ansible_module.execute_ipa_commands(commands)
+
+        # result["failed"] is used only for INCLUDE_RE, EXCLUDE_RE
+        # if entries could not be added that are already there and
+        # if entries could not be removed that are not there.
+        # All other issues like invalid attributes etc. are handled
+        # as exceptions. Therefore the error section is not here as
+        # in other modules.
 
     # Done
     ansible_module.exit_json(changed=changed, **exit_args)
diff --git a/plugins/modules/ipadelegation.py b/plugins/modules/ipadelegation.py
index 846e1277ecc92742c888e3379fe4085f7f38bfbf..3ebbe88c01d31458a80afb2127e1479c37842f6f 100644
--- a/plugins/modules/ipadelegation.py
+++ b/plugins/modules/ipadelegation.py
@@ -289,23 +289,9 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
+        changed = ansible_module.execute_ipa_commands(commands)
 
     # Done
 
diff --git a/plugins/modules/ipadnsrecord.py b/plugins/modules/ipadnsrecord.py
index 3950b31395fe00328e2a99f9472953824b283654..69b9212ab03f9577ed3b3bc441902dfe25b3de58 100644
--- a/plugins/modules/ipadnsrecord.py
+++ b/plugins/modules/ipadnsrecord.py
@@ -1241,7 +1241,7 @@ def create_reverse_ip_record(module, zone_name, name, ips):
                 'idnsname': to_text(reverse_host),
                 "ptrrecord": "%s.%s" % (name, zone_name)
             }
-            _cmds.append([reverse_zone, 'dnsrecord_add', rev_args])
+            _cmds.append([to_text(reverse_zone), 'dnsrecord_add', rev_args])
 
     return _cmds
 
@@ -1408,6 +1408,14 @@ def define_commands_for_absent_state(module, zone_name, entry, res_find):
     return _commands
 
 
+# pylint: disable=unused-argument
+def exception_handler(module, ex):
+    if isinstance(ex, (ipalib_errors.EmptyModlist,
+                       ipalib_errors.DuplicateEntry)):
+        return True
+    return False
+
+
 def main():
     """Execute DNS record playbook."""
     ansible_module = configure_module()
@@ -1468,30 +1476,10 @@ def main():
             if cmds:
                 commands.extend(cmds)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(
-                    command, to_text(name), args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-
-            except ipalib_errors.EmptyModlist:
-                continue
-            except ipalib_errors.DuplicateEntry:
-                continue
-            except Exception as e:
-                error_message = str(e)
 
-                ansible_module.fail_json(
-                    msg="%s: %s: %s" % (command, name, error_message))
+        changed = ansible_module.execute_ipa_commands(
+            commands, exception_handler=exception_handler)
 
     # Done
     ansible_module.exit_json(changed=changed, host=exit_args)
diff --git a/plugins/modules/ipagroup.py b/plugins/modules/ipagroup.py
index c19644c2b2df4cce147187cfb6ade5edbad06fdd..bbc8e9739957a99a36c4c72cad93f06cc942848f 100644
--- a/plugins/modules/ipagroup.py
+++ b/plugins/modules/ipagroup.py
@@ -658,34 +658,10 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
-            # Get all errors
-            # result are ignored. All others are reported.
-            errors = []
-            for failed_item in result.get("failed", []):
-                failed = result["failed"][failed_item]
-                for member_type in failed:
-                    for member, failure in failed[member_type]:
-                        errors.append("%s: %s %s: %s" % (
-                            command, member_type, member, failure))
-            if len(errors) > 0:
-                ansible_module.fail_json(msg=", ".join(errors))
+        changed = ansible_module.execute_ipa_commands(
+            commands, fail_on_member_errors=True)
 
     # Done
 
diff --git a/plugins/modules/ipahbacrule.py b/plugins/modules/ipahbacrule.py
index ea8fd73aca656484ee6479d541da16f005e9e510..1d6a3b2fe4e881b883b9e66f015734e7fefef866 100644
--- a/plugins/modules/ipahbacrule.py
+++ b/plugins/modules/ipahbacrule.py
@@ -595,35 +595,10 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        errors = []
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
-            # Get all errors
-            # result are ignored. All others are reported.
-            if "failed" in result and len(result["failed"]) > 0:
-                for item in result["failed"]:
-                    failed_item = result["failed"][item]
-                    for member_type in failed_item:
-                        for member, failure in failed_item[member_type]:
-                            errors.append("%s: %s %s: %s" % (
-                                command, member_type, member, failure))
-        if len(errors) > 0:
-            ansible_module.fail_json(msg=", ".join(errors))
+        changed = ansible_module.execute_ipa_commands(
+            commands, fail_on_member_errors=True)
 
     # Done
 
diff --git a/plugins/modules/ipahbacsvc.py b/plugins/modules/ipahbacsvc.py
index c6e31d47b7c99192e85833f9a136426e816825f8..12c8476d76b84a93d2fbbc23229e656ba0d5ff01 100644
--- a/plugins/modules/ipahbacsvc.py
+++ b/plugins/modules/ipahbacsvc.py
@@ -180,19 +180,9 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        for name, command, args in commands:
-            try:
-                ansible_module.ipa_command(command, name, args)
-                changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
+        changed = ansible_module.execute_ipa_commands(commands)
 
     # Done
 
diff --git a/plugins/modules/ipahbacsvcgroup.py b/plugins/modules/ipahbacsvcgroup.py
index 5e301d6a318b6edcaa8a930623874c971154bf21..1e6e3439b846f65933326125a5b405b28da6471d 100644
--- a/plugins/modules/ipahbacsvcgroup.py
+++ b/plugins/modules/ipahbacsvcgroup.py
@@ -136,6 +136,21 @@ def gen_member_args(hbacsvc):
     return _args
 
 
+# pylint: disable=unused-argument
+def result_handler(module, result, command, name, args, errors):
+    # Get all errors
+    # All "already a member" and "not a member" failures in the
+    # result are ignored. All others are reported.
+    if "failed" in result and "member" in result["failed"]:
+        failed = result["failed"]["member"]
+        for member_type in failed:
+            for member, failure in failed[member_type]:
+                if "already a member" not in failure \
+                   and "not a member" not in failure:
+                    errors.append("%s: %s %s: %s" % (
+                        command, member_type, member, failure))
+
+
 def main():
     ansible_module = IPAAnsibleModule(
         argument_spec=dict(
@@ -278,36 +293,9 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
-        errors = []
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
-            # Get all errors
-            # All "already a member" and "not a member" failures in the
-            # result are ignored. All others are reported.
-            if "failed" in result and "member" in result["failed"]:
-                failed = result["failed"]["member"]
-                for member_type in failed:
-                    for member, failure in failed[member_type]:
-                        if "already a member" not in failure \
-                           and "not a member" not in failure:
-                            errors.append("%s: %s %s: %s" % (
-                                command, member_type, member, failure))
-        if len(errors) > 0:
-            ansible_module.fail_json(msg=", ".join(errors))
+
+        changed = ansible_module.execute_ipa_commands(commands, result_handler)
 
     # Done
 
diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py
index 86453a775abd6203c581f8f9f16e7873a191def4..934030014a34c485b8a084e33ee1cc7dd0bee824 100644
--- a/plugins/modules/ipahost.py
+++ b/plugins/modules/ipahost.py
@@ -572,6 +572,54 @@ def check_parameters(   # pylint: disable=unused-argument
                         "'member' for state '%s'" % (x, state))
 
 
+# pylint: disable=unused-argument
+def result_handler(module, result, command, name, args, errors, exit_args,
+                   one_name):
+    if "random" in args and command in ["host_add", "host_mod"] \
+       and "randompassword" in result["result"]:
+        if one_name:
+            exit_args["randompassword"] = \
+                result["result"]["randompassword"]
+        else:
+            exit_args.setdefault(name, {})["randompassword"] = \
+                result["result"]["randompassword"]
+
+    # All "already a member" and "not a member" failures in the
+    # result are ignored. All others are reported.
+    if "failed" in result and len(result["failed"]) > 0:
+        for item in result["failed"]:
+            failed_item = result["failed"][item]
+            for member_type in failed_item:
+                for member, failure in failed_item[member_type]:
+                    if "already a member" in failure \
+                       or "not a member" in failure:
+                        continue
+                    errors.append("%s: %s %s: %s" % (
+                        command, member_type, member, failure))
+
+
+# pylint: disable=unused-argument
+def exception_handler(module, ex, errors, exit_args, one_name):
+    msg = str(ex)
+    if "already contains" in msg \
+       or "does not contain" in msg:
+        return True
+
+    #  The canonical principal name may not be removed
+    if "equal to the canonical principal name must" in msg:
+        return True
+
+    # Host is already disabled, ignore error
+    if "This entry is already disabled" in msg:
+        return True
+
+    # Ignore no modification error.
+    if "no modifications to be performed" in msg:
+        return True
+
+    return False
+
+
 def main():
     host_spec = dict(
         # present
@@ -1343,68 +1391,11 @@ def main():
 
         del host_set
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        errors = []
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-
-                if "random" in args and command in ["host_add", "host_mod"] \
-                   and "randompassword" in result["result"]:
-                    if len(names) == 1:
-                        exit_args["randompassword"] = \
-                            result["result"]["randompassword"]
-                    else:
-                        exit_args.setdefault(name, {})["randompassword"] = \
-                            result["result"]["randompassword"]
-
-            except Exception as e:
-                msg = str(e)
-                if "already contains" in msg \
-                   or "does not contain" in msg:
-                    continue
-
-                #  The canonical principal name may not be removed
-                if "equal to the canonical principal name must" in msg:
-                    continue
-
-                # Host is already disabled, ignore error
-                if "This entry is already disabled" in msg:
-                    continue
-
-                # Ignore no modification error.
-                if "no modifications to be performed" in msg:
-                    continue
-
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             msg))
-
-            # Get all errors
-            # All "already a member" and "not a member" failures in the
-            # result are ignored. All others are reported.
-            if "failed" in result and len(result["failed"]) > 0:
-                for item in result["failed"]:
-                    failed_item = result["failed"][item]
-                    for member_type in failed_item:
-                        for member, failure in failed_item[member_type]:
-                            if "already a member" in failure \
-                               or "not a member" in failure:
-                                continue
-                            errors.append("%s: %s %s: %s" % (
-                                command, member_type, member, failure))
-
-        if len(errors) > 0:
-            ansible_module.fail_json(msg=", ".join(errors))
+        changed = ansible_module.execute_ipa_commands(
+            commands, result_handler, exception_handler,
+            exit_args=exit_args, one_name=len(names) == 1)
 
     # Done
 
diff --git a/plugins/modules/ipahostgroup.py b/plugins/modules/ipahostgroup.py
index b0f7857b171a871f7d56e4347ea1760a88628e90..b2f553f730fa27e0c5cfc13ee9406f5745af63ca 100644
--- a/plugins/modules/ipahostgroup.py
+++ b/plugins/modules/ipahostgroup.py
@@ -490,34 +490,10 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
-            # Get all errors
-            # All "already a member" and "not a member" failures in the
-            # result are ignored. All others are reported.
-            errors = []
-            for failed_item in result.get("failed", []):
-                failed = result["failed"][failed_item]
-                for member_type in failed:
-                    for member, failure in failed[member_type]:
-                        errors.append("%s: %s %s: %s" % (
-                            command, member_type, member, failure))
-            if len(errors) > 0:
-                ansible_module.fail_json(msg=", ".join(errors))
+
+        changed = ansible_module.execute_ipa_commands(
+            commands, fail_on_member_errors=True)
 
     # Done
 
diff --git a/plugins/modules/ipalocation.py b/plugins/modules/ipalocation.py
index 48d5b4876f5e110cbfccae05e9c11f558165a727..7f10b94459fa0d0171eaf669fc1a2e37f6ac6d4e 100644
--- a/plugins/modules/ipalocation.py
+++ b/plugins/modules/ipalocation.py
@@ -168,23 +168,8 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
-
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
+        changed = ansible_module.execute_ipa_commands(commands)
 
     # Done
 
diff --git a/plugins/modules/ipapermission.py b/plugins/modules/ipapermission.py
index 5921675f53757fbc18134fc04164ea141a1c1a00..2dc6ab1cea590e791aec8669ddb63e6b818da73c 100644
--- a/plugins/modules/ipapermission.py
+++ b/plugins/modules/ipapermission.py
@@ -180,6 +180,22 @@ def gen_args(right, attrs, bindtype, subtree,
     return _args
 
 
+# pylint: disable=unused-argument
+def result_handler(module, result, command, name, args, errors):
+    # Get all errors
+    # All "already a member" and "not a member" failures in the
+    # result are ignored. All others are reported.
+    for failed_item in result.get("failed", []):
+        failed = result["failed"][failed_item]
+        for member_type in failed:
+            for member, failure in failed[member_type]:
+                if "already a member" in failure \
+                   or "not a member" in failure:
+                    continue
+                errors.append("%s: %s %s: %s" % (
+                    command, member_type, member, failure))
+
+
 def main():
     ansible_module = IPAAnsibleModule(
         argument_spec=dict(
@@ -425,38 +441,9 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unknown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
-            # Get all errors
-            # All "already a member" and "not a member" failures in the
-            # result are ignored. All others are reported.
-            errors = []
-            for failed_item in result.get("failed", []):
-                failed = result["failed"][failed_item]
-                for member_type in failed:
-                    for member, failure in failed[member_type]:
-                        if "already a member" in failure \
-                           or "not a member" in failure:
-                            continue
-                        errors.append("%s: %s %s: %s" % (
-                            command, member_type, member, failure))
-            if len(errors) > 0:
-                ansible_module.fail_json(msg=", ".join(errors))
+        changed = ansible_module.execute_ipa_commands(commands, result_handler)
 
     # Done
 
diff --git a/plugins/modules/ipaprivilege.py b/plugins/modules/ipaprivilege.py
index 3256eba97f3633a399cfb77c555b57b7d40df0fa..7b32468fd8af5c5ce1f271ea93d96835d07b41c0 100644
--- a/plugins/modules/ipaprivilege.py
+++ b/plugins/modules/ipaprivilege.py
@@ -126,6 +126,22 @@ def find_privilege(module, name):
         return _result["result"]
 
 
+# pylint: disable=unused-argument
+def result_handler(module, result, command, name, args, errors):
+    # Get all errors
+    # All "already a member" and "not a member" failures in the
+    # result are ignored. All others are reported.
+    for failed_item in result.get("failed", []):
+        failed = result["failed"][failed_item]
+        for member_type in failed:
+            for member, failure in failed[member_type]:
+                if "already a member" in failure \
+                   or "not a member" in failure:
+                    continue
+                errors.append("%s: %s %s: %s" % (
+                    command, member_type, member, failure))
+
+
 def main():
     ansible_module = IPAAnsibleModule(
         argument_spec=dict(
@@ -304,38 +320,9 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(
-                    msg="%s: %s: %s" % (command, name, str(e)))
-            # Get all errors
-            # All "already a member" and "not a member" failures in the
-            # result are ignored. All others are reported.
-            errors = []
-            for failed_item in result.get("failed", []):
-                failed = result["failed"][failed_item]
-                for member_type in failed:
-                    for member, failure in failed[member_type]:
-                        if "already a member" in failure \
-                           or "not a member" in failure:
-                            continue
-                        errors.append("%s: %s %s: %s" % (
-                            command, member_type, member, failure))
-            if len(errors) > 0:
-                ansible_module.fail_json(msg=", ".join(errors))
+        changed = ansible_module.execute_ipa_commands(commands, result_handler)
 
     # Done
 
diff --git a/plugins/modules/ipapwpolicy.py b/plugins/modules/ipapwpolicy.py
index 758ee6dbe8654702e48a7e357da594bfb0643d34..55bedd072d648a59666b5671f906005b6060daa5 100644
--- a/plugins/modules/ipapwpolicy.py
+++ b/plugins/modules/ipapwpolicy.py
@@ -272,19 +272,9 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        for name, command, args in commands:
-            try:
-                ansible_module.ipa_command(command, name, args)
-                changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
+        changed = ansible_module.execute_ipa_commands(commands)
 
     # Done
 
diff --git a/plugins/modules/iparole.py b/plugins/modules/iparole.py
index 4a073e6da9bdda77a75b294afe0edd2ea7502394..55b1e1e32bee742a0940c6edc56546c0167afef4 100644
--- a/plugins/modules/iparole.py
+++ b/plugins/modules/iparole.py
@@ -314,12 +314,12 @@ def ensure_members_are_present(module, name, res_find):
     return commands
 
 
-def process_command_failures(command, result):
+# pylint: disable=unused-argument
+def result_handler(module, result, command, name, args, errors):
     """Process the result of a command, looking for errors."""
     # Get all errors
     # All "already a member" and "not a member" failures in the
     # result are ignored. All others are reported.
-    errors = []
     if "failed" in result and len(result["failed"]) > 0:
         for item in result["failed"]:
             failed_item = result["failed"][item]
@@ -330,37 +330,6 @@ def process_command_failures(command, result):
                         continue
                     errors.append("%s: %s %s: %s" % (
                         command, member_type, member, failure))
-    return errors
-
-
-def process_commands(module, commands):
-    """Process the list of IPA API commands."""
-    errors = []
-    exit_args = {}
-    changed = False
-
-    # Check mode exit
-    if module.check_mode:
-        return len(commands) > 0, exit_args
-
-    for name, command, args in commands:
-        try:
-            result = module.ipa_command(command, name, args)
-            if "completed" in result:
-                if result["completed"] > 0:
-                    changed = True
-            else:
-                changed = True
-
-            errors = process_command_failures(command, result)
-        except Exception as exception:  # pylint: disable=broad-except
-            module.fail_json(
-                msg="%s: %s: %s" % (command, name, str(exception)))
-
-    if errors:
-        module.fail_json(msg=", ".join(errors))
-
-    return changed, exit_args
 
 
 def role_commands_for_name(module, state, action, name):
@@ -454,7 +423,11 @@ def main():
             cmds = role_commands_for_name(ansible_module, state, action, name)
             commands.extend(cmds)
 
-        changed, exit_args = process_commands(ansible_module, commands)
+        exit_args = {}
+
+        # Execute commands
+
+        changed = ansible_module.execute_ipa_commands(commands, result_handler)
 
     # Done
     ansible_module.exit_json(changed=changed, **exit_args)
diff --git a/plugins/modules/ipaselfservice.py b/plugins/modules/ipaselfservice.py
index 81b4461ce87ef6f627fc13defa42eef8280c68c3..53bd5b3b6a626763f183c173cc7945395decce41 100644
--- a/plugins/modules/ipaselfservice.py
+++ b/plugins/modules/ipaselfservice.py
@@ -278,17 +278,7 @@ def main():
 
         # Execute commands
 
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
+        changed = ansible_module.execute_ipa_commands(commands)
 
     # Done
 
diff --git a/plugins/modules/ipaserver.py b/plugins/modules/ipaserver.py
index c1bfe119af8dcbe382aa9b27678c3db9edf6d525..509e3ca16185f92da26018c0aa407e6cbff788bd 100644
--- a/plugins/modules/ipaserver.py
+++ b/plugins/modules/ipaserver.py
@@ -391,17 +391,7 @@ def main():
 
         # Execute commands
 
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
+        changed = ansible_module.execute_ipa_commands(commands)
 
     # Done
 
diff --git a/plugins/modules/ipaservice.py b/plugins/modules/ipaservice.py
index b72e09150d36f226121002d83ab7aed36f2e8185..29ef992bb808ccc9283b6e0e42ec3575ebf4dc7b 100644
--- a/plugins/modules/ipaservice.py
+++ b/plugins/modules/ipaservice.py
@@ -409,6 +409,23 @@ def init_ansible_module():
     return ansible_module
 
 
+# pylint: disable=unused-argument
+def result_handler(module, result, command, name, args, errors):
+    # Get all errors
+    # All "already a member" and "not a member" failures in the
+    # result are ignored. All others are reported.
+    if "failed" in result and len(result["failed"]) > 0:
+        for item in result["failed"]:
+            failed_item = result["failed"][item]
+            for member_type in failed_item:
+                for member, failure in failed_item[member_type]:
+                    if "already a member" in failure \
+                       or "not a member" in failure:
+                        continue
+                    errors.append("%s: %s %s: %s" % (
+                        command, member_type, member, failure))
+
+
 def main():
     ansible_module = init_ansible_module()
 
@@ -831,34 +848,8 @@ def main():
             ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
 
         # Execute commands
-        errors = []
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as ex:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(ex)))
-            # Get all errors
-            # All "already a member" and "not a member" failures in the
-            # result are ignored. All others are reported.
-            if "failed" in result and len(result["failed"]) > 0:
-                for item in result["failed"]:
-                    failed_item = result["failed"][item]
-                    for member_type in failed_item:
-                        for member, failure in failed_item[member_type]:
-                            if "already a member" in failure \
-                               or "not a member" in failure:
-                                continue
-                            errors.append("%s: %s %s: %s" % (
-                                command, member_type, member, failure))
-        if len(errors) > 0:
-            ansible_module.fail_json(msg=", ".join(errors))
+
+        changed = ansible_module.execute_ipa_commands(commands, result_handler)
 
     # Done
     ansible_module.exit_json(changed=changed, **exit_args)
diff --git a/plugins/modules/ipasudorule.py b/plugins/modules/ipasudorule.py
index ca60d090aa9d670e9e58f6618e74e38ff56d80c8..a149c75ceb12eb1db2d9b254e766033550a0373a 100644
--- a/plugins/modules/ipasudorule.py
+++ b/plugins/modules/ipasudorule.py
@@ -824,36 +824,10 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        errors = []
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as ex:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(ex)))
-            # Get all errors
-            # result are ignored. All others are reported.
-            if "failed" in result and len(result["failed"]) > 0:
-                for item in result["failed"]:
-                    failed_item = result["failed"][item]
-                    for member_type in failed_item:
-                        for member, failure in failed_item[member_type]:
-                            errors.append("%s: %s %s: %s" % (
-                                command, member_type, member, failure))
-        if len(errors) > 0:
-            ansible_module.fail_json(msg=", ".join(errors))
+        changed = ansible_module.execute_ipa_commands(
+            commands, fail_on_member_errors=True)
 
     # Done
 
diff --git a/plugins/modules/ipauser.py b/plugins/modules/ipauser.py
index 7e44a3f3c90f2e8452d454efd48b23f80987ff38..1ffee44806bee7ea14bb962323dfeea7812955f4 100644
--- a/plugins/modules/ipauser.py
+++ b/plugins/modules/ipauser.py
@@ -716,6 +716,46 @@ def gen_certmapdata_args(certmapdata):
     return {"ipacertmapdata": to_text(certmapdata)}
 
 
+# pylint: disable=unused-argument
+def result_handler(module, result, command, name, args, errors, exit_args,
+                   one_name):
+
+    if "random" in args and command in ["user_add", "user_mod"] \
+       and "randompassword" in result["result"]:
+        if one_name:
+            exit_args["randompassword"] = \
+                result["result"]["randompassword"]
+        else:
+            exit_args.setdefault(name, {})["randompassword"] = \
+                result["result"]["randompassword"]
+
+    # Get all errors
+    # All "already a member" and "not a member" failures in the
+    # result are ignored. All others are reported.
+    if "failed" in result and len(result["failed"]) > 0:
+        for item in result["failed"]:
+            failed_item = result["failed"][item]
+            for member_type in failed_item:
+                for member, failure in failed_item[member_type]:
+                    if "already a member" in failure \
+                       or "not a member" in failure:
+                        continue
+                    errors.append("%s: %s %s: %s" % (
+                        command, member_type, member, failure))
+
+
+# pylint: disable=unused-argument
+def exception_handler(module, ex, errors, exit_args, one_name):
+    msg = str(ex)
+    if "already contains" in msg \
+       or "does not contain" in msg:
+        return True
+    #  The canonical principal name may not be removed
+    if "equal to the canonical principal name must" in msg:
+        return True
+    return False
+
+
 def main():
     user_spec = dict(
         # present
@@ -1359,58 +1399,11 @@ def main():
 
         del user_set
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        errors = []
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-
-                if "random" in args and command in ["user_add", "user_mod"] \
-                   and "randompassword" in result["result"]:
-                    if len(names) == 1:
-                        exit_args["randompassword"] = \
-                            result["result"]["randompassword"]
-                    else:
-                        exit_args.setdefault(name, {})["randompassword"] = \
-                            result["result"]["randompassword"]
-
-            except Exception as e:
-                msg = str(e)
-                if "already contains" in msg \
-                   or "does not contain" in msg:
-                    continue
-                #  The canonical principal name may not be removed
-                if "equal to the canonical principal name must" in msg:
-                    continue
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             msg))
-
-            # Get all errors
-            # All "already a member" and "not a member" failures in the
-            # result are ignored. All others are reported.
-            if "failed" in result and len(result["failed"]) > 0:
-                for item in result["failed"]:
-                    failed_item = result["failed"][item]
-                    for member_type in failed_item:
-                        for member, failure in failed_item[member_type]:
-                            if "already a member" in failure \
-                               or "not a member" in failure:
-                                continue
-                            errors.append("%s: %s %s: %s" % (
-                                command, member_type, member, failure))
-
-        if len(errors) > 0:
-            ansible_module.fail_json(msg=", ".join(errors))
+        changed = ansible_module.execute_ipa_commands(
+            commands, result_handler, exception_handler,
+            exit_args=exit_args, one_name=len(names) == 1)
 
     # Done
     ansible_module.exit_json(changed=changed, user=exit_args)
diff --git a/utils/templates/ipamodule+member.py.in b/utils/templates/ipamodule+member.py.in
index c3e37727b5a189ee6d30b78e3d38f8a86f09c16d..7e7ad8ea18b1bc90cc1d49e0d3834881870364a2 100644
--- a/utils/templates/ipamodule+member.py.in
+++ b/utils/templates/ipamodule+member.py.in
@@ -286,38 +286,34 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
 
         # Execute commands
 
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
-            # Get all errors
-            # All "already a member" and "not a member" failures in the
-            # result are ignored. All others are reported.
-            errors = []
-            for failed_item in result.get("failed", []):
-                failed = result["failed"][failed_item]
-                for member_type in failed:
-                    for member, failure in failed[member_type]:
-                        if "already a member" in failure \
-                           or "not a member" in failure:
-                            continue
-                        errors.append("%s: %s %s: %s" % (
-                            command, member_type, member, failure))
-            if len(errors) > 0:
-                ansible_module.fail_json(msg=", ".join(errors))
+        #
+        # To handle default member errors there is a static method
+        # IPAAnsibleModule.handle_member_errors. It can be enabled with
+        # fail_on_member_failures=True for execute_ipa_commands.
+        # There might be cases in which this needs to be either done
+        # manually or extended.
+        #
+        # Example:
+        #
+        # pylint: disable=unused-argument
+        # def result_handler(module, result, command, name, args, errors):
+        #     # Get all errors
+        #     IPAAnsibleModule.handle_member_errors(module, result, command,
+        #                                           name, args, errors)
+        #     if "MY ERROR" in result.get("failed",[]):
+        #         errors.append("My error")
+        #
+        # # Execute commands
+        #
+        # changed = ansible_module.execute_ipa_commands(commands,
+        #                                               result_handler)
+        #
+
+        changed = ansible_module.execute_ipa_commands(
+            commands, fail_on_member_failures=True)
 
     # Done
 
diff --git a/utils/templates/ipamodule.py.in b/utils/templates/ipamodule.py.in
index f7d1e538190d0ba3c46f80c80102a864a2020c97..b179a0b8f66ac8fb78a5cbd5a814b90b3e91c757 100644
--- a/utils/templates/ipamodule.py.in
+++ b/utils/templates/ipamodule.py.in
@@ -194,23 +194,9 @@ def main():
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
-        # Check mode exit
-        if ansible_module.check_mode:
-            ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
-
         # Execute commands
 
-        for name, command, args in commands:
-            try:
-                result = ansible_module.ipa_command(command, name, args)
-                if "completed" in result:
-                    if result["completed"] > 0:
-                        changed = True
-                else:
-                    changed = True
-            except Exception as e:
-                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
+        changed = ansible_module.execute_ipa_commands(commands)
 
     # Done