HansenAzurePS.psm1

function Get-GitHubRawPath {
    param(
        
        [Parameter(Mandatory, Position = 1)]
        [String]$File,
        
        [String]$RepoPath = ".\",
        [String]$Remote = "origin",
        [String]$Revision = "HEAD"
    )

    if (-not $(GitAvailable)) {
        throw "Git is not installed"
    }

    $gitTopLevel = $(git rev-parse --show-toplevel)
    if ([String]::IsNullOrEmpty($gitTopLevel)) {
        throw "Current path is not a git repository"
    }

    $fullPath = $(Get-Item $File).FullName
    $fileRelativePath = RelativePath -absolutePath $fullPath -basePath $gitTopLevel
    if ($fileRelativePath.Substring(0, 2) -eq ".\") {
        $fileRelativePath = $fileRelativePath.Substring(2, $fileRelativePath.Length - 2)
    }
    $fileRelativePath = $fileRelativePath -replace "\\", "/"

    try { 
        $remoteUri = $(git remote get-url $Remote) 
    }
    catch {
        trow "$Remote is not a remote of this repository"
    }
    
    $remoteUri = [System.Uri]$remoteUri

    if ($remoteUri.Host -ne "github.com") {
        throw "The remote URL is not a Github location"
    }

    $remotePath = $remoteUri.AbsolutePath 
    if ($remotePath.SubString($remotePath.Length - 4, 4) -eq '.git') {
        $remotePath = $remotePath.Substring(0, $remotePath.Length - 4)
    }

    $rawHost = "https://raw.githubusercontent.com"

    $rawUrl = $rawHost + $remotePath + "/" + $(git rev-parse $Revision) + "/" + $fileRelativePath

    return $rawUrl
}

function Remove-RecoveryServicesVault {
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [String]$ResourceGroupName,

        [Parameter(Mandatory = $true, Position = 2)]
        [String]$VaultName,

        [Parameter(Mandatory = $false, Position = 3)]
        [String]$EnvironmentName
    )

    if ([string]::IsNullOrEmpty($(Get-AzureRmContext).Account)) {
        if ([string]::IsNullOrEmpty($EnvironmentName)) {
            Login-AzureRmAccount
        }
        else {
            Login-AzureRmAccount -Environment $EnvironmentName        
        }
    }

    $rv = Get-AzureRmRecoveryServicesVault -Name $VaultName -ResourceGroupName $ResourceGroupName

    if ($rv -eq $null) {
        Write-Host "Recovery Service Vault Not Found"
        exit 1
    }

    Set-AzureRmRecoveryServicesVaultContext -Vault $rv
    $rcs = Get-AzureRmRecoveryServicesBackupContainer -ContainerType AzureVM

    foreach ($c in $rcs) {
        $bi = Get-AzureRmRecoveryServicesBackupItem -Container $c -WorkloadType AzureVM
        Disable-AzureRmRecoveryServicesBackupProtection -Item $bi -RemoveRecoveryPoints -Force
    }

    Remove-AzureRmRecoveryServicesVault -Vault $rv
}

function New-AppRegForAADAuth {
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [System.Uri]$SiteUri,
    
        [Parameter(Mandatory = $false, Position = 2)]
        [ValidateSet("AzureCloud", "AzureUsGovernment", "AzureGermanCloud", "AzureChinaCloud")]
        [String]$Environment = "AzureCloud",

        [Parameter(Mandatory = $false, Position = 3)]
        [String]$Password,

        [Parameter(Mandatory = $false)]
        [String[]]$AADDelegatePermissions = @("User.Read"),

        [Parameter(Mandatory = $false)]
        [String[]]$GraphDelegatePermissions = @("User.Read", "User.ReadBasic.All")
        
    )

    <#
 
    More details at: http://blog.octavie.nl/index.php/2017/09/19/create-azure-ad-app-registration-with-powershell-part-2
 
    Finding information about App Permissions and Delegated Permissions:
 
    1. Locate the ServicePrincipal (API) you need to access, e.g.:
         
        PS> $svcPrincipal = Get-AzureADServicePrincipal -SearchString "Windows azure active directory"
        PS> $svcPrincipal
 
        ObjectId AppId DisplayName
        -------- ----- -----------
        d80f4d2b-d115-44ad-b39e-69ebdbe6c9fe 00000002-0000-0000-c000-000000000000 Windows Azure Active Directory
 
    2. List App Permissions:
 
        PS> $svcPrincipal.AppRoles | FT Id, Value, DisplayName
 
        Id Value DisplayName
        -- ----- -----------
        5778995a-e1bf-45b8-affa-663a9f3f4d04 Directory.Read.All Read directory data
        abefe9df-d5a9-41c6-a60b-27b38eac3efb Domain.ReadWrite.All Read and write domains
        78c8a3c8-a07e-4b9e-af1b-b5ccab50a175 Directory.ReadWrite.All Read and write directory data
        1138cb37-bd11-4084-a2b7-9f71582aeddb Device.ReadWrite.All Read and write devices
        9728c0c4-a06b-4e0e-8d1b-3d694e8ec207 Member.Read.Hidden Read all hidden memberships
        824c81eb-e3f8-4ee6-8f6d-de7f50d565b7 Application.ReadWrite.OwnedBy Manage apps that this app creates or owns
        1cda74f2-2616-4834-b122-5cb1b07f8a59 Application.ReadWrite.All Read and write all applications
        aaff0dfd-0295-48b6-a5cc-9f465bc87928 Domain.ReadWrite.All Read and write domains
 
    3. List Delegated Permissions:
 
        PS> $svcPrincipal.Oauth2Permissions | FT Id, Value, UserConsentDisplayName
 
        Id Value UserConsentDisplayName
        -- ----- ----------------------
        a42657d6-7f20-40e3-b6f0-cee03008a62a Directory.AccessAsUser.All Access the directory as you
        5778995a-e1bf-45b8-affa-663a9f3f4d04 Directory.Read.All Read directory data
        78c8a3c8-a07e-4b9e-af1b-b5ccab50a175 Directory.ReadWrite.All Read and write directory data
        970d6fa6-214a-4a9b-8513-08fad511e2fd Group.ReadWrite.All Read and write all groups
        6234d376-f627-4f0f-90e0-dff25c5211a3 Group.Read.All Read all groups
        c582532d-9d9e-43bd-a97c-2667a28ce295 User.Read.All Read all user's full profiles
        cba73afc-7f69-4d86-8450-4978e04ecd1a User.ReadBasic.All Read all user's basic profiles
        311a71cc-e848-46a1-bdf8-97ff7156d8e6 User.Read Sign you in and read your profile
        2d05a661-f651-4d57-a595-489c91eda336 Member.Read.Hidden Read your hidden memberships
