azureRMADAppPermissionsReport.psm1

#Requires -Modules ImportExcel
<#
    .SYNOPSIS
    generate a full report of all AzureAD applications
    .DESCRIPTION
    A number of functions to generate a full report of all applications your Azure AD has, including all permissions they require and how they have been assigned (user / admin)
    .EXAMPLE
    To get your report, run get-azureRMADAppPermissionsReport -token (get-azureRMtoken -username jos.lieben@xxx.com -password password01) -reportPath c:\temp\report.xlsx
    .PARAMETER token
    a valid Azure RM token retrieved through my get-azureRMtoken function, can de done on the fly as the example shows
    .PARAMETER reportPath
    Full path to desired report file, if unspecified, will write to temp
    .NOTES
    If you have enabled MFA on your admin account, you may have to manually run the get-azureRMtoken function step by step, because -Credential is buddy as of the time of writing
    filename: azureRMADAppPermissionsReport.psm1
    author: Jos Lieben
    blog: www.lieben.nu
    created: 26/7/2018
#>


function get-azureRMADAppPermissionsReport(){
    <#
      .SYNOPSIS
      Retrieve all permissions an Azure AD application has set
      .DESCRIPTION
       
      .EXAMPLE
      $permissions = get-azureRMADAppPermissionsReport -token (get-azureRMtoken -username jos.lieben@xxx.com -password password01) -reportPath c:\temp\report.xlsx
      .PARAMETER token
      a valid Azure RM token retrieved through my get-azureRMtoken function
      .PARAMETER reportPath
      Full path to desired report file, if unspecified, will write to temp
      .NOTES
      filename: get-azureRMADAppPermissionsReport.ps1
      author: Jos Lieben
      blog: www.lieben.nu
      created: 26/7/2018
    #>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$true)]$token,
        $reportPath=(Join-Path $Env:TEMP -ChildPath "azureRMAppPermissionsReport.xlsx")
    )
    $applications = get-azureRMADAllApplications -token $token
    $userConsent = @()
    $adminConsent = @()
    $userToApp = @{}
    $count = 0
    foreach($application in $applications){
        $permissions = get-azureRMADAppPermissions -token $token -appId $application.objectId
        if($permissions.admin){
            $applications[$count] | Add-Member NoteProperty -Name AdminHasConsented -Value $True
        }else{
            $applications[$count] | Add-Member NoteProperty -Name AdminHasConsented -Value $False
        }
        if($permissions.user){
            $applications[$count] | Add-Member NoteProperty -Name UsersHaveConsented -Value $True
        }else{
            $applications[$count] | Add-Member NoteProperty -Name UsersHaveConsented -Value $False
        }
        $applications[$count] | Add-Member NoteProperty -Name Permissions -Value $permissions
        $count++
    }

    $applications | Select displayName,publisherName,accountEnabled,appRoleAssignmentRequired,isApplicationVisible,AdminHasConsented,UsersHaveConsented,appDisplayName,homePageUrl,ssoConfiguration,appRoles,tags,userAccessUrl | Export-Excel -workSheetName "Applications" -path $reportPath -ClearSheet -TableName "Applications" -AutoSize
    
    foreach($application in ($applications | Where-Object {$_.AdminHasConsented})){
        foreach($permission in $application.permissions.admin){
            $adminConsent += [PSCustomObject]@{
            "appId"=$application.appId
            "appDisplayName"=$application.displayName
            "Resource"=$permission.resourceName
            "Permission"=$permission.permissionId
            "RoleOrScopeClaim"=$permission.roleOrScopeClaim
            "Description"=$permission.permissionDescription}
        }
    }

    $adminConsent | Export-Excel -workSheetName "AdminConsentedRights" -path $reportPath -ClearSheet -TableName "AdminConsentedRights" -AutoSize

    foreach($application in ($applications | Where-Object {$_.UsersHaveConsented})){
        foreach($permission in $application.permissions.user){
            $userConsent += [PSCustomObject]@{
            "appId"=$application.appId
            "appDisplayName"=$application.displayName
            "Resource"=$permission.resourceName
            "Permission"=$permission.permissionId
            "RoleOrScopeClaim"=$permission.roleOrScopeClaim
            "Description"=$permission.permissionDescription}

            foreach($principal in $permission.principalIds){
                if(!$userToApp.$principal){
                    $userToApp.$principal = @()
                }
                $userToApp.$principal += [PSCustomObject]@{
                "appId"=$application.appId
                "appDisplayName"=$application.displayName
                "Resource"=$permission.resourceName
                "Permission"=$permission.permissionId
                "RoleOrScopeClaim"=$permission.roleOrScopeClaim
                "Description"=$permission.permissionDescription}
            }
        }
    }
    
    $userConsent | Export-Excel -workSheetName "UserConsentedRights" -path $reportPath -ClearSheet -TableName "UserConsentedRights" -AutoSize

    $userToAppTranslated = @()
    foreach($user in $userToApp.Keys){
        try{
            $userInfo = get-azureRMADUserInfo -token $token -userGuid $user
        }catch{
            $userInfo = [PSCustomObject]@{
                "UserDisplayName"=$user
                "UserPrincipalName"=$NULL
                "accountEnabled"=$NULL
                "appId"=$NULL
                "appDisplayName"=$NULL
                "Resource"=$NULL
                "Permission"=$NULL
                "RoleOrScopeClaim"=$NULL
                "Description"="FAILED TO RETRIEVE DATA FOR THIS USER GUID: $user"}
        }
        $userToAppTranslated += [PSCustomObject]@{
        "UserDisplayName"=$userInfo.displayName
        "UserPrincipalName"=$userInfo.userPrincipalName
        "accountEnabled"=$userInfo.accountEnabled
        "appId"=$userToApp.$user.appId
        "appDisplayName"=$userToApp.$user.appDisplayName
        "Resource"=$userToApp.$user.Resource
        "Permission"=$userToApp.$user.Permission
        "RoleOrScopeClaim"=$userToApp.$user.RoleOrScopeClaim
        "Description"=$userToApp.$user.Description}
    }
    
    $userToAppTranslated | Export-Excel -workSheetName "UserToAppMapping" -path $reportPath -ClearSheet -TableName "UserToAppMapping" -AutoSize
}

