Dynamically Populate NERM Attributes with PowerShell

Problem

The business would like to automate provisioning of access in Identity Security Cloud for our non-employee user population that we manage with Non-Employee Risk Management (NERM). The current state is that we have a dropdown attribute item in NERM that has three static values listed indicating the type of non-employee which the Sponsor or Collaborator can pick from when adding a new record:

  1. 000AFF – Affiliate
  2. 000SUB – Subcontractor
  3. 000VOL – Volunteer

Because these are the only three options currently available, any role we would build would apply to everyone in that grouping. We have a wide range of access that we would need to automate without over-provisioning. When NERM was originally set up, these three options made sense, and that’s why the attribute was configured in this way. Additionally, when we first set up NERM, automation of access provisioning was out of scope, and we had no reason at the time to go any deeper than these three options. But with the new requirement to 100% automate the provisioning of access for these users, this design was not scalable. While we could just add all the options as static values, this list is not very dynamic and would require change requests submitted every time someone needed a new item added.

Business Requirements

The solution must be scalable and require minimum admin manual input. When the Sponsor or Collaborator clicks the drop down it should be a dynamic list of current options for them to pick from. All existing records need to be updated with the new attribute values without having the Sponsor or Collaborator manually updating the records. All new records will require that this data be filled in.

Design Overview

Based on the requirements the following design was built out. A new profile type was added for department. Subpopulation was already a profile type. The new department profile needed to be added to existing forms for assignments. A new attribute was created to house the value of the new department profile. A new workflow was created in order to build out the new departments with permissions set so that only admins can use this workflow.

With the original design already in production some extra considerations needed to be accounted for. With over 5000 assignments in Prod it was unrealistic to have the Sponsors and Collaborators manually update each user with this new attribute.

For scripting the workflow we call the profiles API and target the assignment profile type. This pulls back all the information on an assignment. Then we push this information into a csv file. A team member will take this file of assignments and break it out by orgs and send to the Sponsors and Collaborators, asking to have the department and subpopulations updated from a list we provide them.

Once they return the file, it is then reviewed to ensure that the values that were updated are correct. Once the values are validated, the file is then fed into another script that will build a PATCH payload and upload this into NERM via API.

Script Code and Workflow

Build the Project

In Visual Studio follow these steps:

  1. Click File
  2. Click New
  3. Click Project
  4. Search and Select Class Library
  5. Be sure to target .Net 8.0
  6. Name your new project
  7. Click Create

Required Packages:

  1. CsvHelper
  2. RestSharp
  3. PowerShellStandard.Library

Directories

You will need to create the following folders for this project:

  • Project
    • Private
      • Controllers
        • Department
        • ExportCsv
        • ImportCsv
        • NERMProfiles
        • SubPopulation
        • UpdateProfiles
      • Models
        • MetaData
        • Profiles
        • Reports
    • Public
      • Tests
        • Departments
        • Profiles
        • Reports
        • SubPopulation
        • UpdateProfile
      • Views

Generating CSV Export of Existing Assignments

Profile Classes (Data Models):

File Name: /Private/Models/Profiles/Profile.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Models.Profiles
{
    public class Profile
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("id")]
        public string? Id { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("uid")]
        public string? Uid { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("name")]
        public string? Name { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("profile_type_id")]
        public string? ProfileTypeId { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("status")]
        public string? Status { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("id_proofing_status")]
        public string? IdProofingStatus { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("updated_at")]
        public string? UpdatedAt { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("created_at")]
        public string? CreatedAt { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("attributes")]
        public ProfileAttribtues? Attributes { get; set; }
    }
}

File Name: /Private/Models/Profiles/ProfileAttributes.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Models.Profiles
{
    public class ProfileAttribtues
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("assignment_facility_id_ne_attribute")]
        public string? AssignmentFacilityIdNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("assignment_location_id_ne_attribute")]
        public string? AssignmentLocationIdNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("assignment_organization_name_ne_attribute")]
        public string? AssignmentOrganizationNameNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("assignment_population")]
        public string? AssignmentPopulation { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("assignment_sub_population")]
        public string? AssignmentSubPopulation { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("assignment_subpopulation_name")]
        public string? AssignmentSubpopulationName { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("department_ne_attribute")]
        public string? DepartmentNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("new_department_ne_attribute")]
        public string? NewDepartmentNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("engagement_end_date")]
        public string? EngagementEndDate { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("engagement_start_date")]
        public string? EngagementStartDate { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("manager_ne_attribute")]
        public string? ManagerNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("organization_for_assignment_ne_attribute")]
        public string? OrganizationForAssignmentNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("personal_email")]
        public string? PersonalEmail { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("personal_first_name")]
        public string? PersonalFirstName { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("personal_last_name")]
        public string? PersonalLastName { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("sailpoint_username_assignment_ne_attribute")]
        public string? SailpointUsernameAssignmentNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("previous_non-employee_ne_attribute")]
        public string? PreviousNonEmployeeNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("department_name_ne_attribute")]
        public string? DepartmentNameNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("department_population_ne_attribute")]
        public string? DepartmentPopulationNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("meta_profile_creation_date")]
        public string? MetaProfileCreationDate {  get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("license_non_employee_ne_attribute")]
        public string? LicenseNonEmployeeNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("license_state_of_issuance")]
        public string? LicenseStateOfIssuance { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("state_license_expiration_ne_attribute")]
        public string? StateLicenseExpirationNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("state_license_ne_attribute")]
        public string? StateLicenseNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("add_license_to_worker_profile_ne_attribute")]
        public string? AddLicenseToWorkerProfileNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("birth_month_ne_attribute")]
        public string? BirthMonthNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("birth_year_ne_attribute")]
        public string? BirthYearNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("day_of_birth_ne_attribute")]
        public string? DayOfBirthNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("last_4_ssn_ne_attribute")]
        public string? Last4SsnNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("new_professional_phone_number_ne_attribute")]
        public string? NewPerfrssionalPhoneNumberNeAttribtue { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("npi_ne_attribute")]
        public string? NpiNeAttribtue { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("personal_country")]
        public string? PersonalCountry { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("state_license_number_ne_attribute")]
        public string? StateLicenseNumberNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("new_personal_phone_number_ne_attribute")]
        public string? NewPersonalPhoneNumberNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("personal_middle_name")]
        public string? PersonalMiddleName {  get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("preferred_name_ne_attribute")]
        public string? PreferredNameNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("professional_email")]
        public string? ProfessionalEmail { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("suffix_ne_attribute")]
        public string? SuffixNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("non-employee_license_ne_attribute")]
        public string?  NonEployeeLicenseNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("professional_active_engagements")]
        public string? ProfessionalActiveEngagements {  get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("facility_id_ne_attribute")]
        public string? FacilityIdNeAttibute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("organization_name_ne_attribute")]
        public string? OrganizationNameNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("personal_city")]
        public string? PersonalCity {  get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("personal_street")]
        public string? PersonalStreet { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("personal_zip")]
        public string? PersonalZip { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("personal_state_or_province")]
        public string? PersonalStateOrProvince { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("organization_population_ne_attribute")]
        public string? OrganizationPropulationNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("ssm_sponsor_con_ne_attribute")]
        public string? SSMSponsorConNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("population_name_ne_attribute")]
        public string? PopulationNameNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("organization_ne_attribute")]
        public string? OrganizationNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("population_sub_population_ne_attribute")]
        public string? PopulationSubPopulationNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("population_department_population_ne_attribute")]
        public string? PopulationDepartmentPopulationNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("location_code_ne_attribute")]
        public string? LocationCodeNeAttribtue { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("location_id_ne_attribute")]
        public string? LocationIdNeAttribtue { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("home_zip_ne_attribute")]
        public string HomeZipNeArrtibute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("home_city_ne_attribute")]
        public string HomeCityNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("invitation_send_date_ne_attribute")]
        public string? InvitationSendDateNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("home_street_address_ne_attribute")]
        public string? HomeStreeAddressNeAttribute { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("profile_id_ne_attribute")]
        public string? ProfileIdNeAttribute { get; set; }
    }
}

