> For the complete documentation index, see [llms.txt](https://docs.realmjoin.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.realmjoin.com/automation/runbooks/runbook-references/org/devices/cleanup-autopilot-devices_scheduled.md).

# Cleanup Autopilot Devices Scheduled

{% hint style="info" %}
This is a scheduled runbook. It is designed to run on a recurring schedule rather than being triggered for a single object. See [Scheduling](/automation/runbooks/scheduling.md) for details on how to configure runbook schedules.
{% endhint %}

### Description

This scheduled runbook performs regular maintenance of Windows Autopilot device registrations by identifying and removing orphaned devices whose serial numbers no longer match any Intune managed device, and optionally removing never-enrolled Autopilot devices that exceed a configurable age threshold. The runbook operates in WhatIf mode by default for safe reporting, and can optionally send an email summary with CSV and/or Excel (xlsx) attachments listing the devices that would be or were deleted. The report files can also be uploaded to an Azure Storage Account, returning time-limited download links. The ReportFileFormat parameter controls which file formats are generated and delivered (CSV only, CSV & XLSX, or XLSX only). When the CSV attachment exceeds the email size limit and "CSV & XLSX" is selected, the email falls back to the Excel workbook alone.

### Setup regarding email sending

Sending an email report is optional and only happens when a recipient (`EmailTo`) is provided. The sender address is taken from the `RJReport.EmailSender` tenant setting.

This runbook sends emails using the Microsoft Graph API. To send emails via Graph API, you need to configure an existing email address in the runbook customization.

See the [RealmJoin Report Settings documentation](https://docs.realmjoin.com/automation/runbooks/runbook-report-settings) for details.

#### Email branding

The report email honors the optional `RJReport.Branding.*` tenant settings: a custom header image, a custom footer image (public HTTPS URLs, PNG/JPEG/GIF, max. 200 KB each), a custom footer link, and custom accent and text colors (6-digit hex values, e.g. `#0052cc`). When these settings are not configured, the default RealmJoin graphics and colors are used. A branding image that cannot be downloaded or validated, or a color value that is not a valid hex color, never prevents the report email - the corresponding default is used instead.

See the [RealmJoin Report Settings documentation](https://docs.realmjoin.com/automation/runbooks/runbook-report-settings) for setup details.

### Location

Organization → Devices → Cleanup Autopilot Devices (Scheduled)

**Full Runbook name**

rjgit-org\_devices\_cleanup-autopilot-devices\_scheduled

### Details

| Property         | Value                                                                                                             |
| ---------------- | ----------------------------------------------------------------------------------------------------------------- |
| Version          | 1.3.0                                                                                                             |
| Required modules | <p>RealmJoin.RunbookHelper (>= 0.8.9)<br>Microsoft.Graph.Authentication (>= 2.39.0)<br>Az.Accounts (>= 5.5.2)</p> |
| Schedulable      | yes                                                                                                               |

### Notes

Prerequisites:

* The Azure Automation managed identity must hold these Microsoft Graph application permissions: DeviceManagementManagedDevices.Read.All, DeviceManagementServiceConfig.ReadWrite.All, Organization.Read.All, Device.ReadWrite.All (Device.ReadWrite.All only when the "Delete Autopilot and Entra device" mode is used), and Mail.Send (Mail.Send only when email reporting is enabled).
* Grant the permissions before the first scheduled run.

Warning - deletion is irreversible:

* Removing an Autopilot device identity permanently deletes it from Windows Autopilot.
* The physical device cannot re-enter Autopilot until its hardware hash is re-uploaded.
* There is no soft-delete or recycle bin for Autopilot records.
* Deleting the Entra (Azure AD) device object is likewise permanent; only do so for records that are genuinely dead (the device will never enroll again).

Recommended first-run procedure:

* Run with Delete mode = "WhatIf (report only)" (the default) and review the output or emailed CSV.
* Confirm the identified devices are genuinely orphaned or never-enrolled.
* Switch to a deletion mode only after the candidate list has been reviewed.

Parameter interactions:

* DeleteMode defaults to "WhatIf (report only)"; no deletions occur in that mode.
* "Delete Autopilot device" removes only the Autopilot identity. "Delete Autopilot and Entra device" additionally removes the matching Entra (Azure AD) device object, which would otherwise be left behind as a stale/dead record once the Autopilot identity is gone.
* CleanupOrphanedDevices and CleanupNeverEnrolledDevices are independent; either or both can be enabled. NeverEnrolledAgeDays applies only to the never-enrolled check.
* GroupTagFilter, ManufacturerFilter and ModelFilter are all optional; leave a filter empty to evaluate all values for that dimension. When more than one filter is set they are combined with AND - a device must match every populated filter to remain in scope. GroupTagFilter matches the group tag exactly (case-insensitive); ManufacturerFilter and ModelFilter match as case-insensitive substrings, so "Dell" matches "Dell Inc." and "Surface" matches "Surface Laptop 3".
* ExcludeSerialNumbers is applied after the AND filters as an exclusion: any device whose serial number is in the list (exact, case-insensitive) is removed from scope regardless of the other filters. Leave empty to exclude nothing.

### Permissions

#### Application permissions

* **Type**: Microsoft Graph
  * Device.ReadWrite.All
    * *Looks up Entra devices by deviceId and deletes them in 'Delete Autopilot and Entra device' mode*
  * DeviceManagementManagedDevices.Read.All
    * *Reads Intune managed Windows devices to find Autopilot serials no longer present in Intune*
  * DeviceManagementServiceConfig.ReadWrite.All
    * *Lists windowsAutopilotDeviceIdentities and deletes stale Autopilot identities*
  * Mail.Send *(optional — feature: Email report)*
    * *Sends the cleanup report email via Send-RjReportEmail when EmailTo is configured*
  * Organization.Read.All
    * *Reads /organization to show the tenant display name in the report output and email*

### Parameters

#### DeleteMode

Controls what the runbook does with the identified cleanup candidates. "WhatIf (report only)" performs no deletion and only reports the candidates (default, safe). "Delete Autopilot device" removes the Autopilot device identities. "Delete Autopilot and Entra device" removes the Autopilot identities and the matching Entra (Azure AD) device objects, which would otherwise remain as stale records.

| Property            | Value                |
| ------------------- | -------------------- |
| Required            | false                |
| Default Value       | WhatIf (report only) |
| Type                | String               |
| Portal display name | Deletion mode        |

#### GroupTagFilter

Comma-separated Autopilot group tags to limit the cleanup scope. Matched exactly (case-insensitive). Leave empty to process all Autopilot devices regardless of group tag.

| Property            | Value                                                                         |
| ------------------- | ----------------------------------------------------------------------------- |
| Required            | false                                                                         |
| Default Value       |                                                                               |
| Type                | String                                                                        |
| Portal display name | Autopilot Group Tag Filter (comma-separated exact match, leave empty for all) |

#### ManufacturerFilter

Comma-separated device manufacturers to limit the cleanup scope. Matched as case-insensitive substrings, so "Dell" matches "Dell Inc.". Combined with the other filters using AND. Leave empty to process all manufacturers.

| Property            | Value                                                                       |
| ------------------- | --------------------------------------------------------------------------- |
| Required            | false                                                                       |
| Default Value       |                                                                             |
| Type                | String                                                                      |
| Portal display name | Manufacturer Filter (comma-separated, substring match, leave empty for all) |

#### ModelFilter

Comma-separated device models to limit the cleanup scope. Matched as case-insensitive substrings, so "Surface" matches "Surface Laptop 3". Combined with the other filters using AND. Leave empty to process all models.

| Property            | Value                                                                |
| ------------------- | -------------------------------------------------------------------- |
| Required            | false                                                                |
| Default Value       |                                                                      |
| Type                | String                                                               |
| Portal display name | Model Filter (comma-separated, substring match, leave empty for all) |

#### ExcludeSerialNumbers

Comma-separated serial numbers to exclude from the cleanup. Matched exactly (case-insensitive). Any device whose serial number is in this list is removed from scope regardless of the other filters. Leave empty to exclude nothing.

| Property            | Value                                                                      |
| ------------------- | -------------------------------------------------------------------------- |
| Required            | false                                                                      |
| Default Value       |                                                                            |
| Type                | String                                                                     |
| Portal display name | Exclude Serial Numbers (comma-separated exact match, leave empty for none) |

#### CleanupOrphanedDevices

When enabled, removes Autopilot devices that have contacted Intune in the past but whose serial number is no longer found among Intune managed devices (the managed device record was deleted).

| Property            | Value                               |
| ------------------- | ----------------------------------- |
| Required            | false                               |
| Default Value       | True                                |
| Type                | Boolean                             |
| Portal display name | Clean up orphaned Autopilot devices |

#### OrphanedLastContactedDays

Age threshold in days for orphaned devices. An Autopilot device is only treated as orphaned when its last contact with Intune was more than this number of days ago and its serial is no longer present in Intune. This prevents removing devices that contacted Intune recently.

| Property            | Value                                           |
| ------------------- | ----------------------------------------------- |
| Required            | false                                           |
| Default Value       | 90                                              |
| Type                | Int32                                           |
| Portal display name | Orphaned device last-contacted threshold (days) |

#### CleanupNeverEnrolledDevices

When enabled, removes never-enrolled Autopilot devices (devices that never contacted Intune).

| Property            | Value                                     |
| ------------------- | ----------------------------------------- |
| Required            | false                                     |
| Default Value       | False                                     |
| Type                | Boolean                                   |
| Portal display name | Clean up never-enrolled Autopilot devices |

**Portal options**

| Portal option                            | Value |
| ---------------------------------------- | ----- |
| Yes - remove aged never-enrolled devices |       |
| No                                       |       |

#### NeverEnrolledAgeDays

Age threshold in days for never-enrolled devices. Measured on the Device creation date.

| Property            | Value                                      |
| ------------------- | ------------------------------------------ |
| Required            | false                                      |
| Default Value       | 90                                         |
| Type                | Int32                                      |
| Portal display name | Never-enrolled device age threshold (days) |
| Hidden in portal    | yes (preset via runbook customization)     |

#### EmailTo

Optional email recipient address for the cleanup summary report. Leave empty to only write results to the runbook log.

| Property            | Value                                          |
| ------------------- | ---------------------------------------------- |
| Required            | false                                          |
| Default Value       |                                                |
| Type                | String                                         |
| Portal display name | Email recipient for cleanup summary (optional) |

#### EmailFrom

The sender email address for the summary report. This is configured via Runbook Customizations.

| Property         | Value                                  |
| ---------------- | -------------------------------------- |
| Required         | false                                  |
| Default Value    |                                        |
| Type             | String                                 |
| Hidden in portal | yes (preset via runbook customization) |

#### BrandingHeaderImageUrl

Optional public HTTPS URL of a custom header image (PNG/JPEG/GIF, max. 200 KB) for the report email. Sourced from the RJReport.Branding.HeaderImageUrl tenant setting. When empty, the default RealmJoin header graphic is used.

| Property         | Value                                  |
| ---------------- | -------------------------------------- |
| Required         | false                                  |
| Default Value    |                                        |
| Type             | String                                 |
| Hidden in portal | yes (preset via runbook customization) |

#### BrandingFooterImageUrl

Optional public HTTPS URL of a custom footer image (PNG/JPEG/GIF, max. 200 KB) for the report email. Sourced from the RJReport.Branding.FooterImageUrl tenant setting. When empty, the default RealmJoin footer graphic is used.

| Property         | Value                                  |
| ---------------- | -------------------------------------- |
| Required         | false                                  |
| Default Value    |                                        |
| Type             | String                                 |
| Hidden in portal | yes (preset via runbook customization) |

#### BrandingFooterLink

Optional URL the footer image links to. Sourced from the RJReport.Branding.FooterLink tenant setting. When empty, the default link (<https://www.realmjoin.com>) is used.

| Property         | Value                                  |
| ---------------- | -------------------------------------- |
| Required         | false                                  |
| Default Value    |                                        |
| Type             | String                                 |
| Hidden in portal | yes (preset via runbook customization) |

#### BrandingAccentColor

| Property         | Value                                  |
| ---------------- | -------------------------------------- |
| Required         | false                                  |
| Default Value    |                                        |
| Type             | String                                 |
| Hidden in portal | yes (preset via runbook customization) |

#### BrandingTextColor

| Property         | Value                                  |
| ---------------- | -------------------------------------- |
| Required         | false                                  |
| Default Value    |                                        |
| Type             | String                                 |
| Hidden in portal | yes (preset via runbook customization) |

#### ReportFileFormat

Controls which report file formats are generated and delivered: "CSV only", "CSV & XLSX" (default) or "XLSX only".

| Property            | Value              |
| ------------------- | ------------------ |
| Required            | false              |
| Default Value       | CSV & XLSX         |
| Type                | String             |
| Portal display name | Report file format |

**Portal options**

| Portal option | Value |
| ------------- | ----- |
| CSV & XLSX    |       |
| CSV only      |       |
| XLSX only     |       |

#### CreateDownloadLink

If enabled, the report files are uploaded to an Azure Storage Account and time-limited download links are returned. Disabled by default.

| Property            | Value                                                   |
| ------------------- | ------------------------------------------------------- |
| Required            | false                                                   |
| Default Value       | False                                                   |
| Type                | Boolean                                                 |
| Portal display name | Create a file download link (upload report to storage)? |

**Portal options**

| Portal option                                  | Value |
| ---------------------------------------------- | ----- |
| Yes - upload report and return a download link | true  |
| No - do not create a download link             | false |

#### ContainerName

Storage container name used for the upload. Configured per runbook (not a global RJReport setting).

| Property         | Value                                  |
| ---------------- | -------------------------------------- |
| Required         | false                                  |
| Default Value    | cleanup-autopilot-devices              |
| Type             | String                                 |
| Hidden in portal | yes (preset via runbook customization) |

#### ResourceGroupName

Resource group that contains the storage account. Sourced from the RJReport tenant settings.

| Property         | Value                                  |
| ---------------- | -------------------------------------- |
| Required         | false                                  |
| Default Value    |                                        |
| Type             | String                                 |
| Hidden in portal | yes (preset via runbook customization) |

#### StorageAccountName

Storage account name used for the upload. Sourced from the RJReport tenant settings.

| Property         | Value                                  |
| ---------------- | -------------------------------------- |
| Required         | false                                  |
| Default Value    |                                        |
| Type             | String                                 |
| Hidden in portal | yes (preset via runbook customization) |

#### LinkExpiryDays

Number of days until the generated download link expires. Sourced from the RJReport tenant settings.

| Property         | Value                                  |
| ---------------- | -------------------------------------- |
| Required         | false                                  |
| Default Value    | 6                                      |
| Type             | Int32                                  |
| Hidden in portal | yes (preset via runbook customization) |

[Back to Runbook Reference overview](/automation/runbooks/runbook-references.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.realmjoin.com/automation/runbooks/runbook-references/org/devices/cleanup-autopilot-devices_scheduled.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
