MSCloudIdUtils.psm1

#Requires –Version 5

<#
  
.SYNOPSIS
    MSCloudIdUtils.psm1 is a Windows PowerShell module with some Azure AD helper functions for common administrative tasks
 
.DESCRIPTION
 
    Version: 1.0.0
 
    MSCloudIdUtils.psm1 is a Windows PowerShell module with some Azure AD helper functions for common administrative tasks
 
 
.DISCLAIMER
    THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
    ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
    THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
    PARTICULAR PURPOSE.
 
    Copyright (c) Microsoft Corporation. All rights reserved.
#>



######################
#HELPER CODE FOR ADAL
######################

$source = @"
using System;
using System.Diagnostics;
using System.Net;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography.X509Certificates;
 
using Microsoft.IdentityModel.Clients.ActiveDirectory;
 
 
public static class AdalHelper
{
 
    public static AuthenticationResult ObtainAadAuthenticationResultByPromptingUserCredential(string aadTokenIssuerUri, string resource, string clientId, string redirectUri)
    {
        AuthenticationContext authenticationContext = new AuthenticationContext(aadTokenIssuerUri);
        AuthenticationResult authenticationResult = authenticationContext.AcquireToken
        (
            resource: resource,
            clientId: clientId,
            redirectUri: new Uri(redirectUri),
            promptBehavior: PromptBehavior.Always,
            userId: UserIdentifier.AnyUser,
            extraQueryParameters: "nux=1"
        );
 
        return authenticationResult;
    }
 
    public static string ObtainAadAccessTokenByPromptingUserCredential(string aadTokenIssuerUri, string resource, string clientId, string redirectUri)
    {
        AuthenticationContext authenticationContext = new AuthenticationContext(aadTokenIssuerUri);
        AuthenticationResult authenticationResult = authenticationContext.AcquireToken
        (
            resource: resource,
            clientId: clientId,
            redirectUri: new Uri(redirectUri),
            promptBehavior: PromptBehavior.Always,
            userId: UserIdentifier.AnyUser,
            extraQueryParameters: "nux=1"
        );
 
        return authenticationResult.AccessToken;
    }
          
    public static string ObtainAadAccessTokenWia(string aadTokenIssuerUri, string resource, string clientId)
    {
        AuthenticationContext authenticationContext = new AuthenticationContext(aadTokenIssuerUri);
        UserCredential uc = new UserCredential();
        AuthenticationResult authenticationResult = authenticationContext.AcquireToken
        (
            resource: resource,
            clientId: clientId,
            userCredential: uc
        );
        return authenticationResult.AccessToken;
    }
 
 
    public static string ObtainAadAccessTokenWithCert(string aadTokenIssuerUri, X509Certificate2 cert, string resource, string clientId)
    {
        AuthenticationContext authenticationContext = new AuthenticationContext(aadTokenIssuerUri);
        ClientAssertionCertificate certCred = new ClientAssertionCertificate(clientId, cert);
        AuthenticationResult authenticationResult = authenticationContext.AcquireToken
        (
            resource: resource,
            clientCertificate: certCred
        );
        return authenticationResult.AccessToken;
    }
 
    public static string ObtainAadAccessTokenOnbehalfOfUser(string aadTokenIssuerUri,NetworkCredential clientCredential, string resource, string userToken)
    {
 
        ClientCredential adalClientCreds = new ClientCredential(clientCredential.UserName, clientCredential.SecurePassword);
        AuthenticationContext authenticationContext = new AuthenticationContext(aadTokenIssuerUri);
        UserAssertion userAssertion = new UserAssertion(userToken);
         
 
        AuthenticationResult authenticationResult = authenticationContext.AcquireToken( resource, adalClientCreds, userAssertion );
        return authenticationResult.AccessToken;
    }
}
"@


function Initialize-ActiveDirectoryAuthenticationLibrary()
{
   $moduleDirPath = [Environment]::GetFolderPath("MyDocuments") + "\WindowsPowerShell\Modules"
   $modulePath = $moduleDirPath + "\MSCloudIdUtils"

   if (Test-Path $modulePath) 
   {
      $adalPackageDirectories = (Get-ChildItem -Path ($modulePath+"\Nugets") -Filter "Microsoft.IdentityModel.Clients.ActiveDirectory*" -Directory)

      $ADAL_Assembly = (Get-ChildItem "Microsoft.IdentityModel.Clients.ActiveDirectory.dll" -Path $adalPackageDirectories[$adalPackageDirectories.length-1].FullName -Recurse)

      $ADAL_WindowsForms_Assembly = (Get-ChildItem "Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll" -Path $adalPackageDirectories[$adalPackageDirectories.length-1].FullName -Recurse)

      if($ADAL_Assembly.Length -gt 0 -and $ADAL_WindowsForms_Assembly.Length -gt 0)
      {
        Write-Host "Loading ADAL Assemblies ..." -ForegroundColor Green
        [System.Reflection.Assembly]::LoadFrom($ADAL_Assembly[0].FullName) | out-null
        [System.Reflection.Assembly]::LoadFrom($ADAL_WindowsForms_Assembly.FullName) | out-null
        $reqAssem = @($ADAL_Assembly[0].FullName, $ADAL_WindowsForms_Assembly.FullName)
        Add-Type -ReferencedAssemblies $reqAssem -TypeDefinition $source -Language CSharp -IgnoreWarnings
        return $true
      }
      else
      {
        Write-Host "Fixing Active Directory Authentication Library package directories ..." -ForegroundColor Yellow
        $adalPackageDirectories | Remove-Item -Recurse -Force | Out-Null
        Write-Host "Not able to load ADAL assembly. Delete the Nugets folder under" $modulePath ", restart PowerShell session and try again ..."
        return $false
      }
    }
    else
    {
        Write-Host "Current module is not part of the Powershell Module path. Please run Install-MSCloudIdUtilsModule, restart the PowerShell session and try again.." -ForegroundColor Yellow
    }
}

