Microsoft Active Directory v2 Spoke Action: Does User Exists (does_user_exists_v2) not working

Joe Walters
Mega Sage

Is anyone successfully using the "Does User Exists (does_user_exists_v2)" spoke action in the Microsoft Active Directory v2 Spoke?

 

It seems to work perfectly so long as the user does exist, returning an output value of true and no errors.

But if the user does not exist in AD, the script doesn't simply return an output value of false, as expected, it throws an error and bombs.

 

Ultimately, I'm trying to invoke the spoke action from a Run Server Side ATF Test step, but it fails even when using the Test button inside Flow Designer, so I think there's an issue with the behavior of the Flow Action itself.

 

Test 1: User Name DOES exist in AD

  1. From Flow Designer, open the Does User Exists spoke action and click the Test button
  2. Enter a User Name that is known to exist in AD, then click Run Test
  3. Click the "Your test has finished running. View the Action execution details." link
  4. In the Execution Details page, note the Test Run's State=Completed and the Output Data shows: Action Status = {"Action Status":{"code":0,"message":"Success"}}

Test 2: User Name DOES NOT exist in AD

  1. From Flow Designer, open the Does User Exists spoke action and click the Test button
  2. Enter a User Name that is known to NOT exist in AD (e.g. "gobbleDeeBlah"), then click Run Test
  3. Click the "Your test has finished running. View the Action execution details." link
  4. In the Execution Details page, note the Test Run's State=Completed (error skipped) and the Output Data shows:
    Action Status = {"Action Status": {"code": 1,"message": "Error: Invalid User Name : invalidUserName (Process Automation.f001d66e1b16cf90ae6aa794604bcb8e; line 35)"}}
    JoeWalters_0-1785259607228.png

     


 

1 ACCEPTED SOLUTION

Joe Walters
Mega Sage

I was able to workaround the bug in the “Does User Exists” MS AD v2 spoke action (among others) by using the "Look up Objects by Filter" spoke action instead, since it doesn't have the same bug in its code.

 

In case it helps someone else in the future, to create ATF Test Steps that checks if a user is in AD or not, here's what I did:

 

  1. Create an ATF Test
  2. Create a Run Server Side Script Test Step that:
    1. Gets the username value you want to query AD for from a previous ATF Test Step, so it can be used as an input to the spoke action
    2. Sets the LDAP Search filter
      NOTE: In my example, I'm querying sAMAccountName, but you can change this to what you need, so long as the LDAP Search filter is valid
    3. Sets the relative search base (optional)
    4. Sets the AD properties to return (also optional)
    5. Calls the MS AD v2 "Look Up Object By Filter" spoke action, supplying to it, the inputs above
    6. Gets the outputs from the execution of the spoke action
    7. Reads the records_found output variable to determine whether the expected result was achieved or not and returns true (test passes) or false (test fails) accordingly
    8. Only 'bombs' if it encounters an actual error (such as invalid credentials) and logs the error if that occurs.

Depending on whether I need to check if a user IS vs IS NOT in AD, I configure an appropriate Run Server Side Script test step in my ATF Test, like below:

 

Script to check if a user DOES exist in AD:

(function() {
    try {
        var username = steps('ca2a75a41b2a431441a15425604bcb5b').u_string_output;
        var inputs = {};
        inputs['filter'] = 'LDAPFilter'; // Choice 
        inputs['search_filter'] = '(&(objectClass=user)(sAMAccountName=' + username + '))'; // String 
        inputs['search_base'] = ''; // String 
        inputs['properties'] = ''; // String 

        // Start Asynchronously: Uncomment to run in background. Code snippet will not have access to outputs.
        // sn_fd.FlowAPI.getRunner().action('sn_ms_ad_v2_spoke.look_up_object_by_filter').inBackground().withInputs(inputs).run();

        // Execute Synchronously: Run in foreground. Code snippet has access to outputs.
        var result = sn_fd.FlowAPI.getRunner().action('sn_ms_ad_v2_spoke.look_up_object_by_filter').inForeground().withInputs(inputs).run();
        var outputs = result.getOutputs();

        // Get Outputs:
        // Note: outputs can only be retrieved when executing synchronously.
        var records_found = outputs['records_found']; // True/False
        var number_of_records = outputs['number_of_records']; // Integer

        if (records_found) {
            //Success
            //var objects = outputs['objects']; // JSON
            stepResult.setOutputMessage('user: ' + username + ' found');
            return true;
        } else {
            //Fail
            stepResult.setOutputMessage('user: ' + username + ' NOT found');
            return false;
        }

    } catch (ex) {
        var message = ex.getMessage();
        gs.error("ERROR: " + message);
    }
})();

 

Script to check if a user DOES exist in AD:

