Public/Discovery/Get-EntraInformation.ps1

function Get-EntraInformation {
    [cmdletbinding(DefaultParameterSetName = 'Other')]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = 'ObjectId')]
        [string]$ObjectId,

        [Parameter(Mandatory = $false, ParameterSetName = 'Name')]
        [string]$Name,

        [Parameter(Mandatory = $false, ParameterSetName = 'UserPrincipalName')]
        [ValidatePattern('^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', ErrorMessage = "The value '{1}' is not a valid UPN format")]
        [string]$UserPrincipalName,

        [Parameter(ParameterSetName = 'ObjectId')]
        [Parameter(ParameterSetName = 'Name')]
        [Parameter(ParameterSetName = 'UserPrincipalName')]
        [Parameter(ParameterSetName = 'Other')]
        [switch]$Group,

        [Parameter(Mandatory = $false, ParameterSetName = 'Other')]
        [switch]$CurrentUser
    )

    begin {
        Write-Verbose "Starting function $($MyInvocation.MyCommand.Name)"
        $MyInvocation.MyCommand.Name | Invoke-BlackCat -ResourceTypeName 'MSGraph'

        $userInfo = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
    }

    process {
        try {
            # Construct query based on parameter set
            switch ($PSCmdlet.ParameterSetName) {
                'ObjectId' {
                    if ($Group) {
                        $response = Invoke-MsGraph -relativeUrl "groups/$ObjectId" -NoBatch
                        $isGroup = $true
                    } else {
                        $response = Invoke-MsGraph -relativeUrl "users/$ObjectId" -NoBatch
                        $isGroup = $false
                    }
                }
                'Name' {
                    if ($Group) {
                        $response = Invoke-MsGraph -relativeUrl "groups?`$filter=startswith(displayName,'$Name')"
                        $isGroup = $true
                    } else {
                        $response = Invoke-MsGraph -relativeUrl "users?`$filter=startswith(displayName,'$Name') or startswith(userPrincipalName,'$Name')"
                        $isGroup = $false
                    }
                }
                'UserPrincipalName' {
                    if ($Group) {
                        Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "The -Group parameter cannot be used with -UserPrincipalName. parameter." -Severity 'Error'
                    }
                    $response = Invoke-MsGraph -relativeUrl "users?`$filter=userPrincipalName eq '$UserPrincipalName'"
                    $isGroup = $false
                }
                'Other' {
                    $isGroup = $false
                    $response = $null

                    if ($CurrentUser) {
                        # Detect service principal context first (GUID account or token AppId), then fall back to /me for users
                        $spAppId = $null

                        # Try from current access token
                        try {
                            if ($script:SessionVariables -and $script:SessionVariables.accessToken) {
                                $rawToken = $script:SessionVariables.accessToken
                                if ($rawToken -and ($rawToken -split '\.').Count -ge 2) {
                                    $tokenInfo = ConvertFrom-JWT -Base64JWT $rawToken
                                    if ($tokenInfo.AppId) { $spAppId = $tokenInfo.AppId }
                                }
                            }
                        }
                        catch {
                            Write-Verbose "Could not parse access token for AppId: $($_.Exception.Message)"
                        }

                        # Try from Az context account Id (GUID implies SPN)
                        if (-not $spAppId) {
                            try {
                                $ctx = Get-AzContext -ErrorAction SilentlyContinue
                                if ($ctx -and $ctx.Account -and $ctx.Account.Id -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') {
                                    $spAppId = $ctx.Account.Id
                                }
                            }
                            catch {
                                Write-Verbose "Az context lookup failed: $($_.Exception.Message)"
                            }
                        }

                        if ($spAppId) {
                            Write-Verbose "Current context appears to be a service principal (AppId: $spAppId). Fetching permissions."
                            return Get-ServicePrincipalsPermission -AppId $spAppId
                        }

                        # User context fallback
                        try {
                            $response = Invoke-MsGraph -relativeUrl "me" -NoBatch
                        }
                        catch {
                            Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "'/me' request failed and no service principal AppId could be determined for -CurrentUser" -Severity 'Error'
                            return
                        }
                    }
                    else {
                        $response = Invoke-MsGraph -relativeUrl "me" -NoBatch
                    }
                }
            }
            $roleDetails = Invoke-MsGraph -relativeUrl 'roleManagement/directory/roleDefinitions'

            # foreach ($item in $response) {
                $response | ForEach-Object {
                    $item = $_

                if ($isGroup) {
                    # Get group members
                    $members = Invoke-MsGraph -relativeUrl "groups/$($item.id)/members"

                    # Get group roles and permissions
                    $roles = Invoke-MsGraph -relativeUrl "groups/$($item.id)/transitiveMemberOf/microsoft.graph.directoryRole"

                    # Create custom object with group information
                    $currentItem = [PSCustomObject]@{
                        DisplayName     = $item.displayName
                        ObjectId        = $item.id
                        Description     = $item.description
                        Roles           = $roles.displayName
                        Members         = $members.displayName
                        GroupType       = $item.groupTypes
                        MailEnabled     = $item.mailEnabled
                        SecurityEnabled = $item.securityEnabled
                        IsPrivileged    = $False
                    }

                } else {
                    # Get group memberships and filter out null/empty display names
                    $groups = Invoke-MsGraph -relativeUrl "users/$($item.id)/memberOf"
                    $groupDisplayNames = @()
                    foreach ($g in $groups) { if ($g.displayName) { $groupDisplayNames += $g.displayName } }

                    # Get directory roles and filter out null/empty display names
                    $roles = Invoke-MsGraph -relativeUrl "users/$($item.id)/transitiveMemberOf/microsoft.graph.directoryRole"
                    $roleDisplayNames = @()
                    foreach ($r in $roles) { if ($r.displayName) { $roleDisplayNames += $r.displayName } }

                    $currentItem = [PSCustomObject]@{
                        DisplayName       = $item.displayName
                        ObjectId          = $item.id
                        UserPrincipalName = $item.userPrincipalName
                        JobTitle          = $item.jobTitle
                        Department        = $item.department
                        GroupMemberships  = $groupDisplayNames
                        Roles             = $roleDisplayNames
                        Mail              = $item.mail
                        AccountEnabled    = $item.accountEnabled
                        IsPrivileged      = $False
                    }

                }
                foreach ($role in $roles) {
                    $privileged = ($roleDetails | Where-Object { $_.displayName -eq $role.displayName }).IsPrivileged

                    if ($privileged -eq $true) {
                        $currentItem.IsPrivileged = $true

                    }
                }

                ($userInfo).Add($currentItem)
            }
            return $userInfo
        }
        catch {
            Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message $($_.Exception.Message) -Severity 'Error'
        }
    }
