Skip to content

Commit 315584c

Browse files
authored
Merge pull request #994 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 7239dbb + c7cebfe commit 315584c

43 files changed

Lines changed: 475 additions & 77 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertExpiringLicenses.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function Get-CIPPAlertExpiringLicenses {
2424
}
2525

2626
$AlertData = @(
27-
Get-CIPPLicenseOverview -TenantFilter $TenantFilter | ForEach-Object {
27+
Get-CIPPLicenseOverview -TenantFilter $TenantFilter -AlertMode | ForEach-Object {
2828

2929
$UnassignedCount = [int]$_.CountAvailable
3030

Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertOverusedLicenses.ps1

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,23 @@ function Get-CIPPAlertOverusedLicenses {
1313

1414

1515
try {
16-
$LicenseTable = Get-CIPPTable -TableName ExcludedLicenses
17-
$ExcludedSkuList = Get-CIPPAzDataTableEntity @LicenseTable
18-
$AlertData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $TenantFilter | ForEach-Object {
19-
$skuid = $_
20-
foreach ($sku in $skuid) {
21-
if ($sku.skuId -in $ExcludedSkuList.GUID) { continue }
22-
$PrettyName = Convert-SKUname -SkuID $sku.skuId
23-
if (!$PrettyName) { $PrettyName = $sku.skuPartNumber }
24-
if ($sku.prepaidUnits.enabled - $sku.consumedUnits -lt 0) {
25-
[PSCustomObject]@{
26-
Message = "$PrettyName has Overused licenses. Using $($sku.consumedUnits) of $($sku.prepaidUnits.enabled)."
27-
LicenseName = $PrettyName
28-
SkuId = $sku.skuId
29-
SkuPartNumber = $sku.skuPartNumber
30-
ConsumedUnits = $sku.consumedUnits
31-
EnabledUnits = $sku.prepaidUnits.enabled
32-
Tenant = $TenantFilter
33-
}
16+
$AlertData = Get-CIPPLicenseOverview -TenantFilter $TenantFilter -AlertMode | ForEach-Object {
17+
if ([int]$_.CountAvailable -lt 0) {
18+
[PSCustomObject]@{
19+
Message = "$($_.License) has Overused licenses. Using $($_.CountUsed) of $($_.TotalLicenses)."
20+
LicenseName = $_.License
21+
SkuId = $_.skuId
22+
SkuPartNumber = $_.skuPartNumber
23+
ConsumedUnits = $_.CountUsed
24+
EnabledUnits = $_.TotalLicenses
25+
Tenant = $TenantFilter
3426
}
3527
}
3628
}
29+
3730
if ($AlertData) {
3831
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData
3932
}
40-
4133
} catch {
4234
$ErrorMessage = Get-CippException -Exception $_
4335
Write-LogMessage -API 'Alerts' -tenant $TenantFilter -message "Overused Licenses Alert Error occurred: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage

Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertUnusedLicenses.ps1

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,16 @@ function Get-CIPPAlertUnusedLicenses {
1212
)
1313

1414
try {
15-
$LicenseTable = Get-CIPPTable -TableName ExcludedLicenses
16-
$ExcludedSkuList = Get-CIPPAzDataTableEntity @LicenseTable
17-
$AlertData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $TenantFilter | ForEach-Object {
18-
$SkuId = $_
19-
foreach ($sku in $SkuId) {
20-
if ($sku.skuId -in $ExcludedSkuList.GUID) { continue }
21-
$PrettyName = Convert-SKUname -SkuID $sku.skuId
22-
if (!$PrettyName) { $PrettyName = $sku.skuPartNumber }
23-
if ($sku.prepaidUnits.enabled - $sku.consumedUnits -gt 0) {
24-
[PSCustomObject]@{
25-
Message = "$PrettyName has unused licenses. Using $($sku.consumedUnits) of $($sku.prepaidUnits.enabled)."
26-
LicenseName = $PrettyName
27-
SkuId = $sku.skuId
28-
SkuPartNumber = $sku.skuPartNumber
29-
ConsumedUnits = $sku.consumedUnits
30-
EnabledUnits = $sku.prepaidUnits.enabled
31-
Tenant = $TenantFilter
32-
}
15+
$AlertData = Get-CIPPLicenseOverview -TenantFilter $TenantFilter -AlertMode | ForEach-Object {
16+
if ([int]$_.CountAvailable -gt 0) {
17+
[PSCustomObject]@{
18+
Message = "$($_.License) has unused licenses. Using $($_.CountUsed) of $($_.TotalLicenses)."
19+
LicenseName = $_.License
20+
SkuId = $_.skuId
21+
SkuPartNumber = $_.skuPartNumber
22+
ConsumedUnits = $_.CountUsed
23+
EnabledUnits = $_.TotalLicenses
24+
Tenant = $TenantFilter
3325
}
3426
}
3527
}

Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ function Get-CIPPLicenseOverview {
44
param (
55
$TenantFilter,
66
$APIName = 'Get License Overview',
7-
$Headers
7+
$Headers,
8+
[switch]$AlertMode
89
)
910

1011
$Requests = @(
@@ -62,6 +63,16 @@ function Get-CIPPLicenseOverview {
6263
$ExcludedSkuList = Get-CIPPAzDataTableEntity @LicenseTable
6364
}
6465

66+
# In AlertMode, exclude all licenses in the table (both ExcludedEverywhere and alert-only)
67+
# In normal mode, only exclude licenses where ExcludedEverywhere is true (or null for backward compat)
68+
if ($AlertMode) {
69+
$EffectiveExcludedGuids = @($ExcludedSkuList.GUID)
70+
} else {
71+
$EffectiveExcludedGuids = @($ExcludedSkuList | Where-Object {
72+
$null -eq $_.ExcludedEverywhere -or $_.ExcludedEverywhere -eq $true
73+
} | ForEach-Object { $_.GUID })
74+
}
75+
6576
$AllLicensedUsers = @(($Results | Where-Object { $_.id -eq 'licensedUsers' }).body.value) | Sort-Object -Property displayName
6677
$UsersBySku = @{}
6778
foreach ($User in $AllLicensedUsers) {
@@ -109,7 +120,7 @@ function Get-CIPPLicenseOverview {
109120
$GraphRequest = foreach ($singleReq in $RawGraphRequest) {
110121
$skuId = $singleReq.Licenses
111122
foreach ($sku in $skuId) {
112-
if ($sku.skuId -in $ExcludedSkuList.GUID) { continue }
123+
if ($sku.skuId -in $EffectiveExcludedGuids) { continue }
113124
$PrettyNameAdmin = $AdminPortalLicenses | Where-Object { $_.aadSkuId -eq $sku.skuId } | Select-Object -ExpandProperty displayName -First 1
114125
$PrettyNameCSV = ($ConvertTable | Where-Object { $_.guid -eq $sku.skuid }).'Product_Display_Name' | Select-Object -Last 1
115126
$PrettyName = $PrettyNameAdmin ?? $PrettyNameCSV ?? $sku.skuPartNumber

Modules/CIPPCore/Public/New-CIPPGroup.ps1

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,19 @@ function New-CIPPGroup {
192192
} catch {
193193
Write-Warning "Failed to write group creation cache for $($GroupObject.displayName): $($_.Exception.Message)"
194194
}
195+
196+
# Assign licenses for Security groups
197+
if ($NormalizedGroupType -eq 'Generic' -and $GroupObject.licenses) {
198+
$LicenseSkuIds = @($GroupObject.licenses | Where-Object { $_ } | ForEach-Object { $_.value ?? $_ })
199+
if ($LicenseSkuIds.Count -gt 0) {
200+
try {
201+
$null = Set-CIPPGroupLicense -GroupId $GraphRequest.id -GroupName $GroupObject.displayName -AddLicenses $LicenseSkuIds -TenantFilter $TenantFilter -APIName $APIName
202+
} catch {
203+
Write-Warning "Failed to assign licenses to group $($GroupObject.displayName): $($_.Exception.Message)"
204+
}
205+
}
206+
}
207+
195208
if ($GroupObject.subscribeMembers) {
196209
#Waiting for group to become available in Exo.
197210
Start-Sleep -Seconds 10

Modules/CIPPCore/Public/Remove-CIPPGroup.ps1

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ function Remove-CIPPGroup {
1616
return "Successfully Deleted $($GroupType) group $($DisplayName)"
1717

1818
} elseif ($GroupType -eq 'Microsoft 365' -or $GroupType -eq 'Security') {
19+
# Remove any assigned licenses
20+
if ($GroupType -eq 'Security') {
21+
$Group = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/groups/$($ID)?`$select=assignedLicenses" -tenantid $TenantFilter
22+
$CurrentSkus = @($Group.assignedLicenses.skuId | Where-Object { $_ })
23+
if ($CurrentSkus.Count -gt 0) {
24+
$null = Set-CIPPGroupLicense -GroupId $ID -GroupName $DisplayName -RemoveLicenses $CurrentSkus -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName
25+
}
26+
}
1927
$null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/groups/$($ID)" -tenantid $TenantFilter -type Delete -verbose
2028
Write-LogMessage -headers $Headers -API $APINAME -tenant $($TenantFilter) -message "$($DisplayName) Deleted" -Sev 'Info'
2129
return "Successfully Deleted $($GroupType) group $($DisplayName)"
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
function Set-CIPPGroupLicense {
2+
[CmdletBinding()]
3+
param (
4+
[Parameter(Mandatory = $true)][string]$GroupId,
5+
[string]$GroupName,
6+
[array]$AddLicenses = @(),
7+
[array]$RemoveLicenses = @(),
8+
[switch]$Replace,
9+
[Parameter(Mandatory = $true)][string]$TenantFilter,
10+
$Headers,
11+
$APIName = 'Set Group License'
12+
)
13+
14+
$AddLicenses = @(
15+
$AddLicenses |
16+
ForEach-Object { [string]$_ } |
17+
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
18+
)
19+
$RemoveLicenses = @(
20+
$RemoveLicenses |
21+
ForEach-Object { [string]$_ } |
22+
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
23+
)
24+
25+
if ( [string]::IsNullOrWhiteSpace($GroupName)) {
26+
$GroupName = $GroupId
27+
}
28+
29+
# fetch current assigned licenses, calculate the diff and replace with licenses
30+
if ($Replace.IsPresent) {
31+
try {
32+
$Current = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)?`$select=assignedLicenses" -tenantid $TenantFilter
33+
$CurrentSkus = @($Current.assignedLicenses.skuId)
34+
$RemoveLicenses = @($CurrentSkus | Where-Object { $_ -notin $AddLicenses })
35+
$AddLicenses = @($AddLicenses | Where-Object { $_ -notin $CurrentSkus })
36+
} catch {
37+
$ErrorMessage = Get-CippException -Exception $_
38+
Write-LogMessage -API $APIName -tenant $TenantFilter -Headers $Headers -message "Failed to fetch current licenses for group $GroupName. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage
39+
throw "Failed to fetch current licenses for group $GroupName. Error: $($ErrorMessage.NormalizedError)"
40+
}
41+
}
42+
43+
if ($AddLicenses.Count -eq 0 -and $RemoveLicenses.Count -eq 0) {
44+
return @("No license changes required for group $GroupName.")
45+
}
46+
47+
$AddLicensesArray = foreach ($SkuId in $AddLicenses) {
48+
@{ disabledPlans = @(); skuId = $SkuId }
49+
}
50+
$LicenseBody = @{
51+
addLicenses = @($AddLicensesArray)
52+
removeLicenses = @($RemoveLicenses)
53+
} | ConvertTo-Json -Compress -Depth 10
54+
55+
$Results = [System.Collections.Generic.List[string]]::new()
56+
try {
57+
$null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)/assignLicense" -tenantid $TenantFilter -body $LicenseBody -type POST
58+
59+
if ($AddLicenses.Count -gt 0) {
60+
$Message = "Assigned licenses to group $GroupName. Added: $($AddLicenses -join ', ')"
61+
[void]$Results.Add("$Message. It may take 2-5 minutes for changes to apply to members.")
62+
Write-LogMessage -API $APIName -tenant $TenantFilter -Headers $Headers -message $Message -Sev Info
63+
}
64+
if ($RemoveLicenses.Count -gt 0) {
65+
$Message = "Removed licenses from group $GroupName. Removed: $($RemoveLicenses -join ', ')"
66+
[void]$Results.Add("$Message. It may take 2-5 minutes for changes to apply to members.")
67+
Write-LogMessage -API $APIName -tenant $TenantFilter -Headers $Headers -message $Message -Sev Info
68+
}
69+
return $Results
70+
} catch {
71+
$ErrorMessage = Get-CippException -Exception $_
72+
Write-LogMessage -API $APIName -tenant $TenantFilter -Headers $Headers -message "Failed to update licenses for group $GroupName. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage
73+
throw "Failed to update licenses for group $GroupName. Error: $($ErrorMessage.NormalizedError)"
74+
}
75+
}

Modules/CIPPCore/Public/TenantGroups/Get-TenantGroups.ps1

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,14 @@ function Get-TenantGroups {
180180
}
181181

182182
$Results.Add([PSCustomObject]@{
183-
Id = $Group.RowKey
184-
Name = $Group.Name
185-
Description = $Group.Description
186-
GroupType = $Group.GroupType ?? 'static'
187-
RuleLogic = $Group.RuleLogic ?? 'and'
188-
DynamicRules = $Group.DynamicRules ? @($Group.DynamicRules | ConvertFrom-Json) : @()
189-
Members = @($SortedMembers)
183+
Id = $Group.RowKey
184+
Name = $Group.Name
185+
Description = $Group.Description
186+
GroupType = $Group.GroupType ?? 'static'
187+
RuleLogic = $Group.RuleLogic ?? 'and'
188+
ExcludePartnerTenant = [bool]($Group.ExcludePartnerTenant)
189+
DynamicRules = $Group.DynamicRules ? @($Group.DynamicRules | ConvertFrom-Json) : @()
190+
Members = @($SortedMembers)
190191
})
191192
}
192193

Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,15 @@ function Update-CIPPDynamicTenantGroups {
153153
$ScriptBlock = [ScriptBlock]::Create($WhereString)
154154
$MatchingTenants = $TenantObj | Where-Object $ScriptBlock
155155

156+
if ($Group.ExcludePartnerTenant -eq $true -and $env:TenantID) {
157+
$BeforeCount = ($MatchingTenants | Measure-Object).Count
158+
$MatchingTenants = @($MatchingTenants | Where-Object { $_.customerId -ne $env:TenantID })
159+
$RemovedCount = $BeforeCount - ($MatchingTenants | Measure-Object).Count
160+
if ($RemovedCount -gt 0) {
161+
Write-Information "ExcludePartnerTenant is enabled for group '$($Group.Name)'; removed $RemovedCount partner tenant from results"
162+
}
163+
}
164+
156165
Write-Information "Found $($MatchingTenants.Count) matching tenants for group '$($Group.Name)'"
157166

158167
$CurrentMembers = @($AllGroupMembers | Where-Object { $_.GroupId -eq $Group.RowKey })

0 commit comments

Comments
 (0)