#Bootstrap the initialization of ADAL
Initialize-ActiveDirectoryAuthenticationLibrary


<#
 .Synopsis
  Gets an access token based on a user credential using web authentication to access the Azure AD Graph API.
 
 .Description
  This function returns a string with the access token from a user. This will pop up a web authentication prompt for a user
 
 .Parameter TenantDomain
  The domain name of the tenant you want the token for.
 
 .Parameter ClientId
  The client ID of the application you want the token for
   
 .Parameter Redirect URI
  Redirect URI for the OAuth request
   
 
 .Example
   $accessToken = Get-MSCloudIdAccessTokenFromUser -TenantDomain "contoso.com"
#>

Function Get-MSCloudIdAccessTokenFromUser
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,
        [Parameter(Mandatory=$true)]
        [string]
        $ClientId,
        [Parameter(Mandatory=$true, ParameterSetName="PromptUserCredential")]
        [string]
        $RedirectUri,
        [Parameter(ParameterSetName="WIA")]
        [switch]
        $WindowsAuthentication,
        [Parameter(Mandatory=$true)]
        [string]
        $Resource

    )
    if ($WindowsAuthentication)
    {
        $AadToken = [AdalHelper]::ObtainAadAccessTokenWia("https://login.windows.net/$TenantDomain/", $Resource, $ClientId);
        Write-Output $AadToken
    }
    else
    {
        $AadToken = [AdalHelper]::ObtainAadAccessTokenByPromptingUserCredential("https://login.windows.net/$TenantDomain/", $Resource, $ClientId, $RedirectUri);
        Write-Output $AadToken
    }
}


<#
 .Synopsis
  Gets an access token based on a user credential using web authentication to access the Azure AD Graph API.
 
 .Description
  This function returns a string with the access token from a user. This will pop up a web authentication prompt for a user
 
 .Parameter TenantDomain
  The domain name of the tenant you want the token for.
 
 .Parameter ClientId
  The client ID of the application you want the token for
   
 .Parameter Redirect URI
  Redirect URI for the OAuth request
   
 
 .Example
   $accessToken = Get-MSCloudIdIdTokenFromUser -TenantDomain "contoso.com"
#>

Function Get-MSCloudIdIdTokenFromUser
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,
        [Parameter(Mandatory=$true)]
        [string]
        $ClientId,
        [Parameter(Mandatory=$true, ParameterSetName="PromptUserCredential")]
        [string]
        $RedirectUri,
        [Parameter(Mandatory=$true)]
        [string]
        $Resource

    )
    
    $AuthResult = [AdalHelper]::ObtainAadAuthenticationResultByPromptingUserCredential("https://login.windows.net/$TenantDomain/", $Resource, $ClientId, $RedirectUri);
    Write-Output $AuthResult
}

<#
 .Synopsis
  Gets an access token based on a confidential client credential
 
 .Description
  This function returns a string with the access token for the Azure AD Graph API.
 
 .Parameter TenantDomain
  The domain name of the tenant you want the token for.
 
 .Parameter ClientCredential
  A Powershell Credential with UserName=ClientID, Password=Application Key
 
 
 .Example
   $accessToken = Get-MSCloudIdGraphAPIAccessTokenFromAppKey -TenantDomain "contoso.com" -ClientCredential (Get-Credential)
#>

Function Get-MSCloudIdGraphAPIAccessTokenFromAppKey
{
    [CmdletBinding()]
    param
    (    
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,        
        [Parameter(Mandatory=$true)]
        [pscredential]
        $ClientCredential # Credential object that captures the client credentials
    )

    $ClientID = $ClientCredential.UserName                    
    $ClientSecret = $ClientCredential.GetNetworkCredential().Password         

    if ([String]::IsNullOrWhiteSpace($ClientID))
    {
        throw "Client ID is missing"
    }

    if ([String]::IsNullOrWhiteSpace($ClientSecret))
    {
        throw "Client secret is missing"
    }

    $loginURL = "https://login.windows.net"

    # Get an Oauth 2 access token based on client id, secret and tenant domain
    $body = @{grant_type="client_credentials";resource="https://graph.microsoft.com";client_id=$ClientID;client_secret=$ClientSecret}
    $oauth = Invoke-RestMethod -Method Post -Uri $loginURL/$TenantDomain/oauth2/token?api-version=1.0 -Body $body

    if ($oauth.access_token -eq $null) 
    {
        throw "ERROR: No Access Token"
    }

    Write-Output $oauth.access_token
}

<#
 .Synopsis
  Gets an access token based on a user credential using web authentication to access an application in Azure AD.
 
 .Description
  This function returns a string with the access token from a user. This will pop up a web authentication prompt for a user
 
 .Parameter TenantDomain
  The domain name of the tenant you want the token for.
 
 .Parameter ClientId
  The client ID of the application you want the token for
   
 .Parameter Redirect URI
  Redirect URI for the OAuth request
 
 
 .Example
   $accessToken = Get-MSCloudIdAzureADGraphAccessTokenFromUser -TenantDomain "contoso.com" -Resource "myapp"
#>

Function Get-MSCloudIdAzureADGraphAccessTokenFromUser
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,
        [Parameter(Mandatory=$true)]
        [string]
        $ClientId,
        [Parameter(Mandatory=$true, ParameterSetName="PromptUserCredential")]
        [string]
        $RedirectUri,
        [Parameter(ParameterSetName="WIA")]
        [switch]
        $WindowsAuthentication

    )
    if ($WindowsAuthentication)
    {
        $AadToken = [AdalHelper]::ObtainAadAccessTokenWia("https://login.windows.net/$TenantDomain/", "https://graph.windows.net/", $ClientId);
        Write-Output $AadToken
    }
    else
    {
        $AadToken = [AdalHelper]::ObtainAadAccessTokenByPromptingUserCredential("https://login.windows.net/$TenantDomain/", "https://graph.windows.net/", $ClientId, $RedirectUri);
        Write-Output $AadToken
    }
}

