Skip to content

Commit cdc9e16

Browse files
authored
Merge pull request #553 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 6f594fd + 102f3a8 commit cdc9e16

File tree

5 files changed

+314
-72
lines changed

5 files changed

+314
-72
lines changed

Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,102 @@
1-
function Add-CIPPGroupMember(
2-
$Headers,
3-
[string]$GroupType,
4-
[string]$GroupId,
5-
[string]$Member,
6-
[string]$TenantFilter,
7-
[string]$APIName = 'Add Group Member'
8-
) {
1+
function Add-CIPPGroupMember {
2+
<#
3+
.SYNOPSIS
4+
Adds one or more members to a specified group in Microsoft Graph.
5+
6+
.DESCRIPTION
7+
This function adds one or more members to a specified group in Microsoft Graph, supporting different group types such as Distribution lists and Mail-Enabled Security groups.
8+
9+
.PARAMETER Headers
10+
The headers to include in the request, typically containing authentication tokens. This is supplied automatically by the API
11+
12+
.PARAMETER GroupType
13+
The type of group to which the member is being added, such as Security, Distribution list or Mail-Enabled Security.
14+
15+
.PARAMETER GroupId
16+
The unique identifier of the group to which the member will be added.
17+
18+
.PARAMETER Member
19+
An array of members to add to the group.
20+
21+
.PARAMETER TenantFilter
22+
The tenant identifier to filter the request.
23+
24+
.PARAMETER APIName
25+
The name of the API operation being performed. Defaults to 'Add Group Member'.
26+
#>
27+
[CmdletBinding()]
28+
param(
29+
$Headers,
30+
[string]$GroupType,
31+
[string]$GroupId,
32+
[string[]]$Member,
33+
[string]$TenantFilter,
34+
[string]$APIName = 'Add Group Member'
35+
)
936
try {
1037
if ($Member -like '*#EXT#*') { $Member = [System.Web.HttpUtility]::UrlEncode($Member) }
11-
$MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Member)" -tenantid $TenantFilter).id
12-
$AddMemberBody = "{ `"[email protected]`": $(ConvertTo-Json @($MemberIDs)) }"
38+
$ODataBindString = 'https://graph.microsoft.com/v1.0/directoryObjects/{0}'
39+
$Requests = foreach ($m in $Member) {
40+
@{
41+
id = $m
42+
url = "users/$($m)?`$select=id,userPrincipalName"
43+
method = 'GET'
44+
}
45+
}
46+
$Users = New-GraphBulkRequest -Requests @($Requests) -tenantid $TenantFilter
47+
1348
if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') {
14-
$Params = @{ Identity = $GroupId; Member = $Member; BypassSecurityGroupManagerCheck = $true }
15-
$null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-DistributionGroupMember' -cmdParams $Params -UseSystemMailbox $true
49+
$ExoBulkRequests = [System.Collections.Generic.List[object]]::new()
50+
$ExoLogs = [System.Collections.Generic.List[object]]::new()
51+
52+
foreach ($User in $Users) {
53+
$Params = @{ Identity = $GroupId; Member = $User.body.userPrincipalName; BypassSecurityGroupManagerCheck = $true }
54+
$ExoBulkRequests.Add(@{
55+
CmdletInput = @{
56+
CmdletName = 'Add-DistributionGroupMember'
57+
Parameters = $Params
58+
}
59+
})
60+
$ExoLogs.Add(@{
61+
message = "Added member $($User.body.userPrincipalName) to $($GroupId) group"
62+
target = $User.body.userPrincipalName
63+
})
64+
}
65+
66+
if ($ExoBulkRequests.Count -gt 0) {
67+
$RawExoRequest = New-ExoBulkRequest -tenantid $TenantFilter -cmdletArray @($ExoBulkRequests)
68+
$LastError = $RawExoRequest | Select-Object -Last 1
69+
70+
foreach ($ExoError in $LastError.error) {
71+
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $ExoError -Sev 'Error'
72+
throw $ExoError
73+
}
74+
75+
foreach ($ExoLog in $ExoLogs) {
76+
$ExoError = $LastError | Where-Object { $ExoLog.target -in $_.target -and $_.error }
77+
if (!$LastError -or ($LastError.error -and $LastError.target -notcontains $ExoLog.target)) {
78+
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $ExoLog.message -Sev 'Info'
79+
}
80+
}
81+
}
1682
} else {
83+
$MemberIDs = foreach ($User in $Users) {
84+
$ODataBindString -f $User.body.id
85+
}
86+
$AddMembers = @{
87+
'[email protected]' = @($MemberIDs)
88+
}
89+
$AddMemberBody = ConvertTo-Json -InputObject $AddMembers
1790
$null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)" -tenantid $TenantFilter -type patch -body $AddMemberBody -Verbose
1891
}
19-
$Results = "Successfully added user $($Member) to $($GroupId)."
92+
$UserList = ($Users.body.userPrincipalName -join ', ')
93+
$Results = "Successfully added user $UserList to $($GroupId)."
2094
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -Sev 'Info'
2195
return $Results
2296
} catch {
2397
$ErrorMessage = Get-CippException -Exception $_
24-
$Results = "Failed to add user $($Member) to $($GroupId) - $($ErrorMessage.NormalizedError)"
98+
$UserList = if ($Users) { ($Users.body.userPrincipalName -join ', ') } else { ($Member -join ', ') }
99+
$Results = "Failed to add user $UserList to $($GroupId) - $($ErrorMessage.NormalizedError)"
25100
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -Sev 'error' -LogData $ErrorMessage
26101
throw $Results
27102
}

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-AddScheduledItem.ps1

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ function Invoke-AddScheduledItem {
3737
DesiredStartTime = $Request.Body.DesiredStartTime
3838
}
3939
$Result = Add-CIPPScheduledTask @ScheduledTask
40-
Write-LogMessage -headers $Request.Headers -API $APINAME -message $Result -Sev 'Info'
4140
}
4241
return ([HttpResponseContext]@{
4342
StatusCode = [HttpStatusCode]::OK

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,34 +28,52 @@ function Invoke-ExecCAExclusion {
2828
}
2929
}
3030

31-
$Policy = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)?`$select=id,displayName" -tenantid $TenantFilter -asApp $true
31+
$Policy = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)?`$select=id,displayName,conditions" -tenantid $TenantFilter -asApp $true
3232