#>


    $aadConnection = Connect-AzureAD -AzureEnvironmentName $Environment

    if ([string]::IsNullOrEmpty($Password)) {
        $Password = [System.Convert]::ToBase64String($([guid]::NewGuid()).ToByteArray())
    }

    $Guid = New-Guid
    $startDate = Get-Date     
    $PasswordCredential = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordCredential
    $PasswordCredential.StartDate = $startDate
    $PasswordCredential.EndDate = $startDate.AddYears(1)
    $PasswordCredential.Value = $Password

    $displayName = $SiteUri.Host
    [string[]]$replyUrl = $SiteUri.AbsoluteUri + ".auth/login/aad/callback"

    #AAD Permissions
    $reqAAD = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
    $reqAAD.ResourceAppId = "00000002-0000-0000-c000-000000000000" #See above on how to find GUIDs

    $svcPrincipal = $(Get-AzureADServicePrincipal -SearchString "Windows azure active directory") | Where-Object {$_.AppId -eq "00000002-0000-0000-c000-000000000000"}
    foreach ($perm in $AADDelegatePermissions) {
        $permId = $($svcPrincipal.Oauth2Permissions | Where-Object { $_.Value -eq $perm}).Id
        $delPermission1 = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList $permId, "Scope"
        if ([String]::IsNullOrEmpty($reqAAD.ResourceAccess)) {
            $reqAAD.ResourceAccess = $delPermission1    
        } else {
            $reqAAD.ResourceAccess += $delPermission1
        }
    }

    #Graph Permissions
    $reqGraph1 = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
    $reqGraph1.ResourceAppId = "00000003-0000-0000-c000-000000000000" #See above on how to find GUIDs

    $svcPrincipal = $(Get-AzureADServicePrincipal -SearchString "Microsoft Graph") | Where-Object {$_.AppId -eq "00000003-0000-0000-c000-000000000000"}
    foreach ($perm in $GraphDelegatePermissions) {
        $permId = $($svcPrincipal.Oauth2Permissions | Where-Object { $_.Value -eq $perm}).Id
        $delPermission1 = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList $permId, "Scope" 
        if ([String]::IsNullOrEmpty($reqGraph1.ResourceAccess)) {
            $reqGraph1.ResourceAccess = $delPermission1    
        } else {
            $reqGraph1.ResourceAccess += $delPermission1
        }
    }

    $appReg = New-AzureADApplication -DisplayName $displayName -IdentifierUris $SiteUri -Homepage $SiteUri -ReplyUrls $replyUrl -PasswordCredential $PasswordCredential -RequiredResourceAccess $reqAAD, $reqGraph1

    $loginBaseUrl = $(Get-AzureRmEnvironment -Name $Environment).ActiveDirectoryAuthority

    #Small inconsistency for US gov in current AzureRm module
    if ($loginBaseUrl -eq "https://login-us.microsoftonline.com/") {
        $loginBaseUrl = "https://login.microsoftonline.us/"
    }

    $issuerUrl = $loginBaseUrl + $aadConnection.Tenant.Id.Guid + "/"

    return @{ 'IssuerUrl' = $issuerUrl
        'ClientId' = $appReg.AppId 
        'ClientSecret' = $Password
    }
}