(function() {
    try {
        var username = steps('19cbbde81b2a431441a15425604bcbef').u_string_output; //Get invalid username from previous step
        var inputs = {};
        inputs['filter'] = 'LDAPFilter'; // Choice 
        inputs['search_filter'] = '(&(objectClass=user)(sAMAccountName=' + username + '))'; // String 
        inputs['search_base'] = ''; // String 
        inputs['properties'] = ''; // String 

        // Start Asynchronously: Uncomment to run in background. Code snippet will not have access to outputs.
        // sn_fd.FlowAPI.getRunner().action('sn_ms_ad_v2_spoke.look_up_object_by_filter').inBackground().withInputs(inputs).run();

        // Execute Synchronously: Run in foreground. Code snippet has access to outputs.
        var result = sn_fd.FlowAPI.getRunner().action('sn_ms_ad_v2_spoke.look_up_object_by_filter').inForeground().withInputs(inputs).run();
        var outputs = result.getOutputs();

        // Get Outputs:
        // Note: outputs can only be retrieved when executing synchronously.
        var records_found = outputs['records_found']; // True/False
        var number_of_records = outputs['number_of_records']; // Integer

        if (records_found) {
            //Fail
            stepResult.setOutputMessage('user: ' + username + ' found');
            //var objects = outputs['objects']; // JSON
        } else {
            //Success
            stepResult.setOutputMessage('user: ' + username + ' NOT found');
            return true;
        }
    } catch (ex) {
        var message = ex.getMessage();
    }
})();

 

View solution in original post

4 REPLIES 4

Abhishek Pal1
Giga Contributor

@Joe Walters 
This behavior is coming from the OOTB Microsoft Active Directory v2 spoke action.

The “Does User Exists” action is intended to check whether a user exists, but in this case the action’s Post Processing & Error Handling script treats “user not found” as an error and executes:

throw new Error(error_message);

That is why:

  • Existing AD user → action succeeds and returns true.

  • Non-existing AD user → instead of returning false, the action status becomes an error such as “Invalid User Name”.

ServiceNow documents “Does User Exists” as an action that checks whether a user account exists in AD, so logically a non-existing user should be handled as a valid negative result rather than an unexpected integration failure. (ServiceNow)

Recommended solution:

Do not modify the OOTB spoke action directly.

Create a custom Action or Subflow that calls “Does User Exists” and handles the result.

For example:

  1. Call the OOTB “Does User Exists” action.

  2. If the action completes successfully and the user exists, return:
    User Exists = true

  3. If the returned error is “Invalid User Name”, treat that as:
    User Exists = false

  4. Only throw an error for actual failures such as:

    • Authentication failure

    • MID Server unavailable

    • Connection failure

    • PowerShell/AD execution failure

If you have to modify a copied version of the action, change the error handling logic so “Invalid User Name” does not reach:

throw new Error(error_message);

Instead, handle it separately, for example:

if (error_message.indexOf('Invalid User Name') != -1) {
outputs.user_exists = false;
return;
}

throw new Error(error_message);

For your ATF use case, call this custom wrapper action instead of calling does_user_exists_v2 directly. This allows the ATF step to receive false when the AD user does not exist rather than failing the test.

Also check the installed Microsoft Active Directory v2 Spoke version before customizing. ServiceNow currently documents v2.5.2 as the latest version, so if your instance has an older version, upgrade/test the latest spoke first in a non-production instance.

Tanushree Maiti
Tera Patron

Hi @Joe Walters 

 

Could you please try once with a try-catch-block as suggested in post : https://www.servicenow.com/community/workflow-automation-forum/microsoft-ad-spoke-quot-does-group-ex...

 

 

Please Accept the solution if it assisted you with your question & Mark this response as Helpful.
Regards
Tanushree Maiti
ServiceNow Technical Architect
LinkedIn: https://www.linkedin.com/in/tanushreemaiti

Joe Walters
Mega Sage

Thanks @Abhishek Pal1  & @Tanushree Maiti 

 

Creating a subFlow that calls the Flow Action didn't work for me. The subFlow just ends up with a Status of Completed (error skipped) along with the Action. So, the net effect when I try to invoke it from a Run Server Side Test Script step in ATF is the same (it fails).

 

That said, I've confirmed I can make a copy of the out-of-box Flow Action and modify it's error-handling code to correct the problem like I think it should have been all along. After doing this, I can configure my ATF Test step to do exactly as I want, based on the output from the (custom) Flow Action's 'does_user_exists' output variable.

 

I wish I didn't have to do this, though. 

 

The following spoke actions also have this same buggy behavior (they presume the user/group/computer exists in AD and bomb if they don't):

  • Look up User
  • Look up Group
  • Look up Computer
  • Is User in Group
  • Does User Exists
  • Does Computer Exists
  • Does Group Exists

really don't want to create custom copies of these, too!

 

An alternative that I might explore is using the "Look up Objects by Filter" spoke action. After a quick test on that one, I don't see that it's failing like the others, so this might be the direction I'll head in next.

 

Thanks again for your comments,

-Joe

 

Dad quote: "I'm not mad; just disappointed."

 

Joe Walters
Mega Sage

I was able to workaround the bug in the “Does User Exists” MS AD v2 spoke action (among others) by using the "Look up Objects by Filter" spoke action instead, since it doesn't have the same bug in its code.

 

In case it helps someone else in the future, to create ATF Test Steps that checks if a user is in AD or not, here's what I did:

 

  1. Create an ATF Test
  2. Create a Run Server Side Script Test Step that:
    1. Gets the username value you want to query AD for from a previous ATF Test Step, so it can be used as an input to the spoke action
    2. Sets the LDAP Search filter
      NOTE: In my example, I'm querying sAMAccountName, but you can change this to what you need, so long as the LDAP Search filter is valid
    3. Sets the relative search base (optional)
    4. Sets the AD properties to return (also optional)
    5. Calls the MS AD v2 "Look Up Object By Filter" spoke action, supplying to it, the inputs above
    6. Gets the outputs from the execution of the spoke action
    7. Reads the records_found output variable to determine whether the expected result was achieved or not and returns true (test passes) or false (test fails) accordingly
    8. Only 'bombs' if it encounters an actual error (such as invalid credentials) and logs the error if that occurs.

Depending on whether I need to check if a user IS vs IS NOT in AD, I configure an appropriate Run Server Side Script test step in my ATF Test, like below:

 

Script to check if a user DOES exist in AD:

(function() {
    try {
        var username = steps('ca2a75a41b2a431441a15425604bcb5b').u_string_output;
        var inputs = {};
        inputs['filter'] = 'LDAPFilter'; // Choice 
        inputs['search_filter'] = '(&(objectClass=user)(sAMAccountName=' + username + '))'; // String 
        inputs['search_base'] = ''; // String 
        inputs['properties'] = ''; // String 

        // Start Asynchronously: Uncomment to run in background. Code snippet will not have access to outputs.
        // sn_fd.FlowAPI.getRunner().action('sn_ms_ad_v2_spoke.look_up_object_by_filter').inBackground().withInputs(inputs).run();

        // Execute Synchronously: Run in foreground. Code snippet has access to outputs.
        var result = sn_fd.FlowAPI.getRunner().action('sn_ms_ad_v2_spoke.look_up_object_by_filter').inForeground().withInputs(inputs).run();
        var outputs = result.getOutputs();

        // Get Outputs:
        // Note: outputs can only be retrieved when executing synchronously.
        var records_found = outputs['records_found']; // True/False
        var number_of_records = outputs['number_of_records']; // Integer

        if (records_found) {
            //Success
            //var objects = outputs['objects']; // JSON
            stepResult.setOutputMessage('user: ' + username + ' found');
            return true;
        } else {
            //Fail
            stepResult.setOutputMessage('user: ' + username + ' NOT found');
            return false;
        }

    } catch (ex) {
        var message = ex.getMessage();
        gs.error("ERROR: " + message);
    }
})();

 

Script to check if a user DOES exist in AD:

(function() {
    try {
        var username = steps('19cbbde81b2a431441a15425604bcbef').u_string_output; //Get invalid username from previous step
        var inputs = {};
        inputs['filter'] = 'LDAPFilter'; // Choice 
        inputs['search_filter'] = '(&(objectClass=user)(sAMAccountName=' + username + '))'; // String 
        inputs['search_base'] = ''; // String 
        inputs['properties'] = ''; // String 

        // Start Asynchronously: Uncomment to run in background. Code snippet will not have access to outputs.
        // sn_fd.FlowAPI.getRunner().action('sn_ms_ad_v2_spoke.look_up_object_by_filter').inBackground().withInputs(inputs).run();

        // Execute Synchronously: Run in foreground. Code snippet has access to outputs.
        var result = sn_fd.FlowAPI.getRunner().action('sn_ms_ad_v2_spoke.look_up_object_by_filter').inForeground().withInputs(inputs).run();
        var outputs = result.getOutputs();

        // Get Outputs:
        // Note: outputs can only be retrieved when executing synchronously.
        var records_found = outputs['records_found']; // True/False
        var number_of_records = outputs['number_of_records']; // Integer

        if (records_found) {
            //Fail
            stepResult.setOutputMessage('user: ' + username + ' found');
            //var objects = outputs['objects']; // JSON
        } else {
            //Success
            stepResult.setOutputMessage('user: ' + username + ' NOT found');
            return true;
        }
    } catch (ex) {
        var message = ex.getMessage();
    }
})();