Entra Device Report

This commit is contained in:
AdminDroid 2025-07-07 15:23:49 +05:30
parent 95a479bf15
commit 76bff3d7f0

View File

@ -1,234 +1,267 @@
<# <#
============================================================================================= =============================================================================================
Name: Export Azure Device Report using MS Graph PowerShell Name: Export Entra Device Report using MS Graph PowerShell
Description: This script exports Microsoft 365 Azure AD devices to CSV Description: This script exports all Microsoft 365 devices to CSV
Version: 2.0 Version: 3.0
website: o365reports.com website: o365reports.com
~~~~~~~~~~~~~~~~~
Script Highlights:
~~~~~~~~~~~~~~~~~
1. Exports all Azure AD devices in your organization.
2. Automatically checks the Microsoft Graph PowerShell module and installs it upon your confirmation if its missing.
3. Enables filtering based on following device registration types:
-> Entra registered
-> Entra joined
-> Entra Hybrid joined
4. Generates a report that retrieves enabled devices alone.
5. Helps export disabled devices alone.
6. Find inactive devices based on inactive days.
7. Includes ownership filtering to show company, personal, unknown devices.
8. List all devices that have BitLocker key.
9. The script can be executed with MFA-enabled accounts too.
10. Facilitates filtering devices by users, owners, and groups.
11. Exports the report results in CSV format.
12. Allows exporting devices that match the selected filters.
-> Device management status [ Managed / Unmanaged ]
-> Device compliance status [ Compliant / Non-compliant ]
-> Device rooted state [ Rooted / Nonrooted ]
13. Compatible with certificate-based authentication (CBA).
14. Filter devices by profile types such as IoT, Printer, Secure VM, Shared device, and Registered device.
Script Highlights
1.The script can be executed with MFA-enabled accounts too.
2.Exports output to CSV.
3.Automatically installs the Microsoft Graph PowerShell module in your PowerShell environment upon your confirmation.
4.Supports the method of certificate-based authentication.
5.The script lists all the Azure AD devices of your organization. That too customization of reports is possible according to the major device types like managed, enabled, disabled etc.
For detailed script execution: https://o365reports.com/2023/04/18/get-azure-ad-devices-report-using-powershell/ For detailed script execution: https://o365reports.com/2023/04/18/get-azure-ad-devices-report-using-powershell/
~~~~~~~~~~~
Change Log:
~~~~~~~~~~~
V1.0 (Apr 25, 2023) File created.
V2.0 (Jul 17, 2024) Updated to use MS Graph beta PowerShell module instead of 'Select-MgProfile beta' to load beta commands.
V3.0 (Jul 03, 2025) Upgraded from the 'MS Graph beta module' to Microsoft Graph module and added additional filters such as device join type, profile type, compliance status, and more.
============================================================================================
#> #>
## If you execute via CBA, then your application required "Directory.Read.All" application permissions.
Param Param
( (
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[string]$TenantId, [string]$TenantId,
[string]$ClientId, [string]$ClientId,
[string]$CertificateThumbprint, [string]$CertificateThumbprint,
[switch]$EnabledDevice,
[switch]$DisabledDevice,
[Int]$InactiveDays, [Int]$InactiveDays,
[switch]$ManagedDevice, [string[]]$Users,
[string[]]$Owners,
[string[]]$Groups,
[ValidateSet("Enabled", "Disabled")]
[string]$DeviceStatus,
[ValidateSet("Managed", "Unmanaged")]
[string]$ManagementStatus,
[ValidateSet("Compliant", "NonCompliant")]
[string]$ComplianceStatus,
[ValidateSet("Rooted", "NonRooted")]
[string]$RootedStatus,
[ValidateSet("RegisteredDevice", "SecureVM", "Printer", "Shared", "IoT")]
[string[]]$ProfileType,
[ValidateSet("Entra registered", "Entra joined", "Entra hybrid joined")]
[string[]]$JoinType,
[ValidateSet("Company", "Personal", "Unknown")]
[string[]]$DeviceOwnership,
[switch]$DevicesWithBitLockerKey [switch]$DevicesWithBitLockerKey
) )
$MsGraphBetaModule = Get-Module Microsoft.Graph.Beta -ListAvailable
if($MsGraphBetaModule -eq $null) # Check if Microsoft Graph module is installed
$MsGraphModule = Get-Module Microsoft.Graph -ListAvailable
if($MsGraphModule -eq $null)
{ {
Write-host "Important: Microsoft Graph Beta module is unavailable. It is mandatory to have this module installed in the system to run the script successfully." Write-host "Important: Microsoft Graph module is unavailable. It is mandatory to have this module installed in the system to run the script successfully."
$confirm = Read-Host Are you sure you want to install Microsoft Graph Beta module? [Y] Yes [N] No $confirm = Read-Host Are you sure you want to install Microsoft Graph module? [Y] Yes [N] No
if($confirm -match "[yY]") if($confirm -match "[yY]") {
{ Write-host "Installing Microsoft Graph module..."
Write-host "Installing Microsoft Graph Beta module..." Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber
Install-Module Microsoft.Graph.Beta -Scope CurrentUser -AllowClobber Write-host "Microsoft Graph module is installed in the machine successfully" -ForegroundColor Magenta
Write-host "Microsoft Graph Beta module is installed in the machine successfully" -ForegroundColor Magenta
} }
else else {
{ Write-host "Exiting. `nNote: Microsoft Graph module must be available in your system to run the script" -ForegroundColor Red
Write-host "Exiting. `nNote: Microsoft Graph Beta module must be available in your system to run the script" -ForegroundColor Red
Exit Exit
} }
} }
Write-Host "`nConnecting to Microsoft Graph..."
if(($TenantId -ne "") -and ($ClientId -ne "") -and ($CertificateThumbprint -ne "")) if(($TenantId -ne "") -and ($ClientId -ne "") -and ($CertificateThumbprint -ne ""))
{ {
Connect-MgGraph -TenantId $TenantId -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -ErrorAction SilentlyContinue -ErrorVariable ConnectionError|Out-Null Connect-MgGraph -TenantId $TenantId -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -ErrorAction SilentlyContinue -ErrorVariable ConnectionError | Out-Null
if($ConnectionError -ne $null) if($ConnectionError -ne $null) {
{
Write-Host $ConnectionError -Foregroundcolor Red Write-Host $ConnectionError -Foregroundcolor Red
Exit Exit
} }
$Certificate = (Get-MgContext).CertificateThumbprint Write-Host "Connected to Microsoft Graph PowerShell using certificate-based authentication."
Write-Host "Note: You don't get device with bitlocker key info while using certificate based authentication. If you want to get bitlocker key enabled devices, then you can connect graph using credentials(User interaction based authentication)" -ForegroundColor Yellow
} }
else else
{ {
Connect-MgGraph -Scopes "Directory.Read.All,BitLockerKey.Read.All" -ErrorAction SilentlyContinue -Errorvariable ConnectionError |Out-Null Connect-MgGraph -Scopes "Directory.Read.All,BitLockerKey.Read.All" -ErrorAction SilentlyContinue -Errorvariable ConnectionError | Out-Null
if($ConnectionError -ne $null) if($ConnectionError -ne $null) {
{
Write-Host "$ConnectionError" -Foregroundcolor Red Write-Host "$ConnectionError" -Foregroundcolor Red
Exit Exit
} }
Write-Host "Connected to Microsoft Graph PowerShell."
} }
Write-Host "Microsoft Graph Beta Powershell module is connected successfully" -ForegroundColor Green
Write-Host "`nNote: If you encounter module related conflicts, run the script in a fresh Powershell window." $Location = Get-Location
function CloseConnection $CurrentDate = Get-Date
{
Disconnect-MgGraph | Out-Null
Exit
}
$OutputCsv =".\AzureDeviceReport_$((Get-Date -format MMM-dd` hh-mm-ss` tt).ToString()).csv"
$Report=""
$FilterCondition = @()
$DeviceInfo = Get-MgBetaDevice -All
if($DeviceInfo -eq $null)
{
Write-Host "You have no devices enrolled in your Azure AD" -ForegroundColor Red
CloseConnection
}
if($EnabledDevice.IsPresent)
{
$DeviceInfo = $DeviceInfo | Where-Object {$_.AccountEnabled -eq $True}
}
elseif($DisabledDevice.IsPresent)
{
$DeviceInfo = $DeviceInfo | Where-Object {$_.AccountEnabled -eq $False}
}
if($ManagedDevice.IsPresent)
{
$DeviceInfo = $DeviceInfo | Where-Object {$_.IsManaged -eq $True}
}
$TimeZone = (Get-TimeZone).Id $TimeZone = (Get-TimeZone).Id
Foreach($Device in $DeviceInfo){ $OutputCsv = "$Location\EntraDevicesReport_$($CurrentDate.ToString('yyyy-MMM-dd-ddd hh-mm-ss tt')).csv"
Write-Progress -Activity "Fetching devices: $($Device.DisplayName)" $Report=""
$PrintedLogs=0
$ManagedDevices = Get-MgDeviceManagementManagedDevice | Select-Object AzureAdDeviceId, SerialNumber
Get-MgDevice -All | ForEach-Object {
Write-Progress -Activity "Fetching devices: $($_.DisplayName)"
$LastSigninActivity = "-" $LastSigninActivity = "-"
if(($Device.ApproximateLastSignInDateTime -ne $null)) $TrustType = ""
{
$LastSigninActivity = (New-TimeSpan -Start $Device.ApproximateLastSignInDateTime).Days if(($_.ApproximateLastSignInDateTime -ne $null)) {
} $LastSigninActivity = (New-TimeSpan -Start $_.ApproximateLastSignInDateTime).Days
if($Certificate -eq $null)
{
$BitLockerKeyIsPresent = "No"
try {
$BitLockerKeys = Get-MgBetaInformationProtectionBitlockerRecoveryKey -Filter "DeviceId eq '$($Device.DeviceId)'" -ErrorAction SilentlyContinue -ErrorVariable Err
if($Err -ne $null)
{
Write-Host $Err -ForegroundColor Red
CloseConnection
}
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Red
CloseConnection
}
if($BitLockerKeys -ne $null)
{
$BitLockerKeyIsPresent = "Yes"
}
if($DevicesWithBitLockerKey.IsPresent)
{
if($BitLockerKeyIsPresent -eq "No")
{
Continue
}
}
}
if($InactiveDays -ne "")
{
if(($Device.ApproximateLastSignInDateTime -eq $null))
{
Continue
}
if($LastSigninActivity -le $InactiveDays)
{
continue
}
}
$DeviceOwners = Get-MgBetaDeviceRegisteredOwner -DeviceId $Device.Id -All |Select-Object -ExpandProperty AdditionalProperties
$DeviceUsers = Get-MgBetaDeviceRegisteredUser -DeviceId $Device.Id -All |Select-Object -ExpandProperty AdditionalProperties
$DeviceMemberOf = Get-MgBetaDeviceMemberOf -DeviceId $Device.Id -All |Select-Object -ExpandProperty AdditionalProperties
$Groups = $DeviceMemberOf|Where-Object {$_.'@odata.type' -eq '#microsoft.graph.group'}
$AdministrativeUnits = $DeviceMemberOf|Where-Object{$_.'@odata.type' -eq '#microsoft.graph.administrativeUnit'}
if($Device.TrustType -eq "Workplace")
{
$JoinType = "Azure AD registered"
}
elseif($Device.TrustType -eq "AzureAd")
{
$JoinType = "Azure AD joined"
}
elseif($Device.TrustType -eq "ServerAd")
{
$JoinType = "Hybrid Azure AD joined"
} }
if($Device.ApproximateLastSignInDateTime -ne $null) $BitLockerKeyIsPresent = "No"
{ try {
$LastSigninDateTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($Device.ApproximateLastSignInDateTime,$TimeZone) $BitLockerKeys = Get-MgInformationProtectionBitlockerRecoveryKey -Filter "DeviceId eq '$($_.DeviceId)'" -ErrorAction SilentlyContinue -ErrorVariable Err
$RegistrationDateTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($Device.RegistrationDateTime,$TimeZone) if($Err -ne $null) {
Write-Host $Err -ForegroundColor Red
CloseConnection
}
} }
else catch {
{ Write-Host $_.Exception.Message -ForegroundColor Red
CloseConnection
}
if($BitLockerKeys -ne $null) { $BitLockerKeyIsPresent = "Yes" }
if($DevicesWithBitLockerKey.IsPresent) {
if($BitLockerKeyIsPresent -eq "No") { Continue }
}
if($InactiveDays -ne "") {
if(($_.ApproximateLastSignInDateTime -eq $null)) { Continue }
if($LastSigninActivity -le $InactiveDays) { continue }
}
$SerialNumber = ""
if ($_.IsManaged) {
$ManagedDeviceId = $_.DeviceId
$SerialNumber = ($ManagedDevices | Where-Object { $_.AzureAdDeviceId -eq $ManagedDeviceId }).SerialNumber
}
$DeviceOwners = Get-MgDeviceRegisteredOwner -DeviceId $_.Id -All | Select-Object -ExpandProperty AdditionalProperties
$DeviceUsers = Get-MgDeviceRegisteredUser -DeviceId $_.Id -All | Select-Object -ExpandProperty AdditionalProperties
$DeviceMemberOf = Get-MgDeviceMemberOf -DeviceId $_.Id -All | Select-Object -ExpandProperty AdditionalProperties
$DeviceGroups = $DeviceMemberOf | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.group'}
$AdministrativeUnits = $DeviceMemberOf | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.administrativeUnit'}
if ($_.TrustType -eq "Workplace") { $TrustType = "Entra registered" }
elseif ($_.TrustType -eq "AzureAd") { $TrustType = "Entra joined" }
elseif ($_.TrustType -eq "ServerAd") { $TrustType = "Entra hybrid joined" }
if ($_.ApproximateLastSignInDateTime -ne $null) {
$LastSigninDateTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($_.ApproximateLastSignInDateTime,$TimeZone)
$RegistrationDateTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($_.RegistrationDateTime,$TimeZone)
}
else {
$LastSigninDateTime = "-" $LastSigninDateTime = "-"
$RegistrationDateTime = "-" $RegistrationDateTime = "-"
} }
$ExtensionAttributes = $Device.ExtensionAttributes
$AttributeArray = @() if ($_.ComplianceExpirationDateTime -ne $null) {
$Attributes = $ExtensionAttributes.psobject.properties |Where-Object {$_.Value -ne $null -and $_.Name -ne "AdditionalProperties"}| select Name,Value $ComplianceExpirationDateTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($_.ComplianceExpirationDateTime,$TimeZone)
Foreach($Attribute in $Attributes) } else {
{ $ComplianceExpirationDateTime = "-"
$AttributeArray+=$Attribute.Name+":"+$Attribute.Value
} }
$ExportResult = @{'Name' =$Device.DisplayName
'Enabled' ="$($Device.AccountEnabled)" $ExtensionAttributes = $_.ExtensionAttributes
'Operating System' =$Device.OperatingSystem $AttributeArray = @()
'OS Version' =$Device.OperatingSystemVersion $ExtensionAttributes.psobject.properties | Where-Object {$_.Value -ne $null -and $_.Name -ne "AdditionalProperties"} | select Name, Value | ForEach-Object { $AttributeArray+=$_.Name+":"+$_.Value }
'Join Type' =$JoinType
'Owners' =(@($DeviceOwners.userPrincipalName) -join ',') $Print = 1
'Users' =(@($DeviceUsers.userPrincipalName)-join ',')
'Is Managed' ="$($Device.IsManaged)" # Apply filters based on the param values...
'Management Type' =$Device.ManagementType if ($DeviceStatus -eq "Enabled" -and $_.AccountEnabled -ne $true) { $Print = 0 }
'Is Compliant' ="$($Device.IsCompliant)" elseif ($DeviceStatus -eq "Disabled" -and $_.AccountEnabled -ne $false) { $Print = 0 }
'Registration Date Time' =$RegistrationDateTime
'Last SignIn Date Time' =$LastSigninDateTime if ($ManagementStatus -eq "Managed" -and $_.IsManaged -ne $true) { $Print = 0 }
'InActive Days' =$LastSigninActivity elseif ($ManagementStatus -eq "Unmanaged" -and $_.IsManaged -ne $false) { $Print = 0 }
'Groups' =(@($Groups.displayName) -join ',')
'Administrative Units' =(@($AdministrativeUnits.displayName) -join ',') if ($ComplianceStatus -eq "Compliant" -and $_.IsCompliant -ne $true) { $Print = 0 }
'Device Id' =$Device.DeviceId elseif ($ComplianceStatus -eq "NonCompliant" -and $_.IsCompliant -ne $false) { $Print = 0 }
'Object Id' =$Device.Id
'BitLocker Encrypted' =$BitLockerKeyIsPresent if ($RootedStatus -eq "Rooted" -and $_.IsRooted -ne $true) { $Print = 0 }
'Extension Attributes' =(@($AttributeArray)| Out-String).Trim() elseif ($RootedStatus -eq "NonRooted" -and $_.IsRooted -ne $false) { $Print = 0 }
if (!([string]::IsNullOrEmpty($ProfileType)) -and ($_.ProfileType -notin $ProfileType)) { $Print = 0 }
if (!([string]::IsNullOrEmpty($JoinType)) -and ($TrustType -notin $JoinType)) { $Print = 0 }
if (!([string]::IsNullOrEmpty($DeviceOwnership)) -and ($_.DeviceOwnership -notin $DeviceOwnership)) { $Print = 0 }
if (!([string]::IsNullOrEmpty($Users)) -and ($DeviceUsers.Where({ $Users -contains $_.userPrincipalName }, 'First').Count -eq 0)) { $Print = 0 }
if (!([string]::IsNullOrEmpty($Owners)) -and ($DeviceOwners.Where({ $Owners -contains $_.userPrincipalName }, 'First').Count -eq 0)) { $Print = 0 }
if (!([string]::IsNullOrEmpty($Groups)) -and ($DeviceGroups.Where({ $Groups -contains $_.displayName }, 'First').Count -eq 0)) { $Print = 0 }
$ExportResult = @{'Name' = $_.DisplayName
'Enabled' = "$($_.AccountEnabled)"
'Operating System' = $_.OperatingSystem
'OS Version' = $_.OperatingSystemVersion
'Join Type' = $TrustType
'Is Managed' = "$($_.IsManaged)"
'Owners' = (@($DeviceOwners.userPrincipalName) -join ',')
'Users' = (@($DeviceUsers.userPrincipalName)-join ',')
'Management Type' = $_.ManagementType
'Enrollment Type' = $_.EnrollmentType
'Profile Type' = $_.ProfileType
'Model' = $_.Model
'Serial Number' = $SerialNumber
'Device Ownership' = "$($_.DeviceOwnership)"
'Is Compliant' = "$($_.IsCompliant)"
'Is Rooted' = "$($_.IsRooted)"
'Registration Date Time' = $RegistrationDateTime
'Last SignIn Date Time' = $LastSigninDateTime
'Compliance Expiration Date Time' = $ComplianceExpirationDateTime
'InActive Days' = $LastSigninActivity
'Groups' = (@($DeviceGroups.displayName) -join ',')
'Administrative Units' = (@($AdministrativeUnits.displayName) -join ',')
'Object Id' = $_.Id
'Device Id' = $_.DeviceId
'BitLocker Encrypted' = $BitLockerKeyIsPresent
'Extension Attributes' = (@($AttributeArray) | Out-String).Trim()
} }
$Results = $ExportResult.GetEnumerator() | Where-Object {$_.Value -eq $null -or $_.Value -eq ""} $Results = $ExportResult.GetEnumerator() | Where-Object {$_.Value -eq $null -or $_.Value -eq ""}
Foreach($Result in $Results){ Foreach($Result in $Results) {
$ExportResult[$Result.Name] = "-" $ExportResult[$Result.Name] = "-"
} }
$Report = [PSCustomObject]$ExportResult $Report = [PSCustomObject]$ExportResult
if($Certificate -eq $null) if($Print -eq 1) {
{ $PrintedLogs++
$Report|Select 'Name','Enabled','Operating System','OS Version','Join Type','Owners','Users','Is Managed','Management Type','Is Compliant','Registration Date Time','Last SignIn Date Time','InActive Days','Groups','Administrative Units','Device Id','Object Id','BitLocker Encrypted','Extension Attributes' | Export-csv -path $OutputCsv -NoType -Append $Report | Select 'Name','Enabled','Operating System','OS Version','Model','Serial Number','Join Type','Is Managed','Owners','Users','Management Type','Enrollment Type','Profile Type','Device Ownership','Is Compliant','Is Rooted','Registration Date Time','Last SignIn Date Time','InActive Days','Groups','Administrative Units','Object Id','Device Id','BitLocker Encrypted','Extension Attributes' | Export-csv -path $OutputCsv -NoType -Append
} }
else
{
$Report|Select 'Name','Enabled','Operating System','OS Version','Join Type','Owners','Users','Is Managed','Management Type','Is Compliant','Registration Date Time','Last SignIn Date Time','InActive Days','Groups','Administrative Units','Device Id','Object Id','Extension Attributes' | Export-csv -path $OutputCsv -NoType -Append
}
}
if((Test-Path -Path $OutputCsv) -eq "True")
{
Write-Host `n "The Output file availble in:" -NoNewline -ForegroundColor Yellow; Write-Host "$outputCsv" `n
$prompt = New-Object -ComObject wscript.shell
$UserInput = $prompt.popup("Do you want to open output file?",` 0,"Open Output File",4)
if ($UserInput -eq 6)
{
Invoke-Item "$OutputCsv"
Write-Host "Report generated successfully"
}
}
else
{
Write-Host "No devices found"
} }
#Disconnect the session after execution
Disconnect-MgGraph | Out-Null
Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green
Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n`n Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n
CloseConnection
#Open output file after execution
if((Test-Path -Path $OutputCsv) -eq "True") {
Write-Host " Exported report has $PrintedLogs device records."
Write-Host `n "The Output file availble in: " -NoNewline -ForegroundColor Yellow; Write-Host "$outputCsv" `n
$prompt = New-Object -ComObject wscript.shell
$UserInput = $prompt.popup("Do you want to open output file?",` 0,"Open Output File",4)
if ($UserInput -eq 6) {
Invoke-Item "$OutputCsv"
}
}
else {
Write-Host "No devices found"
}