How to create a PowerShell connector for provisioning

Introduction

If an application is not listed among the standard Netwrix Identity Manager connectors, this does not represent a limitation to integration. As long as the application exposes an interface such as LDAP, SQL, or an API, it can be seamlessly integrated into Netwrix Identity Manager through its generic connectors.

Among these, the PowerShell connector stands out as a particularly powerful and flexible option. It enables direct interaction with virtually any system that can be accessed or automated via scripts, APIs, or command-line tools. This makes it possible to manage identities, accounts, entitlements, and lifecycle events even for highly customized, legacy, or cloud-native applications.

By leveraging PowerShell, organizations can reuse existing automation scripts, adapt quickly to application-specific logic, and implement advanced provisioning, reconciliation, and governance scenarios without waiting for a dedicated connector. This approach significantly reduces integration time, increases agility, and ensures that all critical applications remain within the scope of identity governance and compliance.

The goal of this show & tell is to help partners and customer understand and configure a PowerShell connector.

How NIM connectors work in general

Architecture

Provisioning orders generation

This step is common to all connectors, whether standard, generic, or custom. During this phase, Netwrix Identity Manager generates all provisioning orders. the orders are kept in the database waiting to be downloaded by the connector.

Downloading orders

This step is common to all connectors, whether standard, generic, or custom. During this phase, each connector will interrogate the server in order to check for existing orders and download them if there are any.

Provisioning in Targets

This is where the magic happens.
To provision the data to target systems we need first to understand how the PowerShell script works and how the provisioning order is structured.

The PowerShell script structure

The script is composed of 4 sections:

  • Section 1: Script parameters (do not change it)
param(
  [Parameter(Mandatory = $true)][string]$resultsFilePath,
  [Parameter(Mandatory = $true)][string]$ordersPath
)

. "./Usercube-Visit-Orders.ps1"
  • Section 2: The main function where you can write your code for accounts, resources and permissions management. Such as API calls, SQL requests, Files generation, Email notifications, etc.
function Fulfill-Powershell {
    param ([parameter(Mandatory = $true)] $order)
  
    if ($order.ChangeType -eq 'Added') {
        # Here goes the account creation code
    }
    elseif ($order.ChangeType -eq 'Deleted') {
        # Here goes the account deletion code
    }
    elseif ($order.ChangeType -eq 'Modified')  {
        # Here goes the account update code
    } else {
        $artId = $order.AssignedResourceTypeId
        throw "Order changeType : $order.ChangeType  not recognized in assignedResourcetype:'$artId'"
    }
}
  • Section 3: The options that can be setup in the UI. Mainly credentials and URLs
# Get options setup in the UI
$options = [System.Console]::ReadLine()
$options = ConvertFrom-Json $options

$clearOption = $options.clearOption
$secureOption = $options.secureOption
  • Section 4: The function call.
    Note: Make sure that the last parameter “Fulfill-Powershell” matches the name of the function is section 2
Usercube-Visit-Orders $resultsFilePath $ordersPath Fulfill-Powershell

The provisioning order structure

The provisioning is a JSON file composed of 4 sections:

  • ChangeType: defines the type if action to be done.
    3 possible values: Added, Modified, Deleted
"ChangeType": "Modified",
  • Owner data: contains the data from the Identity (Directory_User)
