AzureCommonStuff.psm1

function Connect-AzAccount2 {
    <#
    .SYNOPSIS
    Function for connecting to Azure using Connect-AzAccount command (Az.Accounts module).
 
    .DESCRIPTION
    Function for connecting to Azure using Connect-AzAccount command (Az.Accounts module).
    In case there is already existing connection, stop.
 
    .PARAMETER credential
    Credentials (User or App) for connecting to Azure.
    For App credentials tenantId must be set too!
 
    .PARAMETER servicePrincipal
    Switch for using App/Service Principal authentication instead of User auth.
 
    .PARAMETER tenantId
    Azure tenant ID.
    Mandatory when App authentication is used .
 
    .EXAMPLE
    Connect-AzAccount2
 
    Authenticate to Azure interactively using user credentials. Doesn't work for accounts with MFA!
 
    .EXAMPLE
    $credential = get-credential
    Connect-AzAccount2 -credential $credential
 
    Authenticate to Azure using given user credentials. Doesn't work for accounts with MFA!
 
    .EXAMPLE
    $credential = get-credential
    Connect-AzAccount2 -servicePrincipal -credential $credential -tenantId 1234-1234-1234
 
    Authenticate to Azure using given app credentials (service principal).
 
    .NOTES
    Requires module Az.Accounts.
    #>


    [CmdletBinding()]
    param (
        [System.Management.Automation.PSCredential] $credential,

        [switch] $servicePrincipal,

        [string] $tenantId = $_tenantId
    )

    if (Get-AzContext) {
        Write-Verbose "Already connected to Azure"
        return
    } else {
        if ($servicePrincipal -and !$tenantId) {
            throw "When servicePrincipal auth is used tenantId has to be set"
        }

        $param = @{}
        if ($servicePrincipal) { $param.servicePrincipal = $true }
        if ($tenantId) { $param.tenantId = $tenantId }
        if ($credential) { $param.credential = $credential }

        Connect-AzAccount @param
    }
}

function Connect-PnPOnline2 {
    <#
    .SYNOPSIS
    Proxy function for Connect-PnPOnline with some enhancements like: automatic MFA auth if MFA detected, skipping authentication if already authenticated etc.
 
    .DESCRIPTION
    Proxy function for Connect-PnPOnline with some enhancements like: automatic MFA auth if MFA detected, skipping authentication if already authenticated etc.
 
    .PARAMETER credential
    Credential object you want to use to authenticate to Sharepoint Online
 
    .PARAMETER appAuth
    Switch for using application authentication instead of the user one.
 
    .PARAMETER asMFAUser
    Switch for using user with MFA enabled authentication (i.e. interactive auth)
 
    .PARAMETER url
    Your sharepoint online url ("https://contoso-admin.sharepoint.com")
 
    .EXAMPLE
    Connect-PnPOnline2
 
    Connect to Sharepoint Online using user interactive authentication.
 
    .EXAMPLE
    Connect-PnPOnline2 -asMFAUser
 
    Connect to Sharepoint Online using (MFA-enabled) user interactive authentication.
 
    .EXAMPLE
    Connect-PnPOnline2 -appAuth
 
    Connect to Sharepoint Online using application interactive authentication.
 
    .EXAMPLE
    Connect-PnPOnline2 -appAuth -credential $cred
 
    Connect to Sharepoint Online using application non-interactive authentication.
 
    .EXAMPLE
    Connect-PnPOnline2 -credential $cred
 
    Connect to Sharepoint Online using (non-MFA enabled!) user non-interactive authentication.
 
    .NOTES
    Requires Pnp.PowerShell module.
    #>


    [CmdletBinding()]
    param (
        [System.Management.Automation.PSCredential] $credential,

        [switch] $appAuth,

        [switch] $asMFAUser,

        [ValidateNotNullOrEmpty()]
        [string] $url = $_SPOConnectionUri
    )

    if (!$url) {
        throw "Url parameter is not defined. It should contain your sharepoint URL (for example https://contoso-admin.sharepoint.com)"
    }

    if ($appAuth -and $asMFAUser) {
        Write-Warning "asMFAUser switch cannot be used with appAuth. Ignoring asMFAUser."
        $asMFAUser = $false
    }

    if ($credential -and $asMFAUser) {
        Write-Warning "When logging using MFA-enabled user, credentials cannot be passed i.e. it has to be interactive login"
        $credential = $null
    }

    try {
        Write-Verbose "Already connected to Sharepoint"
        $null = Get-PnPConnection -ea Stop
    } catch {
        Write-Verbose "Connecting to Sharepoint"
        if ($credential -and !$appAuth) {
            try {
                Connect-PnPOnline -Url $url -Credentials $credential -ea Stop
            } catch {
                if ($_ -match "you must use multi-factor authentication to access") {
                    Write-Error "Account $($credential.UserName) has MFA enabled, therefore interactive logon is needed"
                    Connect-PnPOnline -Url $url -Interactive -ForceAuthentication
                } else {
                    throw $_
                }
            }
        } elseif ($credential -and $appAuth) {
            Connect-PnPOnline -Url $url -ClientId $credential.UserName -ClientSecret $credential.GetNetworkCredential().password
        } else {
            # credential is missing
            if ($asMFAUser) {
                Connect-PnPOnline -Url $url -Interactive -ForceAuthentication
            } elseif ($appAuth) {
                $credential = Get-Credential -Message "Using App auth. Enter ClientId and ClientSecret."
                Connect-PnPOnline -Url $url -ClientId $credential.UserName -ClientSecret $credential.GetNetworkCredential().password
            } else {
                Connect-PnPOnline -Url $url
            }
        }
    }
}

