Documentation menu

Users and Teams

Use Octopus docs with AI

Add Microsoft Entra ID login to users

An example script to add Microsoft Entra ID login details to Octopus user accounts.

Octopus supports a number of external authentication providers, including Microsoft Entra ID Authentication. If you want to use Microsoft Entra ID to authenticate but re-use existing Octopus user accounts, the easiest way is to add an Azure AD login:

Add an Microsoft Entra ID login to an Octopus user

This script will add Microsoft Entra ID login details to Octopus user accounts.

Usage

Provide values for:

  • Octopus URL
  • Octopus API Key
  • A list of users, supplied from either:
    • The path to a CSV file containing user records
    • The Octopus Username, Azure email address and (optionally) Azure display name
  • (Optional) whether or not to update the Octopus user’s email address
  • (Optional) whether or not to update the Octopus user’s display name
  • (Optional) whether or not to continue to the next user if an error occurs
  • (Optional) whether or not to force an update of the Azure AD identity if one already exists
  • (Optional) whether or not to perform a dry run (What If?) and not perform any updates
  • (Optional) whether or not to toggle debug (Verbose) logging

Add Microsoft Entra ID identities to single user

AddAzureADLogins -OctopusURL "https://your-octopus-url/" -OctopusAPIKey "API-YOUR-KEY" -OctopusUsername "OctoUser" -AzureEmailAddress "octouser@exampledomain.com" -AzureDisplayName "Octo User" -ContinueOnError $False -Force $False -WhatIf $False -DebugLogging $False

Add Microsoft Entra ID identities for multiple users from CSV file

AddAzureADLogins -OctopusURL "https://your-octopus-url/" -OctopusAPIKey "API-YOUR-KEY" -Path "/path/to/user_azure_ad_logins.csv" -ContinueOnError $False -Force $False -WhatIf $False -DebugLogging $False

Example CSV file

An example of the expected CSV file format is shown below:

OctopusUsername, AzureEmailAddress, AzureDisplayName
OctoUser, octouser@exampledomain.com, Octo User 

The first row should be the header row containing the following columns:

  • OctopusUsername
  • AzureEmailAddress
  • AzureDisplayName

Script

PowerShell (REST API)
function AddAzureADLogins(
    [Parameter(Mandatory=$True)]
    [String]$OctopusURL,
    [Parameter(Mandatory=$True)]
    [String]$OctopusAPIKey,
    [String]$Path,
    [String]$OctopusUsername,
    [String]$AzureEmailAddress,
    [String]$AzureDisplayName = $null,
    [Boolean]$UpdateOctopusEmailAddress = $False,
    [Boolean]$UpdateOctopusDisplayName = $False,
    [Boolean]$ContinueOnError = $False,
    [Boolean]$Force = $False,
    [Boolean]$WhatIf = $True,
    [Boolean]$DebugLogging = $False
)
{
    Write-Host "OctopusURL: $OctopusURL"
    Write-Host "OctopusAPIKey: ********"
    Write-Host "Path: $Path"
    Write-Host "OctopusUsername: $OctopusUsername"
    Write-Host "AzureEmailAddress: $AzureEmailAddress"
    Write-Host "AzureDisplayName: $AzureDisplayName"
    Write-Host "UpdateOctopusEmailAddress: $UpdateOctopusEmailAddress"
    Write-Host "UpdateOctopusDisplayName: $UpdateOctopusDisplayName"
    Write-Host "ContinueOnError: $ContinueOnError"
    Write-Host "Force: $Force"
    Write-Host "WhatIf: $WhatIf"
    Write-Host "DebugLogging: $DebugLogging"
    Write-Host $("=" * 60)
    Write-Host

    if (-not [string]::IsNullOrWhiteSpace($OctopusURL)) {
        $OctopusURL = $OctopusURL.TrimEnd('/')
    }

    if($DebugLogging -eq $True) {
        $DebugPreference = "Continue"
    }

    $header = @{ "X-Octopus-ApiKey" = $octopusAPIKey }
    $usersToUpdate = @()
    $recordsUpdated = 0
    # Validate we have minimum required details.
    if ([string]::IsNullOrWhiteSpace($Path) -eq $true) {
        if([string]::IsNullOrWhiteSpace($OctopusUsername) -eq $true -or [string]::IsNullOrWhiteSpace($AzureEmailAddress) -eq $true) {
            Write-Warning "Path not supplied. OctopusUsername or AzureEmailAddress are either null, or an empty string."
            return
        }
        $usersToUpdate += [PSCustomObject]@{
            OctopusUsername = $OctopusUsername
            AzureEmailAddress = $AzureEmailAddress
            AzureDisplayName = $AzureDisplayName
        }
    }
    else {
        # Validate path 
        if(-not (Test-Path $Path)) {
            Write-Warning "Path '$Path' not found. Does a file exist at that location?"
            return
        }

        $usersToUpdate = Import-Csv -Path $Path -Delimiter ","
    }

    # Check if we have any users. If we do, get existing octopus users
    if($usersToUpdate.Count -gt 0) {
        Write-Host "Users to update: $($usersToUpdate.Count)"
        $ExistingOctopusUsers = @()
        $response = $null
        do {
            $uri = if ($response) { $octopusURL + $response.Links.'Page.Next' } else { "$OctopusURL/api/users" }
            $response = Invoke-RestMethod -Method Get -Uri $uri -Headers $header
            $ExistingOctopusUsers += $response.Items
        } while ($response.Links.'Page.Next')

        Write-Debug "Found $($ExistingOctopusUsers.Count) existing Octopus users"
    }
    else {
        Write-Host "No users to update, exiting."
        return
    }
    
    if($ExistingOctopusUsers.Count -le 0) {
        Write-Warning "No users found in Octopus, exiting."
        return
    }

    foreach($user in $usersToUpdate)
    {
        Write-Host "Working on user $($User.OctopusUsername)"
        try {
            $existingOctopusUser = $ExistingOctopusUsers | Where-Object {$_.Username -eq $user.OctopusUsername} | Select-Object -First 1
            if($null -ne $ExistingOctopusUser) {
                Write-Debug "Found matching octopus user for $($user.OctopusUsername)"
                # Check if its a service account
                if($user.IsService -eq $True) {
                    Write-Debug "User $($user.OctopusUsername) is a Service account. This user won't be updated..."
                    continue
                }
                # Check if its an active account
                if($user.IsActive -eq $False) {
                    Write-Debug "User $($user.OctopusUsername) is an inactive account. This user won't be updated..."
                    continue
                }

                # Check for existing Microsoft Entra ID Identity first.
                $azureAdIdentity = $existingOctopusUser.Identities | Where-Object {$_.IdentityProviderName -eq "Azure AD"} | Select-Object -First 1
                if($null -ne $azureAdIdentity) {
                    Write-Debug "Found existing Microsoft Entra ID login for user $($user.OctopusUsername)"
                    if($Force -eq $True) {
                        Write-Debug "Force set to true. Replacing existing Microsoft Entra ID Claims for Display Name and Email for user $($user.OctopusUsername)"
                        $azureAdIdentity.Claims.email.Value = $User.AzureEmailAddress
                        $azureAdIdentity.Claims.dn.Value = $User.AzureDisplayName
                    }
                    else {
                        Write-Debug "Force set to false. Skipping replacing existing Microsoft Entra ID Claims for Display Name and Email for user $($user.OctopusUsername)"
                    }
                }
                else {
                    Write-Debug "No existing Microsoft Entra ID login found for user $($user.OctopusUsername), creating new"
                    $newAzureADIdentity = @{
                        IdentityProviderName = "Azure AD"
                        Claims = @{
                            email = @{
                                Value = $User.AzureEmailAddress
                                IsIdentifyingClaim = $True
                            }
                            dn = @{
                                Value = $User.AzureDisplayName
                                IsIdentifyingClaim = $False
                            }
                        }
                    }
                    $existingOctopusUser.Identities += $newAzureADIdentity
                }

                # Update user's email address if set AND the value isn't empty.
                if($UpdateOctopusEmailAddress -eq $True -and -not([string]::IsNullOrWhiteSpace($User.AzureEmailAddress) -eq $true)) {
                    Write-Debug "Setting Octopus email address to: $($User.AzureEmailAddress)"
                    $existingOctopusUser.EmailAddress = $User.AzureEmailAddress
                }

                 # Update user's display name if set AND the value isn't empty.
                 if($UpdateOctopusDisplayName -eq $True -and -not([string]::IsNullOrWhiteSpace($User.AzureDisplayName) -eq $true)) {
                    Write-Debug "Setting Octopus display name to: $($User.AzureDisplayName)"
                    $existingOctopusUser.DisplayName = $User.AzureDisplayName
                }

                $userJsonPayload = $($existingOctopusUser | ConvertTo-Json -Depth 10)

                if($WhatIf -eq $True) {
                    Write-Host "What If set to true, skipping update for user $($User.OctopusUsername). For details of the payload, set DebugLogging to True"
                    Write-Debug "Would have done a POST to $OctopusUrl/api/users/$($existingOctopusUser.Id) with body:"
                    Write-Debug $userJsonPayload
                } 
                else {
                    Write-Host "Updating the user $($User.OctopusUsername) in Octopus Deploy"
                    Invoke-RestMethod -Method PUT -Uri "$OctopusUrl/api/users/$($existingOctopusUser.Id)" -Headers $header -Body $userJsonPayload | Out-Null
                    $recordsUpdated += 1
                }
            }
            else {
                Write-Warning "No match found for an existing octopus user with Username: $($User.OctopusUsername)"
            }
        }
        catch {
            If($ContinueOnError -eq $true) {
                Write-Warning "Error encountered updating $($User.OctopusUsername): $($_.Exception.Message), continuing..."
                continue
            }
            else {
                throw
            }
        }
    }
    Write-Host "Updated $($recordsUpdated) user records."
}
PowerShell (Octopus.Client)
# Load assembly
Add-Type -Path 'path:\to\Octopus.Client.dll'

function AddAzureLogins
(
    [Parameter(Mandatory=$True)]
    [String]$OctopusURL,
    [Parameter(Mandatory=$True)]
    [String]$OctopusAPIKey,
    [String]$Path,
    [String]$OctopusUsername,
    [String]$AzureEmailAddress,
    [String]$AzureDisplayName = $null,
    [Boolean]$UpdateOctopusEmailAddress = $False,
    [Boolean]$UpdateOctopusDisplayName = $False,
    [Boolean]$ContinueOnError = $False,
    [Boolean]$Force = $False,
    [Boolean]$WhatIf = $True,
    [Boolean]$DebugLogging = $False
)
{
    Write-Host "OctopusURL: $OctopusURL"
    Write-Host "OctopusAPIKey: ********"
    Write-Host "Path: $Path"
    Write-Host "OctopusUsername: $OctopusUsername"
    Write-Host "AzureEmailAddress: $AzureEmailAddress"
    Write-Host "AzureDisplayName: $AzureDisplayName"
    Write-Host "UpdateOctopusEmailAddress: $UpdateOctopusEmailAddress"
    Write-Host "UpdateOctopusDisplayName: $UpdateOctopusDisplayName"
    Write-Host "ContinueOnError: $ContinueOnError"
    Write-Host "Force: $Force"
    Write-Host "WhatIf: $WhatIf"
    Write-Host "DebugLogging: $DebugLogging"
    Write-Host $("=" * 60)
    Write-Host

    if (-not [string]::IsNullOrWhiteSpace($OctopusURL)) {
        $OctopusURL = $OctopusURL.TrimEnd('/')
    }

    if($DebugLogging -eq $True) {
        $DebugPreference = "Continue"
    }

    
    $endpoint = New-Object Octopus.Client.OctopusServerEndpoint($OctopusURL, $OctopusAPIKey)
    $repository = New-Object Octopus.Client.OctopusRepository($endpoint)
    $client = New-Object Octopus.Client.OctopusClient($endpoint)

    $usersToUpdate = @()
    $recordsUpdated = 0
    # Validate we have minimum required details.
    if ([string]::IsNullOrWhiteSpace($Path) -eq $true) {
        if([string]::IsNullOrWhiteSpace($OctopusUsername) -eq $true -or [string]::IsNullOrWhiteSpace($AzureEmailAddress) -eq $true) {
            Write-Warning "Path not supplied. OctopusUsername or AzureEmailAddress are either null, or an empty string."
            return
        }
        $usersToUpdate += [PSCustomObject]@{
            OctopusUsername = $OctopusUsername
            AzureEmailAddress = $AzureEmailAddress
            AzureDisplayName = $AzureDisplayName
        }
    }
    else {
        # Validate path 
        if(-not (Test-Path $Path)) {
            Write-Warning "Path '$Path' not found. Does a file exist at that location?"
            return
        }

        $usersToUpdate = Import-Csv -Path $Path -Delimiter ","
    }
    
    # Check if we have any users. If we do, get existing octopus users
    if($usersToUpdate.Count -gt 0) {
        Write-Host "Users to update: $($usersToUpdate.Count)"
        $ExistingOctopusUsers = @()

        # Loop through users
        foreach ($user in $usersToUpdate)
        {
            # Retrieve user account from Octopus
            Write-Host "Searching Octopus users for $($user.OctopusUsername) ..."
            $existingOctopusUser = $client.Repository.Users.FindByUsername($user.OctopusUsername)
            
            # Check for null
            if ($null -ne $existingOctopusUser)
            {
                # Check user types
                if ($existingOctopusUser.IsService)
                {
                    # This is a service account and will not be updated
                    Write-Warning "$($user.OctopusUsername) is a service account, skipping ..."

                    continue
                }

                if ($existingOctopusUser.IsActive -eq $False)
                {
                    # Inactive user skipping
                    Write-Warning "$($user.OctopusUsername) is an inactive account, skipping ..."

                    continue
                }

                # Check to see if there's already an Microsoft Entra ID identity
                $azureAdIdentity = $existingOctopusUser.Identities | Where-Object {$_.IdentityProviderName -eq "Azure AD"}
                if($null -ne $azureAdIdentity) 
                {
                    Write-Debug "Found existing Microsoft Entra ID login for user $($user.OctopusUsername)"
                    if($Force -eq $True) 
                    {
                        Write-Debug "Force set to true. Replacing existing Microsoft Entra ID Claims for Display Name and Email for user $($user.OctopusUsername)"
                        $azureAdIdentity.Claims.email.Value = $User.AzureEmailAddress
                        $azureAdIdentity.Claims.dn.Value = $User.AzureDisplayName
                    }
                    else 
                    {
                        Write-Warning "Force set to false. Skipping replacing existing Microsoft Entra ID Claims for Display Name and Email for user $($user.OctopusUsername)"
                    }
                }
                else 
                {
                    Write-Debug "No existing Microsoft Entra ID login found for user $($user.OctopusUsername), creating new"
                    $newAzureADIdentity = New-Object Octopus.Client.Model.IdentityResource
                    $newAzureADIdentity.IdentityProviderName = "Azure AD"
                    
                    $newEmailClaim = New-Object Octopus.Client.Model.IdentityClaimResource
                    $newEmailClaim.IsIdentifyingClaim = $True
                    $newEmailClaim.Value = $user.AzureEmailAddress

                    $newAzureADIdentity.Claims.Add("email", $newEmailClaim) # Claims is a Dictionary object

                    $newDisplayClaim = New-Object Octopus.Client.Model.IdentityClaimResource
                    $newDisplayClaim.IsIdentifyingClaim = $False
                    $newDisplayClaim.Value = $user.AzureDisplayName

                    $newAzureADIdentity.Claims.Add("dn", $newDisplayClaim)

                    $existingOctopusUser.Identities += $newAzureADIdentity # Identities is an array
                }

                # Update user's email address if set AND the value isn't empty.
                if($UpdateOctopusEmailAddress -eq $True -and -not([string]::IsNullOrWhiteSpace($User.AzureEmailAddress) -eq $true)) 
                {
                    Write-Debug "Setting Octopus email address to: $($User.AzureEmailAddress)"
                    $existingOctopusUser.EmailAddress = $User.AzureEmailAddress
                }

                # Update user's display name if set AND the value isn't empty.
                if($UpdateOctopusDisplayName -eq $True -and -not([string]::IsNullOrWhiteSpace($User.AzureDisplayName) -eq $true)) 
                {
                    Write-Debug "Setting Octopus display name to: $($User.AzureDisplayName)"
                    $existingOctopusUser.DisplayName = $User.AzureDisplayName
                }

                if($WhatIf -eq $True) 
                {
                    Write-Host "What If set to true, skipping update for user $($User.OctopusUsername). For details of the payload, set DebugLogging to True"
                    Write-Debug "Would have done a POST to $OctopusUrl/api/users/$($existingOctopusUser.Id) with body:"
                    Write-Debug $userJsonPayload
                } 
                else 
                {
                    Write-Host "Updating the user $($User.OctopusUsername) in Octopus Deploy"
                    $client.Repository.Users.Modify($existingOctopusUser)
                    $recordsUpdated += 1
                }
            }
            else
            {
                # User not found
                Write-Warning "$($user.OctopusUsername) not found!"
            }
        }
        Write-Debug "Found $($ExistingOctopusUsers.Count) existing Octopus users"
    }
    else {
        Write-Host "No users to update, exiting."
        return
    }
}
C#
#r "nuget: Octopus.Client"