"Owner": {
                "Id": "179972",
                "InternalDisplayName": "John Do",
                "IsDraft": false,
                "IsGeneric": false,
                "Login": "john.do",
                "MainFirstName": "John",
                "MainLastName": "Do",
                "MainPhoneNumber": "\u002B20 00000000",
                "NormalizedPhoneNumber": "\u002B20 00000000",
                "PhoneticFirstLastName": "JOHN DO",
                "PhoneticLastFirstName": "DO JOHN",
                "Op_MainRecord_Login": "john.do",
                "Op_MainRecord_Mail": "john.do@acme.com"
            },
  • Resource data: contains all the current data of the account
    This section does not exist if the ChangeType equals “Added”
 "Resource": {
                "Id": "42694001",
                "InternalDisplayName": "John Do",
                "accountEnabled": "True",
                "createdDateTime": "20241112220528",
                "dataType": "users",
                "displayName": "John Do",
                "givenName": "John",
                "mailNickname": "john.do",
                "objectid": "f162b0a3-0dac-48a5-9ee1-9565d48eae49",
                "securityIdentifier": "S-1-12-1-4049776803-1218776492-1704321438-1236176596",
                "signInSessionsValidFromDateTime": "20241112220528",
                "surname": "Do",
                "userPrincipalName": "john.do@acme.onmicrosoft.com",
                "userType": "Member"
            },
  • Changes: lists all data to be updated
    This section is empty if the ChangeType equals “Deleted”
"Changes": {
                "employeeId": "john.do",
                "groups_add": [
                    {
                        "Id": "42678003",
                        "createdDateTime": "20241112210313",
                        "dataType": "groups",
                        "displayName": "DL_HR",
                        "mail": "DL_HR@eacme.onmicrosoft.com",
                        "mailEnabled": "True",
                        "mailNickname": "DL_HR",
                        "objectid": "c48b03e3-4949-4c19-8d33-9b1c8156e0bd",
                        "renewedDateTime": "20241112210313",
                        "securityEnabled": "True",
                        "securityIdentifier": "S-1-12-1-3297444835-1276725577-479933325-3185596033",
                        "visibility": "Private"
                    }
                ]
            }

The provisioning process

  1. First of all, the script downloads the orders to be executed.
  2. Then, it parses the list of orders and for each one of them it call the function developed in the script.
  3. Within the function the order is processed according to its changeType (Added, Modified, Deleted).

Step by step configuration

Note: In this section we assume that the Resource Type and the Scalar and Navigation rules are already configured.

1 - Write the Script

1.1 - Write common code

Start with section 3 (see the PowerShell script structure above) where you can define common code such as:

  • Reading the options set in the UI
  • Configure authentication if needed (SQL connection, Token, etc)
  • Setup global attributes (Paths, HTTP Header, etc.)

Example:

# Start the script 
# Note: this log is displayed in the NIM's UI Joblog 
Write-Host "Start"

$options = [System.Console]::ReadLine()
$options = ConvertFrom-Json $options

$TenantId = $options.TenantId
$ClientId = $options.ApplicationId
$ClientSecret = $options.ApplicationKey
$MicrosoftGraphPathApi = $options.MicrosoftGraphPathApi

# Get Access Token
$TokenUri = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"

$TokenBody = @{
    client_id     = $ClientId
    client_secret = $ClientSecret
    scope         = "https://graph.microsoft.com/.default"
    grant_type    = "client_credentials"
}

