Skip to content

SSMacAdmin/intune-adaptive-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Intune Adaptive Engine

A PowerShell automation engine that automatically keeps Entra ID group membership in sync with live Intune device inventory — driven by YAML rules, no code required. Run on a schedule, on demand, or triggered instantly by a CI/CD pipeline whenever rules change.


Table of Contents

  1. What is the Intune Adaptive Engine?
  2. Prerequisites
  3. Quick Start — One-Command Deployment
  4. Quick Start — Local Testing
  5. Rule Reference
  6. Engine Configuration Reference
  7. CI/CD Pipeline Integration
  8. Troubleshooting

1. What is the Intune Adaptive Engine?

The Problem It Solves

Entra ID Dynamic Groups are the native tool for rule-based group membership, but they come with three meaningful limitations:

  1. Licensing gate. Dynamic Groups require Entra ID P1 or P2 licensing, which many organisations do not have — or do not want to pay for just to get device targeting.

  2. Wrong data source. Dynamic Group rules evaluate against the Entra device object, not the Intune managed device object. These are two separate entities in Microsoft's data model. Intune-specific properties — complianceState, lastSyncDateTime, model, isEncrypted, deviceCategoryDisplayName — live on the Intune managed device record and are not available to dynamic group rules.

  3. No app-based targeting. There is no native way to build a group based on what applications are actually installed on a device, regardless of how they got there.

Assignment Filters are a different tool entirely. They act as per-policy gates evaluated at device check-in — they do not create or manage groups, cannot be queried for membership, and cannot be used outside of Intune.

The Intune Adaptive Engine bridges these gaps. It reads YAML rules you define, queries Intune device inventory from the Microsoft Graph API, evaluates your conditions, and reconciles Entra ID group membership — without agents, without P1/P2 licensing, and using properties that dynamic groups cannot touch.

How It Works

  YAML Rules          Graph API (Intune)         Entra ID Groups
  ──────────    →    ────────────────────   →    ─────────────────
  conditions         managed device inventory     membership synced
  per-rule           detectedApps inventory       add / remove
  groupId ref        current group members        idempotent diff

Each engine run:

  1. Authenticates to Microsoft Graph (Managed Identity or App Registration)
  2. Fetches all managed device records from Intune across all platforms
  3. Fetches detected application inventory
  4. Loads all enabled YAML rule files from the configured rules folder
  5. For rules that reference detectedApps, pre-fetches per-app device lists (smart platform/name filter avoids unnecessary API calls)
  6. Evaluates each rule's conditions against every device
  7. Diffs the evaluated membership against the current Entra group membership
  8. Issues the minimum set of add/remove API calls to bring the group in sync
  9. Emits a structured run summary

A failure in one rule does not abort the run. Only unrecoverable errors (authentication, config, missing module) stop the engine entirely.

Key Capabilities

Capability Detail
20+ device properties All Intune-native Graph properties including osVersion, complianceState, model, lastSyncDateTime, isEncrypted, and more
detectedApps Target groups based on applications detected in Intune inventory — device or user groups
conditionGroups Mixed AND/OR logic across groups of conditions — combine multiple criteria sets with independent logic per group
-WhatIf mode Engine-level dry run: computes what would change, logs it, touches nothing
dryRun per rule Same as WhatIf but scoped to individual rules in the YAML file
Auto group creation Leave groupId blank — the engine creates the group and writes the ID back
Version-controlled rules Plain YAML files, suitable for Git, pull request review, and CI/CD triggers
Platform filtering Per-rule platform field restricts evaluation to macOS, Windows, iOS, Android, or chromeOS

2. Prerequisites

  • Azure subscription — for the Automation Account and supporting resources

  • Azure DevOps organisation and project — repository for rule files and CI/CD pipeline

  • Microsoft Intune tenant — devices must be enrolled and checking in

  • PowerShell 7.2 or later — required locally and in the Azure Automation runtime

  • powershell-yaml module — the engine auto-installs it if missing, or install manually:

    Install-Module powershell-yaml -Scope CurrentUser
  • Microsoft Graph API permissions (Application, assigned to the Managed Identity):

    Permission Purpose
    DeviceManagementManagedDevices.Read.All Read device inventory from Intune
    Group.ReadWrite.All Read group metadata and create Security groups
    GroupMember.ReadWrite.All Add and remove members from existing groups
  • Administrator access — Contributor on the Azure subscription and Global Administrator (or Privileged Role Administrator) in the Entra tenant to assign application permissions to the Managed Identity


