top of page

Windows Autopilot Offline

  • Writer: Pascal
    Pascal
  • Jan 25, 2025
  • 6 min read

Updated: May 7

Windows Autopilot Offline
Windows Autopilot Offline

Introduction

Windows Autopilot offers organizations an efficient way to configure and prepare new devices for use right out of the box. While offline mode using an Autopilot JSON file may seem ideal for scenarios without internet access, it also comes with some important considerations. In this blog, we’ll dive deeper into using the Windows Autopilot Offline JSON file, its benefits, but most importantly, the pitfalls and best practices to avoid issues. This will help ensure that your deployment runs smoothly and performs optimally.


Windows Autopilot Offline Scenarios: When to Choose an Offline JSON File?

Using a Windows Autopilot Offline JSON file can be an excellent choice in specific situations. It provides a solution for challenges where the standard online methodology is not directly applicable. For example, consider the following scenarios:


  • No agreements with a supplier: When no agreements have been made with a hardware supplier to pre-upload Autopilot Hashes to the tenant.


  • Lack of permissions: For new or existing devices that are not enrolled in Microsoft Intune Autopilot, and the rollout is performed by someone without permissions to upload the hashes online to the tenant.


  • Accelerate the process: To get end users up and running faster by immediately rolling out with the right Autopilot profile, without waiting for a device to be fully enrolled in Microsoft Intune Autopilot.


  • Other specific reasons: Such as deployments in environments with limited internet access or when time-critical rollouts are required.


Why be careful using Windows Autopilot Offline JSON?

A Windows Autopilot Offline JSON file contains valuable information such as the Tenant ID and domain suffix . While information such as the Tenant ID can be obtained from other sources (for example, websites such as WhatIsMyTenantID ), using these JSON files carries specific risks.


The Risks: Unauthorized Access and Validation Challenges

  • Unauthorized device enrollment: If an Autopilot Offline JSON file falls into the wrong hands, there is a risk that unauthorized devices can register as legitimate corporate devices in Microsoft Intune.


  • Difficult to distinguish: A device registered via a stolen or abused JSON file can be difficult to distinguish from a valid device, especially without strict validation processes.


  • Environment security: While users are still required to sign in with valid Microsoft 365 accounts, an attacker with access to the JSON file and credentials could cause additional damage, leading to potentially dangerous “supply chain”-style attacks.


Additional Security Measures for Windows Autopilot Offline JSON

Below we discuss some methods to further secure your Autopilot Offline rollout and minimize risks.


Security via Intune settings

  • Block personal devices in Intune:

    Configure Microsoft Intune to prevent personal devices from enrolling.

    • While this has no direct impact on devices using an Offline Autopilot profile (they will automatically be marked as 'Corporate'), it does prevent unauthorized personal devices from logging in outside of Autopilot scenarios.

    • Setting: In Intune, go to Enrollment restrictions and block personal devices.

      Intune restricts personal devices
      Block Personal Owned Devices

Conditional Access policies

Conditional Access allows you to apply additional layers of security, specifically focused on device registration.

  • Configure register or join devices: Set Conditional Access for the User Actions option Register or join devices and always require Multi-Factor Authentication (MFA).

    • This forces users to go through MFA before registering a device, significantly reducing the risk of unauthorized device registrations.

    • Why is this important? This prevents malicious parties from easily gaining access with just the JSON file and credentials.



  • Blocking Offline Autopilot profiles: Because an Offline Autopilot profile generates a specific name, you can configure Conditional Access to:

    • Completely block the use of this profile if it is not needed.

    • Only accept devices using this profile from trusted locations (e.g. an office with a fixed IP address).



Download Windows Autopilot

The Microsoft website explains in detail how to download the Windows Autopilot JSON files via this link .


Please note : PowerShell 7 is required to download the JSON files correctly. You can install it using the following command with Winget:

winget install --id Microsoft.PowerShell --source winget
Install PowerShell 7 with WinGet
Installeren van PowerShell 7 met WinGet