File Name: /Private/Models/Profiles/Profiles.cs

using Community_Connect_Automation.Private.Models.MetaData;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Models.Profiles
{
    public class Profiles
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("profiles")]
        public List<Profile>? Profile { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("_metadata")]
        public ProfileMetaData? Metadata { get; set; }
    }
}

File Name: /Private/Models/Profiles/UpdatePayload.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Models.Profiles
{
    public class UpdatePayloadModel
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("profile")]
        public Profile AssignmentProfile { get; set; }
    }
}

/Private/Models/MetaData/ProfileMetaData.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Models.MetaData
{
    public class ProfileMetaData
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
        [JsonPropertyName("limit")]
        public int Limit { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
        [JsonPropertyName("total")]
        public int Total { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("next")]
        public string? Next {  get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
        [JsonPropertyName("offset")]
        public int OffSet { get; set; }
    }
}

File Name: /Private/Models/Reports/AssignmentReport.cs

using CsvHelper.Configuration.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Models.Reports
{
    public class AssignmentReport
    {
        
        [Name("Id")]
        public string? Id { get; set; }
        
        [Name("Uid")]
        public string? Uid { get; set; }
        
        [Name("Name")]
        public string? Name { get; set; }
        
        [Name("ProfileTypeId")]
        public string? ProfileTypeId { get; set; }
        
        [Name("Status")]
        public string? Status { get; set; }
        
        [Name("IdProffingStatus")]
        public string? IdProofingStatus { get; set; }
        
        [Name("UpdatedAt")]
        public string? UpdatedAt { get; set; }
        
        [Name("CreatedAt")]
        public string? CreatedAt { get; set; }
        
        [Name("AssignmentFacility")]
        public string? AssignmentFacilityIdNeAttribute { get; set; }
        
        [Name("AssignmentLocation")]
        public string? AssignmentLocationIdNeAttribute { get; set; }
        
        [Name("AssignmentOrganization")]
        public string? AssignmentOrganizationNameNeAttribute { get; set; }
        
        [Name("AssignmentPopulation")]
        public string? AssignmentPopulation { get; set; }
        
        [Name("AssignmentTitle")]
        public string? AssignmentSubPopulation { get; set; }
        
        [Name("AssignmentTitleName")]
        public string? AssignmentSubpopulationName { get; set; }
        
        [Name("Department")]
        public string? DepartmentNeAttribute { get; set; }
        
        [Name("EndDate")]
        public string? EngagementEndDate { get; set; }
        
        [Name("StartDate")]
        public string? EngagementStartDate { get; set; }
        
        [Name("Manager")]
        public string? ManagerNeAttribute { get; set; }
        
        [Name("Organization")]
        public string? OrganizationForAssignmentNeAttribute { get; set; }
        
        [Name("Email")]
        public string? PersonalEmail { get; set; }
        
        [Name("FirstName")]
        public string? PersonalFirstName { get; set; }
        
        [Name("LastName")]
        public string? PersonalLastName { get; set; }
        
        [Name("NetworkId")]
        public string? SailpointUsernameAssignmentNeAttribute { get; set; }
        
        [Name("PreviousNonEmployee")]
        public string? PreviousNonEmployeeNeAttribute { get; set; }
    }
}

Methods:

File Name: /Private/Controllers/NERMProfiles/GetAllActiveAssignments.cs

using Community_Connect_Automation.Private.Controllers.Profiles;
using Community_Connect_Automation.Private.Models.Profiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.NERMProfiles
{
    public class GetAllActiveAssignments
    {
        public static List<RestResponse> GetActviceAssginments(string Tenant, string BearerToken, string ProfileTypeId)
        {
            int Limit = 250;
            int Offset = 0;
            int fullcount = 0;
            RestClient restClient = new RestClient($"https://{Tenant}.nonemployee.com");
            RestRequest restRequest = new RestRequest($"/api/profiles?metadata=true&offset={Offset}&limit={Limit}&profile_type_id={ProfileTypeId}", Method.Get);
            restRequest.AddHeader("Accept", "application/json");
            restRequest.AddHeader("Content-Type", "application/json");
            restRequest.AddHeader("Authorization", $"Bearer {BearerToken}");

            List<RestResponse> restResponses = new List<RestResponse>();
            RestResponse restResponse = restClient.Execute(restRequest);
            Models.Profiles.Profiles countProfiles = ConvertToObject.ConvertJsonToObject(restResponse: restResponse);
            fullcount = countProfiles.Metadata.Total;
            restResponses.Add(restResponse);

            if (fullcount <= 250)
            {
                return restResponses;
            }
            do
            {
                Offset += 250;
                restRequest = new RestRequest($"/api/profiles?metadata=true&offset={Offset}&limit={Limit}&profile_type_id={ProfileTypeId}", Method.Get);
                restRequest.AddHeader("Accept", "application/json");
                restRequest.AddHeader("Content-Type", "application/json");
                restRequest.AddHeader("Authorization", $"Bearer {BearerToken}");
                restResponse = restClient.Execute(restRequest);
                restResponses.Add(restResponse);
            }
            while (Offset <= fullcount);

            return restResponses;
        }
    }
}

File Name: /Private/Controllers/NERMProfiles/ConvertToObjects.cs

using Community_Connect_Automation.Private.Models.Profiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.Profiles
{
    internal class ConvertToObject
    {
        internal static Private.Models.Profiles.Profiles ConvertJsonToObject(RestResponse restResponse)
        {
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                NumberHandling = JsonNumberHandling.AllowReadingFromString,
                IncludeFields = true
            };

            Private.Models.Profiles.Profiles profiles = JsonSerializer.Deserialize<Private.Models.Profiles.Profiles>(restResponse.Content, options);
            return profiles;
        }
        internal static List<Private.Models.Profiles.Profiles> ConvertJsonToObjects(List<RestResponse> restResponses)
        {
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                NumberHandling = JsonNumberHandling.AllowReadingFromString,
                IncludeFields = true
            };
            List<Private.Models.Profiles.Profiles> profiles = new List<Private.Models.Profiles.Profiles>();
            foreach (RestResponse response in restResponses)
            {
                Private.Models.Profiles.Profiles profileData = ConvertJsonToObject(restResponse: response);
                profiles.Add(profileData);
            }
            return profiles;

        }
    }
}

