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, [string]$Encoding = "UTF8", [string]$UserIds, [ValidateSet('None', 'Minimal', 'Standard', 'Debug')] [string]$LogLevel = 'Standard' ) Init-Logging Init-OutputDir -Component "Users" -FilePostfix "Users" -CustomOutputDir $OutputDir $requiredScopes = @("User.Read.All") $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes 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 Write-LogFile -Message "[INFO] Found $($mgUsers.Count) users" -Level Standard } $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 $formattedUsers | Export-Csv -Path $script:outputFile -NoTypeInformation -Encoding $Encoding $summary = [ordered]@{ "User Counts" = [ordered]@{ "Total Users" = $mgUsers.Count "Enabled Users" = ($mgUsers | Where-Object { $_.AccountEnabled }).Count "Disabled Users" = ($mgUsers | Where-Object { -not $_.AccountEnabled }).Count "Synced from On-Premises" = ($mgUsers | Where-Object { $_.OnPremisesSyncEnabled }).Count "Guest Users" = ($mgUsers | Where-Object { $_.UserType -eq "Guest" }).Count } "Recent Account Creation" = [ordered]@{ "Last 7 days" = $oneweekold.Count "Last 30 days" = $onemonthold.Count "Last 90 days" = $threemonthold.Count "Last 6 months" = $sixmonthold.Count "Last 1 year" = $OneYear.Count } } Write-Summary -Summary $summary -Title "User Analysis Summary" } 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, [string]$Encoding = "UTF8", [ValidateSet('None', 'Minimal', 'Standard', 'Debug')] [string]$LogLevel = 'Standard' ) Init-Logging Init-OutputDir -Component "Admins" -FilePostfix "AdminUsers" -CustomOutputDir $OutputDir 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 } } 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++ $inactiveAdmins += "$($getUserName.DisplayName) ($userName) - No sign-in data" } $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)" $date = [datetime]::Now.ToString('yyyyMMdd') $safeRoleName = $roleName -replace '[^\w\-_\.]', '_' $rolePath = Split-Path $script:outputFile -Parent $roleFilePath = Join-Path $rolePath "$date-$safeRoleName.csv" $results | Export-Csv -Path $roleFilePath -NoTypeInformation -Encoding $Encoding $exportedFiles += $roleFilePath } else { $rolesWithoutUsers += $roleName } } } # Create merged file $outputDirPath = Split-Path $script:outputFile -Parent $outputDirMerged = Join-Path $outputDirPath "Merged" if (!(Test-Path $outputDirMerged)) { New-Item -ItemType Directory -Force -Path $outputDirMerged > $null } $date = [datetime]::Now.ToString('yyyyMMdd') $mergedFile = Join-Path $outputDirMerged "$date-All-Administrators.csv" # Get all individual admin role files and merge them $adminFiles = Get-ChildItem $outputDirPath -Filter "*Admin*.csv" -ErrorAction SilentlyContinue if ($adminFiles.Count -gt 0) { $adminFiles | ForEach-Object { Import-Csv $_.FullName } | Export-Csv $mergedFile -NoTypeInformation -Encoding $Encoding } $summary = [ordered]@{ "Role Summary" = [ordered]@{ "Total admin roles" = ($rolesWithUsers.Count + $rolesWithoutUsers.Count) "Roles with users" = $rolesWithUsers.Count "Empty roles" = $rolesWithoutUsers.Count "Total administrators" = $totalAdminCount "Inactive administrators (30+ days)" = $inactiveAdminCount } } # Keep the detailed lists before the summary 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 ($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-Summary -Summary $summary -Title "Admin Users Summary" } 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 } } |