<#
 .Synopsis
  Gets an access token based on a user credential using web authentication to access an application in Azure AD.
 
 .Description
  This function returns a string with the access token from a user. This will pop up a web authentication prompt for a user
 
 .Parameter TenantDomain
  The domain name of the tenant you want the token for.
 
 .Parameter ClientId
  The client ID of the application you want the token for
   
 .Parameter Redirect URI
  Redirect URI for the OAuth request
 
  .Parameter Resource
  The Resource you want the token for
   
 
 .Example
   $accessToken = Get-MSCloudIdAzureADGraphAccessTokenFromUser -TenantDomain "contoso.com" -Resource "myapp"
#>

Function Get-MSCloudIdAccessTokenOnBehalfOfUser
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,
        [Parameter(Mandatory=$true)]
        [pscredential]
        $ClientCredential,
        [Parameter(Mandatory=$true)]
        [string]
        $UserToken,
        [Parameter(Mandatory=$true)]
        [string]
        $Resource
    )
    $AadToken = [AdalHelper]::ObtainAadAccessTokenOnbehalfOfUser("https://login.windows.net/$TenantDomain/", $ClientCredential, $Resource, $UserToken );
    Write-Output $AadToken
}

<#
 .Synopsis
  Gets an access token based on a certificate credential
 
 .Description
  This function returns a string with the access token from a certificate credential to access the Azure AD Graph API.
 
 .Parameter TenantDomain
  The domain name of the tenant you want the token for.
 
 .Parameter ClientID
  The client ID of the application that has the certificate
 
 .Parameter Certificate
  The X509Certificate2 certificate. The private key of the certificate should be accessible to obtain the access token
   
 .Example
 
  $ReportingClientId = "9a0112fb-6626-4761-a96b-a5f433c69ef7"
  $Cert = dir Cert:\LocalMachine\my\0EA8A7037A584C3C7BB54119D754DE1024AABAB2
  $AccessToken = Get-MSCloudIdAzureADGraphAccessTokenFromCert -TenantDomain "contoso.com" -ClientId $ReportingClientId -Certificate $Cert
#>

Function Get-MSCloudIdAzureADGraphAccessTokenFromCert
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,
        [Parameter(Mandatory=$true)]
        [string]
        $ClientId,
        [Parameter(Mandatory=$true)]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]
        $Certificate
    )
    $AadToken = [AdalHelper]::ObtainAadAccessTokenWithCert("https://login.windows.net/$TenantDomain/", $Certificate, "https://graph.windows.net/", $ClientId);
    Write-Output $AadToken
}

<#
 .Synopsis
  Gets an access token based on a certificate credential
 
 .Description
  This function returns a string with the access token from a certificate credential to access the Azure AD Graph API.
 
 .Parameter TenantDomain
  The domain name of the tenant you want the token for.
 
 .Parameter ClientID
  The client ID of the application that has the certificate
 
 .Parameter Certificate
  The X509Certificate2 certificate. The private key of the certificate should be accessible to obtain the access token
   
 .Example
 
  $ReportingClientId = "9a0112fb-6626-4761-a96b-a5f433c69ef7"
  $Cert = dir Cert:\LocalMachine\my\0EA8A7037A584C3C7BB54119D754DE1024AABAB2
  $AccessToken = Get-MSCloudIdMSGraphAccessTokenFromCert -TenantDomain "contoso.com" -ClientId $ReportingClientId -Certificate $Cert
#>

Function Get-MSCloudIdMSGraphAccessTokenFromCert
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,
        [Parameter(Mandatory=$true)]
        [string]
        $ClientId,
        [Parameter(Mandatory=$true)]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]
        $Certificate
    )
    $AadToken = [AdalHelper]::ObtainAadAccessTokenWithCert("https://login.windows.net/$TenantDomain/", $Certificate, "https://graph.microsoft.com/", $ClientId);
    Write-Output $AadToken
}

<#
 .Synopsis
  Performs a query against Azure AD Graph API.
 
 .Description
  This functions invokes the Azure AD Graph API and returns the results as objects in the pipeline. This function also traverses all pages of the query, if needed.
 
 .Parameter TenantDomain
  The domain name of the tenant you want the token for.
 
 .Parameter AccessToken
  Access token for Azure AD Graph API
 
 .Parameter GraphQuery
  The Query against Graph API
   
 .Example
 
  $ReportingClientId = "9a0112fb-6626-4761-a96b-a5f433c69ef7"
  $Cert = dir Cert:\LocalMachine\my\0EA8A7037A584C3C7BB54119D754DE1024AABAB2
  $AccessToken = Get-MSCloudIdAzureADGraphAccessTokenFromCert -TenantDomain "contoso.com" -ClientId $ReportingClientId -Certificate $Cert
  $SignInLog = Invoke-MSCloudIdAzureADGraphQuery -AccessToken $AccessToken -TenantDomain $TenantDomain -GraphQuery "/activities/signinEvents?api-version=beta"
#>

Function Invoke-MSCloudIdAzureADGraphQuery
{
    [CmdletBinding()]
    param
    (
       
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain, # For example, contoso.onmicrosoft.com
        [Parameter(Mandatory=$true)]
        [string]
        $AccessToken, # For example, contoso.onmicrosoft.com,
        [string]
        $GraphQuery,
        [ScriptBlock]
        $TokenRenewalCallback
    )

    Write-Progress -Id 1 -Activity "Querying directory" -CurrentOperation "Invoking Azure AD Graph API"

    $headerParams  = @{'Authorization'="Bearer $AccessToken"}
       
    $queryResults = @()
    $originalUrl = "https://graph.windows.net/$TenantDomain/$GraphQuery"
    $queryUrl = "https://graph.windows.net/$TenantDomain/$GraphQuery"
    $queryCount = 0

    while (-not [String]::IsNullOrEmpty($queryUrl))
    {
        $batchResult = (Invoke-WebRequest -Headers $headerParams -Uri $queryUrl).Content | ConvertFrom-Json
        if ($batchResult.value -ne $null)
        {
            $queryResults += $batchResult.value
        }
        else
        {
            $queryResults += $batchResult
        }
        $queryCount = $queryResults.Count
        Write-Progress -Id 1 -Activity "Querying directory" -CurrentOperation "Retrieving results ($queryCount found so far)" 
        $queryUrl = ""

        $odataNextLink = $batchResult | Select-Object -ExpandProperty "@odata.nextLink" -ErrorAction SilentlyContinue

        if ($odataNextLink -ne $null)
        {
            $queryUrl =  $odataNextLink
        }
        else
        {
            $odataNextLink = $batchResult | Select-Object -ExpandProperty "odata.nextLink" -ErrorAction SilentlyContinue
            if ($odataNextLink -ne $null)
            {
                $absoluteUri = [Uri]"https://bogus/$odataNextLink"
                $skipToken = $absoluteUri.Query.TrimStart("?")
                $queryUrl = "https://graph.windows.net/$TenantDomain/$odataNextLink&api-version=1.6" #"$originalUrl&$skipToken"
            }
        }
    }

    Write-Progress -Id 1 -Activity "Querying directory" -Completed

    Write-Output $queryResults
}

<#
 .Synopsis
  Performs a query against Microsoft Graph API.
 
 .Description
  This functions invokes the Microsoft Graph API and returns the results as objects in the pipeline. This function also traverses all pages of the query, if needed.
 
  .Parameter AccessToken
  Access token for Azure AD Graph API
 
 .Parameter GraphQuery
  The Query against Graph API
   
 .Example
 
  $ReportingClientId = "9a0112fb-6626-4761-a96b-a5f433c69ef7"
  $Cert = dir Cert:\LocalMachine\my\0EA8A7037A584C3C7BB54119D754DE1024AABAB2
  $AccessToken = Get-MSCloudIdMSGraphAccessTokenFromCert -TenantDomain "contoso.com" -ClientId $ReportingClientId -Certificate $Cert
  $SignInLog = Invoke-MSCloudIdMSGraphQuery -AccessToken $AccessToken -GraphQuery "/beta/identityRiskEvents"
#>

Function Invoke-MSCloudIdMSGraphQuery
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $AccessToken, # For example, contoso.onmicrosoft.com,
        [string]
        $GraphQuery,
        [ScriptBlock]
        $TokenRenewalCallback
    )

    Write-Progress -Id 1 -Activity "Querying directory" -CurrentOperation "Invoking MS Graph API"

    $headerParams  = @{'Authorization'="Bearer $AccessToken"}
       
    $queryResults = @()
    $originalUrl = "https://graph.microsoft.com/$GraphQuery"
    $queryUrl = "https://graph.microsoft.com/$GraphQuery"
    $queryCount = 0

    while (-not [String]::IsNullOrEmpty($queryUrl))
    {
        $batchResult = (Invoke-WebRequest -Headers $headerParams -Uri $queryUrl).Content | ConvertFrom-Json
        if ($batchResult.value -ne $null)
        {
            $queryResults += $batchResult.value
        }
        else
        {
            $queryResults += $batchResult
        }
        $queryCount = $queryResults.Count
        Write-Progress -Id 1 -Activity "Querying directory" -CurrentOperation "Retrieving results ($queryCount found so far)" 
        $queryUrl = ""

        $odataNextLink = $batchResult | Select-Object -ExpandProperty "@odata.nextLink" -ErrorAction SilentlyContinue

        if ($odataNextLink -ne $null)
        {
            $queryUrl =  $odataNextLink
        }
        else
        {
            $odataNextLink = $batchResult | Select-Object -ExpandProperty "odata.nextLink" -ErrorAction SilentlyContinue
            if ($odataNextLink -ne $null)
            {
                $absoluteUri = [Uri]"https://bogus/$odataNextLink"
                $skipToken = $absoluteUri.Query.TrimStart("?")
                $queryUrl = "https://graph.windows.net/$TenantDomain/$odataNextLink&api-version=1.6" #"$originalUrl&$skipToken"
            }
        }
    }

    Write-Progress -Id 1 -Activity "Querying directory" -Completed

    Write-Output $queryResults
}

<#
 .Synopsis
  Generates a Report of all assignments to applications.
 
 .Description
  This function queries all the applications, and for each one, obtain the list of role assignments.
 
 .Parameter TenantDomain
  The domain name of the tenant you want the token for.
 
 .Parameter AccessToken
  Access token for Azure AD Graph API
 
   
 .Example
  $ReportingClientId = "9a0112fb-6626-4761-a96b-a5f433c69ef7"
  $Cert = dir Cert:\LocalMachine\my\0EA8A7037A584C3C7BB54119D754DE1024AABAB2
  $AccessToken = Get-MSCloudIdAzureADGraphAccessTokenFromCert -TenantDomain "contoso.com" -ClientId $ReportingClientId -Certificate $Cert
  $SignInLog = Invoke-AzureADAppAssignmentReport -AccessToken $AccessToken -TenantDomain $TenantDomain
#>

Function Get-MSCloudIdAppAssignmentReport
{    
    [CmdletBinding()]
    param
    (       
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,  
        [Parameter(Mandatory=$true)]
        [string]
        $AccessToken           
    )
    
    Write-Progress -Id 10 -Activity "Building app assignment report" -CurrentOperation "Getting list of applications" 


    $apps = Invoke-MSCloudIdAzureADGraphQuery -AccessToken $AccessToken -TenantDomain $TenantDomain -GraphQuery "servicePrincipals?api-version=1.5"

    $results = @()
    $appCount = $apps.Count
    $appIndex = 1

    foreach($app in $apps)
    {
        Write-Progress -Id 10 -Activity "Building app assignment report" -PercentComplete (100 * $appIndex / $appCount)  -CurrentOperation "Extracting permissions for each application ($appIndex/$appCount)"  

        $appObjectId = $app.objectId
        $appRoles = Invoke-MSCloudIdAzureADGraphQuery -AccessToken $AccessToken -TenantDomain $TenantDomain -GraphQuery "servicePrincipals/$appObjectId/appRoleAssignedTo?api-version=1.5"
        foreach($appPermission in $appRoles)
        {
            $result = New-Object -TypeName PSObject
            $result | add-member -MemberType NoteProperty -name "appObjectId" -value $app.objectId
            $result | add-member -MemberType NoteProperty -name "appDisplayName" -value $app.appDisplayName
            $result | add-member -MemberType NoteProperty -name "principalId" -value $appPermission.principalId
            $result | add-member -MemberType NoteProperty -name "principalDisplayName" -value $appPermission.principalDisplayName
            $result | add-member -MemberType NoteProperty -name "principalType" -value $appPermission.principalType
            $results += $result
        }
        $appIndex++
    }

    Write-Progress -Id 10 -Activity "Building app assignment report" -Completed

    Write-Output $results
}

$script:TenantSkus = $null

Function Get-AzureADTenantSkus
{
    if ($script:TenantSkus -eq $null)
    {
        $script:TenantSkus = Get-AzureADSubscribedSku
    }
    Write-Output $script:TenantSkus
}

Function Get-MSCloudIdUserLastSigninDateTime 
{

    [CmdletBinding()]
    param
    (       
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,  
        [Parameter(Mandatory=$true)]
        [string]
        $AccessToken,
        [Parameter(Mandatory=$true)]
        [Int]
        $CutOffDays,
        [Parameter(Mandatory=$true)]
        [string]
        $UserPrincipalName

    )

    $CutOffDateFilter = "{0:s}Z" -f (Get-Date).AddDays(-1 * $CutOffDays)
   
    #Step 1: Get sign in info from the user
    $signInActivity = Invoke-MSCloudIdAzureADGraphQuery -TenantDomain $TenantDomain -AccessToken $AccessToken -GraphQuery "/activities/signinEvents?api-version=beta&`$filter=signinDateTime ge $CutOffDateFilter and userPrincipalName eq '$UserPrincipalName'"

    #If we had at least one result, then get-member will retrieve the property metadata
    $atLeastOneSignIn = $signInActivity | Get-Member userId

    if ($atLeastOneSignIn -eq $null)
    {
        Write-Output $null
    }
    else
    {
        $lastSignin = $signInActivity | Sort-Object -Property "signinDateTime" -Descending | Select-Object -First 1 -ExpandProperty signInDateTime
        Write-Output $lastSignin
    }

}

Function Get-MSCloudIdAppStaleLicensingReportByUser
{
    [CmdletBinding()]
    param
    (       
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,  
        [Parameter(Mandatory=$true)]
        [string]
        $AccessToken,
        [Parameter(Mandatory=$true)]
        [Int]
        $CutOffDays,
        [Parameter(Mandatory=$true)]
        [string]
        $UserPrincipalName

    )
    $LastSignIn = Get-MSCloudIdUserLastSigninDateTime -TenantDomain $TenantDomain -AccessToken $AccessToken -CutOffDays $CutOffDays -UserPrincipalName $UserPrincipalName
    $TenantSKUs = Get-AzureADTenantSkus
    $user= Get-AzureADUser -SearchString $UserPrincipalName
    $userSkus = $user.AssignedLicenses
    
    $skuString = ""
        
    if ($userSkus -ne $null)
    {

        $skuString = ""

        foreach ($userSku in $userSkus)
        {
            $skuName = $TenantSKUs | where {$_.SkuId -eq $userSku.SkuId} | Select-Object -ExpandProperty SkuPartNumber
            $skuString +=  $skuName + ";"

        }
    }

    $signinStaleStatus = $null

    if ($LastSignIn -eq $null)
    {
        $signinStaleStatus = "Stale"
    }
    else
    {
        $signinStaleStatus = "Not stale"
    }

    $result = New-Object PSObject -Property @{"UPN" = $UserPrincipalName; "SKUs" = $skuString; "StaleStatus" = $signinStaleStatus; "Last Sign In" = $LastSignIn }
    Write-Output $result
}

Function Get-AzureADSignInReportByApp 
{
    [CmdletBinding()]
    param
    (       
        [Parameter(Mandatory=$true)]
        [string]
        $TenantDomain,  
        [Parameter(Mandatory=$true)]
        [string]
        $AccessToken,
        [Parameter(Mandatory=$true)]
        [Int]
        $CutOffDays
    )
    $CutOffDateFilter = "{0:s}Z" -f (Get-Date).AddDays(-1 * $CutOffDays)
   
    #Step 1: Get all sign ins from all folks
    $signInActivity = Invoke-MSCloudIdAzureADGraphQuery -TenantDomain $TenantDomain -AccessToken $AccessToken -GraphQuery "/activities/signinEvents?api-version=beta&`$filter=signinDateTime ge $CutOffDateFilter"

    $signInActivity | Group-Object  -NoElement 


}