using Octopus.Client;
using Octopus.Client.Model;
using System.Linq;

public class UserToUpdate
{
    public string OctopusUserName
    {
        get;
        set;
    }

    public string AzureEmailAddress
    {
        get;
        set;
    }

    public string AzureDisplayName
    {
        get;
        set;
    }
}

public static void AddAzureLogins(string OctopusUrl, string ApiKey, string Path = "", string OctopusUserName = "", string AzureEmailAddress = "", string AzureDisplayName = "", bool UpdateOctopusEmail = false, bool UpdateOctopusDisplayName = false, bool Force = false, bool WhatIf = false)
{
    // Display passed in information
    Console.WriteLine(string.Format("OctopusURL: {0}", OctopusUrl));
    Console.WriteLine("OctopusAPIKey: ****");
    Console.WriteLine(string.Format("OctopusUsername: {0}", OctopusUserName));
    Console.WriteLine(string.Format("AzureEmailAddress: {0}", AzureEmailAddress));
    Console.WriteLine(string.Format("AzureDisplayName: {0}", AzureDisplayName));
    Console.WriteLine(string.Format("UpdateOctopusEmailAddress: {0}", UpdateOctopusEmail.ToString()));
    Console.WriteLine(string.Format("UpdateOctopusDisplayName: {0}", UpdateOctopusDisplayName.ToString()));
    Console.WriteLine(string.Format("Force: {0}", Force.ToString()));
    Console.WriteLine(string.Format("WhatIf: {0}", WhatIf.ToString()));

    // Check to see url is empty
    if (!string.IsNullOrWhiteSpace(OctopusUrl))
    {
        // Remove trailing /
        OctopusUrl = OctopusUrl.TrimEnd('/');
    }

    // Create Octopus.Client objects
    var endpoint = new Octopus.Client.OctopusServerEndpoint(OctopusUrl, ApiKey);
    var repository = new Octopus.Client.OctopusRepository(endpoint);
    var client = new Octopus.Client.OctopusClient(endpoint);

    // Declare collection of users to update
    var usersToUpdate = new System.Collections.Generic.List<UserToUpdate>();

    // Test to see if path was provided
    if (string.IsNullOrWhiteSpace(Path))
    {
        if (!string.IsNullOrWhiteSpace(OctopusUserName) || !string.IsNullOrWhiteSpace(AzureEmailAddress))
        {
            // Create new user to update object
            var userToUpdate = new UserToUpdate();
            userToUpdate.AzureDisplayName = AzureDisplayName;
            userToUpdate.AzureEmailAddress = AzureEmailAddress;
            userToUpdate.OctopusUserName = OctopusUserName;

            // Add to collection
            usersToUpdate.Add(userToUpdate);
        }
    }
    else
    {
        // Read from csv
        using (var reader = new System.IO.StreamReader(Path))
        {
            while (!reader.EndOfStream)
            {
                var line = reader.ReadLine();
                var columns = line.Split(',');

                // Create new user to update object
                var userToUpdate = new UserToUpdate();
                userToUpdate.AzureDisplayName = columns[0];
                userToUpdate.AzureEmailAddress = columns[1];
                userToUpdate.OctopusUserName = columns[2];

                // Add to collection
                usersToUpdate.Add(userToUpdate);
            }
        }
    }

    // Check to see if we have anything to update
    if (usersToUpdate.Count > 0)
    {
        Console.WriteLine(string.Format("Users to update: {0}", usersToUpdate.Count));

        // Loop through collection
        foreach (var userToUpdate in usersToUpdate)
        {
            Console.WriteLine(string.Format("Searching for user {0}", userToUpdate.OctopusUserName));
            var existingOctopusUser = client.Repository.Users.FindByUsername(userToUpdate.OctopusUserName);

            // Check to see if something was returned
            if (null != existingOctopusUser)
            {
                // Check to see if it is a service account
                if (existingOctopusUser.IsService)
                {
                    Console.WriteLine(string.Format("{0} is a service account, skipping ...", userToUpdate.OctopusUserName));
                    continue;
                }

                // Check to see if user is active
                if (!existingOctopusUser.IsActive)
                {
                    Console.WriteLine(string.Format("{0} is not an active account, skipping ...", userToUpdate.OctopusUserName));
                }

                // Get existing Microsoft Entra ID identity, if exists
                var azureAdIdentity = existingOctopusUser.Identities.FirstOrDefault(i => i.IdentityProviderName == "Azure AD");

                // Check to see if something was returned
                if(null != azureAdIdentity)
                {
                    // Check to see if force update was set
                    if (Force)
                    {
                        Console.WriteLine(string.Format("Force set to true, replacing existing entries for {0}", userToUpdate.OctopusUserName));
                        azureAdIdentity.Claims["email"].Value = userToUpdate.AzureEmailAddress;
                        azureAdIdentity.Claims["dn"].Value = userToUpdate.AzureDisplayName;
                    }
                }
                else
                {
                    Console.WriteLine(string.Format("No existing AzureAD login found for user {0}", userToUpdate.OctopusUserName));

                    // Create new octopus objects
                    var newAzureIdentity = new Octopus.Client.Model.IdentityResource();
                    newAzureIdentity.IdentityProviderName = "Azure AD";

                    var newEmailClaim = new Octopus.Client.Model.IdentityClaimResource();
                    newEmailClaim.IsIdentifyingClaim = true;
                    newEmailClaim.Value = userToUpdate.AzureEmailAddress;

                    newAzureIdentity.Claims.Add("email", newEmailClaim);

                    var newDisplayNameClaim = new Octopus.Client.Model.IdentityClaimResource();
                    newDisplayNameClaim.IsIdentifyingClaim = false;
                    newDisplayNameClaim.Value = userToUpdate.AzureDisplayName;

                    newAzureIdentity.Claims.Add("dn", newDisplayNameClaim);

                    // Add identity object to user
                    var identityCollection = new System.Collections.Generic.List<Octopus.Client.Model.IdentityResource>(existingOctopusUser.Identities);
                    identityCollection.Add(newAzureIdentity);
                    existingOctopusUser.Identities = identityCollection.ToArray();
                }

                if (UpdateOctopusDisplayName && !string.IsNullOrWhiteSpace(userToUpdate.AzureDisplayName))
                {
                    Console.WriteLine(string.Format("Setting Octopus Display Name to: {0}", userToUpdate.AzureDisplayName));
                    existingOctopusUser.DisplayName = userToUpdate.AzureDisplayName;
                }

                if (UpdateOctopusEmail && !string.IsNullOrWhiteSpace(userToUpdate.AzureEmailAddress))
                {
                    Console.WriteLine(string.Format("Setting Octopus Email Address to: {0}", userToUpdate.AzureEmailAddress));
                    existingOctopusUser.EmailAddress = userToUpdate.AzureEmailAddress;
                }

                if (WhatIf)
                {
                    Console.WriteLine(string.Format("WhatIf is set to true, skipping update of user: {0}", userToUpdate.OctopusUserName));
                    Console.WriteLine(existingOctopusUser);
                }
                else
                {
                    // Update account
                    Console.WriteLine(string.Format("Updating: {0}", userToUpdate.OctopusUserName));
                    client.Repository.Users.Modify(existingOctopusUser);
                }
            }
        }
    }
}
Python3
import json
import requests
import csv

