diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertVulnerabilities.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertVulnerabilities.ps1 index 7b6c374eecf7..598e84844b34 100644 --- a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertVulnerabilities.ps1 +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertVulnerabilities.ps1 @@ -18,6 +18,8 @@ function Get-CIPPAlertVulnerabilities { $AgeThresholdHours = if ($InputValue.VulnerabilityAgeHours) { [int]$InputValue.VulnerabilityAgeHours } else { 0 } # Autocomplete inputs store value in .value subproperty $CVSSSeverity = if ($InputValue.CVSSSeverity.value) { $InputValue.CVSSSeverity.value } else { 'low' } + # Switch inputs are stored as boolean + $NewerThanMode = [bool]($InputValue.NewerThanMode) # Multi-select autocomplete returns array of objects with .value if ($InputValue.ExploitabilityLevels) { foreach ($level in $InputValue.ExploitabilityLevels) { @@ -25,6 +27,7 @@ function Get-CIPPAlertVulnerabilities { } } } else { + $NewerThanMode = $false # Backward compatibility: simple value = hours threshold $AgeThresholdHours = if ($InputValue) { [int]$InputValue } else { 0 } $CVSSSeverity = 'low' @@ -52,9 +55,17 @@ function Get-CIPPAlertVulnerabilities { $FirstVuln = $Group.Group | Sort-Object firstSeenTimestamp | Select-Object -First 1 $HoursOld = [math]::Round(((Get-Date) - [datetime]$FirstVuln.firstSeenTimestamp).TotalHours) - # Skip if vulnerability is not old enough - if ($HoursOld -lt $AgeThresholdHours) { - continue + # Skip based on age threshold mode + if ($NewerThanMode) { + # Newer-than mode: only alert on items newer than threshold + if ($HoursOld -gt $AgeThresholdHours) { + continue + } + } else { + # Older-than mode (default): only alert on items older than threshold + if ($HoursOld -lt $AgeThresholdHours) { + continue + } } # Skip if CVSS score is below minimum threshold diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 index 8643b34e1084..2a4357ec7d98 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 @@ -39,31 +39,33 @@ function Push-DomainAnalyserDomain { } $Result = [PSCustomObject]@{ - Tenant = $Tenant.Tenant - TenantID = $Tenant.TenantGUID - GUID = $($Domain.Replace('.', '')) - LastRefresh = $(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') - Domain = $Domain - NSRecords = (Read-NSRecord -Domain $Domain).Records - ExpectedSPFRecord = '' - ActualSPFRecord = '' - SPFPassAll = '' - ActualMXRecords = '' - MXPassTest = '' - DMARCPresent = '' - DMARCFullPolicy = '' - DMARCActionPolicy = '' - DMARCReportingActive = '' - DMARCPercentagePass = '' - DNSSECPresent = '' - MailProvider = '' - DKIMEnabled = '' - DKIMRecords = '' - MSCNAMEDKIMSelectors = '' - Score = '' - MaximumScore = 160 - ScorePercentage = '' - ScoreExplanation = '' + Tenant = $Tenant.Tenant + TenantID = $Tenant.TenantGUID + GUID = $($Domain.Replace('.', '')) + LastRefresh = $(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') + Domain = $Domain + NSRecords = (Read-NSRecord -Domain $Domain).Records + ExpectedSPFRecord = '' + ActualSPFRecord = '' + SPFPassAll = '' + ActualMXRecords = '' + MXPassTest = '' + DMARCPresent = '' + DMARCFullPolicy = '' + DMARCActionPolicy = '' + DMARCReportingActive = '' + DMARCPercentagePass = '' + DNSSECPresent = '' + MailProvider = '' + DKIMEnabled = '' + DKIMRecords = '' + MSCNAMEDKIMSelectors = '' + EnterpriseEnrollment = '' + EnterpriseRegistration = '' + Score = '' + MaximumScore = 160 + ScorePercentage = '' + ScoreExplanation = '' } $Scores = [PSCustomObject]@{ @@ -246,6 +248,51 @@ function Push-DomainAnalyserDomain { } #EndRegion DKIM Check + #Region Intune Enrollment CNAME Check + try { + # Check enterpriseenrollment CNAME + $EnrollmentResult = Resolve-DnsHttpsQuery -Domain "enterpriseenrollment.$Domain" -RecordType CNAME + if ($EnrollmentResult.Answer) { + $EnrollmentCNAME = ($EnrollmentResult.Answer | Where-Object { $_.type -eq 5 }).data -replace '\.$' + if ($EnrollmentCNAME -eq 'enterpriseenrollment-s.manage.microsoft.com') { + $Result.EnterpriseEnrollment = 'Correct' + } elseif ($EnrollmentCNAME -eq 'enterpriseenrollment.manage.microsoft.com') { + $Result.EnterpriseEnrollment = 'Legacy' + $ScoreExplanation.Add('Enterprise Enrollment CNAME points to legacy endpoint (enterpriseenrollment.manage.microsoft.com)') | Out-Null + } else { + $Result.EnterpriseEnrollment = "Unexpected: $EnrollmentCNAME" + $ScoreExplanation.Add('Enterprise Enrollment CNAME points to unexpected target') | Out-Null + } + } else { + $Result.EnterpriseEnrollment = 'No CNAME' + $ScoreExplanation.Add('No Enterprise Enrollment CNAME record found') | Out-Null + } + } catch { + $Result.EnterpriseEnrollment = 'Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $DomainObject.TenantId -message "Enterprise Enrollment CNAME error for $Domain" -LogData (Get-CippException -Exception $_) -sev Error + } + + try { + # Check enterpriseregistration CNAME + $RegistrationResult = Resolve-DnsHttpsQuery -Domain "enterpriseregistration.$Domain" -RecordType CNAME + if ($RegistrationResult.Answer) { + $RegistrationCNAME = ($RegistrationResult.Answer | Where-Object { $_.type -eq 5 }).data -replace '\.$' + if ($RegistrationCNAME -eq 'enterpriseregistration.windows.net') { + $Result.EnterpriseRegistration = 'Correct' + } else { + $Result.EnterpriseRegistration = "Unexpected: $RegistrationCNAME" + $ScoreExplanation.Add('Enterprise Registration CNAME points to unexpected target') | Out-Null + } + } else { + $Result.EnterpriseRegistration = 'No CNAME' + $ScoreExplanation.Add('No Enterprise Registration CNAME record found') | Out-Null + } + } catch { + $Result.EnterpriseRegistration = 'Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $DomainObject.TenantId -message "Enterprise Registration CNAME error for $Domain" -LogData (Get-CippException -Exception $_) -sev Error + } + #EndRegion Intune Enrollment CNAME Check + #Region MSCNAME DKIM Records # Get Microsoft DKIM CNAME selector Records # Ugly, but i needed to create a scope/loop i could break out of without breaking the rest of the function diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Reports/Invoke-ListActiveSyncDevices.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Reports/Invoke-ListActiveSyncDevices.ps1 new file mode 100644 index 000000000000..04853d3790b9 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Reports/Invoke-ListActiveSyncDevices.ps1 @@ -0,0 +1,40 @@ +function Invoke-ListActiveSyncDevices { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.Mailbox.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $TenantFilter = $Request.Query.TenantFilter + + try { + $GraphRequest = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-MobileDevice' -cmdParams @{ ResultSize = 'Unlimited' } | + Select-Object @{ Name = 'userDisplayName'; Expression = { $_.UserDisplayName } }, + @{ Name = 'userPrincipalName'; Expression = { ($_.Identity -split '\\')[0] } }, + @{ Name = 'deviceFriendlyName'; Expression = { if ([string]::IsNullOrEmpty($_.FriendlyName)) { 'Unknown' } else { $_.FriendlyName } } }, + @{ Name = 'deviceModel'; Expression = { $_.DeviceModel } }, + @{ Name = 'deviceOS'; Expression = { $_.DeviceOS } }, + @{ Name = 'deviceType'; Expression = { $_.DeviceType } }, + @{ Name = 'clientType'; Expression = { $_.ClientType } }, + @{ Name = 'clientVersion'; Expression = { $_.ClientVersion } }, + @{ Name = 'deviceAccessState'; Expression = { $_.DeviceAccessState } }, + @{ Name = 'firstSyncTime'; Expression = { if ($_.FirstSyncTime) { $_.FirstSyncTime.ToString('yyyy-MM-ddTHH:mm:ssZ') } else { '' } } }, + @{ Name = 'lastSyncAttemptTime'; Expression = { if ($_.LastSyncAttemptTime) { $_.LastSyncAttemptTime.ToString('yyyy-MM-ddTHH:mm:ssZ') } else { '' } } }, + @{ Name = 'lastSuccessSync'; Expression = { if ($_.LastSuccessSync) { $_.LastSuccessSync.ToString('yyyy-MM-ddTHH:mm:ssZ') } else { '' } } }, + @{ Name = 'deviceID'; Expression = { $_.DeviceId } }, + @{ Name = 'identity'; Expression = { $_.Identity } }, + @{ Name = 'Guid'; Expression = { $_.Guid } } + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $StatusCode = [HttpStatusCode]::Forbidden + $GraphRequest = $ErrorMessage + } + return ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @($GraphRequest) + }) +} diff --git a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 index 3848a446457c..b3a1e425fbf0 100644 --- a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 @@ -20,7 +20,7 @@ function Set-CIPPDefaultAPDeploymentProfile { ) try { - if ($Language -in @('user-select', 'os-default')) { $Language = '' } + if ($Language -in @('user-select', 'os-default')) { $Language = "$null" } # userType in outOfBoxExperienceSetting is only valid for user-driven (singleUser) mode. # The Intune API rejects it for self-deploying (shared) mode.