Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
b34d7e2
Add JIT group membership support and fix directory role activation
Mar 15, 2026
2637204
Fix expected value type for comparison
sw-bencarrier Mar 17, 2026
65ac58a
Remove duplicate CurrentValue declaration
sw-bencarrier Mar 17, 2026
0051057
feat: add script to regenerate intuneCollection.json
kris6673 Mar 17, 2026
3ef2df3
chore: update intuneCollection.json
kris6673 Mar 17, 2026
094ff2b
Replace += usage
sw-bencarrier Mar 18, 2026
1db8de5
fix: improve error handling and logging for webhook alerts
JohnDuprey Mar 18, 2026
63d3b00
fix(add-user): separate username copyField from display label
kris6673 Mar 18, 2026
a01ce89
Test with split data queries.
KelvinTegelaar Mar 18, 2026
433ef94
Merge branch 'dev' of https://github.com/KelvinTegelaar/CIPP-API into…
KelvinTegelaar Mar 18, 2026
c3dbea0
fix: omit userType from Autopilot OOBE settings in self-deploying mode
ZenTopBrandon Mar 18, 2026
5dce35e
add some profiling
KelvinTegelaar Mar 18, 2026
d184e34
groups
KelvinTegelaar Mar 18, 2026
037ac58
feat: added remaining options for Teams Global Meeting Policy standard
RSI-KaleGraybill Mar 18, 2026
0555087
v up in case we hf
KelvinTegelaar Mar 18, 2026
e04ae5c
feat: add ability to edit existing User Default Templates
Mar 18, 2026
cf748f6
Merge pull request #1926 from RSI-KaleGraybill/dev
KelvinTegelaar Mar 18, 2026
379e664
Merge pull request #1921 from kris6673/username-clipboard-label
KelvinTegelaar Mar 18, 2026
c7fc92d
Merge pull request #1925 from luimen6/feat/edit-user-default-templates
KelvinTegelaar Mar 18, 2026
f337555
Merge pull request #1904 from luimen6/feat/jit-group-membership
KelvinTegelaar Mar 18, 2026
2d96010
Merge pull request #1917 from benzzc/standard-comparison-fix
KelvinTegelaar Mar 18, 2026
1f03dc4
Merge pull request #1920 from kris6673/intunecollection-update
KelvinTegelaar Mar 18, 2026
5ae9927
Merge pull request #1923 from ZenTopBrandon/bug/autopilot-profile-sta…
KelvinTegelaar Mar 18, 2026
83fd3e7
fix: enable MDE connector before applying settings
JohnDuprey Mar 18, 2026
79d9ca6
fix: include headers in raw response output
JohnDuprey Mar 18, 2026
e82aaf4
feat: add date range filtering for incidents
kris6673 Mar 18, 2026
9178206
Improvements to mailbox cache sizes
KelvinTegelaar Mar 18, 2026
36ab59b
Merge branch 'dev' of https://github.com/KelvinTegelaar/CIPP-API into…
KelvinTegelaar Mar 18, 2026
1db85d2
CalCachedFolderCount - faster processing by killing at least 1 request.
KelvinTegelaar Mar 18, 2026
d5e686f
Use Graph lookup and update mail contact
Zacgoose Mar 19, 2026
5918e13
fix: improve MDE Connector handling and update compliance settings
JohnDuprey Mar 19, 2026
07f4754
Merge branch 'dev' of https://github.com/KelvinTegelaar/CIPP-API into…
JohnDuprey Mar 19, 2026
8c542e8
Fix for repoting outlook button sendto email
Zacgoose Mar 19, 2026
b45cbfb
Updates standard to use graph where possible and deprecate remediatio…
Zacgoose Mar 19, 2026
742fc67
fix: Get-CIPPAlertExpiringLicenses.ps1
bastiaanvandenbussche Mar 19, 2026
973b5a1
Update Set-CIPPMailboxRule.ps1
TecharyJames Mar 19, 2026
5834025
Update Set-CIPPMailboxRule.ps1
TecharyJames Mar 19, 2026
f26d398
Updates standard to use graph where possible and deprecate remediatio…
KelvinTegelaar Mar 19, 2026
2da43f2
Fix: Reporting outlook button sendto email standard (#1930)
KelvinTegelaar Mar 19, 2026
0766fc1
Use Graph lookup and update mail contact (#1929)
KelvinTegelaar Mar 19, 2026
d81f98c
Fix/bec mailbox rules (#1934)
KelvinTegelaar Mar 19, 2026
deddfc2
fix: Get-CIPPAlertExpiringLicenses.ps1 (#1933)
KelvinTegelaar Mar 19, 2026
852a72d
feat: Add date range filtering for incidents (#1927)
KelvinTegelaar Mar 19, 2026
2985129
fix: Update troubleshooting links in GDAP relationship checks
bmsimp Mar 19, 2026
5250fc3
fix: Update environment variable retrieval for orchestrator and offlo…
JohnDuprey Mar 19, 2026
fc0c864
fix: Remove extraneous backticks
kris6673 Mar 19, 2026
3ce505b
refactor: massive changes handle with care (#1937)
KelvinTegelaar Mar 19, 2026
213cda2
Update troubleshooting links in GDAP relationship checks (#1936)
KelvinTegelaar Mar 19, 2026
0e246b2
v up
KelvinTegelaar Mar 19, 2026
951d2db
Dev to hf (#1939)
KelvinTegelaar Mar 19, 2026
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
66 changes: 39 additions & 27 deletions Modules/CIPPCore/Public/Alerts/Get-CIPPAlertExpiringLicenses.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ function Get-CIPPAlertExpiringLicenses {
$InputValue,
$TenantFilter
)

try {
# Parse input parameters - default to 30 days if not specified
# Support both old format (direct value) and new format (object with properties)
Expand All @@ -22,41 +23,52 @@ function Get-CIPPAlertExpiringLicenses {
$UnassignedOnly = $false
}

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

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

foreach ($Term in $TermData) {
if ($Term.DaysUntilRenew -lt $DaysThreshold -and $Term.DaysUntilRenew -gt 0) {
$Message = if ($UnassignedOnly) {
"$($_.License) has $UnassignedCount unassigned license(s) expiring in $($Term.DaysUntilRenew) days. The estimated term is $($Term.Term)"
} else {
"$($_.License) will expire in $($Term.DaysUntilRenew) days. The estimated term is $($Term.Term)"
}
# If unassigned only filter is enabled, skip licenses with no unassigned units
if ($UnassignedOnly -and $UnassignedCount -le 0) {
return
}

# FIX: term rows are in TermInfo on the overview object
$TermData = @($_.TermInfo)

Write-Host $Message
[PSCustomObject]@{
Message = $Message
License = $_.License
SkuId = $_.skuId
DaysUntilRenew = $Term.DaysUntilRenew
Term = $Term.Term
Status = $Term.Status
TotalLicenses = $Term.TotalLicenses
CountUsed = $_.CountUsed
CountAvailable = $UnassignedCount
NextLifecycle = $Term.NextLifecycle
Tenant = $_.Tenant
foreach ($Term in $TermData) {
$DaysUntilRenew = [int]$Term.DaysUntilRenew

if ($DaysUntilRenew -lt $DaysThreshold -and $DaysUntilRenew -gt 0) {

$Message = if ($UnassignedOnly) {
"$($_.License) has $UnassignedCount unassigned license(s) expiring in $DaysUntilRenew days. The estimated term is $($Term.Term)"
} else {
"$($_.License) will expire in $DaysUntilRenew days. The estimated term is $($Term.Term)"
}

[PSCustomObject]@{
Message = $Message
License = $_.License
SkuId = $_.skuId
DaysUntilRenew = $DaysUntilRenew
Term = $Term.Term
Status = $Term.Status
TotalLicenses = $Term.TotalLicenses
CountUsed = $_.CountUsed
CountAvailable = $UnassignedCount
NextLifecycle = $Term.NextLifecycle
Tenant = $_.Tenant
}
}
}
}
}
)

Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData

} catch {
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -error $_
throw
}
}
151 changes: 151 additions & 0 deletions Modules/CIPPCore/Public/Enable-CIPPMDEConnector.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
function Enable-CIPPMDEConnector {
<#
.SYNOPSIS
Provisions the Microsoft Defender for Endpoint Intune connector for a tenant.
.DESCRIPTION
Checks whether the MDE mobile threat defense connector (partnerState) is already 'available' or 'enabled'.
If not, iterates through regional MDE API portal endpoints until one succeeds, then verifies
the connector state afterwards. Endpoints are ordered so that the tenant's likely region
(based on org countryLetterCode) is tried first.
.PARAMETER TenantFilter
The tenant domain or ID to provision the connector for.
.FUNCTIONALITY
Internal
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$TenantFilter
)

# MDE connector ID is fixed across all tenants
$ConnectorId = 'fc780465-2017-40d4-a0c5-307022471b92'
$ConnectorUri = "https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/$ConnectorId"

# All known regional provisioning endpoints
$AllEndpoints = @(
'mde-rsp-apiportal-prd-eus.securitycenter.windows.com'
'mde-rsp-apiportal-prd-eus3.securitycenter.windows.com'
'mde-rsp-apiportal-prd-cus.securitycenter.windows.com'
'mde-rsp-apiportal-prd-cus3.securitycenter.windows.com'
'mde-rsp-apiportal-prd-weu.securitycenter.windows.com'
'mde-rsp-apiportal-prd-weu3.securitycenter.windows.com'
'mde-rsp-apiportal-prd-neu.securitycenter.windows.com'
'mde-rsp-apiportal-prd-neu3.securitycenter.windows.com'
'mde-rsp-apiportal-prd-uks.securitycenter.windows.com'
'mde-rsp-apiportal-prd-ukw.securitycenter.windows.com'
'mde-rsp-apiportal-prd-aue.securitycenter.windows.com'
'mde-rsp-apiportal-prd-aus.securitycenter.windows.com'
'mde-rsp-apiportal-prd-aec0a.securitycenter.windows.com'
'mde-rsp-apiportal-prd-aen0a.securitycenter.windows.com'
'mde-rsp-apiportal-prd-ins0a.securitycenter.windows.com'
'mde-rsp-apiportal-prd-inc0a.securitycenter.windows.com'
'mde-rsp-apiportal-prd-sww0a.securitycenter.windows.com'
'mde-rsp-apiportal-prd-swn0a.securitycenter.windows.com'
)

# Country code -> likely regional endpoint prefixes (used to prioritize, not restrict)
$RegionPriority = @{
'US' = @('eus', 'eus3', 'cus', 'cus3')
'CA' = @('eus', 'eus3', 'cus', 'cus3')
'GB' = @('uks', 'ukw')
'AU' = @('aue', 'aus', 'aec0a', 'aen0a')
'IN' = @('ins0a', 'inc0a')
'SE' = @('sww0a', 'swn0a')
'DE' = @('weu', 'weu3')
'FR' = @('weu', 'weu3')
'NL' = @('weu', 'weu3')
'BE' = @('weu', 'weu3')
'AT' = @('weu', 'weu3')
'CH' = @('weu', 'weu3')
'IE' = @('neu', 'neu3')
'FI' = @('neu', 'neu3')
'NO' = @('neu', 'neu3')
'DK' = @('neu', 'neu3')
}

# Check current connector state
try {
$ConnectorState = New-GraphGetRequest -uri $ConnectorUri -tenantid $TenantFilter
} catch {
$ErrorMessage = Get-CippException -Exception $_
Write-LogMessage -API 'MDEConnector' -tenant $TenantFilter -message "Failed to retrieve MDE connector state. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage
throw "Failed to retrieve MDE connector state for $TenantFilter. Error: $($ErrorMessage.NormalizedError)"
}

if ($ConnectorState.partnerState -in @('available', 'enabled')) {
Write-LogMessage -API 'MDEConnector' -tenant $TenantFilter -message 'MDE Intune connector is already in available state.' -Sev Info
return [PSCustomObject]@{
Success = $true
AlreadyDone = $true
PartnerState = $ConnectorState.partnerState
}
}

# Build a prioritized endpoint list based on tenant country
$PrioritizedEndpoints = [System.Collections.Generic.List[string]]::new()
try {
$OrgInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $TenantFilter
$CountryCode = $OrgInfo.countryLetterCode
if ($CountryCode -and $RegionPriority.ContainsKey($CountryCode)) {
$PrefixHints = $RegionPriority[$CountryCode]
foreach ($endpoint in $AllEndpoints) {
foreach ($prefix in $PrefixHints) {
if ($endpoint -like "*-prd-$prefix.*") {
$PrioritizedEndpoints.Add($endpoint)
break
}
}
}
}
Write-Information "MDE connector provisioning for $TenantFilter (country: $CountryCode): prioritized $($PrioritizedEndpoints.Count) regional endpoint(s)"
} catch {
Write-Information "Could not retrieve org country for $TenantFilter - will try all endpoints"
}

# Append remaining endpoints that weren't already prioritized
foreach ($endpoint in $AllEndpoints) {
if ($endpoint -notin $PrioritizedEndpoints) {
$PrioritizedEndpoints.Add($endpoint)
}
}

# Try each endpoint until one succeeds
$ProvisionBody = '{"timeout":60000}'
$ProvisionScope = 'https://api.securitycenter.windows.com/.default'
$SuccessfulEndpoint = $null

foreach ($endpoint in $PrioritizedEndpoints) {
$ProvisionUri = "https://$endpoint/api/cloud/portal/onboarding/intune/provision"
try {
Write-Information "Attempting MDE provisioning for $TenantFilter via $endpoint"
$null = New-GraphPOSTRequest -uri $ProvisionUri -tenantid $TenantFilter -body $ProvisionBody -scope $ProvisionScope
$SuccessfulEndpoint = $endpoint
Write-LogMessage -API 'MDEConnector' -tenant $TenantFilter -message "MDE Intune connector provisioned successfully via $endpoint" -Sev Info
break
} catch {
$ErrorMessage = Get-CippException -Exception $_
Write-Information "Endpoint $endpoint failed for $TenantFilter`: $($ErrorMessage.NormalizedError)"
}
}

if (-not $SuccessfulEndpoint) {
$Msg = "Failed to provision MDE Intune connector for $TenantFilter - all regional endpoints were unsuccessful."
Write-LogMessage -API 'MDEConnector' -tenant $TenantFilter -message $Msg -Sev Error
throw $Msg
}

# Verify the connector state after provisioning
try {
$UpdatedState = New-GraphGetRequest -uri $ConnectorUri -tenantid $TenantFilter
} catch {
$UpdatedState = $null
}

return [PSCustomObject]@{
Success = $UpdatedState.partnerState -in @('available', 'enabled')
AlreadyDone = $false
Endpoint = $SuccessfulEndpoint
PartnerState = $UpdatedState.partnerState
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,42 @@ function Push-ExecCIPPDBCache {
Generic wrapper to execute CIPP DB cache functions

.DESCRIPTION
Executes the specified Set-CIPPDBCache* function with the provided parameters
Supports two modes:
- Grouped collection: When CollectionType is specified, delegates to Invoke-CIPPDBCacheCollection
which runs all cache functions for that license group sequentially in one activity.
- Single type (legacy): When Name is specified, executes a single Set-CIPPDBCache* function.
Used by the HTTP endpoint for on-demand single-type cache refreshes.

.FUNCTIONALITY
Entrypoint
#>
[CmdletBinding()]
param($Item)

$Name = $Item.Name
$TenantFilter = $Item.TenantFilter
$QueueId = $Item.QueueId
$Types = $Item.Types

try {
# Grouped collection mode — runs all cache types for a license category in one activity
if ($Item.CollectionType) {
Write-Information "Collecting $($Item.CollectionType) group for tenant $TenantFilter"

$Params = @{
CollectionType = $Item.CollectionType
TenantFilter = $TenantFilter
}
if ($QueueId) { $Params.QueueId = $QueueId }

$Result = Invoke-CIPPDBCacheCollection @Params

Write-Information "Completed $($Item.CollectionType) group for $TenantFilter - $($Result.Success)/$($Result.Total) succeeded"
return "Successfully executed $($Item.CollectionType) collection for $TenantFilter ($($Result.Success)/$($Result.Total))"
}

# Single-type mode (legacy) — used by HTTP endpoint for on-demand cache refresh
$Name = $Item.Name
$Types = $Item.Types

Write-Information "Collecting $Name for tenant $TenantFilter"

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

} catch {
$ErrorMsg = "Failed to execute $Name for tenant $TenantFilter : $($_.Exception.Message)"
$ErrorMsg = "Failed to execute $(if ($Item.CollectionType) { "$($Item.CollectionType) collection" } else { $Item.Name }) for tenant $TenantFilter : $($_.Exception.Message)"
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message $ErrorMsg -sev Error
throw $ErrorMsg
}
Expand Down
Loading