Scripts/Get-UsersInfo.ps1

function Get-Users {
<#
    .SYNOPSIS
    Retrieves the creation time and date of the last password change for all users.
    Script inspired by: https://github.com/tomwechsler/Microsoft_Graph/blob/main/Entra_ID/Create_time_last_password.ps1
 
    .DESCRIPTION
    Retrieves the creation time and date of the last password change for all users.
 
    .PARAMETER OutputDir
    OutputDir is the parameter specifying the output directory.
    Default: Output\Users
 
    .PARAMETER Encoding
    Encoding is the parameter specifying the encoding of the CSV output file.
    Default: UTF8
 
    .PARAMETER LogLevel
    Specifies the level of logging:
    None: No logging
    Minimal: Critical errors only
    Standard: Normal operational logging
    Debug: Verbose logging for debugging purposes
    Default: Standard
 
    .PARAMETER UserIds
    UserId is the parameter specifying a single user ID or UPN to filter the results.
    Default: All users will be included if not specified.
     
    .EXAMPLE
    Get-Users
    Retrieves the creation time and date of the last password change for all users.
 
    .EXAMPLE
    Get-Users -Encoding utf32
    Retrieves the creation time and date of the last password change for all users and exports the output to a CSV file with UTF-32 encoding.
         
    .EXAMPLE
    Get-Users -OutputDir C:\Windows\Temp
    Retrieves the creation time and date of the last password change for all users and saves the output to the C:\Windows\Temp folder.
#>

    [CmdletBinding()]
    param(
        [string]$OutputDir = "Output\Users",
        [string]$Encoding = "UTF8",
        [string]$UserIds,
        [ValidateSet('None', 'Minimal', 'Standard', 'Debug')]
        [string]$LogLevel = 'Standard'
    )

    Set-LogLevel -Level ([LogLevel]::$LogLevel)
    $isDebugEnabled = $script:LogLevel -eq [LogLevel]::Debug

    if ($isDebugEnabled) {
        Write-LogFile -Message "[DEBUG] PowerShell Version: $($PSVersionTable.PSVersion)" -Level Debug
        Write-LogFile -Message "[DEBUG] Input parameters:" -Level Debug
        Write-LogFile -Message "[DEBUG] OutputDir: '$OutputDir'" -Level Debug
        Write-LogFile -Message "[DEBUG] Encoding: '$Encoding'" -Level Debug
        Write-LogFile -Message "[DEBUG] UserIds: '$UserIds'" -Level Debug
        Write-LogFile -Message "[DEBUG] LogLevel: '$LogLevel'" -Level Debug
        
        $graphModules = Get-Module -Name Microsoft.Graph* -ErrorAction SilentlyContinue
        if ($graphModules) {
            Write-LogFile -Message "[DEBUG] Microsoft Graph Modules loaded:" -Level Debug
            foreach ($module in $graphModules) {
                Write-LogFile -Message "[DEBUG] - $($module.Name) v$($module.Version)" -Level Debug
            }
        } else {
            Write-LogFile -Message "[DEBUG] No Microsoft Graph modules loaded" -Level Debug
        }
    }

    $requiredScopes = @("User.Read.All")
    $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes

    if ($isDebugEnabled) {
        Write-LogFile -Message "[DEBUG] Graph authentication details:" -Level Debug
        Write-LogFile -Message "[DEBUG] Required scopes: $($requiredScopes -join ', ')" -Level Debug
        Write-LogFile -Message "[DEBUG] Authentication type: $($graphAuth.AuthType)" -Level Debug
        Write-LogFile -Message "[DEBUG] Current scopes: $($graphAuth.Scopes -join ', ')" -Level Debug
        if ($graphAuth.MissingScopes.Count -gt 0) {
            Write-LogFile -Message "[DEBUG] Missing scopes: $($graphAuth.MissingScopes -join ', ')" -Level Debug
        } else {
            Write-LogFile -Message "[DEBUG] Missing scopes: None" -Level Debug
        }
    }

    if (!(Test-Path $OutputDir)) {
        New-Item -ItemType Directory -Force -Path $OutputDir > $null
    } 
    else {
        if (!(Test-Path -Path $OutputDir)) {
            Write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop
            Write-LogFile -Message "[Error] Custom directory invalid: $OutputDir exiting script" -Level Minimal
        }
    }    

    Write-LogFile -Message "=== Starting Users Collection ===" -Color "Cyan" -Level Standard

    try {
        $selectobjects = "UserPrincipalName","DisplayName","Id","CompanyName","Department","JobTitle","City","Country","Identities","UserType","LastPasswordChangeDateTime","AccountEnabled","CreatedDateTime","CreationType","ExternalUserState","ExternalUserStateChangeDateTime","SignInActivity","OnPremisesSyncEnabled"
        $mgUsers = @()

        if ($UserIds) {
            Write-LogFile -Message "[INFO] Filtering results for user: $UserIds" -Level Standard
            
            try {
                $mgUsers = Get-Mguser -Filter "userPrincipalName eq '$UserIds'" -select $selectobjects
                                
                if (-not $mgUsers) {
                    Write-LogFile -Message "[WARNING] User not found: $UserIds" -Color "Yellow" -Level Standard
                    $mgUsers = @()
                }
            } catch {
                Write-LogFile -Message "[WARNING] Error retrieving user $UserIds`: $($_.Exception.Message)" -Color "Yellow" -Level Standard
                $mgUsers = @()
            }
        } else {
            $mgUsers = Get-MgUser -All -Select $selectobjects
        }

        $formattedUsers = $mgUsers | ForEach-Object {
            [PSCustomObject]@{
                UserPrincipalName = $_.UserPrincipalName
                DisplayName = $_.DisplayName
                Id = $_.Id
                Department = $_.Department
                JobTitle = $_.JobTitle
                AccountEnabled = $_.AccountEnabled
                CreatedDateTime = $_.CreatedDateTime
                LastPasswordChangeDateTime = $_.LastPasswordChangeDateTime
                UserType = $_.UserType
                OnPremisesSyncEnabled = $_.OnPremisesSyncEnabled
                Mail = $_.Mail
                LastSignInDateTime = $_.SignInActivity.LastSignInDateTime
                LastNonInteractiveSignInDateTime = $_.SignInActivity.LastNonInteractiveSignInDateTime
                IdentityProvider = ($_.Identities | Where-Object { $_.SignInType -eq "federated" }).Issuer
                City = $_.City
                Country = $_.Country
                UsageLocation = $_.UsageLocation
            }
        }

        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] User formatting completed:" -Level Debug
            Write-LogFile -Message "[DEBUG] Original users: $($mgUsers.Count)" -Level Debug
            Write-LogFile -Message "[DEBUG] Formatted users: $($formattedUsers.Count)" -Level Debug
            Write-LogFile -Message "[DEBUG] Starting user analysis by creation date..." -Level Debug
        }

        $date = (Get-Date).AddDays(-7)
        $oneweekold = $mgUsers | Where-Object {
            $_.CreatedDateTime -gt $date
        }

        $date = (Get-Date).AddDays(-30)
        $onemonthold = $mgUsers | Where-Object {
            $_.CreatedDateTime -gt $date
        }

        $date = (Get-Date).AddDays(-90)
        $threemonthold = $mgUsers | Where-Object {
            $_.CreatedDateTime -gt $date
        }

        $date = (Get-Date).AddDays(-180)
        $sixmonthold = $mgUsers | Where-Object {
            $_.CreatedDateTime -gt $date
        }

        $date = (Get-Date).AddDays(-360)
        $OneYear = $mgUsers | Where-Object {
            $_.CreatedDateTime -gt $date
        }

        Get-MgUser | Get-Member > $null
        $date = Get-Date -Format "yyyyMMddHHmm"
        $filePath = "$OutputDir\$($date)-Users.csv"
        $formattedUsers  | Export-Csv -Path $filePath -NoTypeInformation -Encoding $Encoding

        $userSummary = [PSCustomObject]@{
            TotalUsers = $mgUsers.Count
            EnabledUsers = ($mgUsers | Where-Object { $_.AccountEnabled }).Count
            DisabledUsers = ($mgUsers | Where-Object { -not $_.AccountEnabled }).Count
            SyncedUsers = ($mgUsers | Where-Object { $_.OnPremisesSyncEnabled }).Count
            GuestUsers = ($mgUsers | Where-Object { $_.UserType -eq "Guest" }).Count
            LastSevenDays = $oneweekold.Count
            LastThirtyDays = $onemonthold.Count
            LastNinetyDays = $threemonthold.Count
            Onehundredeighty = $sixmonthold.Count
            OneYear = $OneYear.Count
        }

        Write-LogFile -Message "User Analysis Results:" -Color "Cyan" -Level Standard
        Write-LogFile -Message "Total Users: $($userSummary.TotalUsers)" -Level Standard
        Write-LogFile -Message " - Enabled: $($userSummary.EnabledUsers)" -Level Standard
        Write-LogFile -Message " - Disabled: $($userSummary.DisabledUsers)" -Level Standard
        Write-LogFile -Message " - Synced from On-Premises: $($userSummary.SyncedUsers)" -Level Standard
        Write-LogFile -Message " - Guest Users: $($userSummary.GuestUsers)" -Level Standard

        Write-LogFile -Message "`nRecent Account Creation:" -Color "Cyan" -Level Standard
        Write-LogFile -Message " - Last 7 days: $($userSummary.LastSevenDays)" -Level Standard
        Write-LogFile -Message " - Last 30 days: $($userSummary.LastThirtyDays)" -Level Standard
        Write-LogFile -Message " - Last 90 days: $($userSummary.LastNinetyDays)" -Level Standard
        Write-LogFile -Message " - Last 6 months: $($userSummary.Onehundredeighty)" -Level Standard
        Write-LogFile -Message " - Last 1 year $($userSummary.OneYear)" -Level Standard

        Write-LogFile -Message "`nExported File:" -Color "Cyan" -Level Standard
        Write-LogFile -Message " - File: $filePath" -Level Standard
    }
    catch {
        Write-logFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Color "Red" -Level Minimal
        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Error details:" -Level Debug
            Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug
            Write-LogFile -Message "[DEBUG] Error message: $($_.Exception.Message)" -Level Debug
            Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug
        }
        throw
    }   
}

