Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions backend/Config/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,12 @@
"DisplayName": {
"type": "string"
},
"GroupIds": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LabelValue"
}
},
"selectedTenants": {
"$ref": "#/components/schemas/LabelValue"
}
Expand Down Expand Up @@ -13577,6 +13583,77 @@
"x-cipp-role": "Endpoint.Application.ReadWrite"
}
},
"/api/ExecAssignAutopilotProfile": {
"post": {
"summary": "ExecAssignAutopilotProfile",
"operationId": "ExecAssignAutopilotProfile",
"tags": [
"Endpoint > Autopilot"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"AssignTo": {
"type": "string"
},
"GroupIds": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LabelValue"
}
},
"ProfileId": {
"type": "string"
},
"ProfileName": {
"type": "string"
},
"tenantFilter": {
"type": "string"
}
},
"required": [
"AssignTo",
"ProfileId",
"tenantFilter"
]
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StandardResults"
}
}
}
},
"401": {
"description": "Unauthorized - invalid or missing bearer token"
},
"403": {
"description": "Forbidden - caller lacks the required RBAC role"
},
"500": {
"description": "Internal server error"
}
},
"security": [
{
"bearerAuth": []
}
],
"x-cipp-role": "Endpoint.Autopilot.ReadWrite"
}
},
"/api/ExecAssignmentFilter": {
"post": {
"summary": "ExecAssignmentFilter",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ function Set-CIPPDefaultAPDeploymentProfile {
$DeploymentMode,
$HideChangeAccount = $true,
$AssignTo,
$GroupIds,
$HidePrivacy,
$HideTerms,
$AutoKeyboard,
Expand Down Expand Up @@ -103,6 +104,34 @@ function Set-CIPPDefaultAPDeploymentProfile {
$ErrorMessage = Get-CippException -Exception $_
Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to assign Autopilot profile $($DisplayName) to $($AssignTo): $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage
}
} elseif (@($GroupIds) -and @($GroupIds).Count -gt 0) {
try {
$Assigned = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($GraphRequest.id)/assignments" -tenantid $TenantFilter
$ExistingGroupIds = @($Assigned |
Where-Object { $_.target.'@odata.type' -eq '#microsoft.graph.groupAssignmentTarget' } |
ForEach-Object { $_.target.groupId })
$CreatedGroupIds = [System.Collections.Generic.List[string]]::new()
foreach ($GroupId in @($GroupIds)) {
if (-not $GroupId -or $ExistingGroupIds -contains $GroupId) { continue }
$GroupAssignBody = @{
target = @{
'@odata.type' = '#microsoft.graph.groupAssignmentTarget'
groupId = $GroupId
}
} | ConvertTo-Json -Depth 5 -Compress
if ($PSCmdlet.ShouldProcess($GroupId, "Assign Autopilot profile $DisplayName to group")) {
$null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($GraphRequest.id)/assignments" -tenantid $TenantFilter -type POST -body $GroupAssignBody
$CreatedGroupIds.Add($GroupId)
}
}
if (@($CreatedGroupIds).Count -gt 0) {
Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Assigned autopilot profile $($DisplayName) to group(s): $($CreatedGroupIds -join ', ')" -Sev 'Info'
}
} catch {
$ErrorMessage = Get-CippException -Exception $_
Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to assign Autopilot profile $($DisplayName) to groups: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage
throw
}
}
"Successfully $($Type)ed profile for $($TenantFilter)"
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ function Invoke-AddAutopilotConfig {
$UserType = if ($Profbod.NotLocalAdmin -eq 'true') { 'standard' } else { 'administrator' }
$DeploymentMode = if ($Profbod.DeploymentMode -eq 'true') { 'shared' } else { 'singleUser' }

# The frontend group picker sends option objects ({ value, label }); accept those plus
# bare id strings from direct API callers, and drop anything empty.
$GroupIds = @(
$Request.Body.GroupIds | ForEach-Object {
if ($_ -is [string]) { $_.Trim() } elseif ($_ -and $_.value) { $_.value }
} | Where-Object { $_ }
)

# If deployment mode is shared, disable white glove (pre-provisioning) as it's not supported
$AllowWhiteGlove = if ($DeploymentMode -eq 'shared') { $false } else { $Profbod.allowWhiteGlove }

Expand All @@ -33,6 +41,7 @@ function Invoke-AddAutopilotConfig {
UserType = $UserType
DeploymentMode = $DeploymentMode
AssignTo = $Request.Body.Assignto
GroupIds = $GroupIds
DeviceNameTemplate = $Profbod.DeviceNameTemplate
AllowWhiteGlove = $AllowWhiteGlove
CollectHash = $Profbod.CollectHash
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
function Invoke-ExecAssignAutopilotProfile {
<#
.FUNCTIONALITY
Entrypoint
.ROLE
Endpoint.Autopilot.ReadWrite
#>
[CmdletBinding()]
param($Request, $TriggerMetadata)

$APIName = $Request.Params.CIPPEndpoint
$Headers = $Request.Headers
$TenantFilter = $Request.Body.tenantFilter
$ProfileId = $Request.Body.ProfileId
$ProfileName = $Request.Body.ProfileName
$AssignTo = $Request.Body.AssignTo

try {
if ([string]::IsNullOrEmpty($TenantFilter)) { throw 'Tenant filter is required' }
if ([string]::IsNullOrEmpty($ProfileId)) { throw 'Profile ID is required' }
if ([string]::IsNullOrEmpty($AssignTo)) { throw 'AssignTo is required' }

$BaseUri = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$ProfileId/assignments"
$Existing = @(New-GraphGETRequest -uri $BaseUri -tenantid $TenantFilter)

if ($AssignTo -eq 'AllDevices') {
$AlreadyAssigned = $Existing | Where-Object { $_.target.'@odata.type' -eq '#microsoft.graph.allDevicesAssignmentTarget' }
if ($AlreadyAssigned) {
$Result = "Profile $ProfileName is already assigned to all devices"
} else {
$Body = '{"target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}}'
$null = New-GraphPOSTRequest -uri $BaseUri -tenantid $TenantFilter -type POST -body $Body
Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Assigned autopilot profile $ProfileName to all devices" -Sev 'Info'
$Result = "Successfully assigned profile $ProfileName to all devices"
}
} elseif ($AssignTo -eq 'RemoveAll') {
if ($Existing.Count -eq 0) {
$Result = "Profile $ProfileName has no assignments to remove"
} else {
$Removed = 0
foreach ($Assignment in $Existing) {
$null = New-GraphPOSTRequest -uri "$BaseUri/$($Assignment.id)" -tenantid $TenantFilter -type DELETE
$Removed++
}
Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Removed all $Removed assignment(s) from autopilot profile $ProfileName" -Sev 'Info'
$Result = "Successfully removed all $Removed assignment(s) from profile $ProfileName"
}
} elseif ($AssignTo -eq 'RemoveGroups') {
$GroupIds = @(
$Request.Body.GroupIds | ForEach-Object {
if ($_ -is [string]) { $_.Trim() } elseif ($_ -and $_.value) { $_.value }
} | Where-Object { $_ }
)
if ($GroupIds.Count -eq 0) { throw 'At least one assignment is required' }

$Removed = 0
foreach ($Assignment in $Existing) {
$TargetId = if ($Assignment.target.'@odata.type' -eq '#microsoft.graph.groupAssignmentTarget') {
$Assignment.target.groupId
} elseif ($Assignment.target.'@odata.type' -eq '#microsoft.graph.allDevicesAssignmentTarget') {
'allDevices'
}
if ($TargetId -and $GroupIds -contains $TargetId) {
$null = New-GraphPOSTRequest -uri "$BaseUri/$($Assignment.id)" -tenantid $TenantFilter -type DELETE
$Removed++
}
}
if ($Removed -gt 0) {
Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Removed $Removed assignment(s) from autopilot profile $ProfileName" -Sev 'Info'
$Result = "Successfully removed $Removed assignment(s) from profile $ProfileName"
} else {
$Result = "No matching assignments found to remove from profile $ProfileName"
}
} else {
# Accept both bare strings and { value, label } option objects
$GroupIds = @(
$Request.Body.GroupIds | ForEach-Object {
if ($_ -is [string]) { $_.Trim() } elseif ($_ -and $_.value) { $_.value }
} | Where-Object { $_ }
)
if ($GroupIds.Count -eq 0) { throw 'At least one group ID is required' }

$ExistingGroupIds = @($Existing |
Where-Object { $_.target.'@odata.type' -eq '#microsoft.graph.groupAssignmentTarget' } |
ForEach-Object { $_.target.groupId })

$Created = [System.Collections.Generic.List[string]]::new()
foreach ($GroupId in $GroupIds) {
if ($ExistingGroupIds -contains $GroupId) { continue }
$Body = @{
target = @{
'@odata.type' = '#microsoft.graph.groupAssignmentTarget'
groupId = $GroupId
}
} | ConvertTo-Json -Depth 5 -Compress
$null = New-GraphPOSTRequest -uri $BaseUri -tenantid $TenantFilter -type POST -body $Body
$Created.Add($GroupId)
}

if ($Created.Count -gt 0) {
Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Assigned autopilot profile $ProfileName to group(s): $($Created -join ', ')" -Sev 'Info'
$Result = "Successfully assigned profile $ProfileName to $($Created.Count) group(s)"
} else {
$Result = "Profile $ProfileName is already assigned to all specified groups"
}
}

$StatusCode = [HttpStatusCode]::OK
} catch {
$ErrorMessage = Get-CippException -Exception $_
Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to assign autopilot profile $ProfileName`: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage
$Result = "Failed to assign profile: $($ErrorMessage.NormalizedError)"
$StatusCode = [HttpStatusCode]::InternalServerError
}

return ([HttpResponseContext]@{
StatusCode = $StatusCode
Body = @{ 'Results' = $Result }
})
}
Loading
Loading