function New-AzureDevOpsAuthHeader {
    <#
    .SYNOPSIS
    Function for getting authentication header for web requests against Azure DevOps.
 
    .DESCRIPTION
    Function for getting authentication header for web requests against Azure DevOps.
 
    Function uses MSAL to authenticate (requires MSAL.PS module).
 
    .EXAMPLE
    $header = New-AzureDevOpsAuthHeader
    Invoke-WebRequest -Uri $uri -Headers $header
 
    .NOTES
    https://docs.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-7.1
    PowerShell module AzSK.ADO > ContextHelper.ps1 > GetCurrentContext
    https://stackoverflow.com/questions/56355274/getting-oauth-tokens-for-azure-devops-api-consumption
    https://stackoverflow.com/questions/52896114/use-azure-ad-token-to-authenticate-with-azure-devops
    #>


    [CmdletBinding()]
    param ()

    # TODO oAuth auth https://github.com/microsoft/azure-devops-auth-samples/tree/master/OAuthWebSample
    # $msalToken = Get-MsalToken -TenantId $TenantID -ClientId $ClientID -UserCredential $Credential -Scopes ([String]::Concat($($ApplicationIdUri), '/user_impersonation')) -ErrorAction Stop

    $clientId = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1" # Visual Studio
    $adoResourceId = "499b84ac-1321-427f-aa17-267ca6975798" # Azure DevOps app ID
    $msalToken = Get-MsalToken -Scopes "$adoResourceId/.default" -ClientId $clientId

    if ($msalToken.accessToken) {
        $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f "", $msalToken.accessToken)))
        return @{Authorization = "Basic $base64AuthInfo" }
    } else {
        throw "Unable to obtain DevOps MSAL token"
    }
}

function Start-AzureSync {
    <#
        .SYNOPSIS
        Invoke Azure AD sync cycle command (Start-ADSyncSyncCycle) on the server where 'Azure AD Connect' is installed.
 
        .DESCRIPTION
        Invoke Azure AD sync cycle command (Start-ADSyncSyncCycle) on the server where 'Azure AD Connect' is installed.
 
        .PARAMETER Type
        Type of sync.
 
        Initial (full) or just delta.
 
        Delta is default.
 
        .PARAMETER ADSynchServer
        Name of the server where 'Azure AD Connect' is installed
 
        .EXAMPLE
        Start-AzureSync -ADSynchServer ADSYNCSERVER
        Invokes synchronization between on-premises AD and AzureAD on server ADSYNCSERVER by running command Start-ADSyncSyncCycle there.
    #>


    [Alias("Sync-ADtoAzure", "Start-AzureADSync")]
    [cmdletbinding()]
    param (
        [ValidateSet('delta', 'initial')]
        [string] $type = 'delta',

        [ValidateNotNullOrEmpty()]
        [string] $ADSynchServer
    )

    $ErrState = $false
    do {
        try {
            Invoke-Command -ScriptBlock { Start-ADSyncSyncCycle -PolicyType $using:type } -ComputerName $ADSynchServer -ErrorAction Stop | Out-Null
            $ErrState = $false
        } catch {
            $ErrState = $true
            Write-Warning "Start-AzureSync: Error in Sync:`n$_`nRetrying..."
            Start-Sleep 5
        }
    } while ($ErrState -eq $true)
}

Export-ModuleMember -function Connect-AzAccount2, Connect-PnPOnline2, New-AzureDevOpsAuthHeader, Start-AzureSync

Export-ModuleMember -alias Start-AzureADSync, Sync-ADtoAzure