3. Quick Start — One-Command Deployment

For a fresh deployment, the interactive setup script handles everything from zero to a running engine in a single session:

cd setup
.\Invoke-IAEDeployment.ps1

The script walks through eight phases:

Phase What it does
1 Collects all inputs interactively (nothing prompted mid-execution)
2 Creates the resource group and Automation Account with system-assigned Managed Identity
3 Assigns the three required Graph API permissions
4 Guides you through the DevOps manual step, then adds the MI to project Contributors
5 Creates the four Automation Variables (IAE_DevOpsOrgUrl, IAE_DevOpsProject, IAE_DevOpsRepo, IAE_DevOpsBranch)
6 Zips and uploads GraphCollector, RuleEngine, and GroupSync modules; installs powershell-yaml from gallery
7 Imports and publishes the Orchestrator runbook directly to the Automation Account
8 Prints a summary with portal links and next steps

After the script completes — two manual steps remain:

Step 1 — Set the runbook runtime to PowerShell 7.2:

  • Portal → Automation Account → Process Automation → Runbooks → Orchestrator
  • Click Edit → change Runtime version from 5.1 to 7.2 → click Publish

Step 2 — Push files to Azure DevOps:

cd intune-adaptive-engine
git init
git remote add origin https://dev.azure.com/{yourorg}/{yourproject}/_git/{yourrepo}
git add IntuneAE/Orchestrator.ps1 IntuneAE/config/ IntuneAE/rules/
git commit -m "Initial IAE deployment"
git branch -M main
git push origin main

The engine fetches engine.yaml and rule files from your DevOps repo at runtime. Without this step the first run fails with a 404 error.

For a step-by-step manual walkthrough, see docs/DEPLOYMENT.md.


4. Quick Start — Local Testing

Local testing uses an App Registration with a client secret. This is the recommended path for development and validation before deploying to production.

4.1 App Registration Setup

  1. Go to Entra admin centre > App registrations > New registration. Name it and click Register.
  2. Note the Application (client) ID and Directory (tenant) ID.
  3. Go to API permissions > Add > Microsoft Graph > Application permissions. Add the three permissions from the table in Section 2.
  4. Click Grant admin consent.
  5. Go to Certificates & secrets > New client secret. Copy the value immediately.

4.2 Write a Rule

Create IntuneAE/rules/my-first-rule.yaml:

name: Non-Compliant macOS Devices
description: All macOS devices currently non-compliant in Intune
groupId:           # leave blank — engine creates the group on first run
groupType: device
enabled: true
dryRun: true       # start with dry-run
logic: AND
platform: macOS
conditions:
  - property: complianceState
    operator: equals
    value: noncompliant

4.3 Run with -WhatIf

pwsh -File IntuneAE/src/Orchestrator.ps1 `
    -AuthMode     AppRegistration `
    -TenantId     'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' `
    -ClientId     'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' `
    -ClientSecret 'your-client-secret-value' `
    -WhatIf `
    -Verbose

Check the output for [WHAT-IF] lines. When satisfied, set dryRun: false in the rule file and remove -WhatIf from the command.


5. Rule Reference

To deep dive further in the rule references, please see 'IAE-Rule-Cheatsheet' in the Docs folder. Or alternatively use the rule-builder that's in the Docs folder as well.

Minimal rule

name: My Rule
groupId:
conditions:
  - property: operatingSystem
    operator: equals
    value: macOS

All fields

name: Legacy Intel Macs                   # required; used as group display name
description: Human-readable summary       # optional
groupId: xxxxxxxx-xxxx-xxxx-xxxx-xxxx    # populated by engine on first run
groupType: device                          # device (default) | user
enabled: true                              # default: true
dryRun: false                              # default: false
logic: AND                                 # AND (default) | OR
platform: macOS                            # macOS | windows | iOS | android | chromeOS

conditions:
  - property: model
    operator: contains
    value: Intel
  - property: osVersion
    operator: lessThan
    value: "14.0"

Supported operators

