A local tool that fetches all Microsoft Intune policies from a tenant, automatically generates meaningful descriptions using Azure OpenAI, and analyzes policy conflicts by detecting overlapping settings across policies.
- Overview
- Architecture
- Supported Policy Types
- Prerequisites
- Installation
- Configuration
- Running the Application
- Usage
- API Reference
- Project Structure
- Troubleshooting
- Security Notes
Many Intune policies have no or inadequate descriptions, and settings are often configured in multiple policies without visibility into overlaps. This tool solves both problems:
- Fetch - Loads all policies from the Intune tenant via Microsoft Graph API (Beta)
- Analyze - Sends the complete policy configuration (JSON) to Azure OpenAI
- Generate - Creates structured, human-readable descriptions per policy
- Compare - Shows a before/after view (original vs. generated description)
- Write Back - Pushes selected descriptions directly back to Intune
- Conflict Analysis - Detects settings configured in multiple policies and highlights conflicts
+------------------+ +------------------+ +---------------------+
| | HTTP | | HTTPS | |
| React Frontend | ------> | FastAPI Backend | ------> | Microsoft Graph |
| (Vite, Port | | (uvicorn, Port | | API (Beta) |
| 5173) | | 8099) | | |
| | | | +---------------------+
+------------------+ | |
| | HTTPS +---------------------+
| | ------> | Azure OpenAI |
| | | (GPT-5-mini) |
+------------------+ +---------------------+
|
| MSAL
v
+------------------+
| Azure AD / |
| Entra ID |
+------------------+
- Single-page application with glassmorphism design
- Communicates with backend via
/api/*proxy - Components:
PolicyList- Table of all policies with search, filter, multi-selectConflictAnalysis- Detects and displays overlapping settings across policiesSettingsPanel- LLM settings (system prompt, template, custom instructions)GenerationProgress- Progress bar during generationDescriptionResult- Before/after comparison with Intune update functionality
- REST API with asynchronous processing
- MSAL-based authentication (interactive browser login)
- Microsoft Graph API client with automatic token renewal and pagination
- Azure OpenAI integration for description generation
- Policy conflict analysis engine
- LLM settings persisted locally in
llm_settings.json
| Policy Type | Graph API Endpoint | Read | Write |
|---|---|---|---|
| Device Configuration | /deviceManagement/deviceConfigurations |
Yes | Yes |
| Settings Catalog | /deviceManagement/configurationPolicies |
Yes (incl. settings) | Yes |
| Compliance Policy | /deviceManagement/deviceCompliancePolicies |
Yes | Yes |
| App Protection | /deviceAppManagement/managedAppPolicies |
Yes | Yes |
| Conditional Access | /identity/conditionalAccess/policies |
Yes | No |
| Endpoint Security | /deviceManagement/intents |
Yes (incl. categories) | Yes |
| App Configuration | /deviceAppManagement/mobileAppConfigurations |
Yes | Yes |
| Autopilot | /deviceManagement/windowsAutopilotDeploymentProfiles |
Yes | No |
| Device Enrollment | /deviceManagement/deviceEnrollmentConfigurations |
Yes | No |
| Remediation Script | /deviceManagement/deviceHealthScripts |
Yes | No |
| PowerShell Script | /deviceManagement/deviceManagementScripts |
Yes | No |
| Group Policy (ADMX) | /deviceManagement/groupPolicyConfigurations |
Yes (incl. definition values) | Yes |
Note: "Write" means the description value can be updated via the Graph API using PATCH. Not all policy types support this.
- Python 3.11 - 3.13 (Python 3.14 is currently not supported due to pydantic-core build issues)
- Node.js 18+ with npm
- Azure CLI (
az) installed and available in PATH - Web browser for interactive Microsoft login
- Azure tenant with Microsoft Intune license
- Azure OpenAI Service with a deployment (e.g.
gpt-5-mini)- Endpoint URL
- API Key
- Deployment name
The application uses the Microsoft Graph PowerShell app registration (Client ID: 14d82eec-204b-4c2f-b7e8-296a70dab67e), which is pre-consented for Graph APIs. The following scopes are requested:
| Scope | Purpose |
|---|---|
DeviceManagementConfiguration.Read.All |
Read device configurations, settings catalog, endpoint security |
DeviceManagementConfiguration.ReadWrite.All |
Write descriptions back to Intune |
DeviceManagementManagedDevices.Read.All |
Read device-related policies |
DeviceManagementApps.Read.All |
Read app protection/configuration policies |
DeviceManagementServiceConfig.Read.All |
Read enrollment configurations |
Policy.Read.All |
Read conditional access policies |
The signed-in user must have these permissions in the tenant (or an admin must consent them).
git clone https://github.com/JayRHa/Intune-PolicyManagement.git
cd Intune-PolicyManagementcd backend
# Create virtual environment (Python 3.13 recommended)
python3.13 -m venv venv
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
# Install dependencies
pip install -r requirements.txt
pip install msalrequirements.txt includes:
| Package | Version | Purpose |
|---|---|---|
| fastapi | 0.115.6 | Web framework |
| uvicorn | 0.34.0 | ASGI server |
| httpx | 0.28.1 | Async HTTP client for Graph API |
| openai | 1.58.1 | Azure OpenAI SDK |
| pydantic | 2.10.4 | Data validation |
| pydantic-settings | 2.7.1 | Environment-based settings |
| python-dotenv | 1.0.1 | Load .env files |
cd frontend
npm installCreate a .env file in the backend/ directory:
cp .env.example backend/.envEdit backend/.env:
AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com
AZURE_OPENAI_API_KEY=your-api-key
AZURE_OPENAI_DEPLOYMENT=gpt-5-mini
AZURE_OPENAI_API_VERSION=2025-04-01-previewImportant: The
.envfile must not be committed to Git. It is already included in.gitignore.
LLM settings (system prompt, template, custom instructions) can be adjusted via the web interface. They are stored in backend/llm_settings.json and persist across restarts.
Default System Prompt:
You are an expert Microsoft Intune administrator. Your task is to analyze Intune policy configurations and generate clear, concise descriptions...
Default Template:
## {policy_name}
**Type:** {policy_type}
**Platform:** {platform}
### Description
{description}
Terminal 1 - Backend:
cd backend
source venv/bin/activate
uvicorn main:app --reload --port 8099Terminal 2 - Frontend:
cd frontend
npm run dev# Note: adjust port in start.sh if needed (default: 8000)
chmod +x start.sh
./start.shNote: Make sure port 8099 is not in use. For port conflicts (e.g. with OrbStack on port 8000), change the port in the
uvicorncommand and infrontend/vite.config.ts.
| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| Backend API | http://localhost:8099 |
| API Docs (Swagger) | http://localhost:8099/docs |
Before starting the application, sign in with Azure CLI:
az loginThe tenant is automatically detected from az account show.
Open http://localhost:5173 in your browser. You will see the login screen.
Click "Sign in with Microsoft". A browser window opens for interactive authentication. Select your account and accept the permissions.
After successful login, you will see the main view. Click "Load Policies" in the top right corner.
Policies are loaded from all 12 policy types in parallel. A spinner indicates the loading process.
- Individual: Click on a row in the table to select/deselect it
- All: Use the "Select All" button
- Search: Use the search field to filter by policy name or description
- Type filter: Filter by policy type via the dropdown (e.g. only "Settings Catalog")
Click the gear icon in the top right corner to open the settings.
You can configure:
| Setting | Description |
|---|---|
| System Prompt | Defines the role and rules for the LLM |
| Output Template | Format template for the generated description |
| Custom Instructions | Additional instructions appended to the system prompt |
Click "Save" to persist the settings. They are stored locally in backend/llm_settings.json.
Click "Generate (N)" in the header. Generation starts and displays a progress bar.
For each selected policy:
- The complete policy configuration is loaded from the Graph API
- The configuration is sent as JSON to Azure OpenAI
- A description is generated based on the system prompt and template
After generation, you see the results view with a before/after comparison:
Features in the results view:
| Action | Description |
|---|---|
| Before/After | Left shows the current Intune description, right shows the generated one |
| Edit | The generated description can be edited directly in the textarea |
| Select | Click on a card to mark the policy for Intune update (blue border) |
| Select All / Deselect All | Select or deselect all policies at once |
| Export | Export all descriptions as a Markdown file |
- Select the policies you want to update in Intune (click on the card or "Select All")
- Click "Sync to Intune (N)"
- Successfully updated policies receive a green "Updated in Intune" badge
Note: Not all policy types support write-back (see table above). Policies without write support will show an error message.
Switch to the "Conflict Analysis" tab using the pill-style tab navigation in the header. This feature scans all policies and identifies settings that are configured in multiple policies.
Click "Start Analysis" to begin. The analyzer fetches detailed settings from every policy and compares them.
Results show two categories:
| Category | Description |
|---|---|
| Conflicts (red) | Same setting configured in multiple policies with different values |
| Duplicates (amber) | Same setting configured in multiple policies with identical values |
Features:
- Stats cards showing total shared settings, conflicts, duplicates, and affected policies
- Filter chips to show all overlaps, only conflicts, or only duplicates
- Search across setting names and policy names
- Expandable rows showing each affected policy with its configured value
- Value comparison with visual highlighting of differing values
All endpoints are available at http://localhost:8099/api/. Interactive documentation at http://localhost:8099/docs.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/auth/status |
Check auth status (local cache only, no network) |
POST |
/api/auth/login |
Start interactive browser login |
GET /api/auth/status - Response:
{
"authenticated": true,
"tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"user": "admin@contoso.onmicrosoft.com"
}| Method | Endpoint | Description |
|---|---|---|
GET |
/api/policies |
Load all policies (12 types in parallel) |
GET |
/api/policies/{type}/{id} |
Policy details with type-specific settings |
GET |
/api/policy-types |
Available policy types and labels |
GET /api/policies - Response:
{
"policies": [
{
"id": "abc-123",
"display_name": "Windows - Antivirus",
"description": "...",
"policy_type": "endpointSecurity",
"platform": "Windows"
}
]
}| Method | Endpoint | Description |
|---|---|---|
POST |
/api/generate-single |
Generate description for a single policy |
POST /api/generate-single - Request:
{
"policy_id": "abc-123",
"policy_type": "deviceConfiguration",
"system_prompt": "Optional: Custom system prompt",
"template": "Optional: Custom template",
"custom_instructions": "Optional: Extra instructions"
}Response:
{
"policy_id": "abc-123",
"policy_name": "Windows - Antivirus",
"policy_type": "deviceConfiguration",
"generated_description": "This policy configures..."
}| Method | Endpoint | Description |
|---|---|---|
POST |
/api/update-descriptions |
Update policy descriptions in Intune (bulk) |
POST /api/update-descriptions - Request:
{
"updates": [
{
"policy_id": "abc-123",
"policy_type": "deviceConfiguration",
"description": "New description..."
}
]
}Response:
{
"results": [
{ "policy_id": "abc-123", "policy_type": "deviceConfiguration", "status": "updated" }
],
"errors": [
{ "policy_id": "def-456", "policy_type": "conditionalAccess", "error": "Policy type does not support..." }
]
}| Method | Endpoint | Description |
|---|---|---|
GET |
/api/analyze-conflicts |
Analyze all policies for overlapping settings |
GET /api/analyze-conflicts - Response:
{
"conflicts": [
{
"setting_key": "#microsoft.graph.windows10GeneralConfiguration:passwordRequired",
"setting_label": "passwordRequired",
"has_different_values": true,
"policies": [
{
"policy_id": "abc-123",
"policy_name": "Windows - Security Baseline",
"policy_type": "deviceConfiguration",
"platform": "Windows",
"value": true
},
{
"policy_id": "def-456",
"policy_name": "Windows - Custom Config",
"policy_type": "deviceConfiguration",
"platform": "Windows",
"value": false
}
]
}
],
"total": 42
}| Method | Endpoint | Description |
|---|---|---|
GET |
/api/settings |
Load current LLM settings |
PUT |
/api/settings |
Save LLM settings |
Intune-PolicyManagement/
|
|-- backend/
| |-- main.py # FastAPI app, CORS, all API endpoints
| |-- auth.py # MSAL authentication, token cache
| |-- graph_client.py # Async HTTP client for Microsoft Graph API
| |-- policy_fetcher.py # Policy types, fetching, details, update
| |-- conflict_analyzer.py # Policy conflict detection engine
| |-- llm_service.py # Azure OpenAI integration
| |-- models.py # Pydantic models (Policy, GenerationResult, etc.)
| |-- config.py # Environment-based settings
| |-- requirements.txt # Python dependencies
| |-- llm_settings.json # Persisted LLM settings (generated at runtime)
| |-- .token_cache.json # MSAL token cache (generated, do not commit!)
| +-- venv/ # Python virtual environment
|
|-- frontend/
| |-- index.html # HTML entry point
| |-- package.json # Node.js dependencies
| |-- vite.config.ts # Vite config with API proxy
| |-- tailwind.config.js # Tailwind CSS configuration
| |-- tsconfig.json # TypeScript configuration
| +-- src/
| |-- main.tsx # React entry point
| |-- App.tsx # Main component (routing, state, tabs)
| |-- index.css # Global styles, glassmorphism
| |-- api/
| | +-- client.ts # API client (fetch-based)
| |-- types/
| | +-- index.ts # TypeScript interfaces, labels
| +-- components/
| |-- PolicyList.tsx # Policy table with filter & search
| |-- PolicyDetail.tsx # Policy detail view (JSON)
| |-- ConflictAnalysis.tsx # Conflict detection & visualization
| |-- SettingsPanel.tsx # LLM settings modal
| |-- GenerationProgress.tsx # Progress indicator
| +-- DescriptionResult.tsx # Before/after + Intune update
|
|-- docs/
| +-- screenshots/ # Application screenshots
|
|-- .env.example # Template for Azure OpenAI credentials
|-- .gitignore # Git ignore rules
+-- start.sh # Start script for both servers
- Uses the Microsoft Graph PowerShell app registration (pre-consented)
- Token is stored in a local file cache (
.token_cache.json) check_auth()only checks the local cache (fast, no network calls)get_graph_token()tries silent login first, then interactive browser login- Tenant ID is detected via
az account showand cached
- Singleton
GraphClientwith connection pooling viahttpx.AsyncClient - Automatic token renewal on 401 responses
get_all()automatically follows@odata.nextLinkfor paginationpatch()for description updates, handles 204 No Content
- 12 policy types defined in
POLICY_ENDPOINTS fetch_all_policies()loads all types in parallel viaasyncio.gather()fetch_policy_details()loads type-specific additional data:- Settings Catalog:
/{id}/settings - Endpoint Security:
/{id}/categorieswith their settings - Group Policy:
/{id}/definitionValues
- Settings Catalog:
update_policy_description()handles PATCH including@odata.typefor device configurationsPATCHABLE_TYPESdefines which types support write-back
- Extracts individual settings from each policy type (Settings Catalog, Device Configuration, Compliance, Endpoint Security, Group Policy)
- Groups settings by key to find overlaps across policies
- Detects conflicts (different values) vs. duplicates (identical values)
- Uses semaphore-controlled concurrency to avoid Graph API rate limits
- Creates a new
AsyncAzureOpenAIclient per request - Uses
max_completion_tokens=1000(notmax_tokens, which is unsupported by newer models) - No
temperatureconfiguration (gpt-5-mini only supports the default value) - Custom instructions are appended to the system prompt
- Tab navigation between Policies and Conflict Analysis
- Three views within Policies tab:
policies|generating|results - Generation runs sequentially (one policy at a time) for progress tracking
- Original description is attached to results from the loaded policy list
- Stats cards showing total overlaps, conflicts, duplicates, and affected policies
- Filter chips (All / Conflicts / Duplicates) and search
- Expandable accordion rows with policy detail cards
- Visual highlighting of conflicting values (red) vs. duplicates (amber)
- Two-column layout: original (read-only) vs. generated (editable)
- Per-policy selection for selective Intune update
- Tracks successfully updated policies ("Updated in Intune" badge)
- Markdown export of all descriptions
The token cache may have expired. Click "Sign in with Microsoft" again - a browser window will open for authentication.
The signed-in user does not have the required Intune permissions. Make sure the user has at least the "Intune Administrator" role or equivalent permissions.
If port 8099 or 5173 is already in use:
Change backend port:
uvicorn main:app --reload --port NEW_PORTAlso update the proxy in frontend/vite.config.ts:
proxy: {
'/api': {
target: 'http://localhost:NEW_PORT',
},
},This issue has already been resolved. The tool uses max_completion_tokens instead of max_tokens. If the error still occurs, check that llm_service.py is up to date.
Device Configuration policies require @odata.type in the PATCH body. The tool automatically reads this value from the existing policy. If the error persists, the policy may be read-only.
pydantic-core cannot be compiled under Python 3.14. Use Python 3.11 - 3.13:
python3.13 -m venv venvOpen the browser console (F12 > Console) and check for JavaScript errors. Common cause: the backend is not reachable. Make sure both servers are running.
- Azure OpenAI credentials must be configured exclusively via
.envfile or environment variables. Never hardcode them in source files. - Token cache (
.token_cache.json) contains refresh tokens and must not be committed to Git. - The application uses the Graph API Beta - this can change without notice.
- ReadWrite permissions: The application requests
DeviceManagementConfiguration.ReadWrite.Allto write descriptions back. This permission theoretically allows other changes - the application exclusively uses it for description updates via PATCH. - The application runs locally and only sends data to Microsoft Graph and Azure OpenAI. No data is transmitted to third parties.