File Name: /Private/Controllers/ExportCsv/ExportCsvReport.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Community_Connect_Automation.Private;
using Community_Connect_Automation.Private.Models.Profiles;
using CsvHelper;

namespace Community_Connect_Automation.Private.Controllers.ExportCsv
{
    internal class ExportCsvReport
    {
        internal static void ExportProfileReport(List<Models.Profiles.Profiles> NERMProfiles) 
        {
            List<Models.Reports.AssignmentReport> NERMProfileList = new List<Models.Reports.AssignmentReport>();

            foreach(Models.Profiles.Profiles NERMProfile in NERMProfiles)
            {
                foreach(Profile Entry in NERMProfile.Profile)
                {
                    Models.Reports.AssignmentReport assignment = new Models.Reports.AssignmentReport();
                    assignment.Id = Entry.Id;
                    assignment.Uid = Entry.Uid;
                    assignment.Name = Entry.Name;
                    assignment.ProfileTypeId = Entry.ProfileTypeId;
                    assignment.Status = Entry.Status;
                    assignment.IdProofingStatus = Entry.IdProofingStatus;
                    assignment.UpdatedAt = Entry.UpdatedAt;
                    assignment.CreatedAt = Entry.CreatedAt;
                    assignment.AssignmentFacilityIdNeAttribute = Entry.Attributes.AssignmentFacilityIdNeAttribute;
                    assignment.AssignmentLocationIdNeAttribute = Entry.Attributes.AssignmentLocationIdNeAttribute;
                    assignment.AssignmentOrganizationNameNeAttribute = Entry.Attributes.AssignmentOrganizationNameNeAttribute;
                    assignment.AssignmentPopulation = Entry.Attributes.AssignmentPopulation;
                    assignment.AssignmentSubPopulation = Entry.Attributes.AssignmentSubPopulation;
                    assignment.AssignmentSubpopulationName = Entry.Attributes.AssignmentSubpopulationName;
                    assignment.DepartmentNeAttribute = Entry.Attributes.DepartmentNeAttribute;
                    assignment.EngagementEndDate = Entry.Attributes.EngagementEndDate;
                    assignment.EngagementStartDate = Entry.Attributes.EngagementStartDate;
                    assignment.ManagerNeAttribute = Entry.Attributes.ManagerNeAttribute;
                    assignment.OrganizationForAssignmentNeAttribute = Entry.Attributes.OrganizationForAssignmentNeAttribute;
                    assignment.PersonalEmail = Entry.Attributes.PersonalEmail;
                    assignment.PersonalFirstName = Entry.Attributes.PersonalFirstName;
                    assignment.PersonalLastName = Entry.Attributes.PersonalLastName;
                    assignment.SailpointUsernameAssignmentNeAttribute = Entry.Attributes.SailpointUsernameAssignmentNeAttribute;
                    assignment.PreviousNonEmployeeNeAttribute = Entry.Attributes.PreviousNonEmployeeNeAttribute;

                    NERMProfileList.Add(assignment);
                }
            }

            string Today = (DateTime.Now).ToString("yyyy-MM-dd");
            string filePath = $"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"OutPutFiles\Assignments\NERMAssignmentsReport_" + Today + ".csv")}";

            using (var writer = new StreamWriter(filePath))
            using (var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture))
            {
                csvWriter.WriteRecords(NERMProfileList);
            }
        }
    }
}

Tests:

File Name: /Public/Tests/Profiles/GetAssignmentsTest.cs

