- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
on 02-19-2018 12:20 PM
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.
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
}
- 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.
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}
Ex2. {"givenName":${workflow.inputs.first},"sn":${workflow.inputs.last}} This sets first and last name
- Active Directory Object always within “ ”
- Colon : will separate the AD object from the SN value
- ServiceNow value
- Comma , Separates the items to let the system know it’s time for the next object
- Active Directory Object always within “ ”
- Colon : will separate the AD object from the SN value
- 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.
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:
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.
Here is an example of the entire object that is created via script:
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.
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..
And lastly,
The Enable AD Account.
So a full test, my ticket Activity would look like this:
- 22,032 Views
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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?
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
What's going on with your OU? Can you show me what you are sending and what error you are getting?
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
Then i get this failure message:
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?
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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}"}
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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!
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
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
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
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
Then Save the new Activity.
Lastly, in the workflow, you then need to set your new activity variable.
Give that a try.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
es, you can use the response in further workflow items.
from within the workflow editor,
to use this in say a script format
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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?
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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......
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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"
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Thanks! We're not using LDAP import at the moment.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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

- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Nice article
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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??
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Not totally related to the topic but just want to check can fire any ad/o365 orchestration activities from the virtual agent.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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?
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Thank you very much for this!!! It was really really helpful!