Author : MD TAREQ HASSAN | Updated : 2021/10/12

What is Service Principal

From Microsoft Doc.
“An Azure Active Directory (Azure AD) service principal is the local representation of an application object in a single tenant or directory. ‎ It functions as the identity of the application instance. Service principals define who can access the application, and what resources the application can access”

Creating Service Principal

Application secret

Role

Create utility class: azure_utility.ps1

#
#
# Azure Utility Class
#
#
class AzureUtility {

    static [void] LoginAndSetContext([string] $targetSubscriptionName) {

        $azureLoginCredential = Get-Credential -Message "Please Enter Azure Login Credential `r`n(Azure AD Login Id & Password)"

        Connect-AzAccount -Credential $azureLoginCredential

        Set-AzContext -Subscription (Get-AzSubscription -SubscriptionName $targetSubscriptionName).Id
    }

    static [string] CreateServicePrincipal([string] $servicePrincipalDisplayName) {

        $spInfo = ""

        #
        # app password == client secret -> use as password during login with service principal
        # appId == clientId == -> use as username during login with service principal
        # 

        $newSp = (New-AzADServicePrincipal -DisplayName $servicePrincipalDisplayName)

        $tenantId = [AzureUtility]::GetCurrentTenantId()
        $applicationId = $newSp.ApplicationId # client id / app id
        $secret = (ConvertFrom-SecureString $newSp.Secret) # client secret / app password (secure string)

        $spInfo = "DisplayName: $servicePrincipalDisplayName`r`nTenantId: $tenantId`r`nApplicationId: $applicationId`r`nSecret: $secret"


        return $spInfo
    }
}

Create service principal: admin_script.ps1

#
# Define rquired variables
#
$servicePrincipalDisplayName = "this-is-test-sp"
# client secret for service principal will be generated automatically (as SecureString)
# by default "Contributor" role will be assigned to the newly created service principal at current subscription scope (AzContext)


#
# Get script folder
#
$scriptFolder = ""
if ($psISE) { # If using ISE
    $scriptFolder = Split-Path -Parent $psISE.CurrentFile.FullPath
} elseif($PSVersionTable.PSVersion.Major -gt 3) { # If Using PowerShell 3 or greater
    $scriptFolder = $PSScriptRoot
} else { # If using PowerShell 2 or lower
    $scriptFolder = split-path -parent $MyInvocation.MyCommand.Path
}

#
# Load utility class
#
. ($scriptFolder + "\azure_utility.ps1")


$newServicePrincipalCredentials = [AzureUtility]::CreateServicePrincipal($servicePrincipalDisplayName)

#
# Save to file since secret is visible only during service principal creation time
#
$fileUri = "$($scriptFolder)\credential_$($servicePrincipalDisplayName).txt"
echo $newServicePrincipalCredentials > $fileUri

Creating Service Principal with Given Password

Create utility class: azure_utility.ps1

#
#
# Azure Utility Class
#
#
class AzureUtility {

    static [string] GeSubscriptionId([string] $subscriptionName) {

        return (Get-AzSubscription -SubscriptionName $subscriptionName).Id
    }


    static [string] GetServicePrincipalObjectId([string] $servicePrincipalDisplayName) { 
        return  (Get-AzADServicePrincipal -DisplayName $servicePrincipalDisplayName).Id
    }

    static [string] GetServicePrincipalInfo([string] $servicePrincipalDisplayName) {

        $spInfo = ""

        $servicePrincipal = (Get-AzADServicePrincipal -DisplayName $servicePrincipalDisplayName)

        if($servicePrincipal){

            $tenantId = [AzureUtility]::GetCurrentTenantId()

            $applicationId = $servicePrincipal.ApplicationId # client id

            $secret = "<only available during service principle creation time>" # client secret
             
            $spInfo = "DisplayName: $servicePrincipalDisplayName`r`nTenantId: $tenantId`r`nApplicationId: $applicationId`r`nSecret: $secret"
        }

        return $spInfo
    }



    static [string] CreateServicePrincipal([string] $servicePrincipalDisplayName, [string] $secret) {

        $spInfo = ""

        #
        # Check if service principal already exists
        #
        $spInfo = [AzureUtility]::GetServicePrincipalInfo($servicePrincipalDisplayName)

        if($spInfo)  { # return existing service principal
            
            return $spInfo
        } 
        
        #else:  create new service principal

        #
        # app password == client secret -> use as password during login with service principal
        # appId == clientId == -> use as username during login with service principal
        # 

        $spCredential = New-Object Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential -Property @{ StartDate = (Get-Date); EndDate = (Get-Date -Year 2050); Password = $secret }

        $newSp = New-AzADServicePrincipal -DisplayName $servicePrincipalDisplayName -PasswordCredential $spCredential

        $tenantId = [AzureUtility]::GetCurrentTenantId()

        $applicationId = $newSp.ApplicationId # use as username during login with service principal

        $spInfo = "DisplayName: $servicePrincipalDisplayName`r`nTenantId: $tenantId`r`nApplicationId: $applicationId`r`nSecret: $secret"

        return $spInfo
    }


    static [void] AssignRoleToServicePrincipal([string] $servicePrincipalObjectId, [string] $roleDefinitionName, [string] $targetScope) {

        New-AzRoleAssignment -ObjectId $servicePrincipalObjectId `
        -RoleDefinitionName $roleDefinitionName `
        -Scope $targetScope
		
        Write-Host "$roleDefinitionName role has been assigned to $targetScope"
    }
}

Create service principal: admin_script.ps1

#
# Define rquired variables
#
$subscriptionName = "xxx"
$servicePrincipalDisplayName = "yyy"
$secret = "zzz" # client secret for service principal
$roleDefinitionName = "Contributor"


#
# Get script folder
#
$scriptFolder = ""
if ($psISE) { # If using ISE
    $scriptFolder = Split-Path -Parent $psISE.CurrentFile.FullPath
} elseif($PSVersionTable.PSVersion.Major -gt 3) { # If Using PowerShell 3 or greater
    $scriptFolder = $PSScriptRoot
} else { # If using PowerShell 2 or lower
    $scriptFolder = split-path -parent $MyInvocation.MyCommand.Path
}

#
# Load utility class
#
. ($scriptFolder + "\azure_utility.ps1")

#
# Create service principal
#
$newSpInfo = [AzureUtility]::CreateServicePrincipal($servicePrincipalDisplayName, $secret)
#Write-Host $newSpInfo


#
# Get service principal object id
#
$servicePrincipalObjectId = [AzureUtility]::GetServicePrincipalObjectId($servicePrincipalDisplayName)


#
# Assign role
#
# Resource Scope Format: /subscriptions/<subscriptionId>/resourcegroups/<resourceGroupName>/providers/<providerName>/<resourceType>/<resourceSubType>/<resourceName>
# For subscription: /subscriptions/<subscriptionId
#
$subscriptionId = [AzureUtility]::GeSubscriptionId($subscriptionName)
$subscriptionScope = "/subscriptions/$subscriptionId"

[AzureUtility]::AssignRoleToServicePrincipal($servicePrincipalObjectId, $roleDefinitionName, $subscriptionScope)

Login Using Service Principal

$tenantId = "xxx"
$appId = "yyy"
$appSecret = "zzz"
$appSecretAsSecureString = (ConvertTo-SecureString $appSecret)


$credential = (New-Object -TypeName PSCredential -ArgumentList $appId, $appSecretAsSecureString) #$credential = Get-Credential
Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant $tenantId

#
# Check service principal login is successful
#
Get-AzContext

Azure CLI

az login --service-principal -u <app-id> -p <password-or-cert> --tenant <tenant>