AzureRMCertAuthentication.psm1

<#
 .Synopsis
  This Module Create or Remove a service principal using self-signed certificate to avoid password prompts on Powershell.
 
 .Description
  This Modulo will Create an service principal and associate a Self-Signed Certificate.
  The intention is to avoid multiple password prompts each time you open Powershell.
  An additional function will be exported to the current user profile to be used to connect to AzureRM without password prompt.
 
  Example of a Function that will be exported:
 
    ##################### Function Connect-AzureRM #####################
    Function Connect-AzureRM{
        $TenantID = "' + $SessionContext.Tenant.Id + '"
        $thumb = "' + $SelfSignedCertificate.thumb + '"
        $ApplicationID = [GUID]"' + $azureAdApplication.ApplicationId.Guid + '"
        Add-AzureRmAccount -TenantId $TenantID -ServicePrincipal -CertificateThumbprint $thumb -ApplicationId $ApplicationID
        if($host.ui.RawUI.WindowTitle -eq "Windows PowerShell"){
            $host.ui.RawUI.WindowTitle = "Connected to: AzureRM"
        }
        elseif($host.ui.RawUI.WindowTitle.contains("Connected")){
            $host.ui.RawUI.WindowTitle = ($host.ui.RawUI.WindowTitle + " & AzureRM")
        }
    }
    ####################################################################
   
  One the service principal is created you just have to type Connect-<FunctionName> each time you open the Powershell.
  The funcion will use the Self-Signed certificate created and associated with a service principal to authenticate with no password.
 
 .Example
   # Creating AzureRMCertAuthentication
   New-AzureRMCertAuthentication -FunctionName AzureRMVMSubscription1
   This Example will create a function named: "Connect-AzureRMVMSubscription1"
 
 .Example
   # In case you have two Azure Account (Subscritpions) you create a different function name for each Subscription.
   New-AzureRMCertAuthentication -FunctionName AzureRMVMSubscription2
   This Example will create a function named: "Connect-AzureRMVMSubscription2"
 
 .Example
   # Removing the exported function, service principal and exported funcion.
   Remove-AzureRMCertAuthentication -Function AzureRMVMSubscription1
 
# A URL to the main website for this project.
ProjectUri = 'https://github.com/welasco/AzureRMCertAuthentication'
Resource = https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal
#>


[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")]
param()

# Function used to check the current Azure login session
Function CheckAzureSession{
    $Check = (Get-AzureRmContext).Account
    If($Check){
        return $true
    }
    else {
        return $false
    }
}

# Function used to create the Self-Signed certificated
Function CreateSelfSignedCertificate {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")]
    Param(
        [Parameter(Mandatory=$true)]
        [PsObject]$FunctionName
    )    
    $currentDate = Get-Date
    $endDate = $currentDate.AddYears(1)
    $notAfter = $endDate.AddYears(1)
    $pwdStr = ([guid]::NewGuid()).guid.tostring().replace("-","")
    $dnsName = ($FunctionName + ".AzurePowershell.local")
    $certPath = Split-Path $profile.CurrentUserAllHosts
    $dstPath = Join-Path -Path $certPath -ChildPath ($dnsName + ".pfx")
    $thumb = (New-SelfSignedCertificate -CertStoreLocation cert:\CurrentUser\my -DnsName $dnsName -KeyExportPolicy Exportable -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -NotAfter $notAfter).Thumbprint
    $pwd = ConvertTo-SecureString -String $pwdStr -Force -AsPlainText
    Export-PfxCertificate -cert "cert:\CurrentUser\my\$thumb" -FilePath $dstPath -Password $pwd
    $return = New-Object PsObject @{
        currentDate=$currentDate
        endDate=$endDate
        pwd=$pwd
        dstPath=$dstPath
        thumb=$thumb
        dnsName=$dnsName
        certPath=$certPath
    }
    return $return
}

# Function load the the Self-Signed certificate and export the KeyCredential
Function CreateKeyCredential{
    Param(
        [Parameter(Mandatory=$true)]
        [PsObject]$SelfSignedCertificate
    )
    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate($SelfSignedCertificate.dstPath, $SelfSignedCertificate.pwd)
    $keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())
    $keyId = [guid]::NewGuid()
    Import-Module AzureRM.Resources
    $keyCredential = New-Object  Microsoft.Azure.Commands.Resources.Models.ActiveDirectory.PSADKeyCredential
    $keyCredential.StartDate = $SelfSignedCertificate.currentDate
    $keyCredential.EndDate= $SelfSignedCertificate.endDate
    $keyCredential.KeyId = $keyId
    $keyCredential.CertValue = $keyValue

    return $keyCredential
}