using Community_Connect_Automation.Private.Controllers.Profiles;
using Community_Connect_Automation.Private.Controllers.NERMProfiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Tests.Profiles
{
    [Cmdlet(VerbsDiagnostic.Test, "GetAssignments")]
    [OutputType(typeof(List<RestResponse>))]
    public class GetAssignmentsTest : PSCmdlet
    {
        [Parameter(Position=0)]
        public string Tenant {  get; set; }
        [Parameter(Position=1)]
        public string BearerToken { get; set; }
        [Parameter(Position =2)]
        public string ProfileTypeId { get; set; }

        protected override void ProcessRecord()
        {
            List<RestResponse> restResponses = GetAllActiveAssignments.GetActviceAssginments(Tenant:Tenant, BearerToken:BearerToken, ProfileTypeId: ProfileTypeId);

            WriteObject(restResponses);
        }
    }
}

File Name: /Public/Tests/Reports/ExportAssignmentCsvTest.cs

using Community_Connect_Automation.Private.Controllers.ExportCsv;
using Community_Connect_Automation.Private.Controllers.Profiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Tests.Reports
{
    [Cmdlet(VerbsDiagnostic.Test, "ExportAssignmentCsv")]
    [OutputType(typeof(void))]
    public class ExportAssignmentCsvTest : PSCmdlet
    {
        [Parameter(Position = 0)]
        public List<RestResponse> RestResponses {  get; set; }


        protected override void BeginProcessing()
        {
            List<Private.Models.Profiles.Profiles> profiles = ConvertToObject.ConvertJsonToObjects(restResponses: RestResponses);
            Console.WriteLine($"DEBUG:: Total number of returned profiles -- {profiles.Count()}");
            ExportCsvReport.ExportProfileReport(profiles);
        }
    }
}

File Name: /Public/Views/NewAssignmentReport.cs

using Community_Connect_Automation.Private.Controllers.Profiles;
using Community_Connect_Automation.Private.Controllers.NERMProfiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Views
{
    [Cmdlet(VerbsCommon.New, "AssignmentReport")]
    [OutputType(typeof(void))]
    public class NewAssignmentReport : PSCmdlet
    {
        [Parameter(Position=0)]
        public string Tenant {  get; set; }
        [Parameter(Position=1)]
        public string BearerToken { get; set; }
        [Parameter(Position=2)]
        public string ProfileTypeId { get; set; }

        protected override void BeginProcessing()
        {
            List<RestResponse> restResponses = GetAllActiveAssignments.GetActviceAssginments(Tenant: Tenant, BearerToken: BearerToken, ProfileTypeId: ProfileTypeId);
            List<Private.Models.Profiles.Profiles> profiles = ConvertToObject.ConvertJsonToObjects(restResponses: restResponses);
            Private.Controllers.ExportCsv.ExportCsvReport.ExportProfileReport(profiles); 
            string Today = (DateTime.Now).ToString("yyyy-MM-dd");
            string filePath = $"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"OutPutFiles\Assignments\NERMAssignmentsReport_" + Today + ".csv")}";
            WriteObject($"Csv File as saved at {filePath}");
        }
    }
}

Updating the Assignments

File Name: /Private/Controllers/Department/ConvertDepartmentToJson.cs

using Community_Connect_Automation.Private.Models.Profiles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.Department
{
    internal class ConvertDepartmenttoJson
    {
        internal static string ConvertDepartmentUpdatedObjectToJson(List<Profile> profile)
        {
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                NumberHandling = JsonNumberHandling.AllowReadingFromString,
                IncludeFields = true
            };

            string jsonData = JsonSerializer.Serialize<List<Profile>>(profile, options);

            return jsonData;
        }
    }
}

File Name: /Private/Controllers/Department/DepartmentIds.cs

using Community_Connect_Automation.Private.Controllers.Profiles;
using Community_Connect_Automation.Private.Models.Profiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.Department
{
    internal class DepartmentIds
    {
        internal static List<Models.Profiles.Profile> DepartmentObjects(List<RestResponse> RestResponses)
        {
            List<Models.Profiles.Profiles> departmentProfiles = ConvertToObject.ConvertJsonToObjects(RestResponses);
            List < Models.Profiles.Profile > deaprtments = new List<Profile> ();
            foreach (Models.Profiles.Profiles departmentProfile in departmentProfiles)
            {
                foreach(Models.Profiles.Profile profile in departmentProfile.Profile)
                {
                    deaprtments.Add(profile);
                }
            }
            return deaprtments;
        }

    }
}

File Name: /Private/Controllers/Department/FindDepartmentId.cs

using Community_Connect_Automation.Private.Models.Profiles;
using Community_Connect_Automation.Private.Models.Reports;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.Department
{
    internal class FindDeaprtmentId
    {
        internal static List<Profile> FindDepartmentByName(List<Profile>DeparmtmentProfiles, List<AssignmentReport>AssignmentProfiles)
        {
            List<Profile> ProfileList = new List<Profile>();
            foreach(AssignmentReport AssignmentProfile in AssignmentProfiles)
            {
                Profile AssignmentDepartmentProfile = new Profile();
                AssignmentDepartmentProfile.Id = AssignmentProfile.Id;
                AssignmentDepartmentProfile.Uid = AssignmentProfile.Uid;
                AssignmentDepartmentProfile.Name = AssignmentProfile.Name;
                AssignmentDepartmentProfile.ProfileTypeId = AssignmentProfile.ProfileTypeId;
                AssignmentDepartmentProfile.IdProofingStatus = AssignmentProfile.IdProofingStatus;
                AssignmentDepartmentProfile.Attributes = new ProfileAttribtues();

                var searchObject = (from department in DeparmtmentProfiles
                                   where AssignmentProfile.DepartmentNeAttribute == department.Name
                                   select department.Id
                                   );
                foreach(var obj in searchObject)
                {
                    AssignmentDepartmentProfile.Attributes.NewDepartmentNeAttribute = obj;
                }
                ProfileList.Add(AssignmentDepartmentProfile);
            }
            return ProfileList;
        }
    }
}

File Name: /Private/Controllers/Department/GetDepartments.cs