function get-azureRMADUserInfo(){
    <#
      .SYNOPSIS
      Retrieve info about specified user
      .DESCRIPTION
      Retrieve info about specified user by GUID
      .EXAMPLE
      $users = get-azureRMADUserInfo -token (get-azureRMtoken -username jos.lieben@xxx.com -password password01) -userGuid 479c3c0d-a103-4899-84ce-54b05e5be5fa
      .PARAMETER token
      a valid Azure RM token retrieved through my get-azureRMtoken function
      .PARAMETER userGuid
      GUID of the user you want to retrieve info about
      .NOTES
      filename: get-azureRMADUserInfo.ps1
      author: Jos Lieben
      blog: www.lieben.nu
      created: 27/7/2018
    #>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$true)]$token,
        [Parameter(Mandatory=$true)]$userGuid
    )
    $header = @{
        'Authorization' = 'Bearer ' + $token
        'X-Requested-With'= 'XMLHttpRequest'
        'x-ms-client-request-id'= [guid]::NewGuid()
        'x-ms-correlation-id' = [guid]::NewGuid()}
        $url = "https://main.iam.ad.ext.azure.com/api/UserDetails/$userGuid"
        Write-Output (Invoke-RestMethod -Uri $url -Headers $header -Method GET -ErrorAction Stop)
}

function get-azureRMADAppPermissions(){
    <#
      .SYNOPSIS
      Retrieve all permissions an Azure AD application has set
      .DESCRIPTION
      Returns a hashtable with 'admin' and 'user' as properties, which contain arrays of all permissions this application has
      .EXAMPLE
      $permissions = get-azureRMADAppPermissions -token (get-azureRMtoken -username jos.lieben@xxx.com -password password01) -appId 479c3c0d-a103-4899-84ce-54b05e5be5fa
      .PARAMETER token
      a valid Azure RM token retrieved through my get-azureRMtoken function
      .PARAMETER appId
      object ID of the application you want to retrieve permissions from
      .NOTES
      filename: get-azureRMADAppPermissions.ps1
      author: Jos Lieben
      blog: www.lieben.nu
      created: 26/7/2018
    #>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$true)]$token,
        [Parameter(Mandatory=$true)]$appId
    )
    $permissions = @{"admin"=@();"user"=@()}
    $header = @{
        'Authorization' = 'Bearer ' + $token
        'X-Requested-With'= 'XMLHttpRequest'
        'x-ms-client-request-id'= [guid]::NewGuid()
        'x-ms-correlation-id' = [guid]::NewGuid()}
    $url = "https://main.iam.ad.ext.azure.com/api/EnterpriseApplications/$appId/ServicePrincipalPermissions?consentType=Admin&userObjectId="
    $res = Invoke-RestMethod -Uri $url -Headers $header -Method GET -ErrorAction Stop -ContentType "application/json"
    $permissions.admin += $res
    $url = "https://main.iam.ad.ext.azure.com/api/EnterpriseApplications/$appId/ServicePrincipalPermissions?consentType=User&userObjectId="
    $res = Invoke-RestMethod -Uri $url -Headers $header -Method GET -ErrorAction Stop -ContentType "application/json"
    $permissions.user += $res
    return $permissions
}

