New devices enrolled in Intune

I have a script generating a report of all newly enrolled devices in Intune. The script pulls the data using API permissions in a registered application in entra. It’s generating a (401) unauthorized error accessing the graph URI and am global admin of the tenant.

When testing logging into the MS.Graph outside the script, I get the same 401 error:

PS C:\Windows\system32> Connect-MSGraph -AdminConsent

UPN                           TenantId
---                           --------
adminxxx@xxxx.com 7bjd3a68-6d43-4699-a397-0287245516f6


PS C:\Windows\system32> Invoke-RestMethod

cmdlet Invoke-RestMethod at command pipeline position 1
Supply values for the following parameters:
Uri: https://graph.microsoft.com/v1.0/deviceManagement/managedDevices
Invoke-RestMethod : The remote server returned an error: (401) Unauthorized.
At line:1 char:1
+ Invoke-RestMethod
+ ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebExc
   eption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

What’s required to gain access to the graph URI

The command you’re running

Invoke-RestMethod https://graph.microsoft.com/v1.0/deviceManagement/managedDevices

Has no clue about you connecting to graph. How would it? You aren’t providing it with any type of access token or anything, so a 401 is expected.

I would take a step back and detail what module you’re using (which module contains the Connect-MSGraph cmdlet.) Then I would look in that module for a cmdlet that retrieves devices. The module should offer cmdlets that can utilize the connection you established with Connect-MsGraph.

Do I need to use the Connect-MSGraph cmdlet when it’s in a registered application in the tenant? I didn’t think so if I registered and granted apps the API permissions. Here’s the entire script:

function Get-AuthHeader{
    param (
        [parameter(Mandatory=$true)]$tenantId,
        [parameter(Mandatory=$true)]$clientId,
        [parameter(Mandatory=$true)]$clientSecret
       )
    
    $authBody=@{
        client_id=$clientId
        client_secret=$clientSecret
        scope="https://graph.microsoft.com/.default"
        grant_type="client_credentials"
    }

    $uri="https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
    $accessToken=Invoke-WebRequest -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $authBody -Method Post -ErrorAction Stop -UseBasicParsing
    $accessToken=$accessToken.content | ConvertFrom-Json

    $authHeader = @{
        'Content-Type'='application/json'
        'Authorization'="Bearer " + $accessToken.access_token
        'ExpiresOn'=$accessToken.expires_in
    }
    
    return $authHeader
}

########################################### Start ###############################################

# Variables
$MailSender = "intune@XXXX.onmicrosoft.com"
$MailTo = "XXXXX@XXXX.com"

# Automation Secrets
$tenantId = Get-AutomationVariable -Name 'tenantId'
$clientId = Get-AutomationVariable -Name 'clientId'
$clientSecret = Get-AutomationVariable -Name 'clientSecret'


$global:authToken = Get-AuthHeader -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret

# Define the time range
$endDate = Get-Date
$startDate = $endDate.AddDays(-7)
$filter = "enrolledDateTime gt $($startDate.ToString("yyyy-MM-ddTHH:mm:ssZ"))&enrolledDateTime le $($endDate.ToString("yyyy-MM-ddTHH:mm:ssZ"))"

# Query Intune devices enrolled in the past week
$graphApiUrl = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices"
$graphApiQuery = "?`$filter=$filter&`$select=id,deviceName,operatingSystem,enrolledDateTime,userPrincipalName,model"
$uri = $graphApiUrl + $graphApiQuery
$response = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:authToken

# Generate CSV report
$reportPath = "NewEnrolledDevicesReport.csv"
$response.value | Select-Object id,deviceName,operatingSystem,enrolledDateTime,userPrincipalName,model | Export-Csv -Path $reportPath -NoTypeInformation
$csv = [Convert]::ToBase64String([IO.File]::ReadAllBytes(".\$reportPath"))

#Send Mail    
$URLsend = "https://graph.microsoft.com/v1.0/users/$MailSender/sendMail"
$BodyJsonsend = @"
{
    "message": {
      "subject": "New enrolled devices",
      "body": {
        "contentType": "Text",
        "content": "Dear Admin, this Mail contains the enrolled devices from the last 7 days"
      },
      "toRecipients": [
        {
          "emailAddress": {
            "address": "$MailTo"
          }
        }
      ],
      "attachments": [
        {
          "@odata.type": "#microsoft.graph.fileAttachment",
          "name": "newEnrolledDevicesReport.csv",
          "contentType": "text/plain",
          "contentBytes": "$csv"
        }
      ]
    }
  }
"@


Invoke-RestMethod -Method POST -Uri $URLsend -Headers $global:authToken -Body $BodyJsonsend

No I wouldn’t think so. I was going off the only information I had which is the 2 lines you provided originally. This script is much, much different than you example.

You can. The powershell SDK let’s you connect with delegated permissions (running commands on behalf of as a signed in user) or with a client secret/cert. Using a cert is better and more secure IMO. There’s some docs online for client secret, that may be better than my code below, but if you can, just use cert based auth because it’s super easy and you can self a signed cert and set it up really quickly. I did demo client secret auth a few weeks ago and it was something like the code below., though I think I actually did a secure string (PSCRED object) and converting itbut you probably just need to play with it some.:

$TenantID = 'TenantID'
$ClientID = 'AppID/ClientID'
$ClientSecret = 'Secret'
 
$Body =  @{
    Grant_Type    = 'client_credentials'
    Scope         = "https://graph.microsoft.com/.default"
    Client_Id     = $ClientID
    Client_Secret = $ClientSecret
}
 
$Invoke= Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" -Method post -Body $body
$Token = $Invoke.access_token
 
Connect-MgGraph -AccessToken $Token

By using the Connect-MgGraph cmdlet, once you connect you’ll be able to run all the powershell wrapped graph commands, so you can do what your script is doing manually. I’d make sure that you connecting using app-based perms and the app actually has the proper permissions needed. If you use delegated, it only will have the permissions of the signed in user.

Lastly, with cert based auth, you don’t have to worry about grabbing a token you literally just install the cert, and use it to auth (it really is as simple as creating a self signed cert, exporting it without the public key and uploading it to the app). However, client secret works too.