Functions/Add-AzVMUserAssignedIdentity.ps1

function Add-AzVMUserAssignedIdentity {
    <#
    .SYNOPSIS
        Adds a user-assigned managed identity to an Azure Virtual Machine.
 
    .DESCRIPTION
        This function adds a user-assigned managed identity to an Azure Virtual Machine.
        The identity must already exist before adding it to the VM.
 
    .PARAMETER VMName
        The name of the Virtual Machine.
 
    .PARAMETER ResourceGroupName
        The name of the Resource Group containing the Virtual Machine.
 
    .PARAMETER IdentityName
        The name of the user-assigned managed identity to add.
 
    .PARAMETER IdentityResourceGroup
        The name of the Resource Group containing the managed identity.
        If not specified, uses the VM's resource group.
 
    .PARAMETER IdentityResourceId
        Alternative parameter: The full resource ID of the user-assigned managed identity.
        Use this instead of IdentityName if you have the full resource ID.
 
    .PARAMETER AzSubscription
        Optional. The Azure subscription to target. If not specified, uses the current context.
 
    .EXAMPLE
        Add-AzVMUserAssignedIdentity -VMName "MyVM" -ResourceGroupName "MyResourceGroup" -IdentityName "MyIdentity"
 
    .EXAMPLE
        Add-AzVMUserAssignedIdentity -VMName "MyVM" -ResourceGroupName "MyRG" -IdentityName "MyIdentity" -IdentityResourceGroup "IdentityRG"
 
    .EXAMPLE
        Add-AzVMUserAssignedIdentity -VMName "MyVM" -ResourceGroupName "MyRG" -IdentityResourceId "/subscriptions/.../resourceGroups/.../providers/Microsoft.ManagedIdentity/userAssignedIdentities/MyIdentity"
 
    .INPUTS
        Input is from command line or called from a script.
 
    .OUTPUTS
        Outputs the updated VM object to the pipeline.
 
    .NOTES
        Author: Lars Panzerbjørn
        Creation Date: 2026.01.23
    #>


    [CmdletBinding(DefaultParameterSetName = 'ByName')]
    param(
        [Parameter(
            Mandatory,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Name of the Virtual Machine')]
        [Alias('VM')]
        [string]$VMName,

        [Parameter(
            Mandatory,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Which Azure resource group would you like to target?')]
        [Alias('AzRG')]
        [string]$ResourceGroupName,

        [Parameter(
            Mandatory,
            ParameterSetName = 'ByName',
            HelpMessage = 'Name of the user-assigned managed identity')]
        [string]$IdentityName,

        [Parameter(
            ParameterSetName = 'ByName',
            HelpMessage = 'Resource group containing the managed identity')]
        [string]$IdentityResourceGroup,

        [Parameter(
            Mandatory,
            ParameterSetName = 'ByResourceId',
            HelpMessage = 'Full resource ID of the user-assigned managed identity')]
        [string]$IdentityResourceId,

        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Which Azure subscription would you like to target?')]
        [Alias('AzSub')]
        [string]$AzSubscription
    )

    BEGIN{
        Write-Verbose "Beginning $($MyInvocation.Mycommand)"

        # Check if Az.Compute module is available
        IF(-not (Get-Module -ListAvailable -Name Az.Compute)) {
            throw "Az.Compute module is not installed. Please install it using: Install-Module -Name Az.Compute"
        }

        # Check if Az.ManagedServiceIdentity module is available
        IF(-not (Get-Module -ListAvailable -Name Az.ManagedServiceIdentity)) {
            throw "Az.ManagedServiceIdentity module is not installed. Please install it using: Install-Module -Name Az.ManagedServiceIdentity"
        }

        # Import the modules if not already loaded
        IF(-not (Get-Module -Name Az.Compute)) {
            Import-Module Az.Compute
        }
        IF(-not (Get-Module -Name Az.ManagedServiceIdentity)) {
            Import-Module Az.ManagedServiceIdentity
        }
    }

    PROCESS{
        Write-Verbose "Processing $($MyInvocation.Mycommand)"

        TRY{
            # Set subscription context if specified
            IF($AzSubscription) {
                Write-Verbose "Setting Azure context to subscription: $AzSubscription"
                Set-AzContext -Subscription $AzSubscription | Out-Null
            }

            # Get the VM
            Write-Verbose "Retrieving Virtual Machine: $VMName from Resource Group: $ResourceGroupName"
            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -ErrorAction Stop

            Write-Host "`nVirtual Machine: $($VM.Name)" -ForegroundColor Cyan
            Write-Host "Resource Group: $($VM.ResourceGroupName)" -ForegroundColor Cyan
            Write-Host "Location: $($VM.Location)" -ForegroundColor Cyan

            # Determine the identity resource ID
            IF($PSCmdlet.ParameterSetName -eq 'ByName') {
                # If IdentityResourceGroup is not specified, use VM's resource group
                IF(-not $IdentityResourceGroup) {
                    $IdentityResourceGroup = $ResourceGroupName
                    Write-Verbose "Using VM's resource group for identity: $IdentityResourceGroup"
                }

                # Get the user-assigned identity
                Write-Verbose "Retrieving managed identity: $IdentityName from Resource Group: $IdentityResourceGroup"
                $Identity = Get-AzUserAssignedIdentity -ResourceGroupName $IdentityResourceGroup -Name $IdentityName -ErrorAction Stop
                $IdentityResourceId = $Identity.Id
            }
            else {
                # Validate the provided resource ID
                Write-Verbose "Using provided identity resource ID: $IdentityResourceId"
            }

            Write-Host "`nAdding user-assigned identity..." -ForegroundColor Yellow
            Write-Host "Identity Resource ID: $IdentityResourceId" -ForegroundColor Gray

            # Check if identity is already assigned
            IF($VM.Identity -and $VM.Identity.UserAssignedIdentities) {
                IF($VM.Identity.UserAssignedIdentities.ContainsKey($IdentityResourceId)) {
                    Write-Host "`nThis identity is already assigned to the VM." -ForegroundColor Yellow
                    return $VM
                }
            }

            # Update the VM with the new identity
            IF(-not $VM.Identity) {
                # No identity exists, create UserAssigned identity type
                Write-Verbose "VM has no identity, creating UserAssigned identity type"
                $VM = Update-AzVM -ResourceGroupName $ResourceGroupName -VM $VM -IdentityType UserAssigned -IdentityId $IdentityResourceId -ErrorAction Stop
            }
            ELSEIF($VM.Identity.Type -eq "SystemAssigned") {
                # VM has system-assigned identity, change to SystemAssigned,UserAssigned
                Write-Verbose "VM has SystemAssigned identity, changing to SystemAssigned,UserAssigned"
                $VM = Update-AzVM -ResourceGroupName $ResourceGroupName -VM $VM -IdentityType "SystemAssigned,UserAssigned" -IdentityId $IdentityResourceId -ErrorAction Stop
            }
            else {
                # VM already has user-assigned identities, add to the collection
                Write-Verbose "Adding identity to existing user-assigned identities"
                $VM = Update-AzVM -ResourceGroupName $ResourceGroupName -VM $VM -IdentityType UserAssigned -IdentityId $IdentityResourceId -ErrorAction Stop
            }

            Write-Host "`nSuccessfully added user-assigned identity to VM: $VMName" -ForegroundColor Green

            # Get updated VM to display current identities
            $UpdatedVM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName

            IF($UpdatedVM.Identity -and $UpdatedVM.Identity.UserAssignedIdentities) {
                Write-Host "`nCurrent User-Assigned Identities ($($UpdatedVM.Identity.UserAssignedIdentities.Count)):" -ForegroundColor Cyan
                foreach ($identity in $UpdatedVM.Identity.UserAssignedIdentities.GetEnumerator()) {
                    $identityId = $identity.Key
                    $idParts = $identityId -split '/'
                    $identityName = $idParts[8]
                    Write-Host " - $identityName" -ForegroundColor Yellow
                    Write-Host " Principal ID: $($identity.Value.PrincipalId)" -ForegroundColor Gray
                    Write-Host " Client ID: $($identity.Value.ClientId)" -ForegroundColor Gray
                }
            }

            return $UpdatedVM
        }
        CATCH{
            Write-Error "Error adding user-assigned identity to VM: $($_.Exception.Message)"
            throw
        }
    }

    END{
        Write-Verbose "Ending $($MyInvocation.Mycommand)"
    }
}