Next, use the PowerShell script below to install the necessary modules and download the JSON files for Autopilot using PowerShell 7:

Powershell Script

# Setup-AutopilotAppRegistration.ps1

# Run as Global Administrator — creates 'WinAutoPilotRegistration' app in Entra ID

# Generated by Win11ImagingTool

$ErrorActionPreference = 'Stop'


#region — Install Microsoft.Graph module if needed

if (-not (Get-Module Microsoft.Graph.Applications -ListAvailable)) {

Write-Host 'Microsoft.Graph module niet gevonden — installeren...' -ForegroundColor Cyan

Install-Module Microsoft.Graph -Scope CurrentUser -Force -AllowClobber

}

#endregion


Write-Host 'Verbinden met Microsoft Graph...' -ForegroundColor Cyan

Connect-MgGraph -Scopes 'Application.ReadWrite.All', 'Directory.ReadWrite.All'


$displayName = 'WinAutoPilotRegistration'


# Controleer of de app al bestaat

$existing = Get-MgApplication -Filter "displayName eq '$displayName'" -ErrorAction SilentlyContinue

$isNew = $null -eq $existing

if ($existing) {

Write-Host "App '$displayName' bestaat al (ID: $($existing.Id)) — wordt bijgewerkt." -ForegroundColor Yellow

$app = $existing

} else {

Write-Host "App '$displayName' aanmaken..." -ForegroundColor Cyan

$app = New-MgApplication -DisplayName $displayName

Write-Host "App aangemaakt (ID: $($app.Id))." -ForegroundColor Green

}


# ── API permissions instellen ────────────────────────────────────────────────

Write-Host ''

Write-Host 'API permissions opzoeken...' -ForegroundColor Cyan

$permNames = @('DeviceManagementServiceConfig.ReadWrite.All', 'DeviceManagementManagedDevices.ReadWrite.All')

$resourceAccess = @()


try {

# Methode 1: Find-MgGraphPermission (Graph module v2+, meest betrouwbaar)

foreach ($perm in $permNames) {

$found = Find-MgGraphPermission -Name $perm -PermissionType Application -ErrorAction Stop |

Where-Object { $_.Name -eq $perm }

if (-not $found) { throw "Exacte match niet gevonden voor: $perm" }

Write-Host " OK $($found.Name) [$($found.Id)]" -ForegroundColor Green

$resourceAccess += @{ id = $found.Id.ToString(); type = 'Role' }

}

} catch {

Write-Host " Find-MgGraphPermission niet beschikbaar of mislukt: $($_.Exception.Message)" -ForegroundColor Yellow

Write-Host ' Alternatieve methode: service principal AppRoles opvragen...' -ForegroundColor Cyan

$resourceAccess = @()

$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'" -Property AppRoles

foreach ($perm in $permNames) {

$role = $graphSp.AppRoles | Where-Object { $_.Value -eq $perm }

if ($role) {

Write-Host " OK $($role.Value) [$($role.Id)]" -ForegroundColor Green

$resourceAccess += @{ id = $role.Id.ToString(); type = 'Role' }

} else {

Write-Host " NIET GEVONDEN: $perm" -ForegroundColor Red

}

}

}


if ($resourceAccess.Count -ne $permNames.Count) {

Write-Host "FOUT: slechts $($resourceAccess.Count) van $($permNames.Count) permissies gevonden." -ForegroundColor Red

Write-Host 'Controleer je Graph module versie en probeer opnieuw.' -ForegroundColor Red

exit 1

}


# JSON body samenstellen en tonen (voor debug)

$body = @{

requiredResourceAccess = @(@{

resourceAppId = '00000003-0000-0000-c000-000000000000'

resourceAccess = $resourceAccess

})

} | ConvertTo-Json -Depth 10


Write-Host ''

Write-Host 'PATCH body:' -ForegroundColor DarkGray

Write-Host $body -ForegroundColor DarkGray


# PATCH uitvoeren met foutafhandeling

Write-Host ''

