How to get an report with all new enrolled devices

Keeping track of newly enrolled devices in your organisation can be a challenging task when relying solely on the Intune console. Wouldn’t it be awesome to receive a comprehensive report automatically via email? As you know, I love automating things. In this blog post, we’ll explore a simple and efficient way to generate a weekly report for all newly enrolled devices using PowerShell, Azure Automation Runbooks, and Microsoft Graph API. This automated solution will save you time and effort, allowing you to focus on more important tasks in managing your organisations devices. So, let’s dive in and learn how to create this valuable report!

How can you get the Script?

You can find the script in my GitHub repository, befor running the script you have to adapt the from and to email address:

What is the purpose of the script?

This PowerShell script retrieves all devices enrolled in the last 7 day and create a csv out of it and attached this csv to a Email.

How can I schedule the Report?

To schedule the report you can create a Azure Automation Runbook and authenticate via a App registration. The only thing that you have to do is to paste the content of the script into the runbook.

How to create the Automation?

Create a App Registration

  • Search for Azure Active Directory
  • Select App registration
  • Select +New registration
  • Enter a Name and click Register
  • Click API permissions and +Add a permission
  • Select Microsoft Graph
  • Select Application permissions
  • Search for DeviceManagementApps.Read.All & Mail.Send
  • Click Grant admin consent for *** and approve with Yes
  • Select Certificates & secrets and click +New client secret
  • Enter a Description and select a Expires time
  • Click Add
  • Copy and save the Value and the Secret ID

Create Automation Account

  • Search for Automation Accounts
  • Click + Create
  • Select a Subscription and a Resource group
  • Enter and account name and select a Region
  • Click Next
  • Click Next
  • Click Next -> Next -> Create

Create the Runbook

  • Open the Automation Account
  • Navigate to Variables and click + Add a variable
  • Add the Secret ValueTenantId, and the App ID as Variable
  • Select Runbooks
  • Click + Create a runbook
  • Enter a Name
  • Select PowerShell as Runbook type
  • Select 5.1 as Runtime version
  • Click Create
  • Insert the Script from my Github repository
  • Add the sender and receiver email, in the script
  • Save and test the script
  • Click Publish
  • Navigate to Schedules and click + Add a schedule
  • Click Link to schedule and add the created schedule

Script

<#
Version: 1.0
Author: Jannik Reinhard (jannikreinhard.com)
Script: Get-NewEnrolledDevicesReport
Description:
Get Email with new enrolled devices
Release notes:
Version 1.0: Init
#> 


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 = "mail@abc.onmicrosoft.com"
$MailTo = "mail@abc.onmicrosoft.com"

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


$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 previous 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

Conclusion

In conclusion, automating the process of generating and emailing reports for newly enrolled devices can save time and effort, ensuring that you stay up-to-date with device management. By following the steps outlined in this post, you can easily set up an automated report that keeps you informed about device enrollment in your organization. Give it a try and experience the benefits of automation firsthand.”

Feel free to modify the conclusion as you see fit or let me know if you would like further assistance.

5 thoughts on “How to get an report with all new enrolled devices

  1. Hi Jannik, thank you for this!
    I am experiencing this error after testing the Runbook,.
    The csv file does exist but it is empty.

    Completed

    The remote server returned an error: (401) Unauthorized.
    Cannot bind argument to parameter ‘InputObject’ because it is null.
    Object reference not set to an instance of an object.
    System.Management.Automation.MethodInvocationException: Exception calling “ReadAllBytes” with “1” argument(s): “Could not find file ‘C:\Windows\System32\NewEnrolledDevicesReport.csv’.” —> System.IO.FileNotFoundException: Could not find file ‘C:\Windows\System32\NewEnrolledDevicesReport.csv’.
    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
    at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
    at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
    at System.IO.File.InternalReadAllBytes(String path, Boolean checkHost)
    at CallSite.Target(Closure , CallSite , Type , String )
    — End of inner exception stack trace —
    at System.Management.Automation.ExceptionHandlingOps.ConvertToMethodInvocationException(Exception exception, Type typeToThrow, String methodName, Int32 numArgs, MemberInfo memberInfo)
    at CallSite.Target(Closure , CallSite , Type , String )
    at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
    at System.Management.Automation.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
    at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
    Object reference not set to an instance of an object.

    Like

    • Same here: The remote server returned an error: (403) Forbidden.
      System.Management.Automation.MethodInvocationException: Exception calling “ReadAllBytes” with “1” argument(s): “Could not find file ‘C:\Windows\System32\NewEnrolledDevicesReport.csv’.” —> System.IO.FileNotFoundException: Could not find file ‘C:\Windows\System32\NewEnrolledDevicesReport.csv’.
      at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
      at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
      at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
      at System.IO.File.InternalReadAllBytes(String path, Boolean checkHost)
      at CallSite.Target(Closure , CallSite , Type , String )
      — End of inner exception stack trace —
      at System.Management.Automation.ExceptionHandlingOps.ConvertToMethodInvocationException(Exception exception, Type typeToThrow, String methodName, Int32 numArgs, MemberInfo memberInfo)
      at CallSite.Target(Closure , CallSite , Type , String )
      at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
      at System.Management.Automation.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
      at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
      The remote server returned an error: (400) Bad Request.

      Like

Comments are closed.