I don’t know if this is an exhaustive way of doing this, but here goes.
Fiddling around with Conditional Access policies at work, and was wondering how best to exclude Teams phones from being targetted. Came across this Microsoft article on excluding such devices from Conditional Access policies using device filters. Another one too says the same.
Here’s a screenshot from the second post on what needs doing.
How do I find the various manufacturers in my environment? Graph to the rescue!
The output of Get-MgBetaDevice
(I didn’t try the non-beta one, that will probably do the trick too) contains the OS. So I do the following to begin with:
1 |
$allAndroidDevices = Get-MgBetaDevice -Filter "OperatingSystem eq 'Android'" -All |
Then I can find the various Manufacturers using:
1 |
$allAndroidDevices | Sort-Object Manufacturer | Group-Object Manufacturer | ft Name,Count |
In my environment I see:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$ $allAndroidDevices | Sort-Object Manufacturer | Group-Object Manufacturer | ft Name,Count Name Count ---- ----- 1018 Amazon 3 Audiocodes 28 Blackview 2 Crestron 10 Google 22 HMD Global 2 Lenovo 3 Logitech 67 Microsoft 2 motorola 19 OnePlus 4 OPPO 4 Poly 113 Polycom 47 realme 1 rockchip 1 samsung 119 TECNO MOBILE LIMITED 1 vivo 8 Yealink 88 |
What’s all those blank ones? Do they have a model?
1 |
$allAndroidDevices | ?{ $_.Manufacturer.Length -eq 0 -and $_.Model.Length -ne 0 } |
No output. So I can focus on just Manufacturer, for now, in my case.
Let’s get a sample device of each so I can confirm if they are mobile phones or Teams phones.
1 2 3 |
$allManufacturers = ($allAndroidDevices | Select-Object -Unique Manufacturer).Manufacturer $selectedDevices = foreach ($manufacturer in $allManufacturers) { Get-MgDevice -Filter "Manufacturer eq '$manufacturer'" -Top 2 } |
Whoa, that doesn’t work!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Get-MgDevice_List: Unsupported or invalid query filter clause specified for property 'manufacturer' of resource 'Device'. Status: 400 (BadRequest) ErrorCode: Request_UnsupportedQuery Date: 2024-04-18T17:11:59 Headers: Cache-Control : no-cache Vary : Accept-Encoding Strict-Transport-Security : max-age=31536000 request-id : 54e171d9-5455-490a-a122-bee882261039 client-request-id : b3e914b9-2e52-4d41-ab06-d61fc73b46b1 x-ms-ags-diagnostic : {"ServerInfo":{"DataCenter":"UK South","Slice":"E","Ring":"3","ScaleUnit":"002","RoleInstance":"YT2PE1F00000167"}} x-ms-resource-unit : 1 Date : Thu, 18 Apr 2024 17:11:58 GM |
Ok, another way.
1 2 3 |
$selectedDevices = foreach ($manufacturer in $allManufacturers) { $allAndroidDevices | Where-Object { $_.Manufacturer -eq "$manufacturer" } | Select-Object -First 15 } $selectedDevices | ft Manufacturer,Model,DisplayName |
This creates an array containing up to 15 devices of each manufacturer. And then I get the manufacturer, model, and display name of these so I can take a look.
Now I can go through the list and identify the ones that look like Teams phones.
1 2 3 4 5 6 7 8 |
Polycom Logitech Audiocodes AudioCodes Crestron rockchip Yealink Poly |
What else can I do?
I know that all the users in my environment with a Teams phone must have the Microsoft 365 Phone System plan. This is a part of the E5 license, or can be purchased separately as the Microsoft Teams Shared Devices license. So if I get a list of everyone with this plan, and then find the devices they have, I can tackle this problem that way too.
Thankfully I already have some experience working with licensing.
The following code will create two hash tables containing the license GUIDs and plan GUIDs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# From https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference $licenseCsvURL = 'https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv' $licenseHashTable = @{} (Invoke-WebRequest -Uri $licenseCsvURL).ToString() -replace "\?\?\?","" | ConvertFrom-Csv | ForEach-Object { $licenseHashTable[$_.Product_Display_Name] = @{ "SkuId" = $_.GUID "SkuPartNumber" = $_.String_Id "DisplayName" = $_.Product_Display_Name } } $planHashTable = @{} (Invoke-WebRequest -Uri $licenseCsvURL).ToString() -replace "\?\?\?","" | ConvertFrom-Csv | ForEach-Object { $planHashTable[$_.Service_Plans_Included_Friendly_Names] = @{ "PlanId" = $_.Service_Plan_Id "DisplayName" = $_.Service_Plans_Included_Friendly_Names "Skus" = if ($planHashTable[$_.Service_Plans_Included_Friendly_Names].Skus.Length -eq 0 ) { @($_.Product_Display_Name) } else { @($planHashTable[$_.Service_Plans_Included_Friendly_Names].Skus) + @($_.Product_Display_Name) } } } |
Based on that, finding the user with the Microsoft 365 Phone System plan is straight forward.
1 2 3 |
$phoneSystemPlanId = $planHashTable["Microsoft 365 Phone System"].PlanId $allPhoneUsers = Get-MgUser -All -Filter "assignedPlans/any(a:a/servicePlanId eq $phoneSystemPlanId)" -ConsistencyLevel Eventual -CountVariable userCount |
Can I then get the devices of each of these users, and look for the Android ones that signed in in the last 10 days (no point looking at stale entries)?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$allAndroidDevices = foreach ($phoneUser in $allPhoneUsers) { $userDevices = @() $userDevices = @(Get-MgUserOwnedDevice -UserId $phoneUser.UserPrincipalName) if ($userDevices.Count -ne 0) { foreach ($userDevice in $userDevices) { $deviceObj = $null $deviceObj = Get-MgBetaDevice -DeviceId $userDevice.Id if ($deviceObj.OperatingSystem -eq 'Android' -and $deviceObj.ApproximateLastSignInDateTime -gt (Get-Date).AddDays(-10)) { [PSCustomObject]@{ "User" = $phoneUser.UserPrincipalName "DeviceManufacturer" = $deviceObj.Manufacturer "DeviceModel" = $deviceObj.Model } } } } } |
Yup, that works. I can then do the following:
1 |
$allAndroidDevices | Select-Object -Unique DeviceManufacturer |
Which gave me the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
DeviceManufacturer ------------------ Poly Crestron Polycom Yealink samsung vivo Blackview Audiocodes Google TECNO MOBILE LIMITED AudioCodes |
Of course, this way will show any regular Android mobile phones of users with a Teams Phone System plan, that’s why I’ve got a few other manufacturers in there. Ferret out the mobile phones manually, and we are left with:
1 2 3 4 5 6 |
Poly Crestron Polycom Yealink Audiocodes AudioCodes |
Compare that to what I got above (reproduced below again), and ‘rockchip’ and ‘Logitech’ are missing.
1 2 3 4 5 6 7 8 |
Polycom Logitech Audiocodes AudioCodes Crestron rockchip Yealink Poly |
Turns out the ‘rockchip’ devices last checked-in in 2021, that’s why! :)
And Logitech is a Teams meeting room device. I am not concerned about Teams meeting room devices as they have separate accounts and don’t belong to users, so I can ignore that too. But something to keep in mind for anyone looking to both Teams meeting rooms and Teams phones (in which case you should also target the Teams meeting room license).
Update (17th Sept 2024): The code was updated to add -replace"\?\?\?",""
because it looks like the Product_Display_Name
column appears as ???Product_Display_Name
in the downloaded file.