Getting stale devices using PowerShell MS graph
I have been getting started with the Microsoft graph and I thought I would start with a simple task like getting devices which haven’t connected to Entra ID in a few months.
Connect to Microsoft graph
To connect to graph I used a certificate. The certificate with the private key is password protected so I would need to provide a password. I usually remove the certificate from my certificate store and have it saved on disk. It is recommended to use certificates over secrets, mainly because the private key of the certificate never leaves the device, in other words the secret is never transmitted. More info here: Secret key or certificate?
param (
[Parameter()]
[string]
$CertPath,
[Parameter()]
[string]
$ClientId,
[Parameter()]
[string]
$TenantId
)
$password = read-host "Enter password" -AsSecureString
$splat = @{
FilePath = $CertPath
password = $password
}
$cert = Get-PfxCertificate @splat
$splat = @{
ClientId = $ClientId
TenantId = $TenantId
Certificate = $cert
}
Connect-MgGraph @splat
Securely storing the password
Instead of having to type in the password at the prompt you could save the password in a PowerShell secret vault. Then to unlock the vault you will need to use a password, you can save the password in an CliXml file. There is a good tutorial to get started with Secret Management in this PDQ blog post: How to manage PowerShell secrets with the SecretsManagement module.
I would therefore replace the line with $password = read-host "Enter password" -AsSecureString
with the commands below
$passwordPath = Join-Path (Split-Path $profile) SecretStore.vault.credential
$password = Import-CliXml -Path $passwordPath
Unlock-SecretStore -Password $password
$Password = Get-Secret -Name PowerShellMSGraph
I suppose you could just have the certificate password in the Clixml, but if you had other secrets to store then this method is a lot more manageable.
Getting stale devices
Additionally to getting stale devices, I wanted to see the owner of the device. I used the the device id with the Get-MgDeviceRegisteredOwner
command to get the user id. The I would use that id with the Get-MgUser
command to get user info, like the UPN. If no user existed for that device you would get an error with Get-MGUser as the parameter would be blank, therefore I added a try catch block to deal with that. In the catch I could have dealt with that device differently to others, but in this case I just wanted to see the error.
param (
[Parameter()]
[int32]
$days = 365
)
$dt = (Get-Date).AddDays(-($days))
$getMgDeviceSplat = @{
Property = 'ApproximateLastSignInDateTime', 'DisplayName', 'DeviceId', 'id'
}
$Devices = Get-MgDevice @getMgDeviceSplat |
Where-Object -FilterScript { $_.ApproximateLastSignInDateTime -le $dt }
foreach ($Device in $Devices) {
$obj = [PSCustomObject]@{
DisplayName = $device.DisplayName
DeviceId = $device.DeviceId
LastLogon = $device.ApproximateLastSignInDateTime
Owner = ""
}
try {
Write-Verbose "Getting owner for Device: [$($device.displayname)]"
$id = Get-MgDeviceRegisteredOwner -DeviceId $device.id |
Select-Object -ExpandProperty id
Write-Verbose "Getting User account with: [$id]"
$owner = Get-MgUser -UserId $id | Select-Object -ExpandProperty UserPrincipalName
# Add owner to the PS custom object
$obj.owner = $owner
}
catch {
Write-Warning "[$($device.displayname)] $($_.exception.message)"
} # try/catch
Write-Output $obj
} # foreach
You can then save this in a script file. for example, StaleDevices.ps1 and use it to out put data to a file using a
.\StaleDevices.ps1 -days 90 | Out-File -FilePath c:\temp\staledevices.csv
In closing
I still haven’t decided what to do with stale devices, I could disable the ones that are a few months old and then delete the ones that are a year old. One thing to consider is that if bitlocker recovery keys are backed up to Entra then deleting the computer from Entra ID would mean losing the recovery key.