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

Connect to Azure

When MFA is enabled

Connect-AzAccount -UseDeviceAuthentication

# Copy device code and click the link

When MFA is not Enabled

#
# Enter id and password
#
$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

Connect using local PowerShell with default browser

#
# Connect to Azure
#
# Default browser will open, you are already logged in to Azure portal
#
Connect-AzAccount

Now, don’t forget to set AzContext to target subscription: see next section

Using Organizational Id Credentials This scenario works only in Windows PowerShell 5.1

#
# Use Azure AD login Id and Password
#
$Credential = Get-Credential
Connect-AzAccount -Credential $Credential

Now, don’t forget to set AzContext to target subscription: see next section

Azure VM using Managed Indentity
https://docs.microsoft.com/en-us/powershell/module/az.accounts/connect-azaccount?view=azps-6.4.0#example-6--connect-using-managed-service-identity-login-and-clientid

Set AzContext

First subscription (from the list of subscriptions belong to the tenant) will be selected. Therefore, you should set AzContext to your target subscription

#
# Set AzContext to target subscription
#
# Set-AzContext -Subscription "xxxx-xxxx-xxxx-xxxx"
#
# If target subscription belongs to different tenant:
# Get-AzSubscription -SubscriptionId "xxxx-xxxx-xxxx-xxxx" -TenantId "yyyy-yyyy-yyyy-yyyy" | Set-AzContext
# https://docs.microsoft.com/en-us/powershell/module/az.accounts/set-azcontext
#
$targetSubscriptionName = "xxx"
$targetSubscriptionId = (Get-AzSubscription -SubscriptionName $targetSubscriptionName).Id
Set-AzContext -Subscription $targetSubscriptionId

# Using name only
Set-AzContext -SubscriptionName $targetSubscriptionName

You should connect to a specific tenant and subscription (organization might have multiple tenants & each tenant might have a list of subscriptions), otherwise you might ended up performating action against wrong subscription

#
# Connect-AzAccount -Tenant 'xxxx-xxxx-xxxx-xxxx' -SubscriptionId 'yyyy-yyyy-yyyy-yyyy'
#
$tenantGuid = "xxx"
$subscriptionGuid = "xxx"

$Credential = Get-Credential
Connect-AzAccount -Credential $Credential -Tenant $tenantGuid -SubscriptionId $subscriptionGuid

Login and Set Target Subscription

azure_login.ps1

function Connect-AzAccountTargetSubscription {
    param (

        [Parameter(Mandatory)]
        [string] $SubscriptionName
    )

    $currentContext = $null
    $loggedInAccount = $null
    $loginIsRequired = $false

    $contextList = (Get-AzContext -ListAvailable -ErrorAction Continue) ?? @()
    if ($contextList.Count -gt 0) {
        $currentContext = Get-AzContext
    }

    if ($null -eq $currentContext) {

        $loginIsRequired = $true
    }
    else {

        $loggedInAccount = $currentContext.Account

        # In Azure CloudShell,
        # by default managed identity is used to connect to Azure Account (account will be some thing like "MSxxx")
        #
        if (-not "$loggedInAccount".Contains("@")) {

            Write-Host "You must login since managed identity is being used `r`n"
            $loginIsRequired = $true
        }
        else {

            #
            # Check if user wants to use currently logged in account
            #
            $userReply = Read-Host -Prompt "Use currently logged in user account? y [yes], n [no]"
            while ("y", "n" -notcontains $userReply) {
                $userReply = Read-Host -Prompt "Use currently logged in user account? y [yes], n [no]"
            }

            if ('n' -eq $userReply) {
                # user wants to login again

                $loginIsRequired = $true

                Write-Host "Clearing Context → [Disconnect-AzAccount, Clear-AzContext] `r`n"

                try {
                    Disconnect-AzAccount -ErrorAction Continue
                    Clear-AzContext -Force -ErrorAction Continue
                }
                catch {
                    Write-Warning $Error[0]
                }
            }
            else {
                Write-Host "`r`nUsing currently logged in account → [$loggedInAccount]"
            }
        }
    }

    Write-Host "Login required: $loginIsRequired `r`n"
    if ($loginIsRequired) {
        Connect-AzAccount -UseDeviceAuthentication
    }

    #
    # Target susbcription as context
    #
    Set-AzContext -Subscription (Get-AzSubscription -SubscriptionName $SubscriptionName).Id
    Start-Sleep -Seconds 1

    #
    # Re-check if context is set properly
    #
    $currentSubscriptionName = (Get-AzContext).Subscription.Name
    if ($currentSubscriptionName -ne $SubscriptionName) {
        throw "Context mismatch"
    }
    else {
        Write-Host "Context set → [subscription: '$SubscriptionName'] `r`n"
    }
}

