Last active
October 17, 2023 21:26
-
-
Save jschlackman/dc72fb422e548ac5dfb94fde67861b55 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Name: Get-DirectoryAccessDetails.ps1 | |
| # Author: James Schlackman | |
| # Last Modified: Oct 17 2023 | |
| # | |
| # Audits all enabled users in specified on-prem AD OUs and combines with it with enabled users from Entra ID. | |
| # Outputs a combined list showing which accounts are federated/on-prem only/cloud only along with details of | |
| # the most recent date they logged in (either on-prem or cloud, whichever is later), when they last changed | |
| # their password, when the account was created, and which AD admin and Entra roles they are assigned (if any). | |
| # | |
| # Last sign in date for Entra requires an Entra ID Premium license. | |
| # | |
| # NOTE: Entra ID only began storing account creation dates in June 2018. CreatedDateTime will be blank | |
| # for accounts created before that time. | |
| # | |
| # Source: https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users/update-mguser | |
| #Requires -Modules ActiveDirectory, Microsoft.Graph.Authentication, Microsoft.Graph.Identity.DirectoryManagement, Microsoft.Graph.Users | |
| Param( | |
| # Export file name | |
| [Parameter()] [String]$OutputPath = "$((Get-Date).ToString("yyMMdd")) Directory Access Details.csv", | |
| # One or more OUs to check. Must include all OUs synced by Entra Connect for identity type field to be accurate. Defaults to root of current domain. | |
| [Parameter()] [String[]]$CheckOUs = (Get-ADDomain).DistinguishedName, | |
| # PIM groups to be included in audit. Must be specified manually until Graph has a way of retrieving a list programatically. | |
| [Parameter()] [String[]]$EntraPimGroups | |
| ) | |
| # Init empty hash table | |
| $AllUsers = @{} | |
| # Function to convert UTC datetimes returned by Graph to local time | |
| $LocalTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Eastern Standard Time") | |
| function LocalTime() { | |
| param( | |
| $utcDateTime | |
| ) | |
| If ([bool]$utcDateTime) { | |
| [System.TimeZoneInfo]::ConvertTimeFromUtc($utcDateTime, $LocalTimeZone) | |
| } | |
| } | |
| # Connect to Graph with required scopes | |
| Connect-Graph -Scopes User.Read.All,AuditLog.Read.All,RoleManagement.Read.All | |
| Write-Host "`nLoading AD admin group users..." -ForegroundColor Cyan | |
| # Get all privileged groups with their members | |
| $ADAdminAudit = Get-ADGroup -Filter 'AdminCount -eq 1' | Select 'name',@{Name='members';Expression={(Get-ADGroupMember -Identity $_ -Recursive).sAMAccountName}} | |
| If ([bool]$ADAdminAudit) { | |
| Write-Host "Complete." -ForegroundColor Green | |
| } | |
| Write-Host "`nLoading AD user details..." -ForegroundColor Cyan | |
| $ADLoadProperties = 'passwordLastSet','mail','description','lastLogonTimestamp','msDS-UserPasswordExpiryTimeComputed','title','department','whenCreated' | |
| # Load details for each specified OU | |
| $ADUsers = $CheckOUs | ForEach-Object { | |
| Write-Host $_ | |
| # Get user details | |
| Get-ADUser -SearchBase $_ -SearchScope Subtree -Filter {(Enabled -eq $true ) -and (passwordLastSet -gt 0)} -Properties $ADLoadProperties | ForEach-Object { | |
| [PSCustomObject]@{ | |
| UserPrincipalName = If($_.UserPrincipalName -ne $null){$_.UserPrincipalName} Else {$_.sAMAccountName} | |
| Name = $_.Name | |
| Mail = $_.Mail | |
| SharedMailbox = '' | |
| IdentityType = 'On-Premise' | |
| Description = If ($_.Title -ne $null) {$_.Title} Else {$_.Description} | |
| Department = $_.Department | |
| Created = $_.whenCreated | |
| 'Password Last Set' = $_.passwordLastSet | |
| LastSignIn = If ($_.lastLogonTimestamp -gt 0) {[datetime]::FromFileTime($_.lastLogonTimestamp)} Else {''} | |
| 'AD Admin Groups' = (($ADAdminAudit | Where-Object -Property Members -Contains $_.sAMAccountName).name) -join ', ' | |
| 'Entra Roles' = '' | |
| OnPremisesDistinguishedName =$_.DistinguishedName | |
| } | |
| } | |
| } | |
| If ([bool]$ADUsers) { | |
| Write-Host "Complete." -ForegroundColor Green | |
| } | |
| # Add AD users to hash table | |
| $ADUsers | ForEach-Object { | |
| $AllUsers.Add($_.UserPrincipalName, $_) | |
| } | |
| # Get all active AAD roles and the users assigned to them | |
| Write-Host "`nRetrieving Entra role assignments..." -ForegroundColor Cyan | |
| $RoleAudit = Get-MgDirectoryRole | Select DisplayName,ID,@{Name='Members';Expression={$members=Get-MgDirectoryRoleMember -DirectoryRoleId $_.Id;If ([bool]$members) {$members | Select -ExpandProperty Id} Else {$null}}} | |
| If ([bool]$RoleAudit) { | |
| Write-Host "Complete." -ForegroundColor Green | |
| } | |
| If ([bool]$EntraPimGroups) { | |
| # Get PIM groups and the users assigned to them | |
| Write-Host "`nRetrieving Entra PIM group assignments..." -ForegroundColor Cyan | |
| $PIMAudit = $EntraPimGroups | ForEach-Object {Get-MgGroup -GroupId $_ |Select DisplayName,ID,@{Name='Members';Expression={$members=Get-MgGroupMember -GroupId $_.Id;If ([bool]$members) {$members | Select -ExpandProperty Id} Else {$null}}}} | |
| If ([bool]$PIMAudit) { | |
| Write-Host "Complete." -ForegroundColor Green | |
| } | |
| } | |
| Write-Host "`nRetrieving Entra ID user details..." -ForegroundColor Cyan | |
| # Define user properties to load | |
| $EntraLoadProperties = 'DisplayName','UserPrincipalName','Mail','JobTitle','Department','CreatedDateTime','LastPasswordChangeDateTime','SignInActivity','AccountEnabled','AssignedLicenses','OnPremisesDistinguishedName' | |
| # Get all member users and filter for enabled users only | |
| $EnabledMembers = Get-MgUser -Filter "UserType eq 'Member'" -All -Property $EntraLoadProperties | Where-Object -Property AccountEnabled -eq $true | |
| $EnabledMembers = $EnabledMembers | ForEach-Object { | |
| $SignIn = $_.SignInActivity.LastSignInDateTime; | |
| # Fallback to last non-interactive sign-in if no standard sign-in date is available | |
| If ([bool]$SignIn){ | |
| $SignIn = LocalTime($SignIn) | |
| } Else { | |
| $SignIn = LocalTime($_.SignInActivity.LastNonInteractiveSignInDateTime) | |
| } | |
| [PSCustomObject]@{ | |
| UserPrincipalName = $_.UserPrincipalName | |
| Name = $_.DisplayName | |
| Mail = $_.Mail | |
| # Assume shared mailbox if email attribute is set but no license | |
| SharedMailbox = [bool]$_.Mail -and !([bool]$_.AssignedLicenses) | |
| IdentityType = 'Cloud' | |
| Description = $_.JobTitle | |
| Department = $_.Department | |
| Created = LocalTime($_.CreatedDateTime) | |
| 'Password Last Set' = LocalTime($_.LastPasswordChangeDateTime) | |
| LastSignIn = $SignIn | |
| 'AD Admin Groups' = '' | |
| 'Entra Roles' = (($RoleAudit + $PIMAudit | Where-Object -Property Members -Contains $_.Id).DisplayName) -join ', ' | |
| OnPremisesDistinguishedName = $_.OnPremisesDistinguishedName | |
| } | |
| } | |
| If ([bool]$EnabledMembers) { | |
| Write-Host "Complete." -ForegroundColor Green | |
| } | |
| # Init counts | |
| $FederatedCount = $CloudCount = 0 | |
| # Match Entra ID users with on-prem AD users | |
| $EnabledMembers | ForEach-Object { | |
| $Linked = $false | |
| # If user is synced from on-prem AD | |
| If ([bool]$_.OnPremisesDistinguishedName) { | |
| $UPN = $_.UserPrincipalName | |
| If ([bool]$AllUsers[$UPN]) { | |
| # Add any Entra roles for this user | |
| $AllUsers[$UPN].'Entra Roles' = $_.'Entra Roles' | |
| $AllUsers[$UPN].SharedMailbox = $_.SharedMailbox | |
| $AllUsers[$UPN].IdentityType = 'Federated' | |
| $FederatedCount += 1 | |
| # Update last sign in if Entra has a later date | |
| If ($AllUsers[$UPN].LastSignIn -lt $_.LastSignIn) { | |
| $AllUsers[$UPN].LastSignIn = $_.LastSignIn | |
| } | |
| $Linked = $true | |
| } Else { | |
| # Entra user was originally synced from AD but on-prem AD account not found | |
| $_.IdentityType = 'Orphaned' | |
| $CloudCount += 1 | |
| } | |
| } Else { | |
| #On-prem only user | |
| $OnPremCount -= 1 | |
| } | |
| If (!([bool]$Linked)) { | |
| #Entra only user, add to hash table as-is | |
| $AllUsers.Add($_.UserPrincipalName, $_) | |
| $CloudCount += 1 | |
| } | |
| } | |
| $PrivilegedUsers = ($AllUsers.Values | Where-Object {($_.'Entra Roles' -ne '') -or ($_.'AD Admin Groups' -ne '')}) | |
| Write-Host "`nTOTAL USERS: $($AllUsers.Count)`nOn-prem users: $($ADUsers.Count - $FederatedCount)`nFederated users: $FederatedCount`nCloud users: $CloudCount`nPrivileged users: $($PrivilegedUsers.Count)`n" | |
| $AuditOutput = ($AllUsers.Values | Sort-Object -Property UserPrincipalName) | |
| # Display/export output | |
| $AuditOutput | Out-GridView | |
| Write-Host 'See grid export for details.' | |
| If ([bool]$OutputPath) { | |
| # Optionally export output to file | |
| Write-Host "`nExport details to CSV? " -ForegroundColor Cyan -NoNewline | |
| If ((Read-Host '[y/N]').ToUpper() -eq 'Y') { | |
| Write-Host 'Exporting to ' -NoNewline | |
| Write-Host $OutputPath -ForegroundColor Green | |
| $AuditOutput | Export-Csv -NoTypeInformation -Path ($OutputPath) -Encoding UTF8 | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment