We are delighted to share our new EIC Delivery Methodology for efficiently managing Saviynt Implementations and delivering quick time to value. CLICK HERE.
No ratings
pruthvi_t
Saviynt Employee
Saviynt Employee

Use Case


This KB article outlines the process to manually link the unlinked Saviynt users with Servicenow users.

Pre-requisites


There are two option to perform user linking: 

  • Bulk option using script: The scripts retrieve all users that were updated in the last 24 hours and link them based on the mapping provided in the script. Note – it does Not invoke the business rules (or Servicenow user link properties) for correlation logic and link user based on mapping defined in the script.
  • Single user link using Servicenow UI: Using this option only one user can be linked at a time, and it makes use of correlation logic configured in Servicenow “user link properties” to link the user.

Solution


When to execute this script?

 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.

 

Script to perform User Linking in bulk

Following are the steps to execute the script to perform user linking
  1. Navigate to Saviynt IGA -> Saviynt Scheduled Job. Screenshot 2023-08-15 at 3.11.26 PM.png
  2. Select New at the top right corner.
  3. Select ‘Automatically run a script of your choosing’ from the list. Screenshot 2023-08-15 at 3.13.02 PM.png
  4. Provide the name of the job and the schedule (one time run or schedule to run periodically). Then add the below script under the ‘Run this script’. Screenshot 2023-08-15 at 3.13.57 PM.png
  5. Schedule the frequency of the job as per the requirement and save the job.

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;

 

}

Single User linking using Servicenow UI

In case you want to link single user from UI then refer to following steps

  1. Navigate to Saviynt IGA -> Users -> Select the user. Screenshot 2023-08-15 at 3.36.52 PM.png

  2. 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. Screenshot 2023-08-15 at 3.37.37 PM.png

Comments
naveenss
All-Star
All-Star

Hi @pruthvi_t  do we have any documentation around the code that is mentioned above to understand the code better?

 

BarCar
Regular Contributor
Regular Contributor

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?

BarCar
Regular Contributor
Regular Contributor

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:

  • Total count is the number of users which need linking
  • Error count is the number of users where a link was found but failed
  • Import count is the number of users where a link was established.

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);

 

pruthvi_t
Saviynt Employee
Saviynt Employee

Hi @BarCar ,

Thanks for bringing this to our notice. We will review it internally and revert on this.

Rosemary
New Contributor
New Contributor

Hello @pruthvi_t 

Why in our SNOW UI, I don't see the option 'Link to ServiceNow User’'? 

Rosemary_0-1703050867696.png

Currently, we have more than 20 users where the userID in UD SNOW are not linked with correct Saviynt ID, how to fix it?

 

Version history
Last update:
‎08/15/2023 04:00 PM
Updated by:
Contributors