using Community_Connect_Automation.Private.Controllers.Profiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.Department
{
    internal class GetDepartments
    {
        internal static List<RestResponse> GetNERMDepartments(string Tenant, string BearerToken, string ProfileTypeId)
        {
            int Limit = 250;
            int Offset = 0;
            int fullcount = 0;

            RestClient restClient = new RestClient($"https://{Tenant}.nonemployee.com");
            RestRequest restRequest = new RestRequest($"/api/profiles?metadata=true&offset={Offset}&limit={Limit}&profile_type_id={ProfileTypeId}", Method.Get);

            restRequest.AddHeader("Accept", "application/json");
            restRequest.AddHeader("Content-Type", "application/json");
            restRequest.AddHeader("Authorization", $"Bearer {BearerToken}");

            RestResponse countResponse = restClient.Execute(restRequest);
            List<RestResponse> restResponses = new List<RestResponse>();

            Models.Profiles.Profiles countProfiles = ConvertToObject.ConvertJsonToObject(restResponse: countResponse);
            fullcount = countProfiles.Metadata.Total;
            restResponses.Add(countResponse);

            if (fullcount <= 250)
            {
                return restResponses;
            }
            do
            {
                Offset += 250;
                restRequest = new RestRequest($"/api/profiles?metadata=true&offset={Offset}&limit={Limit}&profile_type_id={ProfileTypeId}", Method.Get);
                RestResponse restResponse = restClient.Execute(restRequest);
                restResponses.Add(restResponse);
            }
            while (Offset <= fullcount);
            return restResponses;

        }
    }
}

File Name: /Private/Controllers/ImportCsv/ImportCsvFile.cs

using Community_Connect_Automation.Private.Models.Reports;
using CsvHelper;
using CsvHelper.Configuration;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.ImportCsv
{
    public class ImportCsvFile
    {
        public static List<AssignmentReport> ReadCsvReportFile(string filePath)
        {
            List<AssignmentReport> reportList = new List<AssignmentReport>();
            if (!File.Exists(filePath))
            {
                string Today = (DateTime.Now).ToString("yyyy-MM-dd");
                filePath = $"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"OutPutFiles\Assignments\NERMAssignmentsReport_" + Today + ".csv")}";
            }
            var config = new CsvConfiguration(CultureInfo.InvariantCulture)
            {
                HasHeaderRecord = true,
                Delimiter = ",",
                TrimOptions = TrimOptions.Trim,
                MissingFieldFound = null
            };

            using (var reader = new StreamReader(filePath))
            using (var csvReader = new CsvReader(reader, config))
            {
                var records = csvReader.GetRecords<AssignmentReport>().ToList();
                Console.WriteLine("Stepping into Foreach loop.");
                foreach(AssignmentReport assignment in records)
                {
                    Console.WriteLine("If foreach loop");
                    reportList.Add(assignment);
                }
            }
            return reportList;
        }
    }
}

File Name: /Private/Controllers/SubPopulation/ConvertSubPopulationToJson.cs

using Community_Connect_Automation.Private.Models.Profiles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.SubPopulation
{
    internal class ConvertSubPopulationtoJson
    {
        internal static string ConvertSubPopulationUpdatedObjectToJson(List<Profile> profile)
        {
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                NumberHandling = JsonNumberHandling.AllowReadingFromString,
                IncludeFields = true
            };

            string jsonData = JsonSerializer.Serialize<List<Profile>>(profile, options);

            return jsonData;
        }
    }
}

File Name: /Private/Controllers/SubPopulation/FindSubPopulations.cs

using Community_Connect_Automation.Private.Models.Profiles;
using Community_Connect_Automation.Private.Models.Reports;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.SubPopulation
{
    internal class FindSubPopulations
    {
        internal static List<Profile> FindSubPopulationsByName(List<Profile> SubPopulationsProfiles, List<AssignmentReport> AssignmentProfiles)
        {
            List<Profile> ProfileList = new List<Profile>();
            foreach (AssignmentReport AssignmentProfile in AssignmentProfiles)
            {
                Profile AssignmentDepartmentProfile = new Profile();
                AssignmentDepartmentProfile.Id = AssignmentProfile.Id;
                AssignmentDepartmentProfile.Uid = AssignmentProfile.Uid;
                AssignmentDepartmentProfile.Name = AssignmentProfile.Name;
                AssignmentDepartmentProfile.ProfileTypeId = AssignmentProfile.ProfileTypeId;
                AssignmentDepartmentProfile.IdProofingStatus = AssignmentProfile.IdProofingStatus;
                AssignmentDepartmentProfile.Attributes = new ProfileAttribtues();

                var searchObject = (from SubPopulation in SubPopulationsProfiles
                                    where AssignmentProfile.AssignmentSubPopulation == SubPopulation.Name
                                    select SubPopulation.Id
                                   );
                foreach (var obj in searchObject)
                {
                    AssignmentDepartmentProfile.Attributes.AssignmentSubPopulation = obj;
                }
                ProfileList.Add(AssignmentDepartmentProfile);
            }
            return ProfileList;
        }
    }
}

File Name: /Private/Controllers/SubPopulation/GetSubPopulations.cs

using Community_Connect_Automation.Private.Controllers.Profiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.SubPopulation
{
    internal class GetSubPopulations
    {
        internal static List<RestResponse> GetNERMSubPopulations(string Tenant, string BearerToken, string ProfileTypeId)
        {
            int Limit = 250;
            int Offset = 0;
            int fullcount = 0;

            RestClient restClient = new RestClient($"https://{Tenant}.nonemployee.com");
            RestRequest restRequest = new RestRequest($"/api/profiles?metadata=true&offset={Offset}&limit={Limit}&profile_type_id={ProfileTypeId}", Method.Get);

            restRequest.AddHeader("Accept", "application/json");
            restRequest.AddHeader("Content-Type", "application/json");
            restRequest.AddHeader("Authorization", $"Bearer {BearerToken}");

            RestResponse countResponse = restClient.Execute(restRequest);
            List<RestResponse> restResponses = new List<RestResponse>();

            Models.Profiles.Profiles countProfiles = ConvertToObject.ConvertJsonToObject(restResponse: countResponse);
            fullcount = countProfiles.Metadata.Total;
            restResponses.Add(countResponse);

            if (fullcount <= 250)
            {
                return restResponses;
            }
            do
            {
                Offset += 250;
                restRequest = new RestRequest($"/api/profiles?metadata=true&offset={Offset}&limit={Limit}&profile_type_id={ProfileTypeId}", Method.Get);
                RestResponse restResponse = restClient.Execute(restRequest);
                restResponses.Add(restResponse);
            }
            while (Offset <= fullcount);
            return restResponses;

        }
    }
}