function Set-WebAppAADAuth {
    param(

        # Parameter help description
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$ResourceGroupName,

        [Parameter(Mandatory = $true, Position = 2)]
        [string]$WebAppName,

        [Parameter(Mandatory = $true, Position = 3)]
        [string]$ClientId,

        [Parameter(Mandatory = $true, Position = 4)]
        [string]$ClientSecret,

        [Parameter(Mandatory = $true, Position = 4)]
        [string]$IssuerUrl,
    
        [Parameter(Mandatory = $false, Position = 5)]
        [string[]]$AdditionalLoginParams = @( "resource=https://graph.microsoft.com" ),
    
        [Parameter(Mandatory = $false, Position = 6)]
        [ValidateSet("AzureCloud", "AzureUsGovernment", "AzureGermanCloud", "AzureChinaCloud")]
        [String]$Environment = "AzureCloud"
    )

    $azcontext = Get-AzureRmContext
    if ([string]::IsNullOrEmpty($azcontext.Account) -or
        !($azcontext.Environment.Name -eq $Environment)) {
        Login-AzureRmAccount -Environment $Environment        
    }
    $azcontext = Get-AzureRmContext

    $authResourceName = $WebAppName + "/authsettings"
    $auth = Invoke-AzureRmResourceAction -ResourceGroupName $ResourceGroupName -ResourceType Microsoft.Web/sites/config -ResourceName $authResourceName -Action list -ApiVersion 2016-08-01 -Force

    $auth.properties.enabled = "True"
    $auth.properties.unauthenticatedClientAction = "RedirectToLoginPage"
    $auth.properties.tokenStoreEnabled = "True"
    $auth.properties.defaultProvider = "AzureActiveDirectory"
    $auth.properties.isAadAutoProvisioned = "False"
    $auth.properties.clientId = $ClientId
    $auth.properties.clientSecret = $ClientSecret
    $auth.properties.issuer = $IssuerUrl
    $auth.properties.additionalLoginParams = $AdditionalLoginParams

    New-AzureRmResource -PropertyObject $auth.properties -ResourceGroupName $ResourceGroupName -ResourceType Microsoft.Web/sites/config -ResourceName $authResourceName -ApiVersion 2016-08-01 -Force
}

function Get-AzureBearerToken {
    param(    
        [Parameter(Mandatory = $false)]
        [ValidateSet("AzureCloud", "AzureUsGovernment", "AzureGermanCloud", "AzureChinaCloud")]
        [String]$Environment = "AzureCloud"
    )

    # Inspiration for this function from:
    # - ArmClient: https://github.com/projectkudu/ARMClient
    # - https://www.deployazure.com/security/identity/authenticating-to-the-azure-resource-manager-api/
    # - https://www.bizbert.com/bizbert/2015/07/08/SettingUpPostManToCallTheAzureManagementAPIs.aspx
    # - https://gallery.technet.microsoft.com/Get-Azure-AD-Bearer-Token-37f3be03

    $azcontext = Get-AzureRmContext
    if ([string]::IsNullOrEmpty($azcontext.Account) -or
        !($azcontext.Environment.Name -eq $Environment)) {
        $azcontext = Login-AzureRmAccount -Environment $Environment        
    }
    $azcontext = Get-AzureRmContext
    $azenvironment = Get-AzureRmEnvironment -Name $azcontext.Environment

    # Load ADAL Assemblies
    $adal = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
    $adalforms = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll"
    $ignore = [System.Reflection.Assembly]::LoadFrom($adal)
    $ignore = [System.Reflection.Assembly]::LoadFrom($adalforms)

    $adTenant = $azcontext.Tenant.Id

    $authority = $azenvironment.ActiveDirectoryAuthority + $adTenant
    $resourceAppIdURI = $azenvironment.ResourceManagerUrl

    # ClientId and Redirect URL
    # Can be found in ArmClient code or with AzureAD:
    # PS> Connect-AzureAD
    # PS> $sp = Get-AzureADServicePrincipal -SearchString "Microsoft Azure PowerShell"
    # PS> $sp.AppId
    # 1950a258-227b-4e31-a9cf-717495945fc2
    # PS> $sp.ReplyUrls
    # urn:ietf:wg:oauth:2.0:oob

    $clientId = "1950a258-227b-4e31-a9cf-717495945fc2"
    $redirectUri = "urn:ietf:wg:oauth:2.0:oob"

    # Create Authentication Context tied to Azure AD Tenant
    $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority

    # Acquire token
    $authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $redirectUri, "Auto")

    # Create Authorization Header
    $authHeader = $authResult.CreateAuthorizationHeader()

    #Remove "Bearer " and return token
    $token = $authHeader.Substring(7)

    return @{
        'managementUrl' = $azenvironment.ResourceManagerUrl
        'bearerToken' = $token 
        'tenantId' = $adTenant
    }
}

function GitAvailable {
    $gitexists = $false

    if (Get-Command "git" -errorAction SilentlyContinue) {
        $gitexists = $true
    }

    return $gitexists
}

function RelativePath {
    param(
        [String]$absolutePath,
        [String]$basePath
    )

    $currentLoc = Get-Location
    Set-Location $basePath
    $relPath = Resolve-Path $absolutePath -Relative
    Set-Location $currentLoc
    return $relPath
}