Get Currently Logged In User Information

When using CloudShell, by default it used managed identity (MSI) to login. Use Device Authentication (described above) in order to connect using Azure AD user credentials.

#
# Azure AD User Principle Id (currently logged in user)
#

$currentlyLoggedInUserObjectId = (Get-AzADUser -UserPrincipalName (Get-AzContext).Account).Id

echo (Get-AzADUser -UserPrincipalName (Get-AzContext).Account).Id

#
# AzContext Id/SignInName (currently logged in user)
#
echo ((Get-AzContext).Account).Id

Using user defined function

function Get-LoggedInUserObjectId {

    #$currentlyLoggedInUserObjectId = (Get-AzADUser -UserPrincipalName (Get-AzContext).Account).Id
    #Write-Host "Object id of currently logged in user: $currentlyLoggedInUserObjectId"

    (Get-AzADUser -UserPrincipalName (Get-AzContext).Account).Id
}

Get Subscription Id

#
# Taget subscription
#
$targetSubscriptionName = "xxx"
$targetSubscriptionId = (Get-AzSubscription -SubscriptionName $targetSubscriptionName).Id
echo $targetSubscriptionId

#
# Current (context) subscription
#
$currentContextSubscriptionId = ((Get-AzContext).Subscription).Id
echo $currentContextSubscriptionId

# https://docs.microsoft.com/en-us/powershell/module/az.accounts/get-azsubscription

Get Tenant Id

$tenantId = (Get-AzTenant).Id
echo $tenantId

# https://docs.microsoft.com/en-us/powershell/module/az.accounts/get-aztenant

Create Storage Account Context

Using Connected Account

$saContext = (New-AzStorageContext -StorageAccountName $StorageAccountName -UseConnectedAccount)

Using ResourceGroupName and StorageAccountName

$saContext = (Get-AzStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName).Context

Using Storage Account Key

$accountKeys = Get-AzStorageAccountKey -ResourceGroupName $resourceGroupName -Name $storageAccountName  
$storageContext = New-AzStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $accountKeys[0].value 

Using SAS Toekn

$saContext = New-AzStorageContext -StorageAccountName $storageAccountName -SasToken $sasToken

Using user defined function

function  New-StorageAccountKeyContext {
    param (
        [Parameter(Mandatory)]
        [string] $ResourceGroupName,
        [Parameter(Mandatory)]
        [string] $StorageAccountName
    )
    
    # Storage account context can be created in multiple ways,
    # In some case using account key might be required i.e. for creating Stored Access Policy
    #
    # $storageAccountContext = (Get-AzStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName).Context
    # $saContext = (New-AzStorageContext -StorageAccountName $StorageAccountName -UseConnectedAccount) 
    #
    $saKeys = Get-AzStorageAccountKey -ResourceGroupName  $ResourceGroupName -Name $StorageAccountName
    $saKeyContext = New-AzStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $saKeys[0].Value
    
    return $saKeyContext
}

Create Service Principal

Make sure you are logged in and have enough preivilege to create service principal

function New-ServicePrincipal {
    param (

        [Parameter(Mandatory)]
        [string] $ServicePrincipalDisplayName
    )

    $spCredential = @{}

    try {

        $newSp = (New-AzADServicePrincipal -DisplayName $ServicePrincipalDisplayName)
        Start-Sleep -Seconds 3 # cool down

        $spSecretAsStringObj = ConvertTo-String -SecureString $newSp.Secret

        $spCredential[$envVarKeyServicePrincipalDisplayName] = $ServicePrincipalDisplayName
        $spCredential[$envVarKeyServicePrincipalObjectId] = $newSp.Id
        $spCredential[$envVarKeyTenantId] = (Get-AzTenant).Id
        $spCredential[$envVarKeyClientId] = $newSp.ApplicationId
        $spCredential[$envVarKeyClientSecret] = $spSecretAsStringObj
    }
    catch {
        Write-Warning $Error[0]
    }

    return $spCredential
}

Save service principal credential to JsonFile: Write content to file

Role Assignment

