Skip to content

Commit f79102a

Browse files
committed
Refactor Conditional Access templates and add new policies
- Reformatted JSON structure for better readability in the following templates: - Require multifactor authentication for all users - Require multifactor authentication for guest access - Require phishing-resistant multifactor authentication for admins - Securing security info registration - Use application enforced restrictions for O365 apps - Introduced new PowerShell functions: - Get-PremiumP2ServicePlans: Returns service plans that include Azure AD Premium P2 features. - Test-ConditionalAccessPolicyRequiresP2: Checks if a Conditional Access policy requires Premium P2 licensing. - Test-ConditionalAccessPolicyRequiresPreview: Checks if a Conditional Access policy requires private preview features. - Added new Conditional Access policies: - Block access to Office365 apps for users with insider risk - Block all agent identities from accessing resources - Block all agent users from accessing resources - Block high risk agent identities from accessing resources - Require multifactor authentication for risky sign-ins - Require password change for high-risk users - Secure account recovery with identity verification (Preview) - Updated existing policies to ensure compliance with new security standards and practices.
1 parent dea7a5b commit f79102a

31 files changed

Lines changed: 1610 additions & 38 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ Claude.md
3131
Publishing.md
3232
Test-Adhoc.ps1
3333
Templates/MobileApps/*.png
34-
OpenIntuneBaseline/
34+
OpenIntuneBaseline/
35+
scripts/Export-ConditionalAccessTemplates.ps1

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.2.9] - 2026-01-04
9+
10+
### Added
11+
12+
- 7 new Conditional Access policy templates (total now 21 policies)
13+
- Block access to Office365 apps for users with insider risk
14+
- Block all agent identities from accessing resources
15+
- Block all agent users from accessing resources
16+
- Block high risk agent identities from accessing resources
17+
- Require multifactor authentication for risky sign-ins
18+
- Require password change for high-risk users
19+
- Secure account recovery with identity verification (Preview)
20+
- Premium P2 license validation for Conditional Access policies requiring Entra ID P2
21+
- Preview feature detection for Conditional Access policies requiring preview features
22+
- `Get-PremiumP2ServicePlans` helper function for centralized P2 SKU list management
23+
24+
### Changed
25+
26+
- README.md updated with correct Conditional Access count (21 policies) and link to Microsoft Learn documentation
27+
- Enhanced `Test-IntunePrerequisites` with comprehensive E5/A5/EMS suite detection
28+
- Fixed empty rows in hydration summary reports
29+
- Fixed missing Type column values in Conditional Access import results
30+
831
## [0.2.8] - 2026-01-02
932

1033
### Added

IntuneHydrationKit.psd1

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Module manifest for IntuneHydrationKit
33

44
# Version number of this module
5-
ModuleVersion = '0.2.8'
5+
ModuleVersion = '0.2.9'
66

77
# ID used to uniquely identify this module
88
GUID = 'f755f41b-d5fc-48db-8b11-62b7ed71b1cd'
@@ -89,15 +89,26 @@
8989

9090
# Release notes for this module
9191
ReleaseNotes = @'
92-
## v0.2.8
92+
## v0.2.9
9393
9494
- **Added:**
95-
- Automatic replacement of `%OrganizationId%` placeholder with tenant ID during OpenIntuneBaseline import
96-
- Verbose logging when placeholder replacement occurs in policy templates
95+
- 7 new Conditional Access policy templates (total now 21 policies)
96+
- Block access to Office365 apps for users with insider risk
97+
- Block all agent identities from accessing resources
98+
- Block all agent users from accessing resources
99+
- Block high risk agent identities from accessing resources
100+
- Require multifactor authentication for risky sign-ins
101+
- Require password change for high-risk users
102+
- Secure account recovery with identity verification (Preview)
103+
- Premium P2 license validation for Conditional Access policies requiring Entra ID P2
104+
- Preview feature detection for Conditional Access policies requiring preview features
105+
- Get-PremiumP2ServicePlans helper function for centralized P2 SKU list management
97106
98107
- **Changed:**
99-
- `Import-IntuneBaseline` now processes JSON templates and replaces `%OrganizationId%` with actual tenant ID before importing to Graph API
100-
- Affects OneDrive configuration policies that require tenant-specific settings (Known Folder Move, etc.)
108+
- README.md updated with correct Conditional Access count (21 policies) and link to Microsoft Learn documentation
109+
- Enhanced Test-IntunePrerequisites with comprehensive E5/A5/EMS suite detection
110+
- Fixed empty rows in hydration summary reports
111+
- Fixed missing Type column values in Conditional Access import results
101112
102113
'@
103114
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
function Get-PremiumP2ServicePlans {
2+
<#
3+
.SYNOPSIS
4+
Returns the list of service plan names that include Azure AD Premium P2 features
5+
.DESCRIPTION
6+
This function provides a centralized definition of service plans that include
7+
Azure AD Premium P2 capabilities, which are required for risk-based Conditional
8+
Access policies (signInRiskLevels, userRiskLevels, insiderRiskLevels, etc.).
9+
10+
Used by Test-IntunePrerequisites and Import-IntuneConditionalAccessPolicy to
11+
maintain a single source of truth for P2 license detection.
12+
.EXAMPLE
13+
$p2Plans = Get-PremiumP2ServicePlans
14+
if ($plan.servicePlanName -in $p2Plans) { ... }
15+
.OUTPUTS
16+
System.String[]
17+
.NOTES
18+
Reference: https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/concept-conditional-access-conditions
19+
#>
20+
[CmdletBinding()]
21+
[OutputType([string[]])]
22+
param()
23+
24+
return @(
25+
# Azure AD Premium P2 standalone
26+
'AAD_PREMIUM_P2', # Azure AD Premium P2
27+
28+
# Microsoft 365 E5 suites (include Azure AD Premium P2)
29+
'SPE_E5', # Microsoft 365 E5
30+
'SPE_E5_GOV', # Microsoft 365 E5 (Gov)
31+
'M365_E5', # Microsoft 365 E5 (alternate)
32+
'SPE_E5_USGOV_GCCHIGH', # Microsoft 365 E5 GCC High
33+
'INFORMATION_PROTECTION_COMPLIANCE', # Microsoft 365 E5 Compliance
34+
'M365_E5_SUITE_COMPONENTS', # Microsoft 365 E5 Suite
35+
36+
# Microsoft 365 Education A5 (includes Azure AD Premium P2)
37+
'M365EDU_A5_FACULTY', # Microsoft 365 A5 for Faculty
38+
'M365EDU_A5_STUDENT', # Microsoft 365 A5 for Students
39+
40+
# Enterprise Mobility + Security E5
41+
'EMSPREMIUM', # Enterprise Mobility + Security E5
42+
'EMS', # EMS E5 (alternate)
43+
44+
# Identity & Threat Protection (standalone add-on)
45+
'IDENTITY_THREAT_PROTECTION', # Microsoft 365 E5 Security
46+
47+
# Microsoft Defender for Cloud Apps (formerly MCAS) - includes some Identity Protection features
48+
'ADALLOM_S_STANDALONE', # Microsoft Defender for Cloud Apps
49+
50+
# Azure Advanced Threat Protection (now part of Defender for Identity)
51+
'ATA' # Azure ATP
52+
)
53+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
function Test-ConditionalAccessPolicyRequiresP2 {
2+
<#
3+
.SYNOPSIS
4+
Checks if a Conditional Access policy requires Premium P2 licensing
5+
.DESCRIPTION
6+
Analyzes a Conditional Access policy object to determine if it uses features
7+
that require Azure AD Premium P2 licensing. These features include:
8+
- Sign-in risk levels (signInRiskLevels)
9+
- User risk levels (userRiskLevels)
10+
- Insider risk levels (insiderRiskLevels)
11+
- Agent identity risk levels (agentIdRiskLevels)
12+
- Service principal risk levels (servicePrincipalRiskLevels)
13+
.PARAMETER Policy
14+
The Conditional Access policy object to check
15+
.EXAMPLE
16+
$policy = Get-Content -Path "policy.json" | ConvertFrom-Json
17+
Test-ConditionalAccessPolicyRequiresP2 -Policy $policy
18+
.OUTPUTS
19+
System.Boolean
20+
#>
21+
[CmdletBinding()]
22+
[OutputType([bool])]
23+
param(
24+
[Parameter(Mandatory)]
25+
[PSCustomObject]$Policy
26+
)
27+
28+
# Check if policy has conditions
29+
if (-not $Policy.conditions) {
30+
return $false
31+
}
32+
33+
$conditions = $Policy.conditions
34+
35+
# Check for sign-in risk levels
36+
if ($conditions.signInRiskLevels -and
37+
$conditions.signInRiskLevels -is [array] -and
38+
$conditions.signInRiskLevels.Count -gt 0) {
39+
Write-Verbose "Policy requires P2: uses signInRiskLevels"
40+
return $true
41+
}
42+
43+
# Check for user risk levels
44+
if ($conditions.userRiskLevels -and
45+
$conditions.userRiskLevels -is [array] -and
46+
$conditions.userRiskLevels.Count -gt 0) {
47+
Write-Verbose "Policy requires P2: uses userRiskLevels"
48+
return $true
49+
}
50+
51+
# Check for insider risk levels (string value, not array)
52+
if ($null -ne $conditions.insiderRiskLevels -and
53+
$conditions.insiderRiskLevels -ne "null" -and
54+
$conditions.insiderRiskLevels.ToString().Trim() -ne "") {
55+
Write-Verbose "Policy requires P2: uses insiderRiskLevels"
56+
return $true
57+
}
58+
59+
# Check for agent identity risk levels (can be string or array)
60+
if ($null -ne $conditions.agentIdRiskLevels) {
61+
# Handle array format
62+
if ($conditions.agentIdRiskLevels -is [array] -and $conditions.agentIdRiskLevels.Count -gt 0) {
63+
Write-Verbose "Policy requires P2: uses agentIdRiskLevels (array)"
64+
return $true
65+
}
66+
# Handle string format
67+
if ($conditions.agentIdRiskLevels -is [string] -and $conditions.agentIdRiskLevels.Trim() -ne "") {
68+
Write-Verbose "Policy requires P2: uses agentIdRiskLevels (string)"
69+
return $true
70+
}
71+
}
72+
73+
# Check for service principal risk levels (can be string or array)
74+
if ($null -ne $conditions.servicePrincipalRiskLevels) {
75+
# Handle array format
76+
if ($conditions.servicePrincipalRiskLevels -is [array] -and $conditions.servicePrincipalRiskLevels.Count -gt 0) {
77+
Write-Verbose "Policy requires P2: uses servicePrincipalRiskLevels (array)"
78+
return $true
79+
}
80+
# Handle string format
81+
if ($conditions.servicePrincipalRiskLevels -is [string] -and $conditions.servicePrincipalRiskLevels.Trim() -ne "") {
82+
Write-Verbose "Policy requires P2: uses servicePrincipalRiskLevels (string)"
83+
return $true
84+
}
85+
}
86+
87+
return $false
88+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
function Test-ConditionalAccessPolicyRequiresPreview {
2+
<#
3+
.SYNOPSIS
4+
Checks if a Conditional Access policy requires private preview features
5+
.DESCRIPTION
6+
Analyzes a Conditional Access policy object to determine if it uses features
7+
that require explicit tenant authorization for private preview access.
8+
9+
Known private preview features:
10+
- Account Recovery (urn:user:accountrecovery) - requires AccountRecovery preview authorization
11+
.PARAMETER Policy
12+
The Conditional Access policy object to check
13+
.EXAMPLE
14+
$policy = Get-Content -Path "policy.json" | ConvertFrom-Json
15+
Test-ConditionalAccessPolicyRequiresPreview -Policy $policy
16+
.OUTPUTS
17+
System.String - Returns the preview feature name if required, $null otherwise
18+
#>
19+
[CmdletBinding()]
20+
[OutputType([string])]
21+
param(
22+
[Parameter(Mandatory)]
23+
[PSCustomObject]$Policy
24+
)
25+
26+
# Check if policy has conditions
27+
if (-not $Policy.conditions) {
28+
return $null
29+
}
30+
31+
$conditions = $Policy.conditions
32+
33+
# Check for Account Recovery user action (private preview)
34+
if ($conditions.applications -and $conditions.applications.includeUserActions) {
35+
$userActions = $conditions.applications.includeUserActions
36+
37+
if ($userActions -contains "urn:user:accountrecovery") {
38+
Write-Verbose "Policy requires preview: AccountRecovery (urn:user:accountrecovery)"
39+
return "AccountRecovery"
40+
}
41+
}
42+
43+
# Check for other potential preview features
44+
# Add more preview feature checks here as they are discovered
45+
46+
return $null
47+
}

Public/Import-IntuneConditionalAccessPolicy.ps1

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,32 @@ function Import-IntuneConditionalAccessPolicy {
4343
return @()
4444
}
4545

46+
# Check for Premium P2 license once at the start
47+
$premiumP2ServicePlans = Get-PremiumP2ServicePlans
48+
49+
$hasPremiumP2 = $false
50+
try {
51+
$subscribedSkus = Invoke-MgGraphRequest -Method GET -Uri "beta/subscribedSkus" -ErrorAction Stop
52+
foreach ($sku in $subscribedSkus.value) {
53+
if ($sku.capabilityStatus -ne 'Enabled') { continue }
54+
foreach ($plan in $sku.servicePlans) {
55+
if ($plan.servicePlanName -in $premiumP2ServicePlans -and $plan.provisioningStatus -eq 'Success') {
56+
$hasPremiumP2 = $true
57+
break
58+
}
59+
}
60+
if ($hasPremiumP2) { break }
61+
}
62+
}
63+
catch {
64+
Write-Verbose "Failed to check Premium P2 license: $_"
65+
$hasPremiumP2 = $true # Allow attempt if check fails
66+
}
67+
68+
if (-not $hasPremiumP2) {
69+
Write-Warning "No Azure AD Premium P2 license detected. Risk-based Conditional Access policies will be skipped."
70+
}
71+
4672
$results = @()
4773

4874
# Remove existing CA policies if requested
@@ -110,13 +136,28 @@ function Import-IntuneConditionalAccessPolicy {
110136
$templateContent = Get-Content -Path $templateFile.FullName -Raw -Encoding utf8
111137
$policy = $templateContent | ConvertFrom-Json
112138

139+
# Check if policy requires P2 and tenant doesn't have it
140+
if (-not $hasPremiumP2 -and (Test-ConditionalAccessPolicyRequiresP2 -Policy $policy)) {
141+
Write-HydrationLog -Message " Skipped: $displayName - requires Azure AD Premium P2 license (uses risk-based conditions)" -Level Warning
142+
$results += New-HydrationResult -Name $displayName -Type 'ConditionalAccessPolicy' -Action 'Skipped' -Status 'Requires Premium P2 license'
143+
continue
144+
}
145+
146+
# Check if policy requires private preview features
147+
$previewFeature = Test-ConditionalAccessPolicyRequiresPreview -Policy $policy
148+
if ($previewFeature) {
149+
Write-HydrationLog -Message " Skipped: $displayName - requires private preview feature: $previewFeature (tenant must be explicitly authorized)" -Level Warning
150+
$results += New-HydrationResult -Name $displayName -Type 'ConditionalAccessPolicy' -Action 'Skipped' -Status "Requires private preview: $previewFeature"
151+
continue
152+
}
153+
113154
# Check if policy already exists (escape single quotes for OData filter)
114155
$safeDisplayName = $displayName -replace "'", "''"
115156
$existingPolicies = Invoke-MgGraphRequest -Method GET -Uri "beta/identity/conditionalAccess/policies?`$filter=displayName eq '$safeDisplayName'" -ErrorAction Stop
116157

117158
if ($existingPolicies.value.Count -gt 0) {
118159
Write-HydrationLog -Message " Skipped: $displayName" -Level Info
119-
$results += New-HydrationResult -Name $displayName -Id $existingPolicies.value[0].id -Action 'Skipped' -Status 'Already exists' -State $existingPolicies.value[0].state
160+
$results += New-HydrationResult -Name $displayName -Type 'ConditionalAccessPolicy' -Id $existingPolicies.value[0].id -Action 'Skipped' -Status 'Already exists' -State $existingPolicies.value[0].state
120161
continue
121162
}
122163

@@ -144,17 +185,17 @@ function Import-IntuneConditionalAccessPolicy {
144185

145186
Write-HydrationLog -Message " Created: $displayName" -Level Info
146187

147-
$results += New-HydrationResult -Name $displayName -Id $newPolicy.id -Action 'Created' -Status 'Success' -State 'disabled'
188+
$results += New-HydrationResult -Name $displayName -Type 'ConditionalAccessPolicy' -Id $newPolicy.id -Action 'Created' -Status 'Success' -State 'disabled'
148189
}
149190
else {
150191
Write-HydrationLog -Message " WouldCreate: $displayName" -Level Info
151-
$results += New-HydrationResult -Name $displayName -Action 'WouldCreate' -Status 'DryRun' -State 'disabled'
192+
$results += New-HydrationResult -Name $displayName -Type 'ConditionalAccessPolicy' -Action 'WouldCreate' -Status 'DryRun' -State 'disabled'
152193
}
153194
}
154195
catch {
155196
$errMessage = Get-GraphErrorMessage -ErrorRecord $_
156197
Write-HydrationLog -Message " Failed: $displayName - $errMessage" -Level Warning
157-
$results += New-HydrationResult -Name $displayName -Action 'Failed' -Status $errMessage
198+
$results += New-HydrationResult -Name $displayName -Type 'ConditionalAccessPolicy' -Action 'Failed' -Status $errMessage
158199
}
159200
}
160201

Public/Invoke-IntuneHydration.ps1

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -689,8 +689,21 @@ function Invoke-IntuneHydration {
689689
"@
690690

691691
# Build table rows separately to avoid header/row formatting issues
692+
# Filter out results that don't have a Name or Action (invalid/empty results)
692693
$operationLines = foreach ($result in $allResults) {
693-
"| {0} | {1} | {2} | {3} | {4} | {5} |" -f $result.Timestamp, $result.Type, $result.Name, $result.Action, $result.Id, $result.Status
694+
# Skip results with no meaningful data
695+
if (-not $result.Name -and -not $result.Action) {
696+
continue
697+
}
698+
699+
$timestamp = if ($result.Timestamp) { $result.Timestamp } else { '' }
700+
$type = if ($result.PSObject.Properties['Type']) { $result.Type } else { '' }
701+
$name = if ($result.Name) { $result.Name } else { '' }
702+
$action = if ($result.Action) { $result.Action } else { '' }
703+
$id = if ($result.PSObject.Properties['Id']) { $result.Id } else { '' }
704+
$status = if ($result.Status) { $result.Status } else { '' }
705+
706+
"| {0} | {1} | {2} | {3} | {4} | {5} |" -f $timestamp, $type, $name, $action, $id, $status
694707
}
695708

696709
# Explicit newline between header and first row to keep the table rendering clean
@@ -704,7 +717,6 @@ function Invoke-IntuneHydration {
704717
## Important Notes
705718
706719
- **Conditional Access policies** were created in **DISABLED** state. Review and enable as needed.
707-
- **OpenIntuneBaseline policies** were imported using IntuneManagement module.
708720
- Review all configurations before enabling in production.
709721
710722
"@

0 commit comments

Comments
 (0)