$AccessToken = (Invoke-RestMethod -Method Post -Uri $TokenUri -Body $TokenBody `
        -ContentType "application/x-www-form-urlencoded").access_token

$Headers = @{
    'Authorization' = "Bearer $AccessToken"
    'Content-Type'  = "application/json"
}

1.2 - Write the main function

The main function can be configured in section 2 (see the PowerShell script structure above).

The input parameter for this section is the attribute “$order” in JSON format.

First you need to check the order change type (Added, Modified, Deleted)

You can refer to ‘The provisioning order structure’ paragraph

Example:

if ($order.ChangeType -eq 'Added') {...}

Then, for each type you can define the actions to be done.

Example:

    if ($order.ChangeType -eq 'Added') {
        
        # Create the Application in Entra ID
        $appBody = '{
            "displayName": "' + $displayName + '",
            "description": "' + $order.Changes.description + '",
            "signInAudience": "AzureADMyOrg",

            "web": {
                "homePageUrl": "' + $order.Changes.homepage + '",
                "redirectUris": [
                "' + $order.Changes.loginUrl + '"
                ],
                "logoutUrl": "' + $order.Changes.logoutUrl + '"
            },
        }'

        try {
            $app = Invoke-RestMethod `
                -Method Post `
                -Uri "$MicrosoftGraphPathApi/applications" `
                -Headers $Headers `
                -Body $appBody
        }
        catch {
            Write-Host "Invoke-RestMethod error"
            throw $_.Exception.Message
        }

        # Create the Service Principal
        $accountEnabled = "true"
        if ($order.Changes.accountEnabled -ieq "False") { $accountEnabled = "false" }
        $spBody = '{
            "appId": "' + $app.appId + '",
            "accountEnabled": ' + $accountEnabled + ',
            "notes": "' + $order.Changes.notes + '"
        }'

        try {
            $sp = Invoke-RestMethod `
                -Method Post `
                -Uri "$MicrosoftGraphPathApi/servicePrincipals" `
                -Headers $Headers `
                -Body $spBody
        }
        catch {
            Write-Host "Invoke-RestMethod error"
            throw $_.Exception.Message
        }
    }

Note: In case of an error, you can use the “throw” statement to stop the order execution.
The error message is transmitted to the Joblogs and can be displayed in the job execution UI result.
The script moves, then, to the next order to be processed.

1.3 - Examples

Fulfill-Powershell_SQLUser.ps1 (3.4 KB)

Fulfill-Powershell_EntraIDSPN.ps1 (12.5 KB)

2 - Configure the PowerShell connector

2.1 - Create the connector (skip this part if the connector has already been created)

  • Go to the Configuration section in the Identity Manager UI and select Connectors.

  • In the top right corner, click on the addition icon (+) to create a new connector.

  • Fill in the required fields:

    • Identifier: Unique, starts with a letter, contains only letters, numbers or ”-”.

    • Name: Display name for the connector.

    • Agent: Select the agent that will connect to the target system.

    • Complete job: All checked

    • Incremental job: All checked

  • Click on +Create button

2.2 - Configure the connection

  • Create connections for each script (if more than one).

  • In the Connections section, click on the addition icon “+” to create a new Connection

  • Fill in the required fields:

  • Identifier: Unique, starts with a letter, contains only letters, numbers or `-`.

  • Name: Display name for the connection.

  • Package: click on “Select a Package” then search for the “Custom/PowerShellProv” package and click on “Select

  • Fill in the connection settings

    • Connection Settings

      • Path (.ps1): full or relative path to the PowerShell script (for relative paths the reference folder is Runtime)
    • Fulfill Settings

      • Options: list of the clear text variables to be passed to the script (url, login, etc.).
      • Secured Options: list of encrypted variables to be passed to the script (password, connection string, etc.).
  • Click on the Check Connection button to verify the script path

  • Click on “Create & Close” in the top right

  • Repeat the steps for each script.

Note: This step marks the end of the customization phase. All subsequent configuration steps follow the standard Netwrix Identity Manager configuration process and apply uniformly to all connector types, whether standard, generic, or custom.

2.3 - Configure the Resource Types

Note: At this point we assume that the Entity Types are already created.

  • Create resource types that correspond to your application data model (e.g., DemoApp_StandardAccount, DemoApp_AdministratorAccount, etc.).

  • In the Resource Types section, click on the addition icon to create a new Resource Type

  • Fill in the resource type fields according to Netwrix documentation:
    Create a Resource Type | Netwrix Product Documentation

  • In the Provisioning Connection field of the Fulfill Settings section, select the newly created connection.

  • Click on “Create & Close

  • Click on “Reload” in the top left

  • Repeat the steps for each Resource Type

2.4 - Run the provisioning tasks

  • In the Resource Type section click on “Jobs” then, “All Provisioning Tasks

  • In the left side click on “Job Results” to check the connector’s logs and ensure provisioning completes successfully (A new tab opens).

2 Likes