# Function to Remove the Exported Profile Function
Function RemoveProfileFunction{
    Param(
        [Parameter(Mandatory=$true)]
        [PsObject]$FunctionName
    ) 
    $profileFile = Get-Content $profile.CurrentUserAllHosts
    $currentDateTime = (Get-Date).Month.ToString() + (Get-Date).Day.ToString() + (Get-Date).Year.ToString() + (Get-Date).Hour.ToString() + (Get-Date).Minute.ToString() + (Get-Date).Second.ToString()
    $tempFile = ($profile.CurrentUserAllHosts | Split-Path) | Join-Path -ChildPath ("tempProfile" + $currentDateTime + ".txt")
    $bkpFile = ($profile.CurrentUserAllHosts.Replace(".ps1", ("-bkp-" + $currentDateTime + ".ps1")))
    $funcString = ("##################### Function Connect-" + $FunctionName + " #####################")

    Copy-Item $profile.CurrentUserAllHosts $bkpFile

    $dstLinePosition = $null
    foreach($line in $profileFile){
        if ($line -eq $funcString) {
            $dstLinePosition = $line.ReadCount + 13
        }
        if ($dstLinePosition -ne $null) {
            if ($line.ReadCount -le $dstLinePosition) {
                # Don't copy the line
            }
            else {
                $line | Out-File $tempFile -Append
            }
        }
        else {
            $line | Out-File $tempFile -Append
        }
    }

    Copy-Item $tempFile $profile.CurrentUserAllHosts -Force
    Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
}

# Function to double check if should create the Service Principal using the current loging session or not
Function MenuYesNo{
    $title = ("You are currently using the Subscription: "+ (Get-AzureRmContext).Subscription.Name)
    $message = "Do you want to crate a new AzureRMCertAuthentication using this subscription?"

    $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
        "A new AzureRMCertAuthentication will be created using the current Subscription."

    $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
        "Stop creation process of a new AzureRMCertAuthentication."

    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

    $result = $host.ui.PromptForChoice($title, $message, $options, 1) 

    switch ($result)
        {
            0 {return $True}
            1 {return $False}
        }    
}