Write-Host 'API permissions bijwerken...' -ForegroundColor Cyan

try {

Invoke-MgGraphRequest -Method PATCH `

-Uri "https://graph.microsoft.com/v1.0/applications/$($app.Id)" `

-Body $body -ContentType 'application/json'

Write-Host 'API permissions ingesteld.' -ForegroundColor Green

} catch {

Write-Host "PATCH mislukt: $($_.Exception.Message)" -ForegroundColor Red

if ($_.ErrorDetails.Message) { Write-Host $_.ErrorDetails.Message -ForegroundColor Red }

exit 1

}


# Verificatie: app opnieuw uitlezen en tonen

Start-Sleep -Seconds 2

Write-Host ''

Write-Host 'Verificatie (ingestelde permissions):' -ForegroundColor Cyan

$check = Invoke-MgGraphRequest -Method GET `

-Uri "https://graph.microsoft.com/v1.0/applications/$($app.Id)?`$select=requiredResourceAccess"

$check.requiredResourceAccess | ForEach-Object {

Write-Host " ResourceApp: $($_.resourceAppId)"

$_.resourceAccess | ForEach-Object { Write-Host " id=$($_.id) type=$($_.type)" }

}


# ── Client secret aanmaken ───────────────────────────────────────────────────

Write-Host ''

Write-Host 'Client secret aanmaken...' -ForegroundColor Cyan

$secretName = "Win11ImagingTool-$(Get-Date -f 'yyyyMMdd-HHmm')"

try {

$secret = Add-MgApplicationPassword -ApplicationId $app.Id -PasswordCredential @{

DisplayName = $secretName

EndDateTime = (Get-Date).AddYears(2)

}

Write-Host "Secret aangemaakt: $secretName" -ForegroundColor Green

} catch {

Write-Host "Secret aanmaken mislukt: $($_.Exception.Message)" -ForegroundColor Red

exit 1

}


$tenantId = (Get-MgContext).TenantId

$appId = $app.AppId


Write-Host ''

Write-Host '════════════════════════════════════════' -ForegroundColor Green

Write-Host ' App Registration gereed' -ForegroundColor Green

Write-Host '════════════════════════════════════════' -ForegroundColor Green

Write-Host " Tenant ID : $tenantId"

Write-Host " App ID : $appId"

Write-Host " Secret : $($secret.SecretText)"

Write-Host ''

Write-Host 'LET OP: Sla het secret NU op — het kan niet opnieuw worden opgevraagd!' -ForegroundColor Red

Write-Host "Volgende stap: Entra ID > App Registrations > $displayName > API Permissions > Grant admin consent" -ForegroundColor Yellow

Write-Host ''


$csvPath = Join-Path ([Environment]::GetFolderPath('Desktop')) 'WinAutoPilotRegistration_Credentials.csv'

[PSCustomObject]@{

TenantId = $tenantId

AppId = $appId

Secret = $secret.SecretText

Note = 'Win11ImagingTool > Autopilot > Auto-Registration'

} | Export-Csv -Path $csvPath -NoTypeInformation

Write-Host "Credentials opgeslagen: $csvPath" -ForegroundColor Green


Disconnect-MgGraph

Autopilot Offline Profiles Rights
Rechten benodigd voor het downloaden van Autopilot profielen

Entra Dynamic Groups

When using Windows Autopilot Offline profiles, it is good to know that, as mentioned under the Conditional Access section, a specific Enrollment Profile Name is used. This value can also be used in combination with Entra Dynamic Groups. However, it is important to know that Windows Autopilot Offline will always be named OfflineAutopilotprofile-<CorrellatorID> used. The correlator ID, which corresponds to the ZtdCorrelationId, can be found in the Autopilot JSON file and is associated with the Autopilot profile.


If you are using Entra Dynamic Groups where the query is configured on the Autopilot profile name, be aware that the OfflineAutopilotprofile-<CorrellatorID> value will also need to be included in the query.


Sources


Comments


bottom of page