Skip to content

Commit deffbb0

Browse files
authored
Merge pull request #48 from KelvinTegelaar/master
[pull] master from KelvinTegelaar:master
2 parents 4846342 + 951d2db commit deffbb0

33 files changed

+67003
-40857
lines changed

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

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ function Get-CIPPAlertExpiringLicenses {
1010
$InputValue,
1111
$TenantFilter
1212
)
13+
1314
try {
1415
# Parse input parameters - default to 30 days if not specified
1516
# Support both old format (direct value) and new format (object with properties)
@@ -22,41 +23,52 @@ function Get-CIPPAlertExpiringLicenses {
2223
$UnassignedOnly = $false
2324
}
2425

25-
$AlertData = Get-CIPPLicenseOverview -TenantFilter $TenantFilter | ForEach-Object {
26-
$UnassignedCount = [int]$_.CountAvailable
26+
$AlertData = @(
27+
Get-CIPPLicenseOverview -TenantFilter $TenantFilter | ForEach-Object {
2728

28-
# If unassigned only filter is enabled, skip licenses with no unassigned units
29-
if ($UnassignedOnly -and $UnassignedCount -le 0) {
30-
return
31-
}
29+
$UnassignedCount = [int]$_.CountAvailable
3230

33-
foreach ($Term in $TermData) {
34-
if ($Term.DaysUntilRenew -lt $DaysThreshold -and $Term.DaysUntilRenew -gt 0) {
35-
$Message = if ($UnassignedOnly) {
36-
"$($_.License) has $UnassignedCount unassigned license(s) expiring in $($Term.DaysUntilRenew) days. The estimated term is $($Term.Term)"
37-
} else {
38-
"$($_.License) will expire in $($Term.DaysUntilRenew) days. The estimated term is $($Term.Term)"
39-
}
31+
# If unassigned only filter is enabled, skip licenses with no unassigned units
32+
if ($UnassignedOnly -and $UnassignedCount -le 0) {
33+
return
34+
}
35+
36+
# FIX: term rows are in TermInfo on the overview object
37+
$TermData = @($_.TermInfo)
4038

41-
Write-Host $Message
42-
[PSCustomObject]@{
43-
Message = $Message
44-
License = $_.License
45-
SkuId = $_.skuId
46-
DaysUntilRenew = $Term.DaysUntilRenew
47-
Term = $Term.Term
48-
Status = $Term.Status
49-
TotalLicenses = $Term.TotalLicenses
50-
CountUsed = $_.CountUsed
51-
CountAvailable = $UnassignedCount
52-
NextLifecycle = $Term.NextLifecycle
53-
Tenant = $_.Tenant
39+
foreach ($Term in $TermData) {
40+
$DaysUntilRenew = [int]$Term.DaysUntilRenew
41+
42+
if ($DaysUntilRenew -lt $DaysThreshold -and $DaysUntilRenew -gt 0) {
43+
44+
$Message = if ($UnassignedOnly) {
45+
"$($_.License) has $UnassignedCount unassigned license(s) expiring in $DaysUntilRenew days. The estimated term is $($Term.Term)"
46+
} else {
47+
"$($_.License) will expire in $DaysUntilRenew days. The estimated term is $($Term.Term)"
48+
}
49+
50+
[PSCustomObject]@{
51+
Message = $Message
52+
License = $_.License
53+
SkuId = $_.skuId
54+
DaysUntilRenew = $DaysUntilRenew
55+
Term = $Term.Term
56+
Status = $Term.Status
57+
TotalLicenses = $Term.TotalLicenses
58+
CountUsed = $_.CountUsed
59+
CountAvailable = $UnassignedCount
60+
NextLifecycle = $Term.NextLifecycle
61+
Tenant = $_.Tenant
62+
}
5463
}
5564
}
5665
}
57-
}
66+
)
67+
5868
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData
5969

6070
} catch {
71+
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -error $_
72+
throw
6173
}
6274
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
function Enable-CIPPMDEConnector {
2+
<#
3+
.SYNOPSIS
4+
Provisions the Microsoft Defender for Endpoint Intune connector for a tenant.
5+
.DESCRIPTION
6+
Checks whether the MDE mobile threat defense connector (partnerState) is already 'available' or 'enabled'.
7+
If not, iterates through regional MDE API portal endpoints until one succeeds, then verifies
8+
the connector state afterwards. Endpoints are ordered so that the tenant's likely region
9+
(based on org countryLetterCode) is tried first.
10+
.PARAMETER TenantFilter
11+
The tenant domain or ID to provision the connector for.
12+
.FUNCTIONALITY
13+
Internal
14+
#>
15+
[CmdletBinding()]
16+
param(
17+
[Parameter(Mandatory = $true)]
18+
[string]$TenantFilter
19+
)
20+
21+
# MDE connector ID is fixed across all tenants
22+
$ConnectorId = 'fc780465-2017-40d4-a0c5-307022471b92'
23+
$ConnectorUri = "https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/$ConnectorId"
24+
25+
# All known regional provisioning endpoints
26+
$AllEndpoints = @(
27+
'mde-rsp-apiportal-prd-eus.securitycenter.windows.com'
28+
'mde-rsp-apiportal-prd-eus3.securitycenter.windows.com'
29+
'mde-rsp-apiportal-prd-cus.securitycenter.windows.com'
30+
'mde-rsp-apiportal-prd-cus3.securitycenter.windows.com'
31+
'mde-rsp-apiportal-prd-weu.securitycenter.windows.com'
32+
'mde-rsp-apiportal-prd-weu3.securitycenter.windows.com'
33+
'mde-rsp-apiportal-prd-neu.securitycenter.windows.com'
34+
'mde-rsp-apiportal-prd-neu3.securitycenter.windows.com'
35+
'mde-rsp-apiportal-prd-uks.securitycenter.windows.com'
36+
'mde-rsp-apiportal-prd-ukw.securitycenter.windows.com'
37+
'mde-rsp-apiportal-prd-aue.securitycenter.windows.com'
38+
'mde-rsp-apiportal-prd-aus.securitycenter.windows.com'
39+
'mde-rsp-apiportal-prd-aec0a.securitycenter.windows.com'
40+
'mde-rsp-apiportal-prd-aen0a.securitycenter.windows.com'
41+
'mde-rsp-apiportal-prd-ins0a.securitycenter.windows.com'
42+
'mde-rsp-apiportal-prd-inc0a.securitycenter.windows.com'
43+
'mde-rsp-apiportal-prd-sww0a.securitycenter.windows.com'
44+
'mde-rsp-apiportal-prd-swn0a.securitycenter.windows.com'
45+
)
46+
47+
# Country code -> likely regional endpoint prefixes (used to prioritize, not restrict)
48+
$RegionPriority = @{
49+
'US' = @('eus', 'eus3', 'cus', 'cus3')
50+
'CA' = @('eus', 'eus3', 'cus', 'cus3')
51+
'GB' = @('uks', 'ukw')
52+
'AU' = @('aue', 'aus', 'aec0a', 'aen0a')
53+
'IN' = @('ins0a', 'inc0a')
54+
'SE' = @('sww0a', 'swn0a')
55+
'DE' = @('weu', 'weu3')
56+
'FR' = @('weu', 'weu3')
57+
'NL' = @('weu', 'weu3')
58+
'BE' = @('weu', 'weu3')
59+
'AT' = @('weu', 'weu3')
60+
'CH' = @('weu', 'weu3')
61+
'IE' = @('neu', 'neu3')
62+
'FI' = @('neu', 'neu3')
63+
'NO' = @('neu', 'neu3')
64+
'DK' = @('neu', 'neu3')
65+
}
66+
67+
# Check current connector state
68+
try {
69+
$ConnectorState = New-GraphGetRequest -uri $ConnectorUri -tenantid $TenantFilter
70+
} catch {
71+
$ErrorMessage = Get-CippException -Exception $_
72+
Write-LogMessage -API 'MDEConnector' -tenant $TenantFilter -message "Failed to retrieve MDE connector state. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage
73+
throw "Failed to retrieve MDE connector state for $TenantFilter. Error: $($ErrorMessage.NormalizedError)"
74+
}
75+
76+
if ($ConnectorState.partnerState -in @('available', 'enabled')) {
77+
Write-LogMessage -API 'MDEConnector' -tenant $TenantFilter -message 'MDE Intune connector is already in available state.' -Sev Info
78+
return [PSCustomObject]@{
79+
Success = $true
80+
AlreadyDone = $true
81+
PartnerState = $ConnectorState.partnerState
82+
}
83+
}
84+
85+
# Build a prioritized endpoint list based on tenant country
86+
$PrioritizedEndpoints = [System.Collections.Generic.List[string]]::new()
87+
try {
88+
$OrgInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $TenantFilter
89+
$CountryCode = $OrgInfo.countryLetterCode
90+
if ($CountryCode -and $RegionPriority.ContainsKey($CountryCode)) {
91+
$PrefixHints = $RegionPriority[$CountryCode]
92+
foreach ($endpoint in $AllEndpoints) {
93+
foreach ($prefix in $PrefixHints) {
94+
if ($endpoint -like "*-prd-$prefix.*") {
95+
$PrioritizedEndpoints.Add($endpoint)
96+
break
97+
}
98+
}
99+
}
100+
}
101+
Write-Information "MDE connector provisioning for $TenantFilter (country: $CountryCode): prioritized $($PrioritizedEndpoints.Count) regional endpoint(s)"
102+
} catch {
103+
Write-Information "Could not retrieve org country for $TenantFilter - will try all endpoints"
104+
}
105+
106+
# Append remaining endpoints that weren't already prioritized
107+
foreach ($endpoint in $AllEndpoints) {
108+
if ($endpoint -notin $PrioritizedEndpoints) {
109+
$PrioritizedEndpoints.Add($endpoint)
110+
}
111+
}
112+
113+
# Try each endpoint until one succeeds
114+
$ProvisionBody = '{"timeout":60000}'
115+
$ProvisionScope = 'https://api.securitycenter.windows.com/.default'
116+
$SuccessfulEndpoint = $null
117+
118+
foreach ($endpoint in $PrioritizedEndpoints) {
119+
$ProvisionUri = "https://$endpoint/api/cloud/portal/onboarding/intune/provision"
120+
try {
121+
Write-Information "Attempting MDE provisioning for $TenantFilter via $endpoint"
122+
$null = New-GraphPOSTRequest -uri $ProvisionUri -tenantid $TenantFilter -body $ProvisionBody -scope $ProvisionScope
123+
$SuccessfulEndpoint = $endpoint
124+
Write-LogMessage -API 'MDEConnector' -tenant $TenantFilter -message "MDE Intune connector provisioned successfully via $endpoint" -Sev Info
125+
break
126+
} catch {
127+
$ErrorMessage = Get-CippException -Exception $_
128+
Write-Information "Endpoint $endpoint failed for $TenantFilter`: $($ErrorMessage.NormalizedError)"
129+
}
130+
}
131+
132+
if (-not $SuccessfulEndpoint) {
133+
$Msg = "Failed to provision MDE Intune connector for $TenantFilter - all regional endpoints were unsuccessful."
134+
Write-LogMessage -API 'MDEConnector' -tenant $TenantFilter -message $Msg -Sev Error
135+
throw $Msg
136+
}
137+
138+
# Verify the connector state after provisioning
139+
try {
140+
$UpdatedState = New-GraphGetRequest -uri $ConnectorUri -tenantid $TenantFilter
141+
} catch {
142+
$UpdatedState = $null
143+
}
144+
145+
return [PSCustomObject]@{
146+
Success = $UpdatedState.partnerState -in @('available', 'enabled')
147+
AlreadyDone = $false
148+
Endpoint = $SuccessfulEndpoint
149+
PartnerState = $UpdatedState.partnerState
150+
}
151+
}

Modules/CIPPCore/Public/Entrypoints/Activity Triggers/CIPPDBCache/Push-ExecCIPPDBCache.ps1

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,42 @@ function Push-ExecCIPPDBCache {
44
Generic wrapper to execute CIPP DB cache functions
55
66
.DESCRIPTION
7-
Executes the specified Set-CIPPDBCache* function with the provided parameters
7+
Supports two modes:
8+
- Grouped collection: When CollectionType is specified, delegates to Invoke-CIPPDBCacheCollection
9+
which runs all cache functions for that license group sequentially in one activity.
10+
- Single type (legacy): When Name is specified, executes a single Set-CIPPDBCache* function.
11+
Used by the HTTP endpoint for on-demand single-type cache refreshes.
812
913
.FUNCTIONALITY
1014
Entrypoint
1115
#>
1216
[CmdletBinding()]
1317
param($Item)
1418

15-
$Name = $Item.Name
1619
$TenantFilter = $Item.TenantFilter
1720
$QueueId = $Item.QueueId
18-
$Types = $Item.Types
1921

2022
try {
23+
# Grouped collection mode — runs all cache types for a license category in one activity
24+
if ($Item.CollectionType) {
25+
Write-Information "Collecting $($Item.CollectionType) group for tenant $TenantFilter"
26+
27+
$Params = @{
28+
CollectionType = $Item.CollectionType
29+
TenantFilter = $TenantFilter
30+
}
31+
if ($QueueId) { $Params.QueueId = $QueueId }
32+
33+
$Result = Invoke-CIPPDBCacheCollection @Params
34+
35+
Write-Information "Completed $($Item.CollectionType) group for $TenantFilter - $($Result.Success)/$($Result.Total) succeeded"
36+
return "Successfully executed $($Item.CollectionType) collection for $TenantFilter ($($Result.Success)/$($Result.Total))"
37+
}
38+
39+
# Single-type mode (legacy) — used by HTTP endpoint for on-demand cache refresh
40+
$Name = $Item.Name
41+
$Types = $Item.Types
42+
2143
Write-Information "Collecting $Name for tenant $TenantFilter"
2244

2345
# Build the full function name
@@ -53,7 +75,7 @@ function Push-ExecCIPPDBCache {
5375
return "Successfully executed $Name for tenant $TenantFilter"
5476

5577
} catch {
56-
$ErrorMsg = "Failed to execute $Name for tenant $TenantFilter : $($_.Exception.Message)"
78+
$ErrorMsg = "Failed to execute $(if ($Item.CollectionType) { "$($Item.CollectionType) collection" } else { $Item.Name }) for tenant $TenantFilter : $($_.Exception.Message)"
5779
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message $ErrorMsg -sev Error
5880
throw $ErrorMsg
5981
}

0 commit comments

Comments
 (0)