File Name: /Private/Controllers/SubPopulation/SubPopulationsIds.cs

using Community_Connect_Automation.Private.Controllers.Profiles;
using Community_Connect_Automation.Private.Models.Profiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.SubPopulation
{
    internal class SubPopulationIds
    {
        internal static List<Models.Profiles.Profile>SubPopulationObjects(List<RestResponse> RestResponses)
        {
            List<Models.Profiles.Profiles> SubPopulationProfiles = ConvertToObject.ConvertJsonToObjects(RestResponses);
            List<Models.Profiles.Profile> SubPopulations = new List<Profile>();
            foreach (Models.Profiles.Profiles SubPopulationProfile in SubPopulationProfiles)
            {
                foreach (Models.Profiles.Profile profile in SubPopulationProfile.Profile)
                {
                    SubPopulations.Add(profile);
                }
            }
            return SubPopulations;
        }
    }
}

File Name: /Private/Controllers/UpdateProfiles/PayloadConvertToJson.cs

using Community_Connect_Automation.Private.Models.Profiles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.UpdateProfiles
{
    internal class PayloadConvertToJson
    {
        internal static string ConvertPayloadFromObjectToJson(UpdatePayloadModel profile)
        {
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                NumberHandling = JsonNumberHandling.AllowReadingFromString,
                IncludeFields = true
            };

            string jsonPayload = JsonSerializer.Serialize(profile, options);

            return jsonPayload;
        }
    }
}

File Name: /Private/Controllers/UpdateProfiles/UpdatePayload.cs

using Community_Connect_Automation.Private.Models.Profiles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.UpdateProfiles
{
    internal class UpdatePayload
    {
        internal static List<UpdatePayloadModel> BuildUpdatePayloadObject(List<Profile> UpdatedDepartmentProfiles, List<Profile> UpdatedSubPopulationProfiles)
        {
            List<UpdatePayloadModel> profiles = new List<UpdatePayloadModel>();

            foreach(Profile UpdatedDepartmentProfile in UpdatedDepartmentProfiles)
            {
                var searchObject = (from SubPop in UpdatedSubPopulationProfiles
                                    where UpdatedDepartmentProfile.Uid == SubPop.Uid
                                    select SubPop
                                    );

                foreach(var entry in searchObject)
                {
                    Profile profile = new Profile();
                    profile.Uid = entry.Uid;
                    profile.Name = entry.Name;
                    profile.Id = entry.Id;
                    profile.IdProofingStatus = entry.IdProofingStatus;
                    profile.Status = entry.Status;
                    profile.ProfileTypeId = entry.ProfileTypeId;
                    profile.Attributes = new ProfileAttribtues();
                    profile.Attributes.AssignmentSubPopulation = entry.Attributes.AssignmentSubPopulation;
                    profile.Attributes.NewDepartmentNeAttribute = UpdatedDepartmentProfile.Attributes.NewDepartmentNeAttribute;
                    UpdatePayloadModel updatePayload = new UpdatePayloadModel();
                    updatePayload.AssignmentProfile = profile;
                    profiles.Add(updatePayload);
                }
            }
            return profiles;
        }
    }
}

File Name: /Private/Controllers/UpdateProfiles/UpdateProfile.cs

using Community_Connect_Automation.Private.Models.Profiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Private.Controllers.UpdateProfiles
{
    internal class UpdateProfile
    {
        internal static RestResponse UpdateAssignment(string Tenant, string BearerToken, UpdatePayloadModel Assignment)
        {
            RestClient restClient = new RestClient($"https://{Tenant}.nonemployee.com");
            RestRequest restRequest = new RestRequest($"/api/profiles/{Assignment.AssignmentProfile.Id}", Method.Patch);
            restRequest.AddHeader("Content-Type", "application/json");
            restRequest.AddHeader("Accept", "application/json");
            restRequest.AddHeader("Authorization", $"Bearer {BearerToken}");

            string body = PayloadConvertToJson.ConvertPayloadFromObjectToJson(Assignment);

            restRequest.AddBody(body);
            RestResponse restResponse = restClient.Execute(restRequest);
            return restResponse;
        }
    }
}

File Name: /Public/Tests/Deparments/FindDepartmentsTest.cs

using Community_Connect_Automation.Private.Controllers.Department;
using Community_Connect_Automation.Private.Models.Profiles;
using Community_Connect_Automation.Private.Models.Reports;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Tests.Departments
{
    [Cmdlet(VerbsDiagnostic.Test, "FindDepartment")]
    [OutputType(typeof(List<Profile>))]
    public class FindDepartmenttest : PSCmdlet
    {
        [Parameter(Position=0)]
        public List<RestResponse> RestResponsesDepartments {  get; set; }
        [Parameter(Position=1)]
        public List<AssignmentReport> AssignmentProfiles { get; set; }

        protected override void BeginProcessing()
        {
            List<Profile> departmentIds = DepartmentIds.DepartmentObjects(RestResponses: RestResponsesDepartments);
            List<Profile>  AssignmentDepartmentUpdated = FindDeaprtmentId.FindDepartmentByName(DeparmtmentProfiles: departmentIds, AssignmentProfiles: AssignmentProfiles);
            WriteObject(AssignmentDepartmentUpdated);
        }
    }
}

File Name: /Public/Tests/Deparments/GetDepartmentsTest.cs

using Community_Connect_Automation.Private.Controllers.Department;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Tests.Departments
{
    [Cmdlet(VerbsDiagnostic.Test, "GetDepartments")]
    [OutputType(typeof(void))]
    public class GetDepartmentsTest : PSCmdlet
    {
        [Parameter(Position =0)]
        public string Tenant { get; set; }
        [Parameter(Position =1)]
        public string BearerToken { get; set; }
        [Parameter(Position =2)]
        public string ProfileTypeId { get; set; }

        protected override void BeginProcessing()
        {
            List<RestResponse> restResponses = GetDepartments.GetNERMDepartments(Tenant: Tenant, BearerToken: BearerToken, ProfileTypeId: ProfileTypeId);
            WriteObject(restResponses);
        }
    }
}

