InActive-Owners.ps1

$t1 = @'
Designed and managed by
      ___ _
     / _ \ (_)
    / /_\ \ __ _ _ __ ___ _ __ ___ _ _ __
    | _ |/ _` | '_ ` _ \| '_ ` _ \| | '__|
    | | | | (_| | | | | | | | | | | | | |
    \_| |_/\__,_|_| |_| |_|_| |_| |_|_|_|
 
    Contact : aammir.mirza@hotmail.com
'@

$t2 = @'
+------------------+-----------------------------------------------------------------+----------------------------------------------+
| Switch | Usage | Example |
+------------------+-----------------------------------------------------------------+----------------------------------------------+
| SecurityGroup | With this switch you can run accross all the | |
| | SGs within AzureAD for which you (identity | $data = InActive-Owners -SecurityGroup |
| | running functions) have access. | -Verbose |
+------------------+-----------------------------------------------------------------+----------------------------------------------+
| SubscriptionTags | With this switch you can run for subscription scope | $data = InActive-Owners -SubscriptionTags |
| | Tags (service tags), having owner email address. | -Owner_TagKey '<TagName>' |
| | You need to specify the Tag key. | -Verbose |
+------------------+-----------------------------------------------------------------+----------------------------------------------+
| AppReg | With this switch you can run for all Azure App Registrations to | $data = InActive-Owners -AppReg |
| | verify the owners and If they are inActive. | -Verbose |
+------------------+-----------------------------------------------------------------+----------------------------------------------+
| UPN | With this switch you can run for individual UPN/ID to | Check-UserUPN -UserUPN <UserEmailA> |
| | verify the owners and If they are inActive. | -Verbose |
+------------------+-----------------------------------------------------------------+----------------------------------------------+
'@


$t3 = @'
 _____ _ _ _ _ ______
|_ _| | | | | (_) | | ____|
  | | __| | ___ _ __ | |_ _| |_ _ _ | |__ _ __ _ __ ___ _ __
  | | / _` |/ _ \ '_ \| __| | __| | | | | __| | '__| '__/ _ \| '__|
 _| || (_| | __/ | | | |_| | |_| |_| | | |____| | | | | (_) | |
|_____\__,_|\___|_| |_|\__|_|\__|\__, | |______|_| |_| \___/|_|
                                  __/ |
                                 |___/
 
'@


$issues = @'
====================================
Known Issues with Authentication can
be fixed with explisit decleration.
====================================
Example:
 
    try {
        'Logging in to Azure...'
        Connect-AzAccount -Identity
        'Logged in to Azure...'
    }
    catch {
        Write-Error -Message $_.Exception
        throw $_.Exception
    }
 
    $token = (Get-AzAccessToken -ResourceTypeName MSGraph).token
    $authHeader = @{
        'Content-Type' = 'application/json'
        'Authorization' = 'Bearer ' + $token
    }
    Connect-MgGraph -AccessToken $token
'@


# # Initiating MI Login
# $MIConnection = Connect-AzAccount -Identity -ErrorAction SilentlyContinue
# if ($MIConnection) {
# $MIConnection
# $token = (Get-AzAccessToken -ResourceTypeName MSGraph -ErrorAction SilentlyContinue).Token
# }
# # else {
# # Write-Error 'MI authentication failed or missing approproate permission to MI.'
# # }

$authHeader = @{
    'Content-Type'  = 'application/json'
    'Authorization' = 'Bearer ' + ((Get-AzAccessToken -ResourceTypeName MSGraph -ErrorAction SilentlyContinue).token )
}

Function Check-UserUPN {
    [CmdletBinding()]
    param (
        $UserUPN
    )
    # Authentication
    # $token = (Get-AzAccessToken -ResourceTypeName MSGraph -ErrorAction SilentlyContinue).token | ConvertTo-SecureString -AsPlainText -Force -ErrorAction 'SilentlyContinue'
    # $connect = Connect-MgGraph -AccessToken $token -ErrorAction SilentlyContinue
    # Write-Verbose "Connected succesfully (Encrypted) - $($connect)"
    # Validate Authentication $ Authorization
    try {
        if ((!(Get-AzContext)) -or (!(Get-MgContext))) { throw 'Bad thing happened' }
    }
    catch {
        Write-Error 'MI not configured or needed permission to read object accross tenanat. Or Missing MG-Graph Permissions.'
        $t3
        Write-Verbose "Known issue with authentication.`n$($issues)"
    }
    # Check If user exists
    try {
        if ((Get-AzContext)) {
            $uriUE = "https://graph.microsoft.com/v1.0/users/$($UserUPN)"
            $usersExist = Invoke-MgGraphRequest `
                -Uri $uriUE `
                -Method GET
        }
    }
    catch {
        Write-Verbose 'User dose NOT EXIST.'; $userData = 'NOT IN AAD'; Return $userData
        break;
    }
    if ($usersExist) {
        # $res = Get-MgUser -UserId $UPN -EA 'SilentlyContinue'
        $filterOwner = "DisplayName, mail, accountEnabled&filter=mail eq '$UserUPN'"
        $filterInactiveOwner = ' and accountEnabled eq false'
        $body = @{}
        $url = 'https://graph.microsoft.com'
        $endpoint = "users/?`$select="
        $graphversion = 'beta'
        $uri = "$url/$graphversion/$endpoint$filterOwner$filterInactiveOwner"
        $results = Invoke-MgGraphRequest `
            -Uri $uri `
            -Method GET `
            -Body $body
        # function return
        if ($results.value) {
            Write-Verbose " [INACTIVE] $($UserUPN)"
            $userData = 'INACTIVE'
        }
        else {
            Write-Verbose " [ACTIVE] $($UserUPN)"
            $userData = 'ACTIVE'
        }
    }
    Return $userData
}
Function InActive-Owners {
    <#
.SYNOPSIS
    Get DISABLED resource owners details from App reg, Security
    Group and subscription scoped Tags.
 
.DESCRIPTION
    Get list of DISABLED / InActive owners from Azure Security Group,
    Azure App Registration, And any Owner level Tags (limited to subscription scope.)
 
    AUTHENTICATION
    Azure tenant scope authentication should be taken care before calling the commands.
    Identity should also have access for Connect-MgGraph Directory.ReadAll.
 
.PARAMETER SecurityGroup
    Switch used to run thru all the security groups within the tenanat.
    No support for explicit SGs at this point.
 
.PARAMETER AppReg
    Switch used to run thru all the Application Registrations within the tenanat.
    No support for explicit App registrations at this point.
 
.PARAMETER SubscriptionTags
    Switch used in combination with Owner_TagKey.
    For more details look at examples.
 
.PARAMETER Owner_TagKey
    Paramater that need to pass while used with SubscriptionTags.
    It includes the value of Tag name which holds subscription owners information.
 
.PARAMETER UserUPN
    Parameter used for single UserUPN or user status check.
    Pass email address of user for which you need status.
 
.EXAMPLE
    PS> $data = InActive-Owners -SecurityGroup
    $data | format-Table
    $data | Export-Csv -Path 'DisabledOwners.csv' -NoTypeInformation -Force
 
.EXAMPLE
    PS> $data = InActive-Owners -SubscriptionTags -Owner_TagKey '<TagName>'
    $data | format-Table
    $data | Export-Csv -Path 'DisabledOwners.csv' -NoTypeInformation -Force
 
.EXAMPLE
    PS> $data = InActive-Owners -AppReg
    $data | format-Table
    $data | Export-Csv -Path 'DisabledOwners.csv' -NoTypeInformation -Force
 
.EXAMPLE
    PS> Check-UserUPN -UserUPN <UserEmailAddress>
 
.LINK
    Other packages: https://www.powershellgallery.com/packages?q=aammir
#>

    [CmdletBinding()]
    param (
        [Parameter(ParameterSetName = 'One', Position = 1)]$Owner_TagKey <#= 'iicsSubscriptionOwner'#>,
        [Parameter(ParameterSetName = 'One', Position = 0)][switch]$SubscriptionTags,
        [Parameter(ParameterSetName = 'One', Position = 2)]$SubscriptionName,
        [Parameter(ParameterSetName = 'Three')][switch]$SecurityGroup,
        [Parameter(ParameterSetName = 'Four')][switch]$AppReg,
        [Parameter(ParameterSetName = 'Five')]$UPN,
        [Parameter(ParameterSetName = 'Six')]$SecurityGroupId
    )
    # Authentication
    # $token = (Get-AzAccessToken -ResourceTypeName MSGraph -ErrorAction SilentlyContinue).token | ConvertTo-SecureString -AsPlainText -Force -ErrorAction 'SilentlyContinue'
    # $connect = Connect-MgGraph -AccessToken $token -ErrorAction SilentlyContinue
    # Write-Verbose "Connected succesfully (Encrypted) - $($connect)"

    Write-Verbose "`n$($t1)"
    Write-Verbose 'For more help on command and switches use -Verbose'
    Write-Verbose "Switches and there operations.`n$($t2)"
    try {
        if ((!(Get-AzContext)) -or (!(Get-MgContext))) { throw 'Bad thing happened' }
    }
    catch {
        Write-Error 'MI not configured or needed permission to read object accross tenanat. Or Missing MG-Graph Permissions.'
        $t3
        Write-Verbose "Known issue with authentication.`n$($issues)"
    }

    if (Get-MgContext) {
        $url = 'https://graph.microsoft.com'
        $endpoint = "users/?`$select="
        $graphversion = 'beta'
        $invalidUPN = @()
        if ($SubscriptionTags) {
            if ($SubscriptionName) {
                Write-Verbose "Running for $($SubscriptionName)"
                $subsName = Get-AzSubscription -SubscriptionName $SubscriptionName
            }
            else {
                $subsName = @()
                $subsName = (Get-AzSubscription <#| Select-Object -First 25#>)
            }
            foreach ($item in $subsName) {
                # | Where-Object -Property State -EQ 'Enabled')) {
                $context = Set-AzContext $item
                Write-Verbose "Execution for the subscription $($item.Name)"
                if ($($item.State) -eq 'Enabled') {
                    Write-Verbose "Subscription State $($item.State)"
                    # constructing Body
                    $tags = Get-AzTag -ResourceId /subscriptions/$item
                    Write-Verbose 'Parsing the TAGs now...'
                    Write-Verbose "Checking subscription level tags for $($item.Name)"
                    if ($tags) {
                        foreach ($tagKey in $tags.Properties.TagsProperty.Keys) {
                            $tagValue = $tags.Properties.TagsProperty[$tagKey]
                            # Write-Host " | -- - $($tagKey):$($tagValue)" -ForegroundColor DarkYellow
                            if ($tagKey -eq "$Owner_TagKey") { $iicsSubscriptionOwner = $tagValue }
                        }
                        if ($iicsSubscriptionOwner) {
                            $Ownerdata = Check-UserUPN -UserUPN $iicsSubscriptionOwner
                            if (($Ownerdata -eq 'INACTIVE') -or ($Ownerdata -eq 'NOT IN AAD')) {
                                $invalidUPN += [PSCustomObject]@{
                                    'SubscriptionName' = $item.Name
                                    'OwnerUPN'         = $iicsSubscriptionOwner
                                    'Status'           = $Ownerdata
                                }
                            }
                            else {
                                Write-Verbose "$($item.Name) | $($iicsSubscriptionOwner)"
                            }
                        } # If block
                    }
                }
            }
        }
        elseif ($SecurityGroup -or $SecurityGroupId) {
            $invalidUPN = @()
            $sgCount = 0;
            #$groupId = '11e7b41d-d876-416c-abef-0ea1a23ab762'
            $graphversion = 'beta'
            $allGroupsEndpoint = 'groups?$filter=mailEnabled eq false'
            if ($SecurityGroupId) {
                $endpointSg = "groups/$($SecurityGroupId)/owners?`$select=mail"
                $uriSg = "$url/$graphversion/$endpointSg"
                $SgOwners = Invoke-MgGraphRequest -Uri $uriSg -Method GET -Headers $authHeader
                $count = ($SgOwners.Value.mail).Count
                if ($SgOwners.Value.mail) {
                    foreach ($currentItemName in $SgOwners.Value.mail) {
                        $SGdata = Check-UserUPN -UserUPN $currentItemName
                        if ($SGdata -eq 'INACTIVE' -or $SGdata -eq 'NOT IN AAD') {
                            $invalidUPN += [PSCustomObject]@{
                                'SecurityGroupName'    = $($currentSG.DisplayName)
                                'OwnerUPN'             = $currentItemName
                                'Status'               = $SGdata
                                'NumberOfActiveOwners' = $count
                            }
                        }
                        else {
                            Write-Verbose " [ACTIVE] $($currentItemName)"
                        }
                    }
                }
            }
            else {
                # Extracting all Security Groups
                $uriAllSg = "$url/$graphversion/$allGroupsEndpoint"
                $resultsAllSG = Invoke-MgGraphRequest `
                    -Uri $uriAllSg `
                    -Method GET -Headers $authHeader
                $CloudSecurityGroups = $resultsAllSG.Value
                $UserNextLink = $resultsAllSG.'@odata.nextLink'
                while ($UserNextLink -ne $null) {
                    $resultsAllSG = (Invoke-MgGraphRequest -Uri $UserNextLink -Method Get -Headers $authHeader)
                    $UserNextLink = $resultsAllSG.'@odata.nextLink'
                    $CloudSecurityGroups += $resultsAllSG.value
                }
                Write-Verbose '-------------------------------'
                Write-Verbose "Total SG Count - $($CloudSecurityGroups.Count)"
                Write-Verbose '-------------------------------'

                foreach ($currentSG in $CloudSecurityGroups) {
                    $sgCount = $sgCount + 1
                    Write-Verbose "$($sgCount) - $($currentSG.displayName)"
                    $endpointSg = "groups/$($currentSG.id)/owners?`$select=mail"
                    $uriSg = "$url/$graphversion/$endpointSg"
                    $SgOwners = Invoke-MgGraphRequest -Uri $uriSg -Method GET -Headers $authHeader
                    $count = ($SgOwners.Value.mail).Count
                    foreach ($currentItemName in $SgOwners.Value.mail) {
                        if ($currentItemName) {
                            $count = $count - 1
                            $SGdata = Check-UserUPN -UserUPN $currentItemName
                            if ($SGdata -eq 'INACTIVE' -or $SGdata -eq 'NOT IN AAD') {
                                $invalidUPN += [PSCustomObject]@{
                                    'SecurityGroupName'    = $($currentSG.DisplayName)
                                    'OwnerUPN'             = $currentItemName
                                    'Status'               = $SGdata
                                    'NumberOfActiveOwners' = $count
                                }
                            }
                        } # If block
                    }
                    Write-Verbose "Active owners are - $($count)"
                }
            }
        }
        elseif ($AppReg) {
            $invalidUPN = @()
            # Fetching all the App registrations
            $appRegistrations = Get-AzADApplication # | Select-Object -First 25
            foreach ($appRegistration in $appRegistrations) {
                Write-Verbose '-------------------------------------'
                Write-Verbose " +++++ Running for $($appRegistration.DisplayName)"
                $owner = "https://graph.microsoft.com/v1.0/myorganization/applications/$($appRegistration.Id)/owners"
                $response2 = Invoke-RestMethod $owner -Method 'GET' -Headers $authHeader
                $count = ($response2.Value.userPrincipalName).Count
                Write-Verbose "+++ List of owners $($count)`n$($response2.Value.userPrincipalName)"
                foreach ($currentItemName in $response2.Value.userPrincipalName) {
                    if ($currentItemName) {
                        $count = $count - 1
                        $data = Check-UserUPN -UserUPN $currentItemName
                        if (($data -eq 'INACTIVE') -or ($data -eq 'NOT IN AAD')) {
                            $invalidUPN += [PSCustomObject]@{
                                'AppRegName' = $($appRegistration.DisplayName)
                                'OwnerUPN'   = $currentItemName
                                'Status'     = $data
                                # 'NumberOfActiveOwners' = $count
                            }
                        }
                    } # If block
                }
                Write-Verbose "Active owners are - $($count)"
            }
        }
        elseif ($UPN) {
            $data = Check-UserUPN -UserUPN $UPN
            $invalidUPN += [PSCustomObject]@{
                'OwnerUPN' = $UPN
                'Status'   = $data
            }
        }
        else {
            Write-Warning "You must select one of the switch to perform the operation.`n$($t2)"
        }
        # Function Output
        Write-Output $invalidUPN
    }
}