<#
.SYNOPSIS
Retrieves information about Azure AD users or groups using Microsoft Graph API.
 
.DESCRIPTION
Retrieves detailed information about Azure AD users or groups using Microsoft Graph API. This function queries for user attributes, role memberships, group memberships, device associations, and other directory properties. Useful for user/group enumeration and collecting intelligence on Entra ID principals.
 
.PARAMETER ObjectId
Specifies the ObjectId of the user or group to retrieve information for. This parameter is mandatory when using the 'ObjectId' parameter set.
 
.PARAMETER Name
Specifies the display name or userPrincipalName of the user or group to retrieve information for. This parameter is mandatory when using the 'Name' parameter set.
 
.PARAMETER Group
Indicates that the query is for a group. If not specified, the query is assumed to be for a user.
 
.EXAMPLE
Get-EntraInformation -ObjectId "12345-abcde-67890" -Group
Retrieves information about the group with the specified ObjectId.
 
.EXAMPLE
Get-EntraInformation -Name "John Doe"
Retrieves information about the user with the specified display name or userPrincipalName.
 
.EXAMPLE
Get-EntraInformation -Name "Marketing" -Group
Retrieves information about groups with display names starting with "Marketing".
 
.NOTES
- This function requires the Invoke-MsGraph cmdlet to interact with Microsoft Graph API.
- Ensure that the required permissions are granted to the application or user executing this function.
 
.OUTPUTS
[PSCustomObject]
Returns a custom object containing detailed information about the user or group, including roles, memberships, and other attributes.
 
.LINK
MITRE ATT&CK Tactic: TA0007 - Discovery
https://attack.mitre.org/tactics/TA0007/
 
.LINK
MITRE ATT&CK Technique: T1087.004 - Account Discovery: Cloud Account
https://attack.mitre.org/techniques/T1087/004/
 
#>

}