File Name: /Public/Tests/Reports/ImportAssignmentCsvTest.cs

using Community_Connect_Automation.Private.Controllers.ImportCsv;
using Community_Connect_Automation.Private.Models.Reports;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Tests.Reports
{
    [Cmdlet(VerbsDiagnostic.Test, "ImportAssignmentCsv")]
    [OutputType(typeof(void))]
    public class ImportAssignmentCsvTest : PSCmdlet
    {
        [Parameter(Position = 0)]
        public string FilePath { get; set; }

        protected override void ProcessRecord()
        {
            WriteObject(ImportCsvFile.ReadCsvReportFile(filePath: FilePath));
        }
    }
}

File Name: /Public/Tests/SubPopulation/FindSubPopulationsTest.cs

using Community_Connect_Automation.Private.Controllers.Department;
using Community_Connect_Automation.Private.Controllers.SubPopulation;
using Community_Connect_Automation.Private.Models.Profiles;
using Community_Connect_Automation.Private.Models.Reports;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Tests.SubPopulation
{
    [Cmdlet(VerbsDiagnostic.Test, "FindSubPopulation")]
    [OutputType(typeof(void))]
    public class FindSubPopulationsTest : PSCmdlet
    {
        [Parameter(Position = 0)]
        public List<RestResponse> RestResponsesSubPopulation { get; set; }
        [Parameter(Position = 1)]
        public List<AssignmentReport> AssignmentProfiles { get; set; }

        protected override void BeginProcessing()
        {
            List<Profile> subPopIds = SubPopulationIds.SubPopulationObjects(RestResponses:  RestResponsesSubPopulation);
            List<Profile> AssignmentSubPopUpdated = FindSubPopulations.FindSubPopulationsByName(SubPopulationsProfiles: subPopIds, AssignmentProfiles: AssignmentProfiles);
            WriteObject(AssignmentSubPopUpdated);
        }
    }
}

File Name: /Public/Tests/SubPopulation/GetSubPopulationsTest.cs

using Community_Connect_Automation.Private.Controllers.SubPopulation;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Tests.SubPopulation
{
    [Cmdlet(VerbsDiagnostic.Test, "GetSubPopulation")]
    [OutputType(typeof(void))]
    public class GetSubPopulationsTest : PSCmdlet
    {
        [Parameter(Position = 0)]
        public string Tenant { get; set; }
        [Parameter(Position = 1)]
        public string BearerToken { get; set; }
        [Parameter(Position = 2)]
        public string ProfileTypeId { get; set; }

        protected override void BeginProcessing()
        {
            List<RestResponse> restResponses = GetSubPopulations.GetNERMSubPopulations(Tenant: Tenant, BearerToken: BearerToken, ProfileTypeId: ProfileTypeId);
            WriteObject(restResponses);
        }
    }
}

File Name: /Public/Tests/UpdateProfile/PayLoadConvertToJsonTest.cs

using Community_Connect_Automation.Private.Controllers.UpdateProfiles;
using Community_Connect_Automation.Private.Models.Profiles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Tests.UpdateProfile
{
    [Cmdlet(VerbsDiagnostic.Test, "ConvertPayloadToJson")]
    [OutputType(typeof(void))]
    public class PayLoadConvertToJsonTest : PSCmdlet
    {
        [Parameter(Position=0)]
        public UpdatePayloadModel AssignmentProfile { get; set; }

        protected override void BeginProcessing()
        {
            string payload = PayloadConvertToJson.ConvertPayloadFromObjectToJson(AssignmentProfile);
            WriteObject(payload);
        }
    }
}

File Name: /Public/Tests/UpdateProfile/UpdatePayloadTest.cs

using Community_Connect_Automation.Private.Controllers.UpdateProfiles;
using Community_Connect_Automation.Private.Models.Profiles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Tests.UpdateProfile
{
    [Cmdlet(VerbsDiagnostic.Test, "UpdatePayload")]
    [OutputType(typeof(void))]
    public class UpdatePayloadTest : PSCmdlet
    {
        [Parameter(Position = 0)]
        public List<Profile> UpdatedDepartmentProfiles { get; set; }
        [Parameter(Position = 1)]
        public List<Profile> UpdatedSubPopulationProfiles { get; set; }

        protected override void BeginProcessing()
        {
            List<UpdatePayloadModel> profiles = UpdatePayload.BuildUpdatePayloadObject(UpdatedDepartmentProfiles: UpdatedDepartmentProfiles, UpdatedSubPopulationProfiles: UpdatedSubPopulationProfiles);
            WriteObject(profiles);
        }
    }
}

File Name: /Public/Tests/UpdateProfile/UpdateProfilesTest.cs

using Community_Connect_Automation.Private.Controllers.UpdateProfiles;
using Community_Connect_Automation.Private.Models.Profiles;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Tests.UpdateProfile
{
    [Cmdlet(VerbsDiagnostic.Test, "UpdateProfile")]
    [OutputType(typeof(void))]
    public class UpdateProfilesTest : PSCmdlet
    {
        [Parameter(Position=0)]
        public string Tenant {  get; set; }
        [Parameter(Position=1)]
        public string BearerToken { get; set; }
        [Parameter(Position=2)]
        public UpdatePayloadModel AssignMentProfile { get; set; }

        protected override void BeginProcessing()
        {
            RestResponse restResponse = Private.Controllers.UpdateProfiles.UpdateProfile.UpdateAssignment(Tenant: Tenant, BearerToken: BearerToken, Assignment: AssignMentProfile);
            WriteObject(restResponse);
        }
    }
}

File Name: /Public/Views/UpdateAssignments.cs