Operator Applies to
equals / notEquals Any string or boolean property
contains / notContains String properties
startsWith String properties
lessThan / greaterThan Semantic version (14.3.1) or numeric with optional unit suffix (10 GB)
olderThanDays / newerThanDays DateTime properties (lastSyncDateTime, enrolledDateTime)
isTrue / isFalse Boolean properties (isEncrypted, isSupervised)
in Matches if property value is in a YAML list

detectedApps condition

conditions:
  - property: detectedApps
    operator: contains
    value: Google Chrome
    version:             # optional version sub-condition
      operator: lessThan
      value: "120.0"

conditionGroups (mixed AND/OR)

logic: OR              # combine groups with OR at the top level
conditionGroups:
  - logic: AND         # all conditions in this group must match
    conditions:
      - property: model
        operator: contains
        value: Intel
      - property: osVersion
        operator: lessThan
        value: "14.0"
  - logic: AND
    conditions:
      - property: complianceState
        operator: equals
        value: noncompliant

6. Engine Configuration Reference

IntuneAE/config/engine.yaml:

schedule: 60           # run interval in minutes (informational — set in Automation Schedule)

rulesSource:
  type: local          # only 'local' is supported in this release
  path: rules/         # relative to IntuneAE/ project root

auditLog: tableStorage # tableStorage | logAnalytics

autoGroupCreation:
  enabled: true        # create Entra groups for rules with blank groupId
  groupPrefix: "IAE - "  # prepended to rule name for group display name

7. CI/CD Pipeline Integration

azure-pipelines.yml at the repository root triggers the Orchestrator runbook automatically when rule files or engine.yaml change:

  • Pull request → main: runs in WhatIf mode — rule authors see membership previews before the PR is merged
  • Push to main: runs in live mode — group membership is updated right after merge

Required pipeline variables

Set in Azure DevOps → Pipelines → Edit → Variables:

Variable Description
IAE_ResourceGroup Azure resource group containing the Automation Account
IAE_AutomationAccount Automation Account name
IAE_ServiceConnection Azure DevOps service connection with Automation Operator rights

8. Troubleshooting

"Module not found" errors (GraphCollector / RuleEngine / GroupSync)

Modules were uploaded for the wrong runtime version, or import is still in progress. Go to Automation Account > Modules and confirm all three show Available with Runtime version 7.2. If any show a different version or error, delete and re-upload.

"The property 'Response' cannot be found on this object"

The runbook runtime is PowerShell 5.1 instead of 7.2. Go to Process Automation → Runbooks → Orchestrator → Edit, change Runtime version to 7.2, and click Publish.

403 Forbidden on group member add

A required Graph API permission is missing. Re-run:

.\setup\Grant-GraphPermissions.ps1 -ManagedIdentityObjectId "your-object-id"

groupId written back blank / groups duplicated

The Managed Identity does not yet have Code write access to the DevOps project. Re-run:

.\setup\Add-ManagedIdentityToDevOps.ps1 `
    -ManagedIdentityObjectId "your-object-id" `
    -DevOpsOrgUrl "https://dev.azure.com/yourorg" `
    -DevOpsProject "your-project"

Then manually add groupId: <id> to the affected rule file and delete any duplicate Entra groups.


Repository Structure

intune-adaptive-engine/
├── README.md
├── azure-pipelines.yml
├── LICENSE
├── IntuneAE/
│   ├── Orchestrator.ps1          ← deployable runbook (uploaded by Invoke-IAEDeployment.ps1)
│   ├── src/
│   │   ├── GraphCollector.psm1   ← Graph API authentication and data retrieval
│   │   ├── GraphCollector.psd1
│   │   ├── RuleEngine.psm1       ← YAML rule loader and condition evaluator
│   │   ├── RuleEngine.psd1
│   │   ├── GroupSync.psm1        ← Entra group membership reconciliation
│   │   ├── GroupSync.psd1
│   ├── config/
│   │   └── engine.yaml
│   ├── rules/
│   │   └── examples/
│   │       ├── legacy-intel-macs.yaml
│   │       ├── google-chrome-detected-devices.yaml
│   │       └── slack-detected-users.yaml
├── setup/
│   ├── Invoke-IAEDeployment.ps1  ← single-script onboarding (recommended)
│   ├── Grant-GraphPermissions.ps1
│   └── Add-ManagedIdentityToDevOps.ps1
└── docs/
    ├── DEPLOYMENT.md             ← step-by-step manual deployment guide
    └── rule-builder.html         ← browser-based YAML rule builder

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors