Skip to content

Commit 5a0712d

Browse files
committed
Implemented Assignment Filters
- With template support and pages to support it. - Includes standard for deployment - Allows optional usage in "Intune Template" standard for assignment. Fixes KelvinTegelaar/CIPP#4721
1 parent 1d1882a commit 5a0712d

13 files changed

+718
-32
lines changed

Config/standards.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4772,6 +4772,30 @@
47724772
"type": "textField",
47734773
"required": false,
47744774
"helpText": "Enter the group name to exclude from the assignment. Wildcards are allowed."
4775+
},
4776+
{
4777+
"type": "textField",
4778+
"required": false,
4779+
"name": "assignmentFilter",
4780+
"label": "Assignment Filter Name (Optional)",
4781+
"helpText": "Enter the assignment filter name to apply to this policy assignment. Wildcards are allowed."
4782+
},
4783+
{
4784+
"name": "assignmentFilterType",
4785+
"label": "Assignment Filter Mode (Optional)",
4786+
"type": "radio",
4787+
"required": false,
4788+
"helpText": "Choose whether to include or exclude devices matching the filter. Only applies if you specified a filter name above. Defaults to Include if not specified.",
4789+
"options": [
4790+
{
4791+
"label": "Include - Assign to devices matching the filter",
4792+
"value": "include"
4793+
},
4794+
{
4795+
"label": "Exclude - Assign to devices NOT matching the filter",
4796+
"value": "exclude"
4797+
}
4798+
]
47754799
}
47764800
]
47774801
},
@@ -4915,6 +4939,35 @@
49154939
}
49164940
]
49174941
},
4942+
{
4943+
"name": "standards.AssignmentFilterTemplate",
4944+
"label": "Assignment Filter Template",
4945+
"multi": true,
4946+
"cat": "Templates",
4947+
"disabledFeatures": {
4948+
"report": true,
4949+
"warn": true,
4950+
"remediate": false
4951+
},
4952+
"impact": "Medium Impact",
4953+
"addedDate": "2025-10-04",
4954+
"helpText": "Deploy and manage assignment filter templates.",
4955+
"executiveText": "Creates standardized assignment filters with predefined settings. These templates ensure consistent assignment filter configurations across the organization, streamlining assignment management.",
4956+
"addedComponent": [
4957+
{
4958+
"type": "autoComplete",
4959+
"name": "assignmentFilterTemplate",
4960+
"label": "Select Assignment Filter Template",
4961+
"api": {
4962+
"url": "/api/ListAssignmentFilterTemplates",
4963+
"labelField": "Displayname",
4964+
"altLabelField": "displayName",
4965+
"valueField": "GUID",
4966+
"queryKey": "ListAssignmentFilterTemplates"
4967+
}
4968+
}
4969+
]
4970+
},
49184971
{
49194972
"name": "standards.MailboxRecipientLimits",
49204973
"cat": "Exchange Standards",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using namespace System.Net
2+
3+
function Invoke-AddAssignmentFilter {
4+
<#
5+
.FUNCTIONALITY
6+
Entrypoint
7+
.ROLE
8+
Endpoint.MEM.ReadWrite
9+
#>
10+
[CmdletBinding()]
11+
param($Request, $TriggerMetadata)
12+
13+
$APIName = $Request.Params.CIPPEndpoint
14+
$SelectedTenants = if ('AllTenants' -in $Request.body.tenantFilter) { (Get-Tenants).defaultDomainName } else { $Request.body.tenantFilter.value ? $Request.body.tenantFilter.value : $Request.body.tenantFilter }
15+
Write-LogMessage -headers $Request.Headers -API $APIName -message 'Accessed this API' -Sev Debug
16+
17+
18+
$FilterObject = $Request.body
19+
20+
$Results = foreach ($tenant in $SelectedTenants) {
21+
try {
22+
# Use the centralized New-CIPPAssignmentFilter function
23+
$Result = New-CIPPAssignmentFilter -FilterObject $FilterObject -TenantFilter $tenant -APIName $APIName -ExecutingUser $Request.Headers.'x-ms-client-principal-name'
24+
25+
if ($Result.Success) {
26+
"Successfully created assignment filter $($FilterObject.displayName) for $($tenant)"
27+
$StatusCode = [HttpStatusCode]::OK
28+
} else {
29+
throw $Result.Message
30+
}
31+
} catch {
32+
$ErrorMessage = Get-CippException -Exception $_
33+
Write-LogMessage -headers $Request.Headers -API $APIName -tenant $tenant -message "Assignment filter creation API failed. $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage
34+
"Failed to create assignment filter $($FilterObject.displayName) for $($tenant): $($ErrorMessage.NormalizedError)"
35+
$StatusCode = [HttpStatusCode]::InternalServerError
36+
}
37+
}
38+
39+
# Associate values to output bindings by calling 'Push-OutputBinding'.
40+
return ([HttpResponseContext]@{
41+
StatusCode = $StatusCode
42+
Body = @{'Results' = @($Results) }
43+
})
44+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using namespace System.Net
2+
3+
function Invoke-AddAssignmentFilterTemplate {
4+
<#
5+
.FUNCTIONALITY
6+
Entrypoint,AnyTenant
7+
.ROLE
8+
Endpoint.MEM.ReadWrite
9+
#>
10+
[CmdletBinding()]
11+
param($Request, $TriggerMetadata)
12+
$APIName = $Request.Params.CIPPEndpoint
13+
$Headers = $Request.Headers
14+
15+
16+
$GUID = $Request.Body.GUID ?? (New-Guid).GUID
17+
try {
18+
if (!$Request.Body.displayName) {
19+
throw 'You must enter a displayname'
20+
}
21+
22+
if (!$Request.Body.rule) {
23+
throw 'You must enter a filter rule'
24+
}
25+
26+
if (!$Request.Body.platform) {
27+
throw 'You must select a platform'
28+
}
29+
30+
# Normalize field names to handle different casing from various forms
31+
$displayName = $Request.Body.displayName ?? $Request.Body.Displayname ?? $Request.Body.displayname
32+
$description = $Request.Body.description ?? $Request.Body.Description
33+
$platform = $Request.Body.platform
34+
$rule = $Request.Body.rule
35+
$assignmentFilterManagementType = $Request.Body.assignmentFilterManagementType ?? 'devices'
36+
37+
$object = [PSCustomObject]@{
38+
displayName = $displayName
39+
description = $description
40+
platform = $platform
41+
rule = $rule
42+
assignmentFilterManagementType = $assignmentFilterManagementType
43+
GUID = $GUID
44+
} | ConvertTo-Json
45+
$Table = Get-CippTable -tablename 'templates'
46+
$Table.Force = $true
47+
Add-CIPPAzDataTableEntity @Table -Force -Entity @{
48+
JSON = "$object"
49+
RowKey = "$GUID"
50+
PartitionKey = 'AssignmentFilterTemplate'
51+
}
52+
Write-LogMessage -headers $Request.Headers -API $APINAME -message "Created Assignment Filter template named $displayName with GUID $GUID" -Sev 'Debug'
53+
54+
$body = [pscustomobject]@{'Results' = 'Successfully added template' }
55+
} catch {
56+
Write-LogMessage -headers $Request.Headers -API $APINAME -message "Assignment Filter Template Creation failed: $($_.Exception.Message)" -Sev 'Error'
57+
$body = [pscustomobject]@{'Results' = "Assignment Filter Template Creation failed: $($_.Exception.Message)" }
58+
}
59+
60+
61+
# Associate values to output bindings by calling 'Push-OutputBinding'.
62+
return ([HttpResponseContext]@{
63+
StatusCode = [HttpStatusCode]::OK
64+
Body = $body
65+
})
66+
67+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using namespace System.Net
2+
3+
function Invoke-EditAssignmentFilter {
4+
<#
5+
.FUNCTIONALITY
6+
Entrypoint
7+
.ROLE
8+
Endpoint.MEM.ReadWrite
9+
#>
10+
[CmdletBinding()]
11+
param($Request, $TriggerMetadata)
12+
13+
$APIName = $Request.Params.CIPPEndpoint
14+
$Headers = $Request.Headers
15+
16+
Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev Debug
17+
18+
$TenantFilter = $Request.Body.tenantFilter
19+
20+
try {
21+
$FilterId = $Request.Body.filterId
22+
23+
if (!$FilterId) {
24+
throw 'Filter ID is required'
25+
}
26+
27+
# Build the update body
28+
# Note: Platform and assignmentFilterManagementType cannot be changed after creation per Graph API restrictions
29+
$UpdateBody = @{}
30+
31+
if ($Request.Body.displayName) {
32+
$UpdateBody.displayName = $Request.Body.displayName
33+
}
34+
35+
if ($null -ne $Request.Body.description) {
36+
$UpdateBody.description = $Request.Body.description
37+
}
38+
39+
if ($Request.Body.rule) {
40+
$UpdateBody.rule = $Request.Body.rule
41+
}
42+
43+
# Update the assignment filter
44+
$GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceManagement/assignmentFilters/$FilterId" -tenantid $TenantFilter -type PATCH -body ($UpdateBody | ConvertTo-Json -Depth 10)
45+
46+
Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $TenantFilter -message "Updated assignment filter $($Request.Body.displayName)" -Sev Info
47+
48+
$Result = "Successfully updated assignment filter $($Request.Body.displayName)"
49+
$StatusCode = [HttpStatusCode]::OK
50+
} catch {
51+
$ErrorMessage = Get-CippException -Exception $_
52+
Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $TenantFilter -message "Failed to update assignment filter: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage
53+
$Result = "Failed to update assignment filter: $($ErrorMessage.NormalizedError)"
54+
$StatusCode = [HttpStatusCode]::InternalServerError
55+
}
56+
57+
# Associate values to output bindings by calling 'Push-OutputBinding'.
58+
return ([HttpResponseContext]@{
59+
StatusCode = $StatusCode
60+
Body = @{'Results' = $Result }
61+
})
62+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using namespace System.Net
2+
3+
function Invoke-ExecAssignmentFilter {
4+
<#
5+
.FUNCTIONALITY
6+
Entrypoint
7+
.ROLE
8+
Endpoint.MEM.ReadWrite
9+
#>
10+
[CmdletBinding()]
11+
param($Request, $TriggerMetadata)
12+
13+
$APIName = $Request.Params.CIPPEndpoint
14+
$Headers = $Request.Headers
15+
16+
Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev Debug
17+
18+
$TenantFilter = $Request.Query.TenantFilter ?? $Request.Body.tenantFilter
19+
20+
try {
21+
$FilterId = $Request.Body.ID
22+
$Action = $Request.Body.Action
23+
24+
if (!$FilterId) {
25+
throw 'Filter ID is required'
26+
}
27+
28+
switch ($Action) {
29+
'Delete' {
30+
# Delete the assignment filter
31+
$GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceManagement/assignmentFilters/$FilterId" -tenantid $TenantFilter -type DELETE
32+
33+
Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $TenantFilter -message "Deleted assignment filter with ID $FilterId" -Sev Info
34+
35+
$Result = "Successfully deleted assignment filter"
36+
$StatusCode = [HttpStatusCode]::OK
37+
}
38+
default {
39+
throw "Unknown action: $Action"
40+
}
41+
}
42+
} catch {
43+
$ErrorMessage = Get-CippException -Exception $_
44+
Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $TenantFilter -message "Failed to execute assignment filter action: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage
45+
$Result = "Failed to execute assignment filter action: $($ErrorMessage.NormalizedError)"
46+
$StatusCode = [HttpStatusCode]::InternalServerError
47+
}
48+
49+
# Associate values to output bindings by calling 'Push-OutputBinding'.
50+
return ([HttpResponseContext]@{
51+
StatusCode = $StatusCode
52+
Body = @{'Results' = $Result }
53+
})
54+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using namespace System.Net
2+
3+
function Invoke-ListAssignmentFilterTemplates {
4+
<#
5+
.FUNCTIONALITY
6+
Entrypoint,AnyTenant
7+
.ROLE
8+
Endpoint.MEM.Read
9+
#>
10+
[CmdletBinding()]
11+
param($Request, $TriggerMetadata)
12+
13+
$APIName = $Request.Params.CIPPEndpoint
14+
$Headers = $Request.Headers
15+
16+
17+
18+
Write-Host $Request.query.id
19+
20+
#List assignment filter templates
21+
$Table = Get-CippTable -tablename 'templates'
22+
$Filter = "PartitionKey eq 'AssignmentFilterTemplate'"
23+
$Templates = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) | ForEach-Object {
24+
$data = $_.JSON | ConvertFrom-Json
25+
26+
[PSCustomObject]@{
27+
displayName = $data.displayName
28+
description = $data.description
29+
platform = $data.platform
30+
rule = $data.rule
31+
assignmentFilterManagementType = $data.assignmentFilterManagementType
32+
GUID = $_.RowKey
33+
}
34+
} | Sort-Object -Property displayName
35+
36+
if ($Request.query.ID) { $Templates = $Templates | Where-Object -Property GUID -EQ $Request.query.id }
37+
38+
39+
# Associate values to output bindings by calling 'Push-OutputBinding'.
40+
return ([HttpResponseContext]@{
41+
StatusCode = [HttpStatusCode]::OK
42+
Body = @($Templates)
43+
})
44+
45+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using namespace System.Net
2+
3+
function Invoke-ListAssignmentFilters {
4+
<#
5+
.FUNCTIONALITY
6+
Entrypoint
7+
.ROLE
8+
Endpoint.MEM.Read
9+
#>
10+
[CmdletBinding()]
11+
param($Request, $TriggerMetadata)
12+
13+
$APIName = $Request.Params.CIPPEndpoint
14+
$Headers = $Request.Headers
15+
16+
Write-LogMessage -headers $Request.Headers -API $APINAME -message 'Accessed this API' -Sev Debug
17+
18+
# Get the tenant filter
19+
$TenantFilter = $Request.Query.TenantFilter
20+
21+
try {
22+
if ($Request.Query.filterId) {
23+
# Get specific filter
24+
$AssignmentFilters = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/assignmentFilters/$($Request.Query.filterId)" -tenantid $TenantFilter
25+
} else {
26+
# Get all filters
27+
$AssignmentFilters = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/assignmentFilters' -tenantid $TenantFilter
28+
}
29+
30+
$StatusCode = [HttpStatusCode]::OK
31+
} catch {
32+
$ErrorMessage = Get-CippException -Exception $_
33+
Write-LogMessage -headers $Request.Headers -API $APINAME -message "Failed to retrieve assignment filters: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage
34+
$AssignmentFilters = @()
35+
$StatusCode = [HttpStatusCode]::InternalServerError
36+
}
37+
38+
# Associate values to output bindings by calling 'Push-OutputBinding'.
39+
return ([HttpResponseContext]@{
40+
StatusCode = $StatusCode
41+
Body = @($AssignmentFilters)
42+
})
43+
}

0 commit comments

Comments
 (0)