using Community_Connect_Automation.Private.Controllers.Department;
using Community_Connect_Automation.Private.Controllers.ImportCsv;
using Community_Connect_Automation.Private.Controllers.SubPopulation;
using Community_Connect_Automation.Private.Controllers.UpdateProfiles;
using Community_Connect_Automation.Private.Models.Profiles;
using Community_Connect_Automation.Private.Models.Reports;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Community_Connect_Automation.Public.Views
{
    [Cmdlet(VerbsData.Update, "Assignment")]
    [OutputType(typeof(void))]
    public class UpdateAssignments : PSCmdlet
    {
        [Parameter(Position=0)]
        public string Tenant {  get; set; }
        [Parameter(Position=1)]
        public string BearerToken { get; set; }
        [Parameter(Position =2)]
        public string DepartmentProfileTypeId { get; set; }
        [Parameter(Position = 3)]
        public string SubPopulationProfileTypeId { get; set; }
        [Parameter(Position =4)]
        public string FilePath { get; set; }


        protected override void BeginProcessing()
        {
            //Read updated csv file with updated sub population and departments.
            List<AssignmentReport> assignmentReport =  ImportCsvFile.ReadCsvReportFile(filePath: FilePath);

            //Get a list of department IDs
            List<RestResponse> departmentsRestResponses = GetDepartments.GetNERMDepartments(Tenant: Tenant, BearerToken: BearerToken, ProfileTypeId: DepartmentProfileTypeId);
            List<Private.Models.Profiles.Profile> departmentProfiles = DepartmentIds.DepartmentObjects(RestResponses:  departmentsRestResponses);
            List<Profile> departmentProfilesId = FindDeaprtmentId.FindDepartmentByName(DeparmtmentProfiles: departmentProfiles, AssignmentProfiles: assignmentReport);

            //Get a list of SubPopulation IDs 
            List<RestResponse> RestResponsesSubPopulation = GetSubPopulations.GetNERMSubPopulations(Tenant: Tenant, BearerToken: BearerToken, ProfileTypeId: SubPopulationProfileTypeId);
            List<Profile> subPopIds = SubPopulationIds.SubPopulationObjects(RestResponses: RestResponsesSubPopulation);
            List<Profile> AssignmentSubPopUpdated = FindSubPopulations.FindSubPopulationsByName(SubPopulationsProfiles: subPopIds, AssignmentProfiles: assignmentReport);

            //Combine SubPop and Department Updates
            List<UpdatePayloadModel> profiles = UpdatePayload.BuildUpdatePayloadObject(UpdatedDepartmentProfiles: departmentProfilesId, UpdatedSubPopulationProfiles: AssignmentSubPopUpdated);

            //Update each profile. 
            foreach(UpdatePayloadModel profile in profiles)
            {
                RestResponse restResponse = Private.Controllers.UpdateProfiles.UpdateProfile.UpdateAssignment(Tenant: Tenant, BearerToken: BearerToken, Assignment: profile);
            }
        }
    }
}

Code Breakdown

I know this seems like a lot of code to be able to update two fields in NERM. First we need to export all the active assignments in NERM. This will give us the required IDs to pass into the update methods. Once the Sponsors and Collaborators update the file with the required information, we review the file to ensure that the values populated are in the list and that they did not add an option that is not accounted for.

Once the file is imported, we do a lookup for the departments and subpopulations. We then do a lookup for each record in the report to pull the uid for each department and subpopulation. Once we have that information, we combine the two created updated values into a single object. This object is then converted into the PATCH payload that is sent with the PATCH API call to profiles.

Running a Test

Import-Module '.\Community Connect Automation\bin\Debug\net8.0\Community Connect Automation.dll'
$RestRespones = Test-GetAssignments -Tenant "{{REDACTED}}" -BearerToken "{{REDACTED}}" -ProfileTypeId "{{REDACTED}}"
Test-ExportAssignmentCsv -RestResponses $RestRespones

$AssignmentData = Test-ImportAssignmentCsv
$Departments = Test-GetDepartments -Tenant "{{REDACTED}}" -BearerToken "{{REDACTED}}" -ProfileTypeId "{{REDACTED}}"
$UpdatedDeapartments = Test-FindDepartment -RestResponsesDepartments $Departments -AssignmentProfiles $AssignmentData

$SubPops = Test-GetSubPopulation  -Tenant "{{REDACTED}}" -BearerToken "{{REDACTED}}" -ProfileTypeId "{{REDACTED}}"
$UpdatedSubPop = Test-FindSubPopulation -RestResponsesSubPopulation $SubPops -AssignmentProfiles $AssignmentData

$TestPayLoad = Test-UpdatePayload -UpdatedDepartmentProfiles $UpdatedDeapartments -UpdatedSubPopulationProfiles $UpdatedSubPop
$JsonPayloadTest = @()

foreach($Payload in $TestPayLoad){
	$JsonPayloadTest += Test-ConvertPayloadToJson -AssignmentProfile $TestPayloadObject
}

$SendUpdates = $TestPayLoad | select -first 5

foreach($SendUpdate in $TestPayLoad){
	Test-UpdateProfile -Tenant "{{REDACTED}}" -BearerToken "{{REDACTED}}" -AssignMentProfile $SendUpdate
}

Prod Deployment

This process will need to be deployed to Prod in phases.

Phase 1
First we need to move the NERM config to Prod. This allows us to build out the department profiles and update the subpopulation profiles.
Set the permissions to edit for Admin Only on workflow and attributes.

Phase 2
Generate the report and update the profiles.
If you do not have all the values populated as you want them, just create the existing options and run the update so that you can complete Phase 3 while you are working on populating users over time.

Phase 3
Update ISC connector configuration to point to the new attribute. This will ensure that the updated information is populated with attribute sync.

Conclusion

This project had quite a few challenges to overcome. Overall, this process has enabled the team to build roles for our non-employee population and allow for better scalability as more departments are added to NERM. By moving to a profile this also allows us to improve the data we are tracking on departments if the business case ever changes. The custom PowerShell cmdlets allow for mass updates of profiles. With the way that the models are built, this project can easily be expanded on to manage more profiles. This project was a great building block for working with NERM.