<#
 .Synopsis
  Adds certificate Credentials to an application
 
 .Description
  This functions installs a client certificate credentials
 
 .Parameter ApplicationObjectId
  The application Object ID that will be associated to the certificate credential
   
 .Example
 
  $ReportingClientId = "9a0112fb-6626-4761-a96b-a5f433c69ef7"
  $Cert = dir Cert:\LocalMachine\my\0EA8A7037A584C3C7BB54119D754DE1024AABAB2
 
  New-MSCloudIdApplicationCertificateCredential -ApplicationObjectId $ReportingClientId -Certificate $Cert
   
#>

Function New-MSCloudIdApplicationCertificateCredential
{
  param
  (
        [Parameter(Mandatory=$true)]
        [string]
        $ApplicationObjectId,
        [Parameter(Mandatory=$true)]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]
        $Certificate
  )

    $bin = $Certificate.GetRawCertData()
    $base64Value = [System.Convert]::ToBase64String($bin)
    $thumbprint = $Certificate.GetCertHash()
    $base64Thumbprint = [System.Convert]::ToBase64String($thumbprint)

    New-AzureADApplicationKeyCredential `
        -ObjectId $ApplicationObjectId `
        -CustomKeyIdentifier $base64Thumbprint `
        -Type AsymmetricX509Cert `
        -Usage Verify `
        -Value $base64Value `
        -StartDate $Certificate.NotBefore `
        -EndDate $Certificate.NotAfter
}

<#
 .Synopsis
  Provides a report to show all the keys expiration date accross application and service principals
 
 .Description
  Provides a report to show all the keys expiration date accross application and service principals
   
 .Example
  Connect-AzureAD
  Get-MSCloudIdApplicationKeyExpirationReport
   
#>

Function Get-MSCloudIdApplicationKeyExpirationReport
{
    param()
    
    $apps = Get-AzureADApplication -Top 100000

    foreach($app in $apps)
    {
        $appObjectId = $app.ObjectId
        $appName = $app.DisplayName
        

        $appKeys = Get-AzureADApplicationKeyCredential -ObjectId $appObjectId

        foreach($appKey in $appKeys)
        {        
            $result = New-Object PSObject
            $result  | Add-Member -MemberType NoteProperty -Name "Display Name" -Value $appName
            $result  | Add-Member -MemberType NoteProperty -Name "Object Type" -Value "Application"
            $result  | Add-Member -MemberType NoteProperty -Name "KeyType" -Value $appKey.Type
            $result  | Add-Member -MemberType NoteProperty -Name "Start Date" -Value $appKey.StartDate
            $result  | Add-Member -MemberType NoteProperty -Name "End Date" -Value $appKey.EndDate
            $result  | Add-Member -MemberType NoteProperty -Name "Usage" -Value $appKey.Usage
            Write-Output $result
        }

        $appKeys = Get-AzureADApplicationPasswordCredential -ObjectId $appObjectId
        
        foreach($appKey in $app.PasswordCredentials)
        {        
            $result = New-Object PSObject
            $result  | Add-Member -MemberType NoteProperty -Name "Display Name" -Value $appName
            $result  | Add-Member -MemberType NoteProperty -Name "Object Type" -Value "Application"
            $result  | Add-Member -MemberType NoteProperty -Name "KeyType" -Value "Password"
            $result  | Add-Member -MemberType NoteProperty -Name "Start Date" -Value $appKey.StartDate
            $result  | Add-Member -MemberType NoteProperty -Name "End Date" -Value $appKey.EndDate
            Write-Output $result
        }
    }

    
    $servicePrincipals = Get-AzureADServicePrincipal -Top 10000

    foreach($sp in $servicePrincipals)
    {
        $spName = $sp.DisplayName
        $spObjectId = $sp.ObjectId

        $spKeys = Get-AzureADServicePrincipalKeyCredential -ObjectId $spObjectId        

        foreach($spKey in $spKeys)
        {
            $result = New-Object PSObject
            $result  | Add-Member -MemberType NoteProperty -Name "Display Name" -Value $spName
            $result  | Add-Member -MemberType NoteProperty -Name "Object Type" -Value "Service Principal"
            $result  | Add-Member -MemberType NoteProperty -Name "KeyType" -Value $spKey.Type
            $result  | Add-Member -MemberType NoteProperty -Name "Start Date" -Value $spKey.StartDate
            $result  | Add-Member -MemberType NoteProperty -Name "End Date" -Value $spKey.EndDate
            $result  | Add-Member -MemberType NoteProperty -Name "Usage" -Value $spKey.Usage
            Write-Output $result
        }    
        
        $spKeys = Get-AzureADServicePrincipalPasswordCredential -ObjectId $spObjectId    

        
        foreach($spKey in $spKeys)
        {
            $result = New-Object PSObject
            $result  | Add-Member -MemberType NoteProperty -Name "Display Name" -Value $spName
            $result  | Add-Member -MemberType NoteProperty -Name "Object Type" -Value "Service Principal"
            $result  | Add-Member -MemberType NoteProperty -Name "KeyType" -Value "Password"
            $result  | Add-Member -MemberType NoteProperty -Name "Start Date" -Value $spKey.StartDate
            $result  | Add-Member -MemberType NoteProperty -Name "End Date" -Value $spKey.EndDate
            Write-Output $result
        }    

    }
}

<#
 .Synopsis
  Removes all on premises synchronized users from a tenant
 
 .Description
  Removes all on premises synchronized users from a tenant. This cmdlet requires the Azure AD Powershell Module
 
 .Parameter Force
  When this parameter is set, then the confirmation message is not shown to the user.
 
 .Example
  Connect-MSOLService
  Remove-MSCloudIdSyncUsers -Force
#>

Function Remove-MSCloudIdSyncUsers
{
    [CmdletBinding()]
    param
    (   
       [Switch]
       $Force    
    )    

    $Proceed = $Force

    if (-not $Force)
    {
        $title = "Remove Synchronized Accounts"
        $message = "This will remove ALL on-premises synchronized users from your tenant. Do you want to proceed"

        $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
        "Remove all synchronized user accounts from Azure AD. You will need to execute a full sync cycle"

        $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
        "Keep all the objects on premises."

        $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
        $result = $host.ui.PromptForChoice($title, $message, $options, 0) 

        if ($result -eq 0)
        {
            $Proceed = $true
        }
    }

    if ($Proceed)
    {

        Write-Progress -Id 10 -Activity "Removing On-Premises users from your tenant..." -CurrentOperation "Connecting to Azure AD" 
        Connect-MsolService
        Write-Progress -Id 10 -Activity "Removing On-Premises users from your tenant..." -CurrentOperation "Removing users the cloud" 
        $UsersToRemove = Get-MsolUser -Synchronized | Where {$_.UserPrincipalName -notlike "Sync*"}
        $UsersToRemove | %{Remove-MsolUser -ObjectId $_.ObjectId -Force }
        Get-MsolUser -ReturnDeletedUsers | %{ Remove-MsolUser -ObjectId $_.ObjectId -RemoveFromRecycleBin -Force }        
        $UsersCount = $UsersToRemove | Measure-Object  | Select-Object -ExpandProperty Count
        "$UsersCount have been deleted from the tenant. To Resynchronize, clean the Azure AD Connect connector spaces and force an Initial Sync Cycle"
    }
}

<#
 .Synopsis
  Adds a custom signing certificate to a service principal
 
 .Description
  This functions takes an X509Certificate, serializes it and associates it to a service principal.
  It will return the Raw HTTP output of the Azure AD Graph Call. A successful call to this cmdlet should result in an 204 Output code
 
 .Parameter AccessToken
  Access token to Azure AD Graph (See functions *GraphAccessToken* in this module)
   
 .Parameter ServicePrincipalObjectId
  Object ID of the service principal to be updated.
 
 .Parameter Certificate
  Certificate object to be uploaded. This certificate must have the private key accessible.
   
 .Example
  $AccessToken = Get-MSCloudIdAzureADGraphAccessTokenFromUser -TenantDomain contoso.com -ClientId dbf240f7-84cb-471c-978a-a97890bd2393 -RedirectUri urn:your:returnurl
  $ServicePrincipalObjectId = c1bc4a39-3be3-456d-a7f1-5a0d1b8531c2
  $Cert = dir Cert:\LocalMachine\my\0EA8A7037A584C3C7BB54119D754DE1024AA1234
  New-MSCloudIdServicePrincipalSigningCertificate -AccessToken $AccessToken -ServicePrincipalObjectId $ServicePrincipalObjectId -Certificate $Cert
 
  ----------------
  Sample Output
  ----------------
 
  HTTP/1.1 204 No Content
  Pragma: no-cache
...
  X-AspNet-Version: 4.0.30319
  X-Powered-By: ASP.NET,ASP.NET
#>

Function New-MSCloudIdServicePrincipalSigningCertificate
{
    param
    (
            [Parameter(Mandatory=$true)]
            [string]
            $AccessToken,
            [Parameter(Mandatory=$true)]
            [string]
            $ServicePrincipalObjectId,
            [Parameter(Mandatory=$true)]
            [System.Security.Cryptography.X509Certificates.X509Certificate2]
            $Certificate
    )
    #Create the Pfx File Placeholder
    $TempPfxFileInfo = New-TemporaryFile
    $TempPfxFilePath = $TempPfxFileInfo.FullName

    try 
    {
        if (-not $Certificate.HasPrivateKey)
        {
            Write-Error "Certificate supplied does not have the private key."
        }

        #Generate a temporary 128 symmetric key as the password of the exported PFX file
        $pfxKeyBytes = new-object "System.Byte[]" 128 
        $rnd = new-object System.Security.Cryptography.RNGCryptoServiceProvider
        $rnd.GetBytes($pfxKeyBytes)
        $PfxPassword = [Convert]::ToBase64String($pfxKeyBytes)
        $PfxPasswordSecureString = ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText
        $Certificate | Export-PfxCertificate -FilePath $TempPfxFilePath -Password $PfxPasswordSecureString | Out-Null

        #Get the parameters needed in the Azure AD Graph API call
        $StartDate = ([DateTime]$Certificate.NotBefore).ToUniversalTime().ToString("s")+"Z"
        $EndDate = ([DateTime]$Certificate.NotAfter).ToUniversalTime().ToString("s")+"Z"
        $KeyId = [Guid]::NewGuid().Guid.ToString();
        $RawCertBytes = Get-Content -Path $TempPfxFilePath -Encoding Byte
        $RawCertBase64String = [Convert]::ToBase64String($RawCertBytes)

        $PatchBodyTemplate = '{5}
            "keyCredentials":
            [{5}
                "startDate":"{0}",
                "endDate":"{1}",
                "type":"X509CertAndPassword",
                "usage":"Sign",
                "keyId" : "{2}",
                "value": "{3}"
            {6}],
            "passwordCredentials":
            [{5}
                    "startDate":"{0}",
                    "endDate":"{1}",
                    "keyId" : "{2}",
                    "value": "{4}"
            {6}]
        {6}'
 

        $PatchBody = $PatchBodyTemplate -f $StartDate,$EndDate,$KeyId,$RawCertBase64String,$PfxPassword,"{","}"
        $GraphEndpoint = "https://graph.windows.net/myorganization/servicePrincipals/{0}?api-version=1.6" -f $ServicePrincipalObjectId
        $headers  = @{'Authorization'="Bearer $AccessToken"}

        #Invoke Graph API. Result should be 204
        Invoke-WebRequest -Method Patch -Uri $GraphEndpoint -Body $PatchBody -Headers $headers -ContentType "application/json" -UseBasicParsing | Select-Object -ExpandProperty RawContent
    }
    finally
    {
        #Delete the PFX file
        Remove-Item -LiteralPath $TempPfxFilePath
    }
}

function Convert-FromBase64StringWithNoPadding([string]$data)
{
    $data = $data.Replace('-', '+').Replace('_', '/')
    switch ($data.Length % 4)
    {
        0 { break }
        2 { $data += '==' }
        3 { $data += '=' }
        default { throw New-Object ArgumentException('data') }
    }
    return [System.Convert]::FromBase64String($data)
}

function Decode-JWT([string]$rawToken)
{
    $parts = $rawToken.Split('.');
    $headers = [System.Text.Encoding]::UTF8.GetString((Convert-FromBase64StringWithNoPadding $parts[0]))
    $claims = [System.Text.Encoding]::UTF8.GetString((Convert-FromBase64StringWithNoPadding $parts[1]))
    $signature = (Convert-FromBase64StringWithNoPadding $parts[2])

    $customObject = [PSCustomObject]@{
        headers = ($headers | ConvertFrom-Json)
        claims = ($claims | ConvertFrom-Json)
        signature = $signature
    }

    Write-Verbose -Message ("JWT`r`n.headers: {0}`r`n.claims: {1}`r`n.signature: {2}`r`n" -f $headers,$claims,[System.BitConverter]::ToString($signature))
    return $customObject
}

<#
 .Synopsis
  Decodes a JSON Web Token (JWT)
 
 .Description
  This cmdlet takes a JWT, decodes and emits it out in the output stream
 
 .Example
  ConvertFrom-MSCloudIDJWT
 
#>

function ConvertFrom-MSCloudIDJWT
{
    [CmdletBinding()]  
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true)]
        [string] $Token,
        [switch] $Recurse
    )
    
    if ($Recurse)
    {
        $decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Token))
        $DecodedJwt = Decode-JWT -rawToken $decoded
    }
    else
    {
        $DecodedJwt = Decode-JWT -rawToken $Token
    }
     Write-Host ($DecodedJwt | Select headers,claims | ConvertTo-Json)
    return $DecodedJwt
}


<#
 .Synopsis
  Installs this Powershell Module in the Powershell module path, downloading and copying the right dependencies
 
 .Description
  This cmdlet copies the module in the module path, and downloads the ADAL library using Nuget
 
 .Example
  Install-MSCloudIdUtilsModule
 
#>

function Install-MSCloudIdUtilsModule
{
    [CmdletBinding()]
    param()

    $myDocumentsModuleFolderIsInPSModulePath = $false
    [Environment]::GetEnvironmentVariable("PSModulePath") -Split ';' | % {
      if ($_.ToLower() -eq ([Environment]::GetFolderPath("MyDocuments") + "\WindowsPowerShell\Modules").ToLower()){
        $myDocumentsModuleFolderIsInPSModulePath = $true
      }
    }

    if(-not $myDocumentsModuleFolderIsInPSModulePath){
      $newPSModulePath = [Environment]::GetEnvironmentVariable("PSModulePath") + ";" + [Environment]::GetFolderPath("MyDocuments") + "\WindowsPowerShell\Modules";
      [Environment]::SetEnvironmentVariable("PSModulePath",$newPSModulePath, "Process")
      [Environment]::SetEnvironmentVariable("PSModulePath",$newPSModulePath, "User")

    }


    $moduleDirPath = [Environment]::GetFolderPath("MyDocuments") + "\WindowsPowerShell\Modules"
    $modulePath = $moduleDirPath + "\MSCloudIdUtils"

    if (Test-Path $modulePath)
    {
        Write-Host "Removing existing module directory under "$moduleDirPath -ForegroundColor Green
        Remove-Item -Path $modulePath -Recurse -Force | Out-Null
    }

    Write-Host "Creating module directory under "$moduleDirPath -ForegroundColor Green
    New-Item -Path $modulePath -Type "Directory" -Force | Out-Null
    New-Item -Path $modulePath"\Nugets" -Type "Directory" -Force | Out-Null
    New-Item -Path $modulePath"\Cmdlets" -Type "Directory" -Force | Out-Null


  if(-not (Test-Path ($modulePath+"\Nugets"))) {New-Item -Path ($modulePath+"\Nugets") -ItemType "Directory" | out-null}

  $adalPackageDirectories = (Get-ChildItem -Path ($modulePath+"\Nugets") -Filter "Microsoft.IdentityModel.Clients.ActiveDirectory*" -Directory)

  if($adalPackageDirectories.Length -eq 0){
    Write-Host "Active Directory Authentication Library Nuget doesn't exist. Downloading now ..." -ForegroundColor Yellow
    if(-not(Test-Path ($modulePath + "\Nugets\nuget.exe")))
    {
      Write-Host "nuget.exe not found. Downloading from http://www.nuget.org/nuget.exe ..." -ForegroundColor Yellow
      $wc = New-Object System.Net.WebClient
      $wc.DownloadFile("http://www.nuget.org/nuget.exe",$modulePath + "\Nugets\nuget.exe");
    }

    $nugetUpdateExpression = '&"'+$modulePath + '\Nugets\nuget.exe" update -self';
    Invoke-Expression $nugetUpdateExpression

    $nugetDownloadExpression = $modulePath + "\Nugets\nuget.exe install Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.14.201151115 -OutputDirectory " + $modulePath + "\Nugets | out-null"
    Invoke-Expression $nugetDownloadExpression

  }

    Copy-Item "$PSScriptRoot\MSCloudIdUtils.psm1" -Destination $modulePath -Force

    Import-Module MSCloudIdUtils
    Get-Command -Module MSCloudIdUtils

}