diff --git a/Modules/CIPPCore/Private/Get-ExoOnlineStringBytes.ps1 b/Modules/CIPPCore/Private/Get-ExoOnlineStringBytes.ps1 new file mode 100644 index 000000000000..067bd894b8b4 --- /dev/null +++ b/Modules/CIPPCore/Private/Get-ExoOnlineStringBytes.ps1 @@ -0,0 +1,11 @@ +function Get-ExoOnlineStringBytes { + param([string]$SizeString) + + # This exists because various exo cmdlets like to return a human readable string like "3.322 KB (3,402 bytes)" but not the raw bytes value + + if ($SizeString -match '\(([0-9,]+) bytes\)') { + return [int]($Matches[1] -replace ',','') + } + + return 0 +} diff --git a/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 b/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 index 03041d90cfa7..579fdcd913f6 100644 --- a/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 +++ b/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 @@ -1,30 +1,4 @@ function Compare-CIPPIntuneObject { - <# - .SYNOPSIS - Compares two Intune objects and returns only the differences. - - .DESCRIPTION - This function takes two Intune objects and performs a comparison, returning only the properties that differ. - If no differences are found, it returns null. - It's useful for identifying changes between template objects and existing policies. - - .PARAMETER ReferenceObject - The reference Intune object to compare against. - - .PARAMETER DifferenceObject - The Intune object to compare with the reference object. - - .PARAMETER ExcludeProperties - Additional properties to exclude from the comparison. - - .EXAMPLE - $template = Get-CIPPIntunePolicy -tenantFilter $Tenant -DisplayName "Template Policy" -TemplateType "Device" - $existing = Get-CIPPIntunePolicy -tenantFilter $Tenant -DisplayName "Existing Policy" -TemplateType "Device" - $differences = Compare-CIPPIntuneObject -ReferenceObject $template -DifferenceObject $existing - - .NOTES - This function performs a comparison of objects, including nested properties. - #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] @@ -39,7 +13,6 @@ function Compare-CIPPIntuneObject { [string[]]$CompareType = @() ) if ($CompareType -ne 'Catalog') { - # Default properties to exclude from comparison $defaultExcludeProperties = @( 'id', 'createdDateTime', @@ -57,13 +30,9 @@ function Compare-CIPPIntuneObject { 'featureUpdatesPauseStartDate' ) - # Combine default and custom exclude properties $excludeProps = $defaultExcludeProperties + $ExcludeProperties - - # Create a list to store comparison results $result = [System.Collections.Generic.List[PSObject]]::new() - # Helper function to check if a property should be skipped function ShouldSkipProperty { param ( [string]$PropertyName @@ -73,7 +42,6 @@ function Compare-CIPPIntuneObject { $excludeProps -contains $PropertyName) } - # Recursive function to compare objects deeply function Compare-ObjectsRecursively { param ( [Parameter(Mandatory = $true)] @@ -83,15 +51,24 @@ function Compare-CIPPIntuneObject { $Object2, [Parameter(Mandatory = $false)] - [string]$PropertyPath = '' + [string]$PropertyPath = '', + [int]$Depth = 0, + [int]$MaxDepth = 20 ) - # If both objects are null or empty, they're equal + if ($Depth -ge $MaxDepth) { + $result.Add([PSCustomObject]@{ + Property = $PropertyPath + ExpectedValue = '[MaxDepthExceeded]' + ReceivedValue = '[MaxDepthExceeded]' + }) + return + } + if (($null -eq $Object1 -or $Object1 -eq '') -and ($null -eq $Object2 -or $Object2 -eq '')) { return } - # If one object is null but the other isn't, they're different if (($null -eq $Object1 -or $Object1 -eq '') -xor ($null -eq $Object2 -or $Object2 -eq '')) { $result.Add([PSCustomObject]@{ Property = $PropertyPath @@ -101,7 +78,6 @@ function Compare-CIPPIntuneObject { return } - # If objects are of different types, they're different if ($Object1.GetType() -ne $Object2.GetType()) { $result.Add([PSCustomObject]@{ Property = $PropertyPath @@ -111,9 +87,22 @@ function Compare-CIPPIntuneObject { return } - # Handle different object types + # Short-circuit recursion for primitive types + $primitiveTypes = @([double], [decimal], [datetime], [timespan], [guid] ) + foreach ($type in $primitiveTypes) { + if ($Object1 -is $type -and $Object2 -is $type) { + if ($Object1 -ne $Object2) { + $result.Add([PSCustomObject]@{ + Property = $PropertyPath + ExpectedValue = $Object1 + ReceivedValue = $Object2 + }) + } + return + } + } + if ($Object1 -is [System.Collections.IDictionary]) { - # Compare dictionaries $allKeys = @($Object1.Keys) + @($Object2.Keys) | Select-Object -Unique foreach ($key in $allKeys) { @@ -122,9 +111,8 @@ function Compare-CIPPIntuneObject { $newPath = if ($PropertyPath) { "$PropertyPath.$key" } else { $key } if ($Object1.ContainsKey($key) -and $Object2.ContainsKey($key)) { - #only run if both props are not null if ($Object1[$key] -and $Object2[$key]) { - Compare-ObjectsRecursively -Object1 $Object1[$key] -Object2 $Object2[$key] -PropertyPath $newPath + Compare-ObjectsRecursively -Object1 $Object1[$key] -Object2 $Object2[$key] -PropertyPath $newPath -Depth ($Depth + 1) -MaxDepth $MaxDepth } } elseif ($Object1.ContainsKey($key)) { $result.Add([PSCustomObject]@{ @@ -141,14 +129,13 @@ function Compare-CIPPIntuneObject { } } } elseif ($Object1 -is [Array] -or $Object1 -is [System.Collections.IList]) { - # Compare arrays $maxLength = [Math]::Max($Object1.Count, $Object2.Count) for ($i = 0; $i -lt $maxLength; $i++) { $newPath = "$PropertyPath.$i" if ($i -lt $Object1.Count -and $i -lt $Object2.Count) { - Compare-ObjectsRecursively -Object1 $Object1[$i] -Object2 $Object2[$i] -PropertyPath $newPath + Compare-ObjectsRecursively -Object1 $Object1[$i] -Object2 $Object2[$i] -PropertyPath $newPath -Depth ($Depth + 1) -MaxDepth $MaxDepth } elseif ($i -lt $Object1.Count) { $result.Add([PSCustomObject]@{ Property = $newPath @@ -164,7 +151,6 @@ function Compare-CIPPIntuneObject { } } } elseif ($Object1 -is [PSCustomObject] -or $Object1.PSObject.Properties.Count -gt 0) { - # Compare PSCustomObjects or objects with properties $allPropertyNames = @( $Object1.PSObject.Properties | Select-Object -ExpandProperty Name $Object2.PSObject.Properties | Select-Object -ExpandProperty Name @@ -178,9 +164,8 @@ function Compare-CIPPIntuneObject { $prop2Exists = $Object2.PSObject.Properties.Name -contains $propName if ($prop1Exists -and $prop2Exists) { - #only run if both props are not null if ($Object1.$propName -and $Object2.$propName) { - Compare-ObjectsRecursively -Object1 $Object1.$propName -Object2 $Object2.$propName -PropertyPath $newPath + Compare-ObjectsRecursively -Object1 $Object1.$propName -Object2 $Object2.$propName -PropertyPath $newPath -Depth ($Depth + 1) -MaxDepth $MaxDepth } } elseif ($prop1Exists) { $result.Add([PSCustomObject]@{ @@ -197,7 +182,6 @@ function Compare-CIPPIntuneObject { } } } else { - # Compare primitive values $val1 = $Object1.ToString() $val2 = $Object2.ToString() @@ -211,7 +195,6 @@ function Compare-CIPPIntuneObject { } } - # Convert objects to PowerShell objects if they're not already $obj1 = if ($ReferenceObject -is [string]) { $ReferenceObject | ConvertFrom-Json -AsHashtable -Depth 100 } else { @@ -224,13 +207,10 @@ function Compare-CIPPIntuneObject { $DifferenceObject } - # Start the recursive comparison - #only do the compare if the objects are not null if ($obj1 -and $obj2) { Compare-ObjectsRecursively -Object1 $obj1 -Object2 $obj2 } - # If no differences found, return null if ($result.Count -eq 0) { return $null } @@ -425,17 +405,14 @@ function Compare-CIPPIntuneObject { $tempOutput } - # Compare the items and create result $result = [System.Collections.Generic.List[PSObject]]::new() - # Group all items by Key for comparison $allKeys = @($referenceItems | Select-Object -ExpandProperty Key) + @($differenceItems | Select-Object -ExpandProperty Key) | Sort-Object -Unique foreach ($key in $allKeys) { $refItem = $referenceItems | Where-Object { $_.Key -eq $key } | Select-Object -First 1 $diffItem = $differenceItems | Where-Object { $_.Key -eq $key } | Select-Object -First 1 - # Get the setting definition ID from the key $settingId = $key if ($key -like 'Simple-*') { $settingId = $key.Substring(7) @@ -447,20 +424,15 @@ function Compare-CIPPIntuneObject { $settingId = $key.Substring(8) } - # Look up the setting in the collection $settingDefinition = $intuneCollection | Where-Object { $_.id -eq $settingId } - # Get the raw values $refRawValue = if ($refItem) { $refItem.Value } else { $null } $diffRawValue = if ($diffItem) { $diffItem.Value } else { $null } - # Try to translate the values to display names if they're option IDs $refValue = $refRawValue $diffValue = $diffRawValue - # If the setting has options, try to find the display name for the values if ($null -ne $settingDefinition -and $null -ne $settingDefinition.options) { - # For reference value if ($null -ne $refRawValue -and $refRawValue -match '_\d+$') { $option = $settingDefinition.options | Where-Object { $_.id -eq $refRawValue } if ($null -ne $option -and $null -ne $option.displayName) { @@ -468,7 +440,6 @@ function Compare-CIPPIntuneObject { } } - # For difference value if ($null -ne $diffRawValue -and $diffRawValue -match '_\d+$') { $option = $settingDefinition.options | Where-Object { $_.id -eq $diffRawValue } if ($null -ne $option -and $null -ne $option.displayName) { @@ -477,7 +448,6 @@ function Compare-CIPPIntuneObject { } } - # Use the display name for the property label if available $label = if ($null -ne $settingDefinition -and $null -ne $settingDefinition.displayName) { $settingDefinition.displayName } elseif ($refItem) { @@ -488,7 +458,6 @@ function Compare-CIPPIntuneObject { $key } - # Only add to result if values are different or one is missing if ($refRawValue -ne $diffRawValue -or $null -eq $refRawValue -or $null -eq $diffRawValue) { $result.Add([PSCustomObject]@{ Property = $label @@ -502,4 +471,3 @@ function Compare-CIPPIntuneObject { } return $result } - diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 index bf47a28af856..bafe1e39fa59 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 @@ -116,6 +116,7 @@ function Push-ExecScheduledCommand { '*email*' { Send-CIPPAlert -Type 'email' -Title $title -HTMLContent $HTML -TenantFilter $Tenant } '*webhook*' { $Webhook = [PSCustomObject]@{ + 'tenantId' = $TenantInfo.customerId 'Tenant' = $Tenant 'TaskInfo' = $Item.TaskInfo 'Results' = $Results diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-SchedulerCIPPNotifications.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-SchedulerCIPPNotifications.ps1 index e684cc202f73..258ec34cd1c0 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-SchedulerCIPPNotifications.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-SchedulerCIPPNotifications.ps1 @@ -33,6 +33,12 @@ function Push-SchedulerCIPPNotifications { } Write-Information "Alerts: $($Currentlog.count) found" Write-Information "Standards: $($CurrentStandardsLogs.count) found" + + # Get the CIPP URL + $CippConfigTable = Get-CippTable -tablename Config + $CippConfig = Get-CIPPAzDataTableEntity @CippConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey eq 'CIPPURL'" + $CIPPURL = 'https://{0}' -f $CippConfig.Value + #email try try { if ($Config.email -like '*@*') { @@ -42,13 +48,13 @@ function Push-SchedulerCIPPNotifications { foreach ($tenant in ($CurrentLog.Tenant | Sort-Object -Unique)) { $Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity | Where-Object -Property tenant -EQ $tenant) $Subject = "$($Tenant): CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))" - $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' + $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts' } } else { $Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity) $Subject = "CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))" - $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' + $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts' } } @@ -56,7 +62,7 @@ function Push-SchedulerCIPPNotifications { foreach ($tenant in ($CurrentStandardsLogs.Tenant | Sort-Object -Unique)) { $Data = ($CurrentStandardsLogs | Where-Object -Property tenant -EQ $tenant) $Subject = "$($Tenant): Standards are out of sync for $tenant" - $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards' + $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards' -CIPPURL $CIPPURL Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts' $updateStandards = $CurrentStandardsLogs | ForEach-Object { if ($_.PSObject.Properties.Name -contains 'sentAsAlert') { @@ -87,7 +93,7 @@ function Push-SchedulerCIPPNotifications { } if ($CurrentStandardsLogs) { - $JSONContent = New-CIPPAlertTemplate -Data $Data -Format 'json' -InputObject 'table' + $JSONContent = New-CIPPAlertTemplate -Data $Data -Format 'json' -InputObject 'table' -CIPPURL $CIPPURL $CurrentStandardsLogs | ConvertTo-Json -Compress Send-CIPPAlert -Type 'webhook' -JSONContent $JSONContent -TenantFilter $Tenant -APIName 'Alerts' $updateStandards = $CurrentStandardsLogs | ForEach-Object { @@ -110,7 +116,7 @@ function Push-SchedulerCIPPNotifications { try { foreach ($tenant in ($CurrentLog.Tenant | Sort-Object -Unique)) { $Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity | Where-Object -Property tenant -EQ $tenant) - $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' + $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL $Title = "$tenant CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))" Send-CIPPAlert -Type 'psa' -Title $Title -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts' $UpdateLogs = $CurrentLog | ForEach-Object { $_.SentAsAlert = $true; $_ } @@ -119,7 +125,7 @@ function Push-SchedulerCIPPNotifications { foreach ($standardsTenant in ($CurrentStandardsLogs.Tenant | Sort-Object -Unique)) { $Data = ($CurrentStandardsLogs | Where-Object -Property tenant -EQ $standardsTenant) $Subject = "$($standardsTenant): Standards are out of sync for $standardsTenant" - $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards' + $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards' -CIPPURL $CIPPURL Send-CIPPAlert -Type 'psa' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $standardsTenant -APIName 'Alerts' $updateStandards = $CurrentStandardsLogs | ForEach-Object { if ($_.PSObject.Properties.Name -contains 'sentAsAlert') { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 index 6624ed3ad3a7..612918dfe20b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 @@ -25,7 +25,7 @@ Function Invoke-ExecAccessChecks { if ($Request.Query.SkipCache -ne 'true' -or $Request.Query.SkipCache -ne $true) { try { $Cache = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq 'AccessPermissions' and Timestamp and Timestamp ge datetime'$TimestampFilter'" - $Results = $Cache.Data | ConvertFrom-Json + $Results = $Cache.Data | ConvertFrom-Json -ErrorAction Stop } catch { $Results = $null } @@ -62,7 +62,7 @@ Function Invoke-ExecAccessChecks { ExchangeTest = '' } if ($TenantCheck) { - $Data = @($TenantCheck.Data | ConvertFrom-Json) + $Data = @($TenantCheck.Data | ConvertFrom-Json -ErrorAction Stop) $TenantResult.GraphStatus = $Data.GraphStatus $TenantResult.ExchangeStatus = $Data.ExchangeStatus $TenantResult.GDAPRoles = $Data.GDAPRoles @@ -85,7 +85,7 @@ Function Invoke-ExecAccessChecks { $Results = @() } } catch { - Write-Host $_.Exception.Message + Write-Warning "Error running tenant access check - $($_.Exception.Message)" $Results = @() } } @@ -105,7 +105,7 @@ Function Invoke-ExecAccessChecks { if (!$Request.Query.SkipCache -eq 'true' -or !$Request.Query.SkipCache -eq $true) { try { $Cache = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq 'GDAPRelationships' and Timestamp ge datetime'$TimestampFilter'" - $Results = $Cache.Data | ConvertFrom-Json + $Results = $Cache.Data | ConvertFrom-Json -ErrorAction Stop } catch { $Results = $null } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFA.ps1 index 5dbea9130edd..c22b0945e193 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFA.ps1 @@ -6,23 +6,31 @@ function Invoke-ExecPerUserMFA { .ROLE Identity.User.ReadWrite #> - Param( - $Request, - $TriggerMetadata - ) + Param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + $Request = @{ - userId = $Request.Body.userId - TenantFilter = $Request.Body.TenantFilter - State = $Request.Body.State.value ? $Request.Body.State.value : $Request.Body.State - Headers = $Request.Headers + userId = $Request.Body.userId + TenantFilter = $Request.Body.tenantFilter + State = $Request.Body.State.value ? $Request.Body.State.value : $Request.Body.State + Headers = $Headers + APIName = $APIName } - $Result = Set-CIPPPerUserMFA @Request - $Body = @{ - Results = @($Result) + try { + $Result = Set-CIPPPerUserMFA @Request + $StatusCode = [HttpStatusCode]::OK + } catch { + $Result = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError } + + # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Body + StatusCode = $StatusCode + Body = @{ 'Results' = @($Result) } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFAAllUsers.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFAAllUsers.ps1 index f1eec308807f..7a7c296b4016 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFAAllUsers.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFAAllUsers.ps1 @@ -6,17 +6,22 @@ function Invoke-ExecPerUserMFAAllUsers { .ROLE Identity.User.ReadWrite #> - Param( - $Request, - $TriggerMetadata - ) - $TenantFilter = $request.query.TenantFilter + Param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # XXX Seems to be an unused endpoint? - Bobby + + $TenantFilter = $request.Query.tenantFilter $Users = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $TenantFilter $Request = @{ - userId = $Users.id - TenantFilter = $tenantfilter - State = $Request.query.State - Headers = $Request.Headers + userId = $Users.id + TenantFilter = $TenantFilter + State = $Request.Query.State + Headers = $Request.Headers + APIName = $APIName } $Result = Set-CIPPPerUserMFA @Request $Body = @{ diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 index ba87bb0ed755..41832006069f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 @@ -164,7 +164,7 @@ function Invoke-ListUserMailboxDetails { $ProhibitSendQuotaString = $MailboxDetailedRequest.ProhibitSendQuota -split ' ' $ProhibitSendReceiveQuotaString = $MailboxDetailedRequest.ProhibitSendReceiveQuota -split ' ' $TotalItemSizeString = $StatsRequest.TotalItemSize -split ' ' - $TotalArchiveItemSizeString = $ArchiveSizeRequest.TotalItemSize -split ' ' + $TotalArchiveItemSizeString = Get-ExoOnlineStringBytes -SizeString $ArchiveSizeRequest.TotalItemSize.Value $ProhibitSendQuota = try { [math]::Round([float]($ProhibitSendQuotaString[0]), 2) } catch { 0 } $ProhibitSendReceiveQuota = try { [math]::Round([float]($ProhibitSendReceiveQuotaString[0]), 2) } catch { 0 } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecBPA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecBPA.ps1 index a3484765099a..06731cb29663 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecBPA.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecBPA.ps1 @@ -34,7 +34,7 @@ function Invoke-ExecBPA { $Results = [pscustomobject]@{'Results' = 'BPA queued for execution' } } } else { - Start-BPAOrchestrator -TenantFilter $Request.Query.TenantFilter + Start-BPAOrchestrator -TenantFilter $TenantFilter $Results = [pscustomobject]@{'Results' = 'BPA started' } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListStandardsCompare.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListStandardsCompare.ps1 index 6a71b7736a76..4144312551fd 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListStandardsCompare.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListStandardsCompare.ps1 @@ -25,10 +25,14 @@ Function Invoke-ListStandardsCompare { } else { $_.Value = [string]$_.Value } - $object | Add-Member -MemberType NoteProperty -Name $_.Name.Replace('standards_', 'standards.') -Value $_.Value -Force + + $Key = $_.Name.replace('standards_', 'standards.') + $Key = $Key.replace('IntuneTemplate_', 'IntuneTemplate.') + $Key = $Key -replace '__', '-' + + $object | Add-Member -MemberType NoteProperty -Name $Key -Value $_.Value -Force $object.PSObject.Properties.Remove($_.Name) } - } } diff --git a/Modules/CIPPCore/Public/Set-CIPPMailboxLocale.ps1 b/Modules/CIPPCore/Public/Set-CIPPMailboxLocale.ps1 index 5d3fde2bbdf1..d3d7fa740293 100644 --- a/Modules/CIPPCore/Public/Set-CIPPMailboxLocale.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPMailboxLocale.ps1 @@ -2,25 +2,32 @@ function Set-CippMailboxLocale { [CmdletBinding()] param ( $Headers, - $locale, - $username, + $Locale, + $Username, $APIName = 'Mailbox Locale', $TenantFilter ) try { + # Validate the locale. Also if the locale is not valid, it will throw an exception, not wasting a request. + if ([System.Globalization.CultureInfo]::GetCultureInfo($Locale).IsNeutralCulture) { + throw "$Locale is not a valid Locale. Neutral cultures are not supported." + } + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxRegionalConfiguration' -cmdParams @{ - Identity = $username - Language = $locale + Identity = $Username + Language = $Locale LocalizeDefaultFolderName = $true + DateFormat = $null + TimeFormat = $null } -Anchor $username - $Result = "Set locale for $($username) to a $locale" - Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Info' -tenant $TenantFilter + $Result = "Set locale for $($Username) to $Locale" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev Info -tenant $TenantFilter return $Result } catch { $ErrorMessage = Get-CippException -Exception $_ - $Result = "Could not set locale for $($username). Error: $($ErrorMessage.NormalizedError)" - Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + $Result = "Failed to set locale for $($Username). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev Error -tenant $TenantFilter -LogData $ErrorMessage throw $Result } } diff --git a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 index 0cb76246c828..7d5642fe37bc 100644 --- a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 @@ -29,8 +29,10 @@ function Set-CIPPPerUserMFA { [string[]]$userId, [ValidateSet('enabled', 'disabled', 'enforced')] $State = 'enabled', - [string]$Headers = 'CIPP' + $Headers, + $APIName = 'Set-CIPPPerUserMFA' ) + try { $int = 0 $Body = @{ @@ -48,8 +50,7 @@ function Set-CIPPPerUserMFA { } } - $Requests = New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true - + $Requests = New-GraphBulkRequest -tenantid $TenantFilter -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true "Successfully set Per user MFA State for $userId" $Users = foreach ($id in $userId) { @@ -61,10 +62,11 @@ function Set-CIPPPerUserMFA { } } Set-CIPPUserSchemaProperties -TenantFilter $TenantFilter -Users $Users - Write-LogMessage -headers $Headers -API 'Set-CIPPPerUserMFA' -message "Successfully set Per user MFA State to $State for $id" -Sev 'Info' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message "Successfully set Per user MFA State to $State for $id" -Sev Info -tenant $TenantFilter } catch { $ErrorMessage = Get-CippException -Exception $_ - "Failed to set MFA State for $id. Error: $($ErrorMessage.NormalizedError)" - Write-LogMessage -headers $Headers -API 'Set-CIPPPerUserMFA' -message "Failed to set MFA State to $State for $id. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + $Result = "Failed to set MFA State to $State for $id. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev Error -tenant $TenantFilter -LogData $ErrorMessage + throw $Result } } diff --git a/Modules/CIPPCore/Public/Set-CIPPStandardsCompareField.ps1 b/Modules/CIPPCore/Public/Set-CIPPStandardsCompareField.ps1 index f843b6f05123..a3d1aef9891b 100644 --- a/Modules/CIPPCore/Public/Set-CIPPStandardsCompareField.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPStandardsCompareField.ps1 @@ -6,8 +6,12 @@ function Set-CIPPStandardsCompareField { ) $Table = Get-CippTable -tablename 'CippStandardsReports' $TenantName = Get-Tenants | Where-Object -Property defaultDomainName -EQ $Tenant - #if the fieldname does not contain standards. prepend it. + + # Sanitize invalid c#/xml characters for Azure Tables $FieldName = $FieldName.replace('standards.', 'standards_') + $FieldName = $FieldName.replace('IntuneTemplate.', 'IntuneTemplate_') + $FieldName = $FieldName -replace '-', '__' + if ($FieldValue -is [System.Boolean]) { $fieldValue = [bool]$FieldValue } elseif ($FieldValue -is [string]) { @@ -18,24 +22,28 @@ function Set-CIPPStandardsCompareField { } $Existing = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'StandardReport' and RowKey eq '$($TenantName.defaultDomainName)'" - if ($Existing) { - $Existing = $Existing | Select-Object * -ExcludeProperty ETag, TimeStamp | ConvertTo-Json -Depth 10 -Compress | ConvertFrom-Json -AsHashtable - $Existing[$FieldName] = $FieldValue - $Existing['LastRefresh'] = [string]$(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') - $Existing = [PSCustomObject]$Existing + try { + if ($Existing) { + $Existing = $Existing | Select-Object * -ExcludeProperty ETag, TimeStamp | ConvertTo-Json -Depth 10 -Compress | ConvertFrom-Json -AsHashtable + $Existing[$FieldName] = $FieldValue + $Existing['LastRefresh'] = [string]$(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') + $Existing = [PSCustomObject]$Existing - Add-CIPPAzDataTableEntity @Table -Entity $Existing -Force - } else { - $Result = @{ - tenantFilter = "$($TenantName.defaultDomainName)" - GUID = "$($TenantName.customerId)" - RowKey = "$($TenantName.defaultDomainName)" - PartitionKey = 'StandardReport' - LastRefresh = [string]$(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') - } - $Result[$FieldName] = $FieldValue - Add-CIPPAzDataTableEntity @Table -Entity $Result -Force + Add-CIPPAzDataTableEntity @Table -Entity $Existing -Force + } else { + $Result = @{ + tenantFilter = "$($TenantName.defaultDomainName)" + GUID = "$($TenantName.customerId)" + RowKey = "$($TenantName.defaultDomainName)" + PartitionKey = 'StandardReport' + LastRefresh = [string]$(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') + } + $Result[$FieldName] = $FieldValue + Add-CIPPAzDataTableEntity @Table -Entity $Result -Force + } + Write-Information "Adding $FieldName to StandardCompare for $Tenant. content is $FieldValue" + } catch { + Write-Warning "Failed to add $FieldName to StandardCompare for $Tenant. content is $FieldValue - $($_.Exception.Message)" } - Write-Information "Adding $FieldName to StandardCompare for $Tenant. content is $FieldValue" } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 index ed04a79091bd..43117a472282 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 @@ -84,6 +84,8 @@ function Invoke-CIPPStandardIntuneTemplate { assignTo = $Template.AssignTo excludeGroup = $Template.excludeGroup remediate = $Template.remediate + alert = $Template.alert + report = $Template.report existingPolicyId = $ExistingPolicy.id templateId = $Template.TemplateList.value customGroup = $Template.customGroup @@ -100,6 +102,8 @@ function Invoke-CIPPStandardIntuneTemplate { assignTo = $Template.AssignTo excludeGroup = $Template.excludeGroup remediate = $Template.remediate + alert = $Template.alert + report = $Template.report existingPolicyId = $ExistingPolicy.id templateId = $Template.TemplateList.value customGroup = $Template.customGroup @@ -122,8 +126,9 @@ function Invoke-CIPPStandardIntuneTemplate { } - if ($Settings.alert) { - foreach ($Template in $CompareList) { + if ($true -in $Settings.alert) { + foreach ($Template in $CompareList | Where-Object -Property alert -EQ $true) { + Write-Host "working on template alert: $($Template.displayname)" $AlertObj = $Template | Select-Object -Property displayname, description, compare, assignTo, excludeGroup, existingPolicyId if ($Template.compare) { Write-StandardsAlert -message "Template $($Template.displayname) does not match the expected configuration." -object $AlertObj -tenant $Tenant -standardName 'IntuneTemplate' -standardId $Settings.templateId @@ -139,13 +144,14 @@ function Invoke-CIPPStandardIntuneTemplate { } } - if ($Settings.report) { - foreach ($Template in $CompareList) { + if ($true -in $Settings.report) { + foreach ($Template in $CompareList | Where-Object -Property report -EQ $true) { + Write-Host "working on template report: $($Template.displayname)" $id = $Template.templateId $CompareObj = $Template.compare $state = $CompareObj ? $CompareObj : $true Set-CIPPStandardsCompareField -FieldName "standards.IntuneTemplate.$id" -FieldValue $state -TenantFilter $Tenant } - Add-CIPPBPAField -FieldName "policy-$id" -FieldValue $Compare -StoreAs bool -Tenant $tenant + #Add-CIPPBPAField -FieldName "policy-$id" -FieldValue $Compare -StoreAs bool -Tenant $tenant } } diff --git a/version_latest.txt b/version_latest.txt index 18bb4182dd01..a5f017a0a348 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -7.5.0 +7.5.1