Function Get-AdminUsers {
<#
    .SYNOPSIS
    Retrieves all Administrator directory roles.
 
    .DESCRIPTION
    Retrieves Administrator directory roles, including the identification of users associated with each specific role.
 
    .PARAMETER OutputDir
    OutputDir is the parameter specifying the output directory.
    Default: Output\Admins
 
    .PARAMETER Encoding
    Encoding is the parameter specifying the encoding of the CSV output file.
    Default: UTF8
 
    .PARAMETER LogLevel
    Specifies the level of logging:
    None: No logging
    Minimal: Critical errors only
    Standard: Normal operational logging
    Debug: Verbose logging for debugging purposes
    Default: Standard
     
    .EXAMPLE
    Get-AdminUsers
    Retrieves Administrator directory roles, including the identification of users associated with each specific role.
     
    .EXAMPLE
    Get-AdminUsers -Encoding utf32
    Retrieves Administrator directory roles, including the identification of users associated with each specific role and exports the output to a CSV file with UTF-32 encoding.
         
    .EXAMPLE
    Get-AdminUsers -OutputDir C:\Windows\Temp
    Retrieves Administrator directory roles, including the identification of users associated with each specific role and saves the output to the C:\Windows\Temp folder.
#>
    

    [CmdletBinding()]
    param(
        [string]$outputDir = "Output\Admins",
        [string]$Encoding = "UTF8",
        [ValidateSet('None', 'Minimal', 'Standard', 'Debug')]
        [string]$LogLevel = 'Standard'
    )

    Set-LogLevel -Level ([LogLevel]::$LogLevel)
    $isDebugEnabled = $script:LogLevel -eq [LogLevel]::Debug

    if ($isDebugEnabled) {
        Write-LogFile -Message "[DEBUG] PowerShell Version: $($PSVersionTable.PSVersion)" -Level Debug
        Write-LogFile -Message "[DEBUG] Input parameters:" -Level Debug
        Write-LogFile -Message "[DEBUG] OutputDir: '$outputDir'" -Level Debug
        Write-LogFile -Message "[DEBUG] Encoding: '$Encoding'" -Level Debug
        Write-LogFile -Message "[DEBUG] LogLevel: '$LogLevel'" -Level Debug
        
        $graphModules = Get-Module -Name Microsoft.Graph* -ErrorAction SilentlyContinue
        if ($graphModules) {
            Write-LogFile -Message "[DEBUG] Microsoft Graph Modules loaded:" -Level Debug
            foreach ($module in $graphModules) {
                Write-LogFile -Message "[DEBUG] - $($module.Name) v$($module.Version)" -Level Debug
            }
        } else {
            Write-LogFile -Message "[DEBUG] No Microsoft Graph modules loaded" -Level Debug
        }
    }

    $date = Get-Date -Format "yyyyMMddHHmm"
    Write-LogFile -Message "=== Starting Admin Users Collection ===" -Color "Cyan" -Level Standard

    $requiredScopes = @("User.Read.All", "Directory.Read.All")
    $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes

    if ($isDebugEnabled) {
        Write-LogFile -Message "[DEBUG] Graph authentication details:" -Level Debug
        Write-LogFile -Message "[DEBUG] Required scopes: $($requiredScopes -join ', ')" -Level Debug
        Write-LogFile -Message "[DEBUG] Authentication type: $($graphAuth.AuthType)" -Level Debug
        Write-LogFile -Message "[DEBUG] Current scopes: $($graphAuth.Scopes -join ', ')" -Level Debug
        if ($graphAuth.MissingScopes.Count -gt 0) {
            Write-LogFile -Message "[DEBUG] Missing scopes: $($graphAuth.MissingScopes -join ', ')" -Level Debug
        } else {
            Write-LogFile -Message "[DEBUG] Missing scopes: None" -Level Debug
        }
    }
        
    if (!(Test-Path $OutputDir)) {
        New-Item -ItemType Directory -Force -Path $OutputDir > $null
    } 
    else {
        if (!(Test-Path -Path $OutputDir)) {
            Write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop
            Write-LogFile -Message "[Error] Custom directory invalid: $OutputDir exiting script" -Level Minimal
        }
    }    

    Write-LogFile -Message "[INFO] Analyzing administrator roles..." -Level Standard

    $rolesWithUsers = @()
    $rolesWithoutUsers = @()
    $exportedFiles = @()
    $totalAdminCount = 0
    $inactiveAdminCount = 0

    # Track users with no recent sign-in
    $inactiveThreshold = (Get-Date).AddDays(-30)
    $inactiveAdmins = @()

    try {
        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Retrieving all directory roles..." -Level Debug
            $performance = Measure-Command {
                $getRoles = Get-MgDirectoryRole -all
            }
            Write-LogFile -Message "[DEBUG] Directory roles retrieval took $([math]::round($performance.TotalSeconds, 2)) seconds" -Level Debug
            Write-LogFile -Message "[DEBUG] Found $($getRoles.Count) total directory roles" -Level Debug
        } else {
            $getRoles = Get-MgDirectoryRole -all
        }
        
        foreach ($role in $getRoles) {
            $roleId = $role.Id
            $roleName = $role.DisplayName
        
            if ($roleName -like "*Admin*") {
                if ($isDebugEnabled) {
                    Write-LogFile -Message "[DEBUG] Processing admin role: $roleName" -Level Debug
                    Write-LogFile -Message "[DEBUG] Role ID: $roleId" -Level Debug
                }
                
                if ($isDebugEnabled) {
                    $memberPerformance = Measure-Command {
                        $areThereUsers = Get-MgDirectoryRoleMember -DirectoryRoleId $roleId
                    }
                    Write-LogFile -Message "[DEBUG] Role member query took $([math]::round($memberPerformance.TotalSeconds, 2)) seconds" -Level Debug
                } else {
                    $areThereUsers = Get-MgDirectoryRoleMember -DirectoryRoleId $roleId
                }

                if ($null -eq $areThereUsers) {
                    $rolesWithoutUsers += $roleName
                    continue
                }

                $results = @()
                $count = 0
                foreach ($user in $areThereUsers) {
                    $userid = $user.Id
                    if ($userid -eq ".") {
                        if ($isDebugEnabled) {
                            Write-LogFile -Message "[DEBUG] Skipping invalid user ID: $userid" -Level Debug
                        }
                        continue
                    }

                    $count++
                    if ($isDebugEnabled) {
                        Write-LogFile -Message "[DEBUG] Processing user $count/$($areThereUsers.Count): $userid" -Level Debug
                    }
                    try {
                        $selectProperties = @(
                        "UserPrincipalName", "DisplayName", "Id", "Department", "JobTitle", 
                        "AccountEnabled", "CreatedDateTime","SignInActivity"
                        )


                        try {
                            $getUserName = Get-MgUser -UserId $userid -Select $selectProperties -ErrorAction Stop
                        } catch {
                            if ($_.Exception.Response.StatusCode -eq 429) {
                                Start-Sleep -Seconds 5
                                $getUserName = Get-MgUser -UserId $userid -Select $selectProperties -ErrorAction Stop
                            } else {
                                throw
                            }
                        }
                    
                        $userName = $getUserName.UserPrincipalName
                        $userObject = [PSCustomObject]@{
                            UserName = $userName
                            UserId = $userid
                            Role = $roleName
                            DisplayName = $getUserName.DisplayName
                            Department = $getUserName.Department
                            JobTitle = $getUserName.JobTitle
                            AccountEnabled = $getUserName.AccountEnabled
                            CreatedDateTime = $getUserName.CreatedDateTime
                            LastInteractiveSignIn = $getUserName.SignInActivity.LastSignInDateTime
                            LastNonInteractiveSignIn = $getUserName.SignInActivity.LastNonInteractiveSignInDateTime
                        }

                        if ($getUserName.SignInActivity.LastSignInDateTime) {
                            $daysSinceSignIn = (New-TimeSpan -Start $getUserName.SignInActivity.LastSignInDateTime -End (Get-Date)).Days
                            $userObject | Add-Member -MemberType NoteProperty -Name "DaysSinceLastSignIn" -Value $daysSinceSignIn
                            
                            if ($getUserName.SignInActivity.LastSignInDateTime -lt $inactiveThreshold) {
                                $inactiveAdminCount++
                                $inactiveAdmins += "$($getUserName.DisplayName) ($userName) - $daysSinceSignIn days"
                            }
                        } else {
                            $userObject | Add-Member -MemberType NoteProperty -Name "DaysSinceLastSignIn" -Value "No sign-in data"
                            $inactiveAdminCount++
                            $inactiveAd                       
                        }
                        $results += $userObject
                    }
                    catch {
                        Write-LogFile -Message "[WARNING] Error processing user $userid in role $roleName`: $($_.Exception.Message)" -Color "Yellow" -Level Standard
                    }
                }

                if ($results.Count -gt 0) {
                    $totalAdminCount += $results.Count
                    $rolesWithUsers += "$roleName ($($results.Count) users)"
                    
                    $filePath = "$OutputDir\$($date)-$($roleName.Replace(' ','_')).csv"
                    $results | Export-Csv -Path $filePath -NoTypeInformation -Encoding $Encoding
                    $exportedFiles += $filePath
                }
                else {
                    $rolesWithoutUsers += $roleName
                }
            }
        }

        $outputDirMerged = "$OutputDir\Merged\"
        If (!(test-path $outputDirMerged)) {
            New-Item -ItemType Directory -Force -Path $outputDirMerged > $null
        }

        $mergedFile = "$outputDirMerged$($date)-All-Administrators.csv"
        Get-ChildItem $OutputDir -Filter "*Administrator.csv" | 
            Select-Object -ExpandProperty FullName | 
            Import-Csv | 
            Export-Csv $mergedFile -NoTypeInformation -Append

        Write-LogFile -Message "`nRoles with users:" -Color "Green" -Level Standard
        foreach ($role in $rolesWithUsers) {
            Write-LogFile -Message " + $role" -Level Standard
        }

        Write-LogFile -Message "`nEmpty roles:" -Color "Yellow" -Level Standard
        foreach ($role in $rolesWithoutUsers) {
            Write-LogFile -Message " - $role" -Level Standard
        }

        if ($IncludeSignInActivity -and $inactiveAdmins.Count -gt 0) {
            Write-LogFile -Message "`nInactive administrators (30+ days):" -Color "Yellow" -Level Standard
            foreach ($admin in $inactiveAdmins) {
                Write-LogFile -Message " ! $admin" -Level Standard
            }
        }

        Write-LogFile -Message "`nSummary:" -Level Standard -Color "Cyan"
        Write-LogFile -Message " Total admin roles: $($rolesWithUsers.Count + $rolesWithoutUsers.Count)" -Level Standard
        Write-LogFile -Message " Roles with users: $($rolesWithUsers.Count)" -Level Standard
        Write-LogFile -Message " Empty roles: $($rolesWithoutUsers.Count)" -Level Standard
        Write-LogFile -Message " Total administrators: $totalAdminCount" -Level Standard
        if ($IncludeSignInActivity) {
            Write-LogFile -Message " Inactive administrators: $inactiveAdminCount" -Level Standard
        }

        Write-LogFile -Message "`nExported files:" -Level Standard -Color "Cyan"
        Write-LogFile -Message " Individual role files: $OutputDir" -Level Standard
        Write-LogFile -Message " Merged file: $mergedFile" -Level Standard
    }
    catch {
        Write-logFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Color "Red" -Level Minimal
        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Error details:" -Level Debug
            Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug
            Write-LogFile -Message "[DEBUG] Error message: $($_.Exception.Message)" -Level Debug
            Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug
        }
        throw
    }
}