# Define class
class userToUpdate:
    OctopusUsername = ''
    AzureEmailAddress = ''
    AzureDisplayName = ''

# Define Octopus server variables
octopus_server_uri = 'https://your-octopus-url'
octopus_api_key = 'API-YOUR-KEY'


# Create function
def AddAzureLogins(OctopusUrl, 
OctopusAPIKey, Path='', 
OctopusUsername='', 
AzureEmailAddress='', 
AzureDisplayName='',
UpdateOctopusEmailAddress=False, 
UpdateOctopusDisplayName=False, 
Force=False, 
WhatIf=False):
    # Display values passed into function
    print ("OctopusURL: ", OctopusUrl)
    print ("OctopusAPIKey: ", "*******")
    print ("Path: ", Path)
    print ("OctopusUsername: ", OctopusUsername)
    print ("AzureEmailAddress: ", AzureEmailAddress)
    print ("AzureDisplayName: ", AzureDisplayName)
    print ("UpdateOctopusEmailAddress", UpdateOctopusEmailAddress)
    print ("UpdateOctopusDisplayName: ", UpdateOctopusDisplayName)  

    headers = {'X-Octopus-ApiKey': OctopusAPIKey}
    usersToUpdate = []

    if Path:
        # Write something to do extraction
        with open(Path) as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=',')
            for row in csv_reader:
                updateUser = userToUpdate()
                updateUser.AzureDisplayName = row[0]
                updateUser.AzureEmailAddress = row[1]
                updateUser.OctopusUsername = row[3]

                usersToUpdate.append(updateUser)
    else:
        updateUser = userToUpdate()
        updateUser.AzureDisplayName = AzureDisplayName
        updateUser.AzureEmailAddress = AzureEmailAddress
        updateUser.OctopusUsername = OctopusUsername

        usersToUpdate.append(updateUser)

    # Gather users from instance
    existingUsers = []
    uri = '{0}/users'.format(OctopusUrl)
    response = requests.get(uri, headers=headers)
    response.raise_for_status()

    # Decode content
    results = json.loads(response.content.decode('utf-8'))
    existingUsers += results["Items"]

    # Loop through remaining results
    while ("Page.Next" in results["Links"]):
            response = requests.get(uri, headers=headers)
            response.raise_for_status()

            # Decode content
            results = json.loads(response.content.decode('utf-8'))
            existingUsers += results["Items"]
    for user in usersToUpdate:
        # Search for user
        existingUser = next((u for u in existingUsers if u["Username"] == user.OctopusUsername), None)
        
        if (existingUser != None):
            print(existingUser)
            # Check to see if user is a service account
            if (existingUser["IsService"] == True):
                print (f"User {user.OctopusUsername} is a service account, skipping ...")
                continue

            if (existingUser["IsActive"] == False):
                print (f"User {user.OctopusUsername} is inactive, skipping...")
                continue

            if (existingUser["Identities"] != None):
                azureAdIdentity = next((u for u in existingUser["Identities"] if u["IdentityProviderName"] == "Azure AD"), None)
                
                if (azureAdIdentity != None):
                    print (f"Found existing Microsoft Entra ID identity for {user.OctopusUsername} ...")
                    
                    if(Force):
                        print("Force is set to true, overwriting values")
                        azureAdIdentity["Claims"]["email"]["Value"] = user.AzureEmailAddress
                        azureAdIdentity["Claims"]["dn"]["Value"] = user.AzureDisplayName
                    else:
                        print("Force is set to false, skipping...")
                        continue
                else:
                    # Create new Identity
                    newIdentity = {
                        'IdentityProviderName': 'Azure AD',
                        'Claims': {
                            'email': {
                                'Value': user.AzureEmailAddress,
                                'IsIdentifyingClaim': True
                            },
                            'dn':{
                                'Value': user.AzureDisplayName,
                                'IsIdentifyingClaim': False
                            }
                        }
                    }
                    existingUser["Identities"].append(newIdentity)

            if (UpdateOctopusEmailAddress):
                existingUser["EmailAddress"] = user.AzureEmailAddress

            if (UpdateOctopusDisplayName):
                existingUser["DisplayName"] = user.AzureDisplayName

            # Update the user account
            uri = '{0}/users/{1}'.format(OctopusUrl, existingUser['Id'])
            response = requests.put(uri, headers=headers, json=existingUser)
            response.raise_for_status()
    return


AddAzureLogins(octopus_server_uri, octopus_api_key, OctopusUsername='some.email@microsoft.com', AzureDisplayName='DisplayName', AzureEmailAddress='some.email@microsoft.com', Force=True )
Go
package main

import (
	"fmt"
	"log"
	"os"

	"net/url"

	"github.com/OctopusDeploy/go-octopusdeploy/octopusdeploy"

	"encoding/csv"
	"io"
)

type User struct {
	OctopusUsername   string
	AzureEmailAddress string
	AzureDisplayName  string
}

func main() {

	apiURL, err := url.Parse("https://your-octopus-url")
	if err != nil {
		log.Println(err)
	}
	APIKey := "API-YOUR-KEY"

	Path := ""
	Users := []User{}
	OctopusUsername := ""
	AzureEmailAddress := ""
	AzureDisplayName := ""
	OverwriteEmailAddress := false
	OverwriteDisplayName := false

	if Path != "" {
		Users = GetCSVData(Path)
	} else {
		u := User{OctopusUsername: OctopusUsername, AzureEmailAddress: AzureEmailAddress, AzureDisplayName: AzureDisplayName}
		Users = append(Users, u)
	}

	for i := 0; i < len(Users); i++ {
		// Get existing user account
		existingUser := GetUser(apiURL, APIKey, Users[i].OctopusUsername)

		// Check to see if something was returned
		if existingUser != nil {
			fmt.Println("Found " + existingUser.Username)

			// Check to see if it has an identity
			if existingUser.Identities != nil {
				identityIndex := -1
				// Loop through Identities collection
				for j := 0; j < len(existingUser.Identities); j++ {
					if existingUser.Identities[i].IdentityProviderName == "Azure AD" {
						fmt.Println("User has existing Microsoft Entra ID identity")
						identityIndex = j
						break
					}
				}

				if identityIndex > -1 {
					if OverwriteDisplayName {
						existingUser.DisplayName = Users[i].AzureDisplayName
					}

					if OverwriteEmailAddress {
						existingUser.EmailAddress = Users[i].AzureEmailAddress
					}

				} else {
					// Create new identity object
					claimsCollection := make(map[string]octopusdeploy.IdentityClaim)
					emailClaim := octopusdeploy.IdentityClaim{Value: Users[i].AzureEmailAddress, IsIdentifyingClaim: true}
					displayNameClaim := octopusdeploy.IdentityClaim{Value: Users[i].AzureDisplayName, IsIdentifyingClaim: false}
					claimsCollection["email"] = emailClaim
					claimsCollection["dn"] = displayNameClaim
					octopusIdentity := octopusdeploy.Identity{IdentityProviderName: "Azure AD", Claims: claimsCollection}

					// Add new identity
					existingUser.Identities = append(existingUser.Identities, octopusIdentity)
				}

				// Update user account
				client := octopusAuth(apiURL, APIKey, "")
				existingUser, err = client.Users.Update(existingUser)

				if err != nil {
					log.Println(err)
				}
			}
		}
	}
}