function get-azureRMADAllApplications(){
    <#
      .SYNOPSIS
      Retrieve all Azure AD applications
      .DESCRIPTION
      Retrieves Azure AD applications (enterprise and app registrations) from AzureAD including numerous properties that get-AzureADRMApplication does not return
      .EXAMPLE
      $apps = get-azureRMADAllApplications -token (get-azureRMtoken -username jos.lieben@xxx.com -password password01)
      .PARAMETER token
      a valid Azure RM token retrieved through my get-azureRMtoken function
      .PARAMETER returnDisabledApplications
      if specified, also return applications that have been disabled
      .NOTES
      filename: get-azureRMADAllApplications.ps1
      author: Jos Lieben
      blog: www.lieben.nu
      created: 25/7/2018
    #>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$true)]$token,
        [Switch]$returnDisabledApplications,
        [Parameter(DontShow)]$nextLink
    )
    $apps = @()
    $header = @{
        'Authorization' = 'Bearer ' + $token
        'X-Requested-With'= 'XMLHttpRequest'
        'x-ms-client-request-id'= [guid]::NewGuid()
        'x-ms-correlation-id' = [guid]::NewGuid()}
    $body = @{"accountEnabled"=$(if($returnDisabledApplications){$null}else{$True});"isAppVisible"=$null;"appListQuery"=0;"searchText"="";"top"=100;"loadLogo"=$false;"putCachedLogoUrlOnly"=$true;"nextLink"="$nextLink";"usedFirstPartyAppIds"=$null;
    "__ko_mapping__"=@{"ignore"=@();"include"=@("_destroy");"copy"=@();"observe"=@();"mappedProperties"=@{"accountEnabled"=$(if($returnDisabledApplications){$null}else{$True});"isAppVisible"=$true;"appListQuery"=$true;"searchText"=$true;"top"=$true;"loadLogo"=$true;"putCachedLogoUrlOnly"=$true;"nextLink"=$true;"usedFirstPartyAppIds"=$true};"copiedProperties"=@{}}}
    $url = "https://main.iam.ad.ext.azure.com/api/ManagedApplications/List"
    $res = Invoke-RestMethod -Uri $url -Headers $header -Method POST -body ($body | convertto-Json) -ErrorAction Stop -ContentType "application/json"
    foreach($app in $res.applist){
        $additionalInfo = Invoke-RestMethod -Headers $header -Uri "https://main.iam.ad.ext.azure.com/api/EnterpriseApplications/$($app.objectId)/Properties?appId=$($app.appId)&loadLogo={2}" -Method GET -ErrorAction Stop -ContentType "application/json"
        $app | add-member NoteProperty -name userAccessUrl -Value $additionalInfo.userAccessUrl
        $app | add-member NoteProperty -name appRoleAssignmentRequired -Value $additionalInfo.appRoleAssignmentRequired
        $app | add-member NoteProperty -name isApplicationVisible -Value $additionalInfo.isApplicationVisible
        $apps += $app
    }
    if($res.nextLink){
        $apps += get-azureRMADAllApplications -returnDisabledApplications:$returnDisabledApplications -nextLink $res.nextLink -token $token
    }
    return $apps
}
function get-azureRMToken(){
    <#
      .SYNOPSIS
      Retrieve special Azure RM token to use for the main.iam.ad.ext.azure.com endpoint
      .DESCRIPTION
      The Azure RM token can be used for various actions that are not possible using Powershell cmdlets. This is experimental and should be used with caution!
      .EXAMPLE
      $token = get-azureRMToken -Username you@domain.com -Password Welcome01
      .PARAMETER Username
      the UPN of a user with sufficient permissions to call the endpoint (this depends on what you'll use the token for)
      .PARAMETER Password
      Password of Username
      .PARAMETER tenantId
      If supplied, logs in to specified tenant.
      .NOTES
      filename: get-azureRMToken.ps1
      author: Jos Lieben
      blog: www.lieben.nu
      created: 12/6/2018
    #>

    Param(
        [Parameter(Mandatory=$true)]$Username,
        [Parameter(Mandatory=$true)]$Password,
        $tenantId
    )
    $secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
    $mycreds = New-Object System.Management.Automation.PSCredential ($Username, $secpasswd)
    if($tenantId){
        $res = login-azurermaccount -Credential $mycreds -TenantId $tenantId.ToLower()
    }else{
        $res = login-azurermaccount -Credential $mycreds
    }
    $context = Get-AzureRmContext
    $tenantId = $context.Tenant.Id
    $refreshToken = @($context.TokenCache.ReadItems() | where {$_.tenantId -eq $tenantId -and $_.ExpiresOn -gt (Get-Date)})[0].RefreshToken
    $body = "grant_type=refresh_token&refresh_token=$($refreshToken)&resource=74658136-14ec-4630-ad9b-26e160ff0fc6"
    $apiToken = Invoke-RestMethod "https://login.windows.net/$tenantId/oauth2/token" -Method POST -Body $body -ContentType 'application/x-www-form-urlencoded'
    return $apiToken.access_token
}

Export-ModuleMember -Function *