3333
if (-not $Policy) {
3434
throw "Policy with ID $PolicyId not found in tenant $TenantFilter."
3535
}
3636

37+
$SecurityGroups = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$select=id,displayName&`$filter=securityEnabled eq true and mailEnabled eq false&`$count=true" -tenantid $TenantFilter
38+
$VacationGroup = $SecurityGroups | Where-Object { $_.displayName -contains "Vacation Exclusion - $($Policy.displayName)" }
39+
40+
if (!$VacationGroup) {
41+
Write-Information "Creating vacation group: Vacation Exclusion - $($Policy.displayName)"
42+
$Guid = [guid]::NewGuid().ToString()
43+
$GroupObject = @{
44+
groupType = 'generic'
45+
displayName = "Vacation Exclusion - $($Policy.displayName)"
46+
username = "vacation$Guid"
47+
securityEnabled = $true
48+
}
49+
$NewGroup = New-CIPPGroup -GroupObject $GroupObject -TenantFilter $TenantFilter -APIName 'Invoke-ExecCAExclusion'
50+
$GroupId = $NewGroup.GroupId
51+
} else {
52+
Write-Information "Using existing vacation group: $($VacationGroup.displayName)"
53+
$GroupId = $VacationGroup.id
54+
}
55+
56+
if ($Policy.conditions.users.excludeGroups -notcontains $GroupId) {
57+
Set-CIPPCAExclusion -TenantFilter $TenantFilter -ExclusionType 'Add' -PolicyId $PolicyId -Groups @{ value = @($GroupId); addedFields = @{ displayName = @("CIPP-Vacation-$($Policy.displayName)") } } -Headers $Headers
58+
}
59+
3760
$PolicyName = $Policy.displayName
3861
if ($Request.Body.vacation -eq 'true') {
3962
$StartDate = $Request.Body.StartDate
4063
$EndDate = $Request.Body.EndDate
4164

4265
$Parameters = [PSCustomObject]@{
43-
ExclusionType = 'Add'
44-
PolicyId = $PolicyId
45-
}
46-
47-
if ($Users) {
48-
$Parameters | Add-Member -NotePropertyName Users -NotePropertyValue $Users
49-
} else {
50-
$Parameters | Add-Member -NotePropertyName UserID -NotePropertyValue $UserID
66+
GroupType = 'Security'
67+
GroupId = $GroupId
68+
Member = $Users.addedFields.userPrincipalName ?? $Users.value ?? $Users ?? $UserID
5169
}
5270

5371
$TaskBody = [pscustomobject]@{
5472
TenantFilter = $TenantFilter
5573
Name = "Add CA Exclusion Vacation Mode: $PolicyName"
5674
Command = @{
57-
value = 'Set-CIPPCAExclusion'
58-
label = 'Set-CIPPCAExclusion'
75+
value = 'Add-CIPPGroupMember'
76+
label = 'Add-CIPPGroupMember'
5977
}
6078
Parameters = [pscustomobject]$Parameters
6179
ScheduledTime = $StartDate
@@ -65,7 +83,10 @@ function Invoke-ExecCAExclusion {
6583

6684
Add-CIPPScheduledTask -Task $TaskBody -hidden $false
6785
#Removal of the exclusion
68-
$TaskBody.Parameters.ExclusionType = 'Remove'
86+
$TaskBody.Command = @{
87+
label = 'Remove-CIPPGroupMember'
88+
value = 'Remove-CIPPGroupMember'
89+
}
6990
$TaskBody.Name = "Remove CA Exclusion Vacation Mode: $PolicyName"
7091
$TaskBody.ScheduledTime = $EndDate
7192
Add-CIPPScheduledTask -Task $TaskBody -hidden $false

Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,125 @@
1-
function Remove-CIPPGroupMember(
2-
$Headers,
3-
[string]$GroupType,
4-
[string]$GroupId,
5-
[string]$Member,
6-
[string]$TenantFilter,
7-
[string]$APIName = 'Remove Group Member'
8-
) {
1+
function Remove-CIPPGroupMember {
2+
<#
3+
.SYNOPSIS
4+
Removes members from a Microsoft 365 group.
5+
6+
.DESCRIPTION
7+
Removes one or more members from Security Groups, Distribution Groups, or Mail-Enabled Security Groups.
8+
Uses bulk request operations for Exchange groups to improve performance.
9+
10+
.PARAMETER Headers
11+
The headers for the API request, typically containing authentication information.
12+
13+
.PARAMETER TenantFilter
14+
The tenant identifier for the target tenant.
15+
16+
.PARAMETER GroupType
17+
The type of group. Valid values: 'Distribution list', 'Mail-Enabled Security', or standard security groups.
18+
19+
.PARAMETER GroupId
20+
The unique identifier (GUID or name) of the group.
21+
22+
.PARAMETER Member
23+
An array of member identifiers (user GUIDs or UPNs) to remove from the group.
24+
25+
.PARAMETER APIName
26+
The API operation name for logging purposes. Default: 'Remove Group Member'.
27+
28+
.EXAMPLE
29+
Remove-CIPPGroupMember -Headers $Headers -TenantFilter 'contoso.onmicrosoft.com' -GroupType 'Distribution list' -GroupId 'Sales-DL' -Member @('[email protected]', '[email protected]') -APIName 'Remove DL Members'
30+
31+
.EXAMPLE
32+
Remove-CIPPGroupMember -Headers $Headers -TenantFilter 'contoso.onmicrosoft.com' -GroupType 'Security' -GroupId '12345-guid' -Member @('user1-guid')
33+
#>
34+
[CmdletBinding()]
35+
param(
36+
[Parameter(Mandatory = $true)]
37+
[string]$TenantFilter,
38+
39+
[Parameter(Mandatory = $true)]
40+
[string]$GroupType,
41+
42+
[Parameter(Mandatory = $true)]
43+
[string]$GroupId,
44+
45+
[Parameter(Mandatory = $true)]
46+
[string[]]$Member,
47+
48+
[Parameter(Mandatory = $false)]
49+
[string]$APIName = 'Remove Group Member',
50+
51+
$Headers
52+
)
53+
954
try {
55+
$Requests = foreach ($m in $Member) {
56+
if ($m -like '*#EXT#*') { $m = [System.Web.HttpUtility]::UrlEncode($m) }
57+
@{
58+
id = $m
59+
url = "users/$($m)?`$select=id,userPrincipalName"
60+
method = 'GET'
61+
}
62+
}
63+
$Users = New-GraphBulkRequest -Requests @($Requests) -tenantid $TenantFilter
64+
1065
if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') {
11-
$Params = @{ Identity = $GroupId; Member = $Member; BypassSecurityGroupManagerCheck = $true }
12-
$null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-DistributionGroupMember' -cmdParams $Params -UseSystemMailbox $true
66+
$ExoBulkRequests = [System.Collections.Generic.List[object]]::new()
67+
$ExoLogs = [System.Collections.Generic.List[object]]::new()
68+
69+
foreach ($User in $Users) {
70+
$Params = @{ Identity = $GroupId; Member = $User.body.userPrincipalName; BypassSecurityGroupManagerCheck = $true }
71+
$ExoBulkRequests.Add(@{
72+
CmdletInput = @{
73+
CmdletName = 'Remove-DistributionGroupMember'
74+
Parameters = $Params
75+
}
76+
})
77+
$ExoLogs.Add(@{
78+
message = "Removed member $($User.body.userPrincipalName) from $($GroupId) group"
79+
target = $User.body.userPrincipalName
80+
})
81+
}
82+
83+
if ($ExoBulkRequests.Count -gt 0) {
84+
$RawExoRequest = New-ExoBulkRequest -tenantid $TenantFilter -cmdletArray @($ExoBulkRequests)
85+
$LastError = $RawExoRequest | Select-Object -Last 1
86+
87+
foreach ($ExoError in $LastError.error) {
88+
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $ExoError -Sev 'Error'
89+
throw $ExoError
90+
}
91+
92+
foreach ($ExoLog in $ExoLogs) {
93+
$ExoError = $LastError | Where-Object { $ExoLog.target -in $_.target -and $_.error }
94+
if (!$LastError -or ($LastError.error -and $LastError.target -notcontains $ExoLog.target)) {
95+
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $ExoLog.message -Sev 'Info'
96+
}
97+
}
98+
}
1399
} else {
14-
if ($Member -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') {
15-
Write-Information "Member $Member is a GUID, proceeding with removal."
16-
} else {
17-
Write-Information "Member $Member is not a GUID, attempting to resolve to object ID."
18-
if ($Member -like '*#EXT#*') { $Member = [System.Web.HttpUtility]::UrlEncode($Member) }
19-
$UserObject = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Member)?`$select=id" -tenantid $TenantFilter
20-
if ($null -eq $UserObject.id) {
21-
throw "Could not resolve user $Member to an object ID."
100+
$RemovalRequests = foreach ($User in $Users) {
101+
@{
102+
id = $User.body.id
103+
method = 'DELETE'
104+
url = "/groups/$($GroupId)/members/$($User.body.id)/`$ref"
105+
}
106+
}
107+
$RemovalResults = New-GraphBulkRequest -tenantid $TenantFilter -Requests @($RemovalRequests)
108+
foreach ($Result in $RemovalResults) {
109+
if ($Result.status -ne 204) {
110+
throw "Failed to remove member $($Result.id): $($Result.body.error.message)"
22111
}
23-
$Member = $UserObject.id
24-
Write-Information "Resolved member to object ID: $Member"
25112
}
26-
$null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)/members/$($Member)/`$ref" -tenantid $TenantFilter -type DELETE -body '{}' -Verbose
27113
}
28-
$Results = "Successfully removed user $($Member) from $($GroupId)."
114+
$UserList = ($Users.body.userPrincipalName -join ', ')
115+
$Results = "Successfully removed user $UserList from $($GroupId)."
29116
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -Sev Info
30117
return $Results
31118

32119
} catch {
33120
$ErrorMessage = Get-CippException -Exception $_
34-
$Results = "Failed to remove user $($Member) from $($GroupId): $($ErrorMessage.NormalizedError)"
121+
$UserList = if ($Users) { ($Users.body.userPrincipalName -join ', ') } else { ($Member -join ', ') }
122+
$Results = "Failed to remove user $UserList from $($GroupId): $($ErrorMessage.NormalizedError)"
35123
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -Sev Error -LogData $ErrorMessage
36124
throw $Results
37125
}

0 commit comments

Comments
 (0)