func octopusAuth(octopusURL *url.URL, APIKey, space string) *octopusdeploy.Client {
	client, err := octopusdeploy.NewClient(nil, octopusURL, APIKey, space)
	if err != nil {
		log.Println(err)
	}

	return client
}

func GetUser(octopusURL *url.URL, APIKey string, OctopusUserName string) *octopusdeploy.User {
	// Get client
	client := octopusAuth(octopusURL, APIKey, "")

	// Get user account
	userQuery := octopusdeploy.UsersQuery{
		Filter: OctopusUserName,
	}

	userAccounts, err := client.Users.Get(userQuery)

	if err != nil {
		log.Println(err)
	}

	for i := 0; i < len(userAccounts.Items); i++ {
		// Check to see if it's a match
		if userAccounts.Items[i].Username == OctopusUserName {
			return userAccounts.Items[i]
		}
	}

	return nil
}

func GetCSVData(Path string) []User {
	recordFile, err := os.Open(Path)

	if err != nil {
		log.Println(err)
	}

	Users := []User{}

	reader := csv.NewReader(recordFile)
	reader.Comma = ','

	for i := 0; ; i++ {
		record, err := reader.Read()

		if err == io.EOF {
			break
		}

		userAccount := User{OctopusUsername: record[0], AzureEmailAddress: record[1], AzureDisplayName: record[2]}

		Users = append(Users, userAccount)
	}

	return Users
}