function New-RoleAssignement {

    param (

        [Parameter(Mandatory)]
        [string] $RoleDefinitionName,

        [Parameter(Mandatory)]
        [string] $ObjectId,

        [Parameter(Mandatory)]
        [string] $Scope
    )

    #
    # Before trying to assign role,
    # Check that is given role is already assigned or not
    #
    $existingRoleAssignment = (Get-AzRoleAssignment -RoleDefinitionName $RoleDefinitionName -ObjectId $ObjectId -Scope $Scope)
        
    if ($null -eq $existingRoleAssignment) {

        Write-Host "[Role '$($RoleDefinitionName)' at Scope '$($Scope)'] does not exist, creating new role assignment"

        New-AzRoleAssignment -RoleDefinitionName $RoleDefinitionName `
            -ObjectId $ObjectId `
            -Scope $Scope `
        | Out-Null

        Write-Host "Role '$($RoleDefinitionName)' has been assigned to '$($Scope)'"
    }
    else {

        Write-Host "[Role '$($RoleDefinitionName)' at Scope '$($Scope)']  already exists"
    }
}

Create Stored Access Policy

function  New-StoredAccessPolicy {
    param (
        [Parameter(Mandatory)]
        [Microsoft.WindowsAzure.Commands.Storage.AzureStorageContext] $StorageAccountKeyContext,
        [Parameter(Mandatory)]
        [string] $ContainerName,
        [Parameter(Mandatory)]
        [string] $PolicyName,
        [Parameter(Mandatory)]
        [datetime] $ExpiryTime,
        [Parameter(Mandatory)]
        [string] $PermissionFlagString
    )

    $sap = $null
    try {
        $sap = Get-AzStorageContainerStoredAccessPolicy -Context $StorageAccountKeyContext -Container $ContainerName -Policy $PolicyName -ErrorAction Stop
    }
    catch {
        Write-Warning $Error[0]
    }

    $noPolicyWithSameName = ($null -eq $sap)

    if ($noPolicyWithSameName) {

        Write-Host "Stored Access Policy '$($PolicyName)' does not exist, creating new"

        $sapSplat = @{
            Context    = $StorageAccountKeyContext
            Container  = $ContainerName
            Policy     = $PolicyName
            ExpiryTime = $ExpiryTime
            Permission = $PermissionFlagString
        }

        $sap = New-AzStorageContainerStoredAccessPolicy @sapSplat
    
        Write-Host "Stored Access Policy '$($PolicyName)' with expiry time '$($ExpiryTime)' has been created"
    } 
    else {

        Write-Host "Stored Access Policy '$($PolicyName)' already exists"
    }

    $sap | Out-Null
}

function  New-StoredAccessPolicyAllPermissions {
    param (
        [Parameter(Mandatory)]
        [Microsoft.WindowsAzure.Commands.Storage.AzureStorageContext] $StorageAccountKeyContext,
        [Parameter(Mandatory)]
        [string] $ContainerName,
        [Parameter(Mandatory)]
        [string] $PolicyName,
        [Parameter(Mandatory)]
        [datetime] $ExpiryTime
    )

    $splat = @{
        StorageAccountKeyContext = $StorageAccountKeyContext
        ContainerName            = $ContainerName
        PolicyName               = $PolicyName
        ExpiryTime               = $ExpiryTime
        PermissionFlagString     = "racwdl"
    }

    New-StoredAccessPolicy @splat
}

Generate SAS Token

Create Stored Access policy first before generatting SAS token

function New-SasToken {
    param (
        [Parameter(Mandatory)]
        [string] $ResourceGroupName,
        [Parameter(Mandatory)]
        [string] $StorageAccountName,
        [Parameter(Mandatory)]
        [string] $ContainerName,
        [Parameter(Mandatory)]
        [string] $StoredAccessPolicyName,
        [Parameter(Mandatory)]
        [datetime] $ExpiryTime
    )
    
    # To generate SAS, followings are needed
    # 1. Storage Account Context
    # 2. Stored Access Policy
    #
    $saKeys = Get-AzStorageAccountKey -ResourceGroupName  $ResourceGroupName -Name $StorageAccountName
    $saKeyContext = New-AzStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $saKeys[0].Value

    $policySplat = @{
        StorageAccountKeyContext = $saKeyContext
        ContainerName            = $ContainerName
        PolicyName               = $StoredAccessPolicyName
        ExpiryTime               = $ExpiryTime
    }

    New-StoredAccessPolicyAllPermissions @policySplat

    # Generate SAS Token
    #
    $sasSplat = @{
        Context   = $saKeyContext
        Container = $ContainerName
        Policy    = $StoredAccessPolicyName
    }
    $sasString = New-AzStorageContainerSASToken @sasSplat

    $sasToken = "$($sasString.Substring(1))" # removing '?' in the token string
    #Write-Host "Generated SAS token: $($sasToken)"

    return $sasToken # returning token to caller
}

Iterate Over all Subscriptions and Resource Groups

Get-AzSubscription | ForEach-Object {
    $subscriptionName = $_.Name
    Set-AzContext -SubscriptionId $_.SubscriptionId
    (Get-AzResourceGroup).ResourceGroupName | ForEach-Object {     
        [PSCustomObject] @{
            Subscription = $subscriptionName
            ResourceGroup = $_
        }
    }
}

Courtesy: https://stackoverflow.com/a/61680135/4802664