# ### Exported Module Function ###
# This Function will create an AzureRMADApplication and associate with AzureRMADServicePrincipal
# The Self-Signed certificated will be associated with AzureRMADApplication
Function New-AzureRMCertAuthentication{
    Param(
        [Parameter(Mandatory=$true)]
        [PsObject]$FunctionName
    )    
    $isSession = CheckAzureSession

    # Checking current session
    if ($isSession -eq $false) {
        Add-AzureRmAccount -ErrorAction Stop
    }
    else {
        $resultMenu = MenuYesNo
        if($resultMenu -eq $false){
            break
        }
        else {
            Add-AzureRmAccount -ErrorAction Stop
        }
    }

    #Checking if the ADApplication already exist
    $chkADApplication = Get-AzureRmADApplication -DisplayNameStartWith $FunctionName
    if ($chkADApplication) {
        Write-Output ("A previous AzureRMADApplication found with Name: " + $FunctionName)
        Write-OutPut ("Please run Remove-AzureRMCertAuthentication -FunctionName " + $FunctionName + " to remove it first.")
        break
    }

    # Creating the Self-Signed certificate
    $SelfSignedCertificate = CreateSelfSignedCertificate -FunctionName $FunctionName
    # Creating KeyCredential based on Self-Signed certificate
    $PSADKeyCredential = CreateKeyCredential -SelfSignedCertificate $SelfSignedCertificate

    # Create the Azure Active Directory Application
    $azureAdApplication = New-AzureRmADApplication -DisplayName ($FunctionName + "-AzurePowershell-CertAuth") -HomePage ("https://" + $SelfSignedCertificate.dnsName) -IdentifierUris ("https://" + $SelfSignedCertificate.dnsName) -KeyCredentials $PSADKeyCredential

    # Create the Service Principal and connect it to the Application
    New-AzureRmADServicePrincipal -ApplicationId $azureAdApplication.ApplicationId

    # We must sleep 20 seconds waiting Service Principal be created on AzureRM
    Start-Sleep -Seconds 20

    # Give the Service Principal Owner access to the current subscription
    New-AzureRmRoleAssignment -RoleDefinitionName Owner -ServicePrincipalName $azureAdApplication.ApplicationId

    $SessionContext = Get-AzureRmContext

    # Preparing Connect-<FunctionName> to be exported
    $ExportFunction = '
##################### Function Connect-'
 + $FunctionName + ' #####################
Function Connect-'
 + $FunctionName + '{
    $TenantID = "'
 + $SessionContext.Tenant.Id + '"
    $thumb = "'
 + $SelfSignedCertificate.thumb + '"
    $ApplicationID = [GUID]"'
 + $azureAdApplication.ApplicationId.Guid + '"
    Add-AzureRmAccount -TenantId $TenantID -ServicePrincipal -CertificateThumbprint $thumb -ApplicationId $ApplicationID
    if($host.ui.RawUI.WindowTitle -eq "Windows PowerShell"){
        $host.ui.RawUI.WindowTitle = "Connected to: '
 + $FunctionName + '"
    }
    elseif($host.ui.RawUI.WindowTitle.contains("Connected")){
        $host.ui.RawUI.WindowTitle = ($host.ui.RawUI.WindowTitle + " & '
 + $FunctionName + '")
    }
}
####################################################################'

    
    # Exporting Connect-<FunctionName>
    if (Test-Path $profile.CurrentUserAllHosts) {
        $profileFile = Get-Content $profile.CurrentUserAllHosts
    }
    $profileFile += $ExportFunction
    $profileFile | Out-File $profile.CurrentUserAllHosts -Force        



    Write-Output ("Now re-open Powershell and run Connect-" + $FunctionName +" to connect!")
}

# ### Exported Module Function ###
# This Function will remove an AzureRMADApplication, AzureRMADServicePrincipal, Self-Signed Certificate and Exported Function
Function Remove-AzureRMCertAuthentication{
    Param(
        [Parameter(Mandatory=$true)]
        [PsObject]$FunctionName
    )     
    try{
        $AzureRMADApp = Get-AzureRmADApplication -DisplayNameStartWith $FunctionName
        Remove-AzureRmADApplication -ObjectId $AzureRMADApp.ObjectId
        try {
            Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Subject -eq ("CN=" + $FunctionName + ".AzurePowershell.local") } | Remove-Item -Force
            RemoveProfileFunction -FunctionName $FunctionName
            Remove-Item ($profile.CurrentUserAllHosts | Split-Path | Join-Path -ChildPath ($FunctionName + ".AzurePowershell.local.pfx")) -ErrorAction SilentlyContinue -Force
        }
        catch {
            Write-Output ("Failed to remove Self-Signed certificate: " + ("CN=" + $FunctionName + ".AzurePowershell.local"))
        }
        Write-Output "Sucessfully removed AzureRMCertAuthentication"
    }
    catch{
        Write-Output "To remove the a AzureRMCertAuthentication you must login again. Please type Login-AzureRmAccount"
    }

}

# Exporting Powershell Functions from this Module
Export-ModuleMember -Function New-AzureRMCertAuthentication, Remove-AzureRMCertAuthentication