Steven Young
Tera Guru

Hey Awesome Community.

I have Attached this in Word Document, so you can download it for later reading if necessary.

This is going to be a rather long, but instructive post, so i'll try to stay on topic.

 

First off, If you're like me, you're spending a lot of time on the community these days looking for answers, since the Docs are not very good or helpful.  (that's my 2 cents).

 

So There is this thing called Orchestration, that works wonders, if you can figure out how to do it.There are tons of questions on the community (which the docs do not address), so i'm going to attempt to answer many of those in this post.

This is going to be specifically for the Active Directory Orchestration features.
More Specifically to create a user from a request, and activate them in AD where they can login.

 

2nd.  Lets address the elephant in the room:  For a product we spend so much money on, why is the documentation not better?  Why do they not give real life examples?

Ok, Off We Go....

 

First thing is you have have Orchestration Enabled.  That is a license cost you must get enabled from your account Representative.

 

Here is our use case.

We have Clients that use our Active Directory to login to our system.  Those clients (who are external to us) may need to add new users in order to have access to our system.

 

Process:

I have created a Request Item  in the service catalog.
Service Catalog Item submitted,  Gets sent for approval.
                This approval will verify the user fields, verify what the user should have access.  (Close the Task)

After the task is closed, we will use Orchestration to create the AD User Account.
Then We will attemp to activate the AD User Account.
Task to our External User Team to verify user is setup correctly, can login, has all the access needed.

 

 

Beyond the scope of this post:

I created 2 virtual Machines on my computer and for my personal dev site to test this.

1 VM is a Windows Server 2012 R2     Name:  JDCISC2012    Just my Domain Controller In Service Creation 2012  a test server.
IP Address on VM: 192.168.60.132

1 VM is a Windows 7 Pro x64

On the Windows Server, i set the Server as a Domain Controller, DNS Box, and DHCP box.  Fully functional of allowing logins to the server.
on the windows 7 box i added it to the domain from the Server above.  I added a Mid Server connected to my dev site.

 

that is where this documentation starts.

So lets get to it already.

 

 Lets Discuss the "Create AD Object" Activity in the workflow.
find_real_file.png

SN Docs give you very basic example of how to create a user By adding object data:


A JSON object containing Active Directory property names and their corresponding values. For example:

{

  "givenName" : "John",

  "SN" : "Doe",

  "title" : "Sr. Account Specialist",

  "allowLogin" : true

}

  1. This example sets the first name (givenName), last name (SN), and title on the Active Directory user account and allows that user to log in (allowLogin). This field allows expression evaluation via the ${} variable substitution syntax.

 

Then see at the bottom where it says you can use the ${}  but doesn’t give you an example. 
If you used this example in your workflow activity, then every time you created an activity the first name would be john, and so on.
Plus the allowLogin doesn’t work.

SEE AD ATTRIBUTES HERE

 

So where do we go from here:

Lets go back to the  Request that I created.
Keep in mind, this is just a test form to demonstrate for you guys.
 

You can only send values to Active Directory if property already exists there.
Ex.  First Name,  Last Name, Display Name
You cannot send something like  Date of Birth, or Last 4 of SSN.

 find_real_file.png

 

 

So we have our form set,  now what?

Now lets move into the workflow.

 

First off, I did come across some examples on the community of the Object data to dynamically pull from a field.

Ex.  { "givenName" : ${workflow.inputs.first}}   that will set the “givenName value in AD the value of the first name field in your request. (based on the field name:  First Name,  field value is “first”  so that would be the  workflow.inputs. (field value)  first}

 find_real_file.png

Ex2.  {"givenName":${workflow.inputs.first},"sn":${workflow.inputs.last}}   This sets first and last name

  1. Active Directory Object always within “ ”
  2. Colon : will separate the AD object from the SN value
  3. ServiceNow value
  4. Comma , Separates the items to let the system know it’s time for the next object
  5. Active Directory Object always within “ ”
  6. Colon : will separate the AD object from the SN value
  7. ServiceNow value

 

 

Now,  full disclosure.  This way DOES WORK,  if you’re only needing to set  a few fields.
I’ve successfully created a user with 3 values, using this method.
However, as soon as I added a 4th value using this method, the workflow failed every single time.

 

So,  how did I get around this?
Creating a script allows me to pass as many variables as necessary.

I used a script and created the object in the script. And passed the object value to the activity.
So,  lets begin with the script.

 find_real_file.png

We have a very specific criteria for the UserID.  So I’m creating that via Script as well.
Ex.  Martin Luther King     + Year  + letter.
His User ID would be  mlk18a
If there is already a mlk18a   then the second one created would be mlk18b    or mlk18c   and so on.

 

SCRIPT:

generateScratchpad();

function generateScratchpad() {
	//Get First, Middle, Last name fields.  Make sure they are String Fields
	var first = current.variables.first.toString();
	var middle = current.variables.middle.toString();
	var last = current.variables.last.toString();
	
	//Get the first character from the First, Middle, Last names
	var f = first.substring(0,1);
	var m = middle.substring(0,1);
	var l = last.substring(0,1);
	
	
	//get the current glidedate time and then get the year.
	var gdt = new GlideDateTime();
	var year = gdt.getYear();
	var yearString = year.toString();
	
	//create userID  (first itial, middle initial, last initial, last 2 digits of year, letter a)
	var ID = f+m+l + yearString.substring(2,4) + "a";
	
	//set the ID tolowerCase()  to ensure they are all uniform
	var IDlow = ID.toLowerCase();
	
	
	//Set the scratchpad for all needed items.
	workflow.scratchpad.dN = current.variables.first + " " + current.variables.middle + " " + current.variables.last;  //Display Name (first, middle, last)
	workflow.scratchpad.first = current.variables.first;  //First Name
	workflow.scratchpad.middle = current.variables.middle;  //Middle Name
	workflow.scratchpad.last = current.variables.last;  // Last Name
	workflow.scratchpad.dob = current.variables.dob.getDisplayValue(); //Date of Birth  not used in the Create AD Object
	workflow.scratchpad.ssn = current.variables.ssn;   //SSN  not used in the Create AD Object
	workflow.scratchpad.address = current.variables.address;  //Street Address
	workflow.scratchpad.city = current.variables.city;  //City
	workflow.scratchpad.state = current.variables.state;  //State
	workflow.scratchpad.zip = current.variables.zip;  //Zip
	workflow.scratchpad.phone = current.variables.phone;  //phone
	workflow.scratchpad.start = current.variables.start_date.getDisplayValue();  //Users Start Date  Not used in Create AD Object
	workflow.scratchpad.email = current.variables.email.getDisplayValue();  //email Address
	workflow.scratchpad.id = IDlow;
	
	//Generate Random Password and Set AD password.
	//set AD Password activity must have "Password2" type field  which is an encrypted password.
	//Create an Encrypted password to use in Activity, and Create a plain text to send to user for login.
	var encr = new GlideEncrypter();
	var clearString = new PwdCryptoSecureAutoGenPassword().generatePassword();
	var encrString = encr.encrypt(clearString);  //Encrypted password for use in the Set AD Password Activity.
	
	workflow.scratchpad.pass = encrString;  //Set scratchpad password variable to the encrypted password string.
	var decrString = workflow.scratchpad.clearpass = encr.decrypt(encrString);  //Can be used as the clear text password
	
	
	/****************************************************************************************/
	/****************************************************************************************/
	/****************************************************************************************/
	//being to create the object for sending to the create AD Object activity
	
	//Needs to start with a {
		var object = "{ ";
			
			//set the First name in AD
			object += '"givenName" : "' + workflow.scratchpad.first + '" , ';
			
			//set the last name in AD
			object += '"sn" : "' + workflow.scratchpad.last + '" , ';
			
			//set the display name in AD
			object += '"displayName" : "' + workflow.scratchpad.dN + '" , ';
			
			//if Email is not Blank, set the Email in AD
			//email may not be mandatory, and we dont want to send anything if the email is not filled in.
			if(!current.variables.email.nil()){
				object += '"mail" : "' + workflow.scratchpad.email + '" , ';
			}
			
			//set the login id and the domain in AD.
			object += '"userPrincipalName" : "' + IDlow + '@JOKERSTEST.ORG" , ';
			
			//set the user password in AD.
			object += '"userPassword" : "' + decrString + '" , ';
			
			
			if(current.variables.address != ""){
				object += '"streetAddress" : "' + workflow.scratchpad.address + '" , ';
			}
			if(current.variables.city != ""){
				object += '"l" : "' + workflow.scratchpad.city + '" , ';
			}
			if(current.variables.state != ""){
				object += '"st" : "' + workflow.scratchpad.state + '" , ';
			}
			if(current.variables.zip != ""){
				object += '"postalCode" : "' + workflow.scratchpad.zip + '" , ';
			}
			
			if(current.variables.phone != ""){
				object += '"telephoneNumber" : "' + workflow.scratchpad.phone + '" , ';
			}
			
			
			//set the Initials field in AD
			object += '"initials" : "' + m.toUpperCase() + '"';
			
			//end the object with a }
			object += "}";
			/****************************************************************************************/
			/****************************************************************************************/
			/****************************************************************************************/
			
			//for Information purposed and possible troubleshooting, add the whole object string to the work_notes field
			current.work_notes = object;
			
			//Set the object to the scratchpad.
			workflow.scratchpad.object = object;
		}

 

 

 

Next:

find_real_file.png

Remember,  I’m generating the USER ID  in the Object Name via the script.  You don’t have to generate via script.
Then notice I’m only calling 1 item in the object data.

 find_real_file.png

Here is an example of the entire object that is created via script:

 find_real_file.png

 

If it successfully Creates the AD Object,  we get a work note that it was successful.
current.work_notes = "Successfully Created User's Acitve Directory Account";

 

If it fails to create the AD Object,  We are going to get some notes for troubleshooting.
The red box is what is returned from AD.

 

Script for the Run Script Box:

update();
function update() {
	
	var currentID = "RITM's sys_id = " +current.sys_id;
	var contextID;
	var errorMSG;
	var created;
	var context = new GlideRecord("wf_context");
	context.addQuery('id', current.sys_id);
	context.query();
	if(context.next()){
		contextID = "Workflow Context sys_id = " + context.sys_id;
		
		
		var error = new GlideRecord("wf_log");
		error.addEncodedQuery("context=" + context.sys_id);
		error.addEncodedQuery("level=2");
		error.orderByDesc('sys_created_on');
		error.query();
		if(error.next()){
			errorMSG = error.message.getDisplayValue();
			created = error.sys_created_on.getDisplayValue();
			
			
		}
	}
	
	var log = '';
	log += "Failed to Create User's Active Directory Account";
	log += "\n";
	log += currentID;
	log += "\n";
	log += contextID;
	log += "\n";
	log += errorMSG;
	log += "\n";
	log += created;
	
	
	current.work_notes = log;
}

 

 

We are only working on the successful path in this Post, so after it successfully Creates the object,  it’s not much good until it’s activated/enabled in AD, right?

Well,  this took the most time.  No matter what I tried, the Enable AD Object activity always failed.  Said that the Server was unwilling to process request.

So before we can enable the account,  you have to process the password reset request first.

 find_real_file.png

 

 

 

Keep in mind that I set the user ID on the scratchpad,  and also the password on the scratchpad.

 

The Reset AD User Password activity must have the password come in as a Password2 type field.     So where exactly is the documentation on Password2 fields?  When you find it let me know, cuz the docs suck.

So after some testing and struggling, I finally just encrypted the password in the script and sent it to the activity and it works.  It just needs an encrypted value sent to the field..

 find_real_file.png

 

 

And lastly,

The Enable AD  Account.

 find_real_file.png

 

 

So a full test,  my ticket Activity would look like this:

find_real_file.png

 

Comments
spgille
Kilo Contributor

Can you elaborate on how you are notifying the user with their credentials?  Also, I'm assuming you aren't really logging the password in the activity log?!?!

Any thoughts on how you would notifying the user without exposing the password in the ticket activity or work notes?

Steven Young
Tera Guru

So it depends on how you wish to do it.  It depends on policies you have in place, or procedures you use.
I'm not sure if every AD is setup the same way,  but in our AD, we have to set a secure password in order to use orchestration to enable the AD account.

Maybe you never use this randomly generated password, but have your service desk generate a new password.

It really depends on your organization size and customer service level.

 

 

Some Examples:

 

1.  Generate a random password in a script,  you have that password captured in a Variable.    You can then send the userID and password in an email to end user,  (assuming you capture their email address).
You could send 2 different emails.  1 that contains their userID,  and explain that they will get another email with a temporary password,  This way both the userID and password will never be in the same email.  (security issue)

2.  You can create a semi-generic password,  something like  ABC + (last 4 of ssn).    or ABC + (last 4 of phone number) ie. ABC9909.  Or ABC + (month and year of birth) ie.  ABC042018
Then you can send an email to the end user without giving them their entire password.   They will have to know the last 4 of their SSN, or phone number entered, or date of birth.

3.  You can generate the password, and store it.  send an email stating to call your service desk, and have the service desk give them the password  (Not the best option)  but sometimes policies prevent sending passwords in emails.

4.  Or during a New Employee Orientation.  Their password is printed out and entered into their new employee envelope and they get it during employee orientation.


there is no one size fits all for this question.

Steven Young
Tera Guru

In my script i did add in ONLY SUBPROD environments for testing, the password into the worknotes. so i can immediately test the new user.  (but every user created in SUBPROD is only a test user that will be deleted.)
In prod i am NOT adding to the work notes or activity log.

mev
Tera Contributor

You can do this by adding a notification activity at end of workflow that includes the username value (added to scratchpad). Not sure you want to include the password in an email, but that is also in the scratchpad as he stated above. I'm generating a password based off a naming convention that includes dob,name, and other values I'm not going to mention on this post.

You do need to know who that email should go to though.  (Submitter, requested for, etc.). Whoever that person is, you can define in the workflow activity.

Cheers. 

 

mev
Tera Contributor

Great post! I couldn't agree more with the lack of documentation for OOTB orchestration activities.  Not sure why they make learning these basic activities tribal knowledge.  Even their orchestration training and book offered no practical insight into configuring the AD activities, which i would think are the most commonly used.

I did take the password encryption script you used and implemented it successfully (Thank you!). The only issue I'm encountering now is setting OU value in and Update AD User activity. (Error i get is that AD doesn't recognize targetPath as an attribute).  (I'm not creating a user account, otherwise I would try using what you had above).

Thanks,
Patrick

Steven Young
Tera Guru

What's going on with your OU?  Can you show me what you are sending and what error you are getting?

mev
Tera Contributor

Sure.  I created a workflow input with a default value of "OU=Staff,OU=Medical Center,OU=UCSD Healthcare,DC=AD,DC=UCSD,DC=EDU"

Then I referenced this input in the 'Update AD Object" activity.

find_real_file.png

 

Then i get this failure message:

find_real_file.png

I'm not an AD person. My AD contact informed me to update the "targetPath" attribute, but according to this failure note that attribute doesn't exist.

Note: I'm able to update other AD attributes via this same activity type, so i know the issue is not with my access or MID server setup, etc...

Thoughts?

Steven Young
Tera Guru

In your Object data field.  Where you have tagetPath.    

a. if you meant target, you misspelled target.
However,  i'm not sure of anything in AD that is "targetpath"

b.  I would try organizationUnit  in place of targetpath.
{"organizationUnit":"${workflow.inputs.u_ou}"}

Steven Young
Tera Guru

Ok,   so i've been doing some research on this.

where i see you have targetPath there,  it seems to be part of the powershell move command. or part of AD's command for powershell.

 

You could custom build an activity that is just a move to AD OU object.  that way it will be very specialized.

Should not have to do that, i understand.  however, i've learned if there is a method, it may be easier and faster to build a custom activity then attempt to get something functional that no one knows.

so you could create a custom activity and use part of this as the powershell script.

https://stackoverflow.com/questions/13314800/set-aduser-with-powershells-activedirectory-module-changing-the-users-ou

 

mev
Tera Contributor

Thanks for the info above.  I can't believe i misspelled targetpath!  However, even after fixing that, it still did not work (nor did OrganizationUnit).  I have seen a few other posts about building out a custom workflow activity with powershell, was just hoping to be able to use the OOTB AD activity, but it seems like this is not going to be possible.

I'm going to try the above.  Thanks for your time and assistance!

seethamraju
Kilo Contributor

Hi Christian,

 

Hope you are doing good. Thank you for the very well and powerful documentation which servicenow dint provide externally.

 

I am running into an issue, with created AD user.

 

The Samaccountname and the name of the user are the same and I am not able to update them using orchestration ativity.

 

Did you run into this issue. if so,what you did to fix that or if you have any thought on this, that would be helpful for me.

 

Thank you,

Parimala. S. L

 

 

Steven Young
Tera Guru

Hi,  Yes,  i did run into the same issue.

the OOB "Create AD Object" is kind of awkward to say the least.  I sets the sAMAccountName  and the DisplayName the same.  I've asked myself this question over and over and over again.

find_real_file.pngfind_real_file.png

Because of this setup,

a. it requires you to adapt to it and use as is,   
b. Dont use it at all
c. Custom build your own activity with scripts.


I chose c.

We wanted our sAMAccountName to be different from the displayName.
Because of this, i had to create a whole new custom activity.
You can "checkout" a current activity

 

****Warning****
As Active Directory Structures are different,  these fields SHOULD be universal,  I can make no promised this process will work for you.
****Warning****

Create 3 new Items.
2 PowerShell Scripts and a new Custom Activity.

 

So Lets Begin:

#################### STEP 1 ####################
1.  Type Mid Server at the top left search box.  

2.  Down under the Mid Server Application, Click the Script Files Module.

3. Create a New Script File.

Name: ActiveDirectoryv2.psm1
Parent: AD
Description: Collection of functions to manage Active Directory objects
Active: true
Directory: false
Use Attachment: false

Script:  ++++++++++++++++++++++++++++++++++++++

<######################
 #    Fetch a DirectoryEntry object.  This is the base object used to manage AD objects.
 #    
 ######################>
function getDirectoryEntryObject {
	param([string]$path, [boolean]$useCred, [string]$user = "", [string]$password = "")

        if ($useCred) {
            $directoryEntry = New-Object System.DirectoryServices.DirectoryEntry $path, $user, $password;
        } else {
            $directoryEntry = New-Object System.DirectoryServices.DirectoryEntry $path;
        }

        return $directoryEntry;
}

<######################
 #    Given a name and a type, figure out the sAMAccountName.  For Computers, Microsoft documentation states the computer's sAMAccountName must end with $
 #    
 ######################>
function getSAMAccountName {
    param([string]$objectName, [string]$type)

    $sAMAccountName = $objectName;

    if ($type -eq "Computer") {
        $sAMAccountName = $objectName + "$";
    }

    return $sAMAccountName;
}

<######################
 #    Apply properties to an AD object
 #    
 ######################>
function applyProperties {
    param([System.DirectoryServices.DirectoryEntry]$object, [string]$properties)

    if (!$properties) { 
        $properties = "" ;
    }

    $objectData = ConvertFrom-StringData -StringData $properties.replace("``n", "
    ");

    foreach ($property in $objectData.Keys) { 
        $parts = $property.split(":");
        if ($parts.length -eq 2) {
            if ($parts[0] -eq "clear" -and $objectData[$property] -eq "") {
                $object.Properties[$parts[1]].Clear();
            } elseif ($parts[0] -eq "true" -and $objectData[$property] -eq "") {
                $object.Properties[$parts[1]].Value = $true;
            } elseif ($parts[0] -eq "false" -and $objectData[$property] -eq "") {
                $object.Properties[$parts[1]].Value = $false;
            } else {
                # does follow required form for clearing or boolean, so handle it literally
                $object.Properties[$property].Value=$objectData[$property] ;
            }
        } else {
            $object.Properties[$property].Value=$objectData[$property] ;
        }
    };
}

<######################
 #    Fetch an existing AD object by name
 #    
 ######################>
function getADObject {
    param([string]$domainController, [string]$type, [string]$objectName, [string]$displayName,[boolean]$useCred, [string]$user = "", [string]$password = "")

    $rootEntry = getDirectoryEntryObject -path "LDAP://$domainController" -useCred $useCred -user $user -password $password;

    $search = New-Object System.DirectoryServices.DirectorySearcher $rootEntry;
    $sAMAccountName = getSAMAccountName -objectName $objectName -type $type
    $search.Filter = "(&(objectClass=$type)(samaccountname=$sAMAccountName))";
    $result = $search.FindOne();

    if ($result -eq $null) {
        throw New-Object System.ArgumentException($search.Filter + " could not be found");
    }

    $object = $result.GetDirectoryEntry();

    if ($object -eq $null) {
        throw New-Object System.ArgumentException("The object could not be retrieved from: " + $search.Filter);
    }

    return $object;
}

<######################
 #    Create an object in Active Directory.
 #    
 ######################>
function createActiveDirectoryObject {
    param([string]$domainController, [string]$type, [string]$organizationUnit, [string]$objectName, [string]$displayName,[string]$objectProperties, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $rootEntry = getDirectoryEntryObject -path "LDAP://$domainController" -useCred $useCred -user $user -password $password;
    $parentPath = $rootEntry.Path + "/" + $organizationUnit + "," + $rootEntry.Properties["distinguishedName"];
    $parent = getDirectoryEntryObject -path $parentPath -useCred $useCred -user $user -password $password;

    if ($parent -eq $null -or $parent.Children -eq $null) {
        throw New-Object System.ArgumentException("$parentPath could not be found");
    }

    $adObject = $parent.Children.Add("CN=$displayName", $type);

    if ($adObject -eq $null) {
        throw New-Object System.ArgumentException("Unable to add new object (check AD permissions)");
    }

    $adObject.Properties["sAMAccountName"].Value = getSAMAccountName -objectName $objectName -type $type
    $adObject.Properties["name"].Value = $displayName;

    applyProperties -object $adObject -properties $objectProperties
    $adObject.CommitChanges();    
}

<######################
 #    Remove an object from Active Directory
 #    
 ######################>
function removeActiveDirectoryObject {
    param([string]$domainController, [string]$type, [string]$objectName, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $object = getADObject -domainController $domainController -type $type -objectName $objectName -useCred $useCred -user $user -password $password
    $object.DeleteTree();
}

<######################
 #    Update an object in Active Directory
 #    
 ######################>
function updateActiveDirectoryObject {
    param([string]$domainController, [string]$type, [string]$objectName, [string]$objectProperties, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $object = getADObject -domainController $domainController -type $type -objectName $objectName -useCred $useCred -user $user -password $password
    applyProperties -object $object -properties $objectProperties
    $object.CommitChanges();
}

<######################
 #    Query Active Directory for properties
 #    
 ######################>
function queryActiveDirectory {
    param([string]$domainController, [string]$searchFilter, [string]$properties, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $rootEntry = getDirectoryEntryObject -path "LDAP://$domainController" -useCred $useCred -user $user -password $password

    $search= New-Object System.DirectoryServices.DirectorySearcher $rootEntry;
    if ($properties) { 
        foreach ($property in [regex]::split($properties, ", ?")) {
            [void]$search.PropertiesToLoad.Add($property); 
        }
    }

    $search.Filter = $searchFilter;
    $searchResults = $search.FindAll();
    $json="";

    foreach ($searchResult in $searchResults) { 
        $json += "{";
        foreach ($propertyName in $searchResult.Properties.PropertyNames) {
            $json += '"' + $propertyName + '":"' + $searchResult.Properties[$propertyName] + '",'
        }
        $json += '"path":"' + $searchResult.Path + '"},'; 
    }

    if ($json.EndsWith(",")) { 
        $json=$json.substring(0, $json.length -1) 
    }

    Write-Host -NoNewline "<![CDATA[[$json]]]>";
}

<######################
 #    Reset Active Directory user password with unlock option
 #    
 ######################>
function resetActiveDirectoryUserPasswordUnlockOption {
    param([string]$domainController, [string]$username, [string]$accountPassword, [boolean]$forceChange, [boolean]$unlock, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $userObject = getADObject -domainController $domainController -type "User" -objectName $username -useCred $useCred -user $user -password $password
    $userObject.invoke("setpassword", $accountPassword);

    if ($forceChange) {
        $userObject.Properties['pwdLastSet'].Value = 0;
    }

    if ($unlock) {
        unlockAccount -domainController $domainController -username $username -useCred $useCred -user $user -password $password
    }

    $userObject.commitChanges();
}

<######################
 #    Reset Active Directory user password
 #    This version lives to work with the deprecated original version of the activity that enabled the account
 ######################>
function resetActiveDirectoryUserPassword {
    param([string]$domainController, [string]$username, [string]$accountPassword, [boolean]$forceEnable, [boolean]$forceChange, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $userObject = getADObject -domainController $domainController -type "User" -objectName $username -useCred $useCred -user $user -password $password
    $userObject.invoke("setpassword", $accountPassword);

    if ($forceEnable) {
        $userObject.Properties["lockoutTime"].Value = 0;
        $flags = [int]$userObject.Properties["userAccountControl"].Value;
        $userObject.Properties['userAccountControl'].Value = $flags -band (-bnot 2);
    }

    if ($forceChange) {
        $userObject.Properties['pwdLastSet'].Value = 0;
    }

    $userObject.commitChanges();
}


<######################
 #    Change Active Directory user password
 #    
 ######################>
function changeActiveDirectoryUserPassword {
    param([string]$domainController, [string]$username, [string]$oldPassword, [string]$newPassword, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $userObject = getADObject -domainController $domainController -type "User" -objectName $username -useCred $useCred -user $user -password $password
    $userObject.invoke("changepassword", $oldPassword, $newPassword);

    $userObject.commitChanges();
}

<######################
 #    Checks if account is locked
 #    
 ######################>
function isAccountLocked {
    param([string]$domainController, [string]$username, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $userObject = getADObject -domainController $domainController -type "User" -objectName $username -useCred $useCred -user $user -password $password
    $locked = $userObject.invokeGet("IsAccountLocked");

    return $locked
}

<######################
 #    Unlock account
 #    
 ######################>
function unlockAccount {
    param([string]$domainController, [string]$username, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $userObject = getADObject -domainController $domainController -type "User" -objectName $username -useCred $useCred -user $user -password $password
    $isLocked = isAccountLocked -domainController $domainController -username $username -useCred $useCred -user $user -password $password

    if ($isLocked) {
        $userObject.Properties["lockoutTime"].Value = 0 ;
        $userObject.commitChanges();
    }
}


<######################
 #    Enable AD user account
 #    
 ######################>
function enableADUserAccount {
    param([string]$domainController, [string]$username, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $userObject = getADObject -domainController $domainController -type "User" -objectName $username -useCred $useCred -user $user -password $password
    $userObject.Properties["lockoutTime"].Value = 0;
    $userObject.Properties['userAccountControl'].Value = 512;
    $userObject.commitChanges();
}

<######################
 #    Disable AD user account
 #    
 ######################>
function disableADUserAccount {
    param([string]$domainController, [string]$username, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $userObject = getADObject -domainController $domainController -type "User" -objectName $username -useCred $useCred -user $user -password $password
    $userObject.Properties['userAccountControl'].Value = 514;
    $userObject.commitChanges();
}

######################
 #  Add AD user account to Group
 #
 ######################>
function addADUserAccountToGroup {
    param([string]$domainController, [string]$username, [string]$groupname, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $userObject = getADObject -domainController $domainController -type "User" -objectName $username -useCred $useCred -user $user -password $password
    $groupObject = getADObject -domainController $domainController -type "Group" -objectName $groupname -useCred $useCred -user $user -password $password

    $groupObject.add("LDAP://"+$userObject.distinguishedName);
}

###################################
 #  Remove AD user account from Group
 ###################################>
function removeADUserAccountFromGroup {
    param([string]$domainController, [string]$username, [string]$groupname, [boolean]$useCred, [string]$user = "", [string]$password = "")

    $userObject = getADObject -domainController $domainController -type "User" -objectName $username -useCred $useCred -user $user -password $password
    $groupObject = getADObject -domainController $domainController -type "Group" -objectName $groupname -useCred $useCred -user $user -password $password

    $groupObject.remove("LDAP://"+$userObject.distinguishedName);
}


+++++++++++++++++++++++++++++++++++++++++++++++++  Script End

4.  Save this new Record.

#################### STEP 1 END ####################

 

#################### STEP 2 ####################

Under the Script Files,  Create a new Script

1.  Type Mid Server at the top left search box.  

2.  Down under the Mid Server Application, Click the Script Files Module.

3. Create a New Script File.

Name: CreateADObjectv2.ps1
Parent: AD
Description: Creates an object in Active Directory, calling the function in the ActiveDirectoryv2.psm1 module file
Active: true
Directory: false
Use Attachment: false

Script:  ++++++++++++++++++++++++++++++++++++++

import-module "$executingScriptDirectory\AD\ActiveDirectoryv2"

if (test-path env:\SNC_type) {
  $type=$env:SNC_type;
  $organizationUnit=$env:SNC_organizationUnit;
  $objectName=$env:SNC_objectName;
  $displayName=$env:SNC_displayName;
  $objectData=$env:SNC_objectData;
};

createActiveDirectoryObject -domainController $computer -type $type -organizationUnit $organizationUnit -objectName $objectName -displayName $displayName -objectProperties $objectData -useCred $useCred -user $user -password $password;

+++++++++++++++++++++++++++++++++++++++++++++++++  Script End

#################### STEP 2 END ####################

 

 

#################### STEP 3 ####################
From within the workflow editor

1. Click on the Create AD Object - v1  (as seen in the screenshot above)
2. Click the Checkout
find_real_file.png

3.  Add a DisplayName in the Imput

A. Click the Imput option
B. Click the "new" + icon
C. Type DisplayName,  Make sure it's a String Field, and Mandatory is true.
D. Click Continue
find_real_file.png

 

4. Update the Execution Command

A. Click the Execution Command
B. Select the Mid Server SCript file you just created.  CreateADObjectv2.ps1
C. Click the + to add a new Variable
D.   Name:  displayName        Value: ${activityInput.DisplayName}    Type:  Plain
E.  Continue

find_real_file.png

 

Then Save the new Activity.


Lastly, in the workflow, you then need to set your new activity variable.
find_real_file.png

Give that a try.  

seethamraju
Kilo Contributor

Oh, That's really a great explanation with clear details.

Before I do that, just want to check with you, can we not achieve this with OOTB activities, such as Update AD object, because my organization and even service now doesn't want customizations as they don't provide further support.

Thank you,

Parimala. S. L

 

Steven Young
Tera Guru

My understanding from doing research online is that once a sAMAccountName is set, it cannot be changed.

Also, once a Display Name is Set, it has to be manually changed, by going into AD, right Clicking the object and clicking rename.

I played with the update Object, and everytime i tried to change the display name it always failed.  gave some error and i dont remember what it is.

a few articles i read said it may be possible by writing a custom powershell script just for the display name.
and after reading that, i felt like, if i had to adjust it, why not just do it right from the beginning?

why create it incorrectly, and then have to go touch it again later to fix it?

 

I understand not wanting to create things if you dont have to.  I asked SN this question and if they could break it out and they said.  "You have the ability to create any custom activities you'd like"

 

So i did.

seethamraju
Kilo Contributor
Thank you. My only requirement is to change or update the 'Name ' attribute in the ad. Can that be achieve with update ad object?
Steven Young
Tera Guru

i dont think you can change the name field.

you can however change the first name, middle initial and last name.   and i think that changes the name.

 

There are just certain elements that once set, cannot be changed, the object would need to be deleted for it to be changed.  When i say changed,  at least not changed from outside of AD directly.

 

Keep in mind,  my experience is ServiceNow,  not AD.  So my experience in AD is just from reading articles online and playing with orchestration and seeing the failures.

seethamraju
Kilo Contributor

I have tried the custom activity and this is the error I am receiving.

 

The specified directory service attribute or value does not exist.Stack Trace: at System.DirectoryServices.DirectoryEntry.CommitChanges()at CommitChanges(Object , Object[] )at System.Management.Automation.DotNetAdapter.AuxiliaryMethodInvoke(Object target, Object[] arguments, MethodInformation methodInformation, Object[] originalArguments)

 

Can you let me know, how to fix this.

 

Thank you,

Parimala. S. L

seethamraju
Kilo Contributor

Hi Steve,

 

After I used your custom activity, the AD system is giving out an error, Object already exists,when the current samaccountname dint exist, but have same display name and name.

 

Do you think its the issue with the activity or with the AD unique keys? Can you help me with this.

 

Thank you,

Parimala. S. L

Steven Young
Tera Guru

Hey,

 

It's hard to say.  I would try with a brand new user,  create a bugs bunny or donald duck user.

If a user already exists, it will always throw an error.

This script may not work for you.  it's hard for me to say because all AD structures are different.  
I encountered that error alot during development,  i cant remember what was happening or how i fixed it.

 

Unfortunately you may just have to go through the powershell script and edit it as needed.

seethamraju
Kilo Contributor

Ok. thank you for that. Did you create home directory for AD user using Service now, because its part of our requirement, so I dint find any activity for that in Service now.

seethamraju
Kilo Contributor

Hi Steve,

 

I am back again.

 

Can you let me know, how to handle the output from the query AD activity.

 

I would like to use that output in my further activities.

 

Any help over this is appreciated.

 

Thank you,

Parimala. S.L

Steven Young
Tera Guru

es,  you can use the response in further  workflow items.

from within the workflow editor,
find_real_file.png

 

to use this in say a script formatfind_real_file.png

 

 

 

 

Steven Young
Tera Guru

We have not ventured down this road yet.  If you have figured it out, please post and let us know.  This is on our roadmap for probably the end of this year.

 

seethamraju
Kilo Contributor

Hi Steve,

Can you please provide details on how to achieve this requirement .

 

Inbound email action should update the office variable in the RITM and also in the user table during onboarding.

 

Thank you,

Parimala. S. L

Steven Young
Tera Guru

I'm not sure i follow what you're asking here, but this question does not sound like it's related to the current thread.
i wold recommend that you post this question as it's own thread to get specific help.  Taking this thread off topic will only make this one longer and more confusing.

Community Alums
Not applicable

Hi Steve,

Thanks for your wonderful post.  i got stuck in an issue . We are facing RPC server is unavailable issue.

Actually what we did is 

 

create a workflow begin to end

then added Runscript in which have added all the code which you gave for object creation 

and then passed the value in Create AD , so far good ,user is getting created as well, now we added Reset AD just

after that ,since we were not able to understand what code you put in the runscript which is attached between

create and reset AD .

 

Can you please help us as the issue is very critical.

 

 

Steven Young
Tera Guru

The specified directory service attribute or value does not exist

 

this is saying that you are trying to write to or update an AD attribute that either does not exist or you do not have permission to update it.

 

i cant help you without seeing the script you are using.

Steven Young
Tera Guru

that script is just updating successful work notes.
current.work_notes = "Successfully Created Users AD Account";

the password is being set from a previous script.

	//Generate Random Password and Set AD password.
	//set AD Password activity must have "Password2" type field  which is an encrypted password.
	//Create an Encrypted password to use in Activity, and Create a plain text to send to user for login.
	var encr = new GlideEncrypter();
	var clearString = new PwdCryptoSecureAutoGenPassword().generatePassword();
	var encrString = encr.encrypt(clearString);  //Encrypted password for use in the Set AD Password Activity.
	
	workflow.scratchpad.pass = encrString;  //Set scratchpad password variable to the encrypted password string.

 

i'm using this workflow.scratchpad.pass is what's setting the password.

 

are you getting any logs?
Errors?

Community Alums
Not applicable

steve whatever script you have written in the blog I have used that only but still when the workflow executes

it successfully creates user  but when it is going to RESET AD it is finishing but giving error

RPC service is unavailable.......(something in HX...number).  

         I am clueless as what I need to do , because when I researched I found that this error generally comes

when firewall is blocking connection b/w Midserver and Windows Server , but then how is the user getting created, even

that should have been stopped but it is not.

 

Help me if you can??

 

Note : Don't know whether this would help in getting you to any conclusion ,  but currently we are passing   

CN=Users   in OU while creating user   rather than something like CN=Joe helps DC,DC......

Steven Young
Tera Guru

It's really hard to say.  If the user is being created, then you have communication between mid and AD DC.
What i would ask is to verify that the server "name" or "Ip Address" is the same in the Set password, as it is in the create user activity.

i found myself a long time ago with a different IP address in different activities.  That is why i switched over to the scratchpad setting.  I even now have created a property to store the IP address.

 

I cannot logically think of a reason that 1 would work and 1 would fail regularly and give that error.  If you want to copy and paste the script you are using,  and take screenshots of the 2 activities, i might be able to examine a little more.

 

Also, another thought...  if you have multiple mid servers, make sure the same mid is being used for both activities.

seethamraju
Kilo Contributor

Hi Steven,

 

Hope you are doing good.

I got stuck with one activity using update AD object

Use case is : When ever the Ad user last name changes the AD attributes that needs to be updated are (Name, Display Name, Surname)

Using update AD object orchestration activity I am able to update Display Name, Surname.but not able to do Name update.

I have read through many Microsoft blogs are understood, the Name change wouldn't work as update to the AD Object, but it is a rename.

Did you write Rename customization with your project.

Any help on this is Appreciated.

 

Thank you,

Parimala. S.L

 

rsanon
Tera Contributor

Any ideas on how to set the manager field in AD? 

In the workflow, I have a "Query AD" activity that looks for the "distinguishedName" in AD. 

The databus output sends back the full path that includes the "distinguishedname", adspath, and "path". 

Example:

distinguishedname":"CN=lastname\, firstname,OU=userlocation,OU=Users,DC=company,DC=com","adspath":"LDAP://domaincontroller/CN=lastname\, firstname,OU=userlocation,OU=Users,DC=company,DC=com","path":"LDAP://domaincontroller/CN=lastname\, firstname,OU=userlocation,OU=Users,DC=company,DC=com"}]" , 

I did a run script and used the string split method to capture and set just the first part of that path that has the "distinguishedname"

Example:

distinguishedname":"CN=lastname\, firstname,OU=userlocation,OU=Users,DC=company,DC=com"

But still when I try to update the AD Object it returns an error message: The specified directory service attribute or value does not exist.

Steven Young
Tera Guru

So if you are using an LDAP import, the Active Directory CN is stored in the "Source" field on the user record.

you have to update the manager field based on the AD CN.

so for example,  you'd have to add

 

"manager":"(full CN)"

"manager":"CN=Tim Testerson\,OU=InformationServices\,OU=CORPORATE\,OU=Users\,DC=domain\,DC=org"

 

 

rsanon
Tera Contributor

Thanks! We're not using LDAP import at the moment. 

seethamraju
Kilo Contributor

Hi Steve,

 

I have used the PwdCryptoSecureAutoGenPassword() algorithm in my orchestration and we are seeing spaces in the password.

 

How can we get rid of those that are randomly generated in the password.

 

Any help on this is appreciated.

 

Thank you,

Parimala. S. L

vinothkumar
Tera Guru

Nice article

RD9
Kilo Contributor

Hi Steven, 

 

Its really a nice article and very helpful. I have one question if you could please help. 

 

I have servicenow instance which is integration with X Active Directory, but I have to orchestrate Y Active Directory with workflow. is it possible via Active Directory pack? or Powershell. And how can I store and pass the service account (user and password), which is allowed to make changes on the Y active directory. 

 

Thanks.

Michael M1
Giga Expert

I continuously get the error message: [Update AD Object activity]

"Data line 'function (from, to) {' is not in 'name=value' format."

I have tried everything including hard coded values in this format

"title":"president"

"title"="preisndet"

title=president

It doesn't like it. 

Any ideas??

Inactive_US1603
Kilo Contributor

Not totally related to the topic but just want to check can fire any ad/o365 orchestration activities from the virtual agent.

Rajesh38
Tera Contributor

I stuck at Reset AD User Password activity. Can you please share Run script logic to know how are doing  encrypt pwd (before Reset password activity) ? Thanks in Advance

 

Steven Young
Tera Guru

Here is what i'm doing.  This is generating a random character password.
then encrypting it
setting it to the scratchpad to be used in the activities.
and if you want it in cleartext (for testing or for emailing to someone)  the decrString can be used.

 

 

//Generate Random Password and Set AD password.
//set AD Password activity must have "Password2" type field which is an encrypted password.
//Create an Encrypted password to use in Activity, and Create a plain text to send to user for login.
var encr = new GlideEncrypter();
var clearString = new PwdCryptoSecureAutoGenPassword().generatePassword();
var encrString = encr.encrypt(clearString); //Encrypted password for use in the Set AD Password Activity.
workflow.scratchpad.pass = encrString; //Set scratchpad password variable to the encrypted password string.
var decrString = workflow.scratchpad.clearpass = encr.decrypt(encrString); //Can be used as the clear text password

stevebogart
Tera Expert

Thank you so much for putting this all together in one post.  I was trying to piece it all together from so many different posts.  This was extremely helpful and I was able to pull out the pieces I needed for my implementation.

Saranya VS
Giga Contributor

Thank you so much for this detailed document, its very helpful. I have successfully implemented same code. But one clarification required on 'userAccountControl', user is getting created with userAccountControl as 546. How can we make it as 514?

Community Alums
Not applicable

Thank you very much for this!!! It was really really helpful!

Version history
Last update:
‎02-19-2018 12:20 PM
Updated by: