on 08/21/2023 02:10 PM
This KB article outlines the process to manually link the unlinked Saviynt users with Servicenow users.
There are two option to perform user linking:
By default, Saviynt SNOWapp user import process triggers business rules for linking the user as part of import process. However, in certain cases when users are imported by other means such as csv upload Or the linking didn’t worked because of data issue, there can be unlinked users present in servicenow.
The unlinked users can not submit any request in SNOWapp, so they must be linked as defined in this document.
Note: This script is customized according to the requirement to use SNOW Employee ID for correlation mappings. In case the corelation logic is different, then update the script accordingly.
Also, this script retrieves all users updated in the last 24 hours and link them. In case there are users which were last updated more than 24 hours ago, then either update the script to increase the duration to a value more than 24 hours Or use the UI option to link single user as defined in section “Single User linking using Servicenow UI”
// Runs the user linking process for all disconnected users
//
var userHelper = new x_saviy_iga.UserHelper();
var jobLogHelper = new x_saviy_iga.JobLogHelper();
var Saviynt = new x_saviy_iga.Saviynt();
var jobName = "LINK_SAVIYNT_TO_SN_USERS";
var jobLogID = jobLogHelper.createJobLog(jobName, "USER_LINKING", "INTERNAL_LINKING", "INTERNAL", false);
jobLogHelper.setJobRunning(jobLogID);
var svntUserField = Saviynt.getProperty(Saviynt.PROPERTY_USER_LINK_USERNAME, "username");
var svntEmailField = Saviynt.getProperty(Saviynt.PROPERTY_USER_LINK_EMAIL, "email");
var svntField = Saviynt.getProperty(Saviynt.PROPERTY_USER_LINK_SVNT_CORRELATION_FIELD, null);
var totalRecords = 0;
var totalImported = 0;
var rankUsername = parseInt(Saviynt.getProperty(Saviynt.PROPERTY_USER_LINK_RANK_USERNAME, 1));
var rankEmail = parseInt(Saviynt.getProperty(Saviynt.PROPERTY_USER_LINK_RANK_EMAIL, 2));
var rankField = parseInt(Saviynt.getProperty(Saviynt.PROPERTY_USER_LINK_CORRELATION_RANK, 0));
var snField = Saviynt.getProperty(Saviynt.PROPERTY_USER_LINK_SN_CORRELATION_FIELD, null);
var savUsers = new GlideRecord("x_saviy_iga_user");
/*
if (this.disableUserLinkingActive == true) {
jobLogHelper.log(jobLogID, "DisableUserLinking activated: Limiting linking to new Users only");
savUsers.addEncodedQuery("active=true^disable_user_linking=false^userISEMPTY");
} else {
jobLogHelper.log(jobLogID, "Linking all Saviynt users not already linked");
savUsers.addEncodedQuery("active=true^userISEMPTY");
}
*/
var dateToday = new GlideDate();
//savUsers.addEncodedQuery("last_updatedRELATIVELT@hour@ago@24^last_detected_dateRELATIVEGT@hour@ahead@24");
savUsers.addEncodedQuery("last_updatedRELATIVELT@hour@ahead@24^last_updatedRELATIVEGT@hour@ago@24");
jobLogHelper.log(jobLogID, "Linking all Saviynt users updated in last 24 hours and also 24 hours after");
savUsers.query();
var saviyntUserMap = {};
saviyntUserMap['username'] = 'saviynt_user';
saviyntUserMap['email'] = 'email';
saviyntUserMap['systemUserName'] = 'system_user_name';
//saviyntUserMap['employeeid'] = 'employee_id';
saviyntUserMap['SNOWEMPLOYEEID'] = 'employee_id';
while(savUsers.next()){
var svntUserDataFromMap = saviyntUserMap[svntUserField];
var svntEmailDataFromMap = saviyntUserMap[svntEmailField];
var svntCustomDataFromMap = saviyntUserMap[svntField];
var svntUserData = savUsers.getValue(svntUserDataFromMap);
var svntEmailData = savUsers.getValue(svntEmailDataFromMap);
var svntCustomData = savUsers.getValue(svntCustomDataFromMap);
var svntID = savUsers.getValue("saviynt_user");
var userLinked = linkRemoteUsers(svntUserData, svntEmailData, svntCustomData, svntID,savUsers.active,jobLogHelper,jobLogID,rankUsername,rankEmail,rankField,snField);
totalRecords++;
jobLogHelper.setTotalRecords(jobLogID, totalRecords);
if (userLinked) {
totalImported++;
jobLogHelper.setTotalImported(jobLogID, totalImported);
}
}
jobLogHelper.log(jobLogID, "Job Completed - Total Linked: " + totalImported);
jobLogHelper.setJobComplete(jobLogID);
function linkRemoteUsers(saviyntUsername, saviyntEmail, saviyntCustom, saviyntID, saviyntActive,jobLogHelper,jobLogID,rankUsername,rankEmail,rankField,snField) {
//
// Links the remote users with the saviynt user base.
//
var searchObj = [{
rank: rankUsername,
field: "user_name",
qry: "user_name=",
data: saviyntUsername
},
{
rank: rankEmail,
field: "email",
qry: "email=",
data: saviyntEmail
},
{
rank: rankField,
field: snField,
qry: snField + "=",
data: saviyntCustom
}
];
searchObj.sort(function (a, b) {
return a.rank - b.rank;
});
var userLinked = false;
searchObj.forEach(function (item) {
// proceed with linking if the rank is not 0, a query exists, data exists, and a Saviynt ID exists
if (item.rank !== 0 && item.qry && item.data && saviyntID) {
var sysUserRecord = new GlideRecord("sys_user");
// verify we are working with a valid field
if (sysUserRecord.isValidField(item.field)) {
//sysUserRecord.addNullQuery("x_saviy_iga_saviynt_id");
sysUserRecord.addEncodedQuery(item.qry + item.data);
sysUserRecord.query();
if (sysUserRecord.next()) {
sysUserRecord.setValue("x_saviy_iga_saviynt_id", saviyntID); // populate sys_user field with Saviynt ID
sysUserRecord.setValue("x_saviy_iga_saviynt_user_active", saviyntActive);
if (sysUserRecord.update()) {
userLinked = true;
}
}
} else {
jobLogHelper.log(jobLogID, "The ServiceNow Correlation field " + item.field + " is not a valid field");
}
}
});
var savUser = new GlideRecord(Saviynt.SAVIYNT_USER_TABLE);
savUser.addQuery("saviynt_user", saviyntID);
savUser.query();
if (savUser.next()) {
var userMatchRecord = new GlideRecord("sys_user");
userMatchRecord.addQuery("x_saviy_iga_saviynt_id", saviyntID);
userMatchRecord.query();
if (userMatchRecord.next()) {
savUser.user = userMatchRecord.getValue("sys_id");
}
savUser.disable_user_linking = true;
if (!savUser.update()) {
jobLogHelper.log(jobLogID,"Unable to update Saviynt User with Sys User reference" + saviyntID);
}
}
if (userLinked == false) {
jobLogHelper.log(jobLogID,"Unable to link Saviynt User ID " + saviyntID);
}
return userLinked;
}
In case you want to link single user from UI then refer to following steps
Navigate to Saviynt IGA -> Users -> Select the user.
Click on the ‘Link to ServiceNow User’
System will then evaluate the user link properties and will link the user accordingly. If the linking is successful, the user will appear in the ‘Sys users with Saviynt ID’ list.
Hi @pruthvi_t do we have any documentation around the code that is mentioned above to understand the code better?
This script contains errors - there are a few calls to jobLogHelper functions which don't exist.
Also, the linkRemoteUsers function is already defined in the UserHelper script include. Does it need to be duplicated in the scheduled job script?
I had issues getting this script to work and found the hard-coded linking properties confusing. So I reworked it to consume the link properties from the Savinyt User Link Properties.
I also fixed the record counters which were exponentially incrementing and added a lot of comments to make it easier to understand what is going on.
Regarding the record counters:
As posted, the script does not count users where a link was not found as errors but you could easily change this by uncommenting line 126.
I have scheduled this to run regularly in our instance and it is working well. The only other change you might need is the savEncodedQuery on line 169 which determines the Saviynt user objects which should be linked - we link on employee id.
// Runs the user linking process for all disconnected users who have an employee_id
// Uses the savEncodedQuery on line 169 to find Saviynt users who are eligible for linking
// Link properties and rank are taken from the Saviynt application settings
// Based on logic at https://forums.saviynt.com/t5/saviynt-knowledge-base/script-to-manually-link-saviynt-user-with-snow-users/ta-p/48873
function linkRemoteUsers(saviyntUsername, saviyntEmail, saviyntCustom, saviyntID, saviyntActive, jobLogHelper, jobLogID) {
//
// Links the remote users with the saviynt user base.
//
// Start of linking from ServiceNow user to Saviynt user
// Build the searchObject and sort by rank
var searchObj = [{
rank: rankUsername,
field: "user_name",
qry: snUsername + "=",
data: saviyntUsername
},
{
rank: rankEmail,
field: "email",
qry: snEmail + "=",
data: saviyntEmail
},
{
rank: rankField,
field: savField,
qry: snField + "=",
data: saviyntCustom
}
];
searchObj.sort(function(a, b) {
return a.rank - b.rank;
});
// start off with the Saviynt user being unlinked
var userLinked = false;
// Loop through the searches by rank
searchObj.forEach(function(item) {
// proceed with linking if the rank is not 0, a query exists, data exists, and a Saviynt ID exists
if (item.rank !== 0 && item.qry && item.data && saviyntID) {
// search against ServiceNow sys_user table
var sysUserRecord = new GlideRecord("sys_user");
// verify we are working with a valid search field
if (sysUserRecord.isValidField(item.field)) {
// build the query and execute
sysUserRecord.addEncodedQuery(item.qry + item.data);
sysUserRecord.query();
// check if we got a match from sys_user
if (sysUserRecord.next()) {
// if we did, update the sys_user object with the Saviynt ID
sysUserRecord.setValue("x_saviy_iga_saviynt_id", saviyntID);
sysUserRecord.setValue("x_saviy_iga_saviynt_user_active", saviyntActive);
// if we are able to save the update, mark the user as linked
if (sysUserRecord.update()) {
userLinked = true;
}
}
} else {
// Log an error
jobLogHelper.log(jobLogID, "The ServiceNow Correlation field " + item.field + " is not a valid field");
jobLogHelper.updateTotalErrorsCount(jobLogID, 1);
}
}
});
// End of linking from ServiceNow user to Saviynt user
//////////////////////////////////////////////////////
// Start of linking from Savinyt user to ServiceNow user
// look for this Saviynt ID in the Saviynt user table
var savUser = new GlideRecord(Saviynt.SAVIYNT_USER_TABLE);
savUser.addQuery("saviynt_user", saviyntID);
savUser.query();
// if we found a matching Saviynt user
if (savUser.next()) {
// Look for a matching ServiceNow sys_user record (using SaviyntID)
var userMatchRecord = new GlideRecord("sys_user");
userMatchRecord.addQuery("x_saviy_iga_saviynt_id", saviyntID);
userMatchRecord.query();
// If we found a matching ServiceNow user
if (userMatchRecord.next()) {
// Update the Saviynt user to link to the ServiceNow user
savUser.user = userMatchRecord.getValue("sys_id");
}
savUser.disable_user_linking = true;
// check if we are able to save the update to the Saviynt User
if (!savUser.update()) {
// Log an error
jobLogHelper.updateTotalErrorsCount(jobLogID, 1);
jobLogHelper.log(jobLogID, "Unable to update Saviynt User with Sys User reference" + saviyntID);
}
}
// End of linking from Savinyt user to ServiceNow user
//////////////////////////////////////////////////////
// Log if we failed to link the Saviynt user
if (userLinked == false) {
// jobLogHelper.updateTotalErrorsCount(jobLogID, 1);
jobLogHelper.log(jobLogID, "Unable to link Saviynt User ID " + saviyntID);
}
// Return the result of the match attempt
return userLinked;
}
// End of linkRemoteUsers function
////////////////////////////////////////////////////////////////////////////////////////
// Start of main script code
// Extract linking properties from Saviynt configuration
// Query for users available for linking, loop through and attempt link for each
var userHelper = new x_saviy_iga.UserHelper();
var jobLogHelper = new x_saviy_iga.JobLogHelper();
var Saviynt = new x_saviy_iga.Saviynt();
// Create a Saviynt job for logging
var jobName = "LINK_SAVIYNT_TO_SN_USERS";
var jobLogID = jobLogHelper.createJobLog(jobName, "USER_LINKING", "INTERNAL_LINKING", "INTERNAL", false);
jobLogHelper.setJobRunning(jobLogID);
var totalImported = 0;
jobLogHelper.updateTotalInsertsCount(jobLogID, 0);
// Retrieve mapping and rank for Username
var rankUsername = parseInt(this.Saviynt.getProperty(this.Saviynt.PROPERTY_USER_LINK_RANK_USERNAME, 1));
var snUsername = this.Saviynt.getProperty(this.Saviynt.PROPERTY_USER_LINK_USERNAME, "username");
// Retrieve mapping and rank for Email
var rankEmail = parseInt(this.Saviynt.getProperty(this.Saviynt.PROPERTY_USER_LINK_RANK_EMAIL, 2));
var snEmail = this.Saviynt.getProperty(this.Saviynt.PROPERTY_USER_LINK_EMAIL, "email");
// Retrieve mapping and rank for custom Correlation Property
var rankField = parseInt(this.Saviynt.getProperty(this.Saviynt.PROPERTY_USER_LINK_CORRELATION_RANK, 0));
var snField = this.Saviynt.getProperty(this.Saviynt.PROPERTY_USER_LINK_SN_CORRELATION_FIELD, null);
var savField = this.Saviynt.getProperty(this.Saviynt.PROPERTY_USER_LINK_SVNT_CORRELATION_FIELD, null);
// Query for unlinked Saviynt Users
var savUsers = new GlideRecord("x_saviy_iga_user");
var savEncodedQuery = "active=true^user=NULL^employee_id!=NULL";
savUsers.addEncodedQuery(savEncodedQuery);
jobLogHelper.log(jobLogID, "Linking all Saviynt users with query " + savEncodedQuery);
savUsers.query();
// Loop through all the unlinked Saviynt Users
while (savUsers.next()) {
// Add 1 to the Total Record Count
jobLogHelper.updateTotalRecordCount(jobLogID, 1);
// Call the function to attempt linking for the Savinyt user
var userLinked = linkRemoteUsers(
savUsers.getValue('saviynt_user'),
savUsers.getValue('email'),
savUsers.getValue(savField),
savUsers.getValue('saviynt_user'),
savUsers.getValue('active'),
jobLogHelper,
jobLogID
);
// If the user was linked
if (userLinked) {
// add 1 to the Imported Record Count
totalImported++;
jobLogHelper.updateTotalInsertsCount(jobLogID, 1);
}
}
// Close the job log
jobLogHelper.log(jobLogID, "Job Completed - Total Linked: " + totalImported);
jobLogHelper.setJobComplete(jobLogID);
Hi @BarCar ,
Thanks for bringing this to our notice. We will review it internally and revert on this.