diff --git a/ExecScheduledCommand/run.ps1 b/ExecScheduledCommand/run.ps1 index 615b6b8471cf..180df3368b87 100644 --- a/ExecScheduledCommand/run.ps1 +++ b/ExecScheduledCommand/run.ps1 @@ -10,8 +10,7 @@ Write-Host 'started task' try { try { $results = & $QueueItem.command @commandParameters - } - catch { + } catch { $results = "Task Failed: $($_.Exception.Message)" } @@ -31,8 +30,7 @@ try { if ($StoredResults.Length -gt 64000 -or $task.Tenant -eq 'AllTenants') { $StoredResults = @{ Results = 'The results for this query are too long to store in this table, or the query was meant for All Tenants. Please use the options to send the results to another target to be able to view the results. ' } | ConvertTo-Json -Compress } -} -catch { +} catch { $errorMessage = $_.Exception.Message if ($task.Recurrence -gt 0) { $State = 'Failed - Planned' } else { $State = 'Failed' } Update-AzDataTableEntity @Table -Entity @{ @@ -71,8 +69,7 @@ if ($task.Recurrence -le '0' -or $task.Recurrence -eq $null) { Results = "$StoredResults" TaskState = 'Completed' } -} -else { +} else { $nextRun = (Get-Date).AddDays($task.Recurrence) $nextRunUnixTime = [int64]($nextRun - (Get-Date '1/1/1970')).TotalSeconds Update-AzDataTableEntity @Table -Entity @{ diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddAlert.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddAlert.ps1 index 475147d04df3..b2ac6be60384 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddAlert.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddAlert.ps1 @@ -10,14 +10,16 @@ Function Invoke-AddAlert { $APIName = $TriggerMetadata.FunctionName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - $Tenants = ($Request.body | Select-Object Select_*).psobject.properties.value + $Tenants = $Request.body.tenantFilter + $Table = get-cipptable -TableName 'SchedulerConfig' + $Results = foreach ($Tenant in $tenants) { try { - $TenantID = if ($tenant -ne 'AllTenants') { - (get-tenants | Where-Object -Property defaultDomainName -EQ $Tenant).customerId - } - else { - 'AllTenants' + Write-Host "Working on $Tenant" + if ($tenant -ne 'AllTenants') { + $TenantID = (get-tenants | Where-Object -Property defaultDomainName -EQ $Tenant).customerId + } else { + $TenantID = 'AllTenants' } if ($Request.body.SetAlerts) { $CompleteObject = @{ @@ -45,45 +47,52 @@ Function Invoke-AddAlert { RowKey = $TenantID PartitionKey = 'Alert' } - $Table = get-cipptable -TableName 'SchedulerConfig' Add-CIPPAzDataTableEntity @Table -Entity $CompleteObject -Force - } - $URL = ($request.headers.'x-ms-original-url').split('/api') | Select-Object -First 1 - if ($Tenant -eq 'AllTenants') { - Get-Tenants | ForEach-Object { - foreach ($eventType in $Request.body.EventTypes.value) { + } else { + $URL = ($request.headers.'x-ms-original-url').split('/api') | Select-Object -First 1 + if ($Tenant -eq 'AllTenants') { + Get-Tenants | ForEach-Object { $params = @{ - TenantFilter = $_.defaultDomainName - auditLogAPI = $true - operations = ($Request.body.Operations.value -join ',') - allowedLocations = ($Request.body.AllowedLocations.value -join ',') - BaseURL = $URL - EventType = $eventType - ExecutingUser = $Request.headers.'x-ms-client-principal' + TenantFilter = $_.defaultDomainName + auditLogAPI = $true + operations = 'Audit.AzureActiveDirectory,Audit.Exchange,Audit.SharePoint,Audit.General' + BaseURL = $URL + ExecutingUser = $Request.headers.'x-ms-client-principal' } Push-OutputBinding -Name Subscription -Value $Params } - } - } - else { - foreach ($eventType in $Request.body.EventTypes.value) { + $CompleteObject = @{ + tenant = 'AllTenants' + type = 'webhookcreation' + RowKey = 'AllTenantsWebhookCreation' + PartitionKey = 'webhookcreation' + } + Add-CIPPAzDataTableEntity @Table -Entity $CompleteObject -Force + } else { $params = @{ - TenantFilter = $tenant - auditLogAPI = $true - operations = ($Request.body.Operations.value -join ',') - allowedLocations = ($Request.body.AllowedLocations.value -join ',') - BaseURL = $URL - EventType = $eventType - ExecutingUser = $Request.headers.'x-ms-client-principal' + TenantFilter = $tenant + auditLogAPI = $true + operations = 'Audit.AzureActiveDirectory,Audit.Exchange,Audit.SharePoint,Audit.General' + BaseURL = $URL + ExecutingUser = $Request.headers.'x-ms-client-principal' } New-CIPPGraphSubscription @params } + $CompleteObject = @{ + Tenant = [string]$tenant + if = [string](ConvertTo-Json -Depth 10 -Compress -InputObject $Request.body.ifs) + execution = [string](ConvertTo-Json -Depth 10 -Compress -InputObject $Request.body.do) + type = 'WebhookAlert' + RowKey = [string](New-Guid) + PartitionKey = 'WebhookAlert' + } + Add-CIPPAzDataTableEntity @Table -Entity $CompleteObject -Force + } "Successfully added Alert for $($Tenant) to queue." Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Successfully added Alert for $($Tenant) to queue." -Sev 'Info' - } - catch { + } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Failed to add Alert for for $($Tenant) to queue" -Sev 'Error' "Failed to add Alert for for $($Tenant) to queue $($_.Exception.message)" } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecGDAPInvite.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecGDAPInvite.ps1 index 8eb97c978ce5..b427e3e89e2b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecGDAPInvite.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecGDAPInvite.ps1 @@ -11,7 +11,6 @@ Function Invoke-ExecGDAPInvite { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' $RoleMappings = $Request.body.gdapRoles - $Results = [System.Collections.Generic.List[string]]::new() if ($RoleMappings.roleDefinitionId -contains '62e90394-69f5-4237-9190-012177145e10') { $AutoExtendDuration = 'PT0S' @@ -48,29 +47,32 @@ Function Invoke-ExecGDAPInvite { if ($NewRelationshipRequest.action -eq 'lockForApproval') { $InviteUrl = "https://admin.microsoft.com/AdminPortal/Home#/partners/invitation/granularAdminRelationships/$($NewRelationship.id)" + $Uri = ([System.Uri]$TriggerMetadata.Headers.referer) + $OnboardingUrl = $Uri.AbsoluteUri.Replace($Uri.PathAndQuery, '/tenant/administration/tenant-onboarding-wizard?tableFilter=Complex: id eq {0}' -f $NewRelationship.id) $InviteEntity = [PSCustomObject]@{ - 'PartitionKey' = 'invite' - 'RowKey' = $NewRelationship.id - 'InviteUrl' = $InviteUrl - 'RoleMappings' = [string](@($RoleMappings) | ConvertTo-Json -Depth 10 -Compress) + 'PartitionKey' = 'invite' + 'RowKey' = $NewRelationship.id + 'InviteUrl' = $InviteUrl + 'OnboardingUrl' = $OnboardingUrl + 'RoleMappings' = [string](@($RoleMappings) | ConvertTo-Json -Depth 10 -Compress) } Add-CIPPAzDataTableEntity @Table -Entity $InviteEntity - $Results.add('GDAP relationship invite created. Copy the URL below and log in as a Global Admin for the new tenant to approve the invite.') + $Message = 'GDAP relationship invite created. Log in as a Global Admin in the new tenant to approve the invite.' } else { - $Results.add('Error creating GDAP relationship request') + $Message = 'Error creating GDAP relationship request' } } } catch { - $Results.add('Error creating GDAP relationship') + $Message = 'Error creating GDAP relationship' Write-Host "GDAP ERROR: $($_.Exception.Message)" } Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created GDAP Invite - $InviteUrl" -Sev 'Info' $body = @{ - Results = @($Results) + Message = $Message Invite = $InviteEntity } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecGraphExplorerPreset.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecGraphExplorerPreset.ps1 new file mode 100644 index 000000000000..b4d51d4f5d18 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecGraphExplorerPreset.ps1 @@ -0,0 +1,78 @@ +using namespace System.Net + +Function Invoke-ExecGraphExplorerPreset { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + $Username = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($request.headers.'x-ms-client-principal')) | ConvertFrom-Json).userDetails + # Write to the Azure Functions log stream. + Write-Host 'PowerShell HTTP trigger function processed a request.' + + + switch ($Request.Body.Action) { + 'Copy' { + $Id = (New-Guid).Guid + } + 'Save' { + $Id = $Request.Body.values.reportTemplate.value + } + 'Delete' { + $Id = $Request.Body.values.reportTemplate.value + } + } + + $params = $Request.Body.values | Select-Object endpoint, '$filter', '$select', '$count', '$expand', '$search', NoPagination, '$top', IsShared + $Preset = [PSCustomObject]@{ + PartitionKey = 'Preset' + RowKey = [string]$Id + id = [string]$Id + name = [string]$Request.Body.values.name + Owner = [string]$Username + IsShared = $Request.Body.values.IsShared + params = [string](ConvertTo-Json -InputObject $params -Compress) + } + + try { + $Success = $false + $Table = Get-CIPPTable -TableName 'GraphPresets' + $Message = '{0} preset succeeded' -f $Request.Body.Action + if ($Request.Body.Action -eq 'Copy') { + Add-CIPPAzDataTableEntity @Table -Entity $Preset + $Success = $true + } else { + $Entity = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$Id'" + if ($Entity.Owner -eq $Username ) { + if ($Request.Body.Action -eq 'Delete') { + Remove-AzDataTableEntity @Table -Entity $Entity + } elseif ($Request.Body.Action -eq 'Save') { + Add-CIPPAzDataTableEntity @Table -Entity $Preset -Force + } + $Success = $true + } else { + $Message = 'Error: You can only modify your own presets.' + $Success = $false + } + } + + $StatusCode = [HttpStatusCode]::OK + } catch { + $Success = $false + $Message = $_.Exception.Message + $StatusCode = [HttpStatusCode]::BadRequest + } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{ + Results = $Message + Success = $Success + } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOffboardTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOffboardTenant.ps1 index 8a43ad253d09..ef42bc907663 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOffboardTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOffboardTenant.ps1 @@ -37,6 +37,7 @@ Function Invoke-ExecOffboardTenant { $BulkResults = New-GraphBulkRequest -Requests $BulkRequests -tenantid $TenantFilter $results.Add('Succesfully removed guest users') + Write-LogMessage -user $ExecutingUser -API $APIName -message "CSP Guest users were removed" -Sev "Info" -tenant $TenantFilter } else { $results.Add('No guest users found to remove') } @@ -46,7 +47,6 @@ Function Invoke-ExecOffboardTenant { } if ($request.body.RemoveCSPnotificationContacts) { - Write-Host "DO WE GET HERE?" # Remove all email adresses that match the CSP tenants domains from the contact properties in /organization try { try { @@ -79,6 +79,7 @@ Function Invoke-ExecOffboardTenant { try { New-GraphPostRequest -type PATCH -body $patchContactBody -Uri "https://graph.microsoft.com/v1.0/organization/$($orgContacts.id)" -tenantid $Tenantfilter -ContentType "application/json" $results.Add("Succesfully removed notification contacts from $($property): $(($propertyContacts | Where-Object { $domains -contains $_.Split("@")[1] }))") + Write-LogMessage -user $ExecutingUser -API $APIName -message "Contacts were removed from $($property)" -Sev "Info" -tenant $TenantFilter } catch { $errors.Add("Failed to update property $($property): $($_.Exception.message)") } @@ -90,16 +91,17 @@ Function Invoke-ExecOffboardTenant { } - if ($request.body.RemoveMSPvendorApps) { - # 9fcfb031-1bf6-4848-8732-5573fd64fc09 - Augmentt - # 9359814a-7403-4af9-9113-d5c8cab020ed - Rewst CSP connector - # 06bfda05-2d5e-4b3b-ac5d-79f07e402973 - Rewst Prod - # c19d36e8-6537-4998-9872-ea8b962bd0b6 - Rewst Azure Integration - # d7db2a1c-c38b-4bd1-a30f-0915167ba928 - Datto Backupify/Saas Protection - # 0c3cdc94-15ba-4b89-9222-29f599727b1c - AutoTask Client Portal SSO - # 62603940-b9b0-454f-b138-eb8d571f21d3 - Eshgro Smarter 365? - # Possible others, Scapmann, PatchMyPC, Datto M365 management, Kaseya crap, Exclaimer(?), HP, Lenovo, Dell, Apple(???), resellers(all region tenants?), Action1, Liquit - # Current idea, do a filtered serviceprincipals request based on the appOwner tenantids of known MSP vendors, load that data into a multi-select on the GUI + if ($request.body.RemoveVendorApps) { + $request.body.RemoveVendorApps | ForEach-Object { + try { + $delete = (New-GraphPostRequest -type 'DELETE' -Uri "https://graph.microsoft.com/v1.0/serviceprincipals/$($_.value)" -tenantid $Tenantfilter) + $results.Add("Succesfully removed app $($_.label)") + Write-LogMessage -user $ExecutingUser -API $APIName -message "App $($_.label) was removed" -Sev "Info" -tenant $TenantFilter + } catch { + #$results.Add("Failed to removed app $($_.displayName)") + $errors.Add("Failed to removed app $($_.label)") + } + } } # All customer tenant specific actions ALWAYS have to be completed before this action! @@ -112,6 +114,7 @@ Function Invoke-ExecOffboardTenant { try { $delete = (New-GraphPostRequest -type 'DELETE' -Uri "https://graph.microsoft.com/v1.0/serviceprincipals/$($_.id)" -tenantid $Tenantfilter) $results.Add("Succesfully removed app $($_.displayName)") + Write-LogMessage -user $ExecutingUser -API $APIName -message "App $($_.displayName) was removed" -Sev "Info" -tenant $TenantFilter } catch { #$results.Add("Failed to removed app $($_.displayName)") $errors.Add("Failed to removed app $($_.displayName)") @@ -119,7 +122,7 @@ Function Invoke-ExecOffboardTenant { } } catch { #$results.Add("Failed to retrieve multitenant apps, no apps have been removed: $($_.Exception.message)") - $errors.Add("Failed to retrieve multitenant apps, no apps have been removed: $($_.Exception.message)") + $errors.Add("Failed to retrieve multitenant CSP apps, no apps have been removed: $($_.Exception.message)") } } @@ -131,6 +134,7 @@ Function Invoke-ExecOffboardTenant { try { $terminate = (New-GraphPostRequest -type 'POST' -Uri "https://graph.microsoft.com/v1.0/tenantRelationships/delegatedAdminRelationships/$($_.id)/requests" -body '{"action":"terminate"}' -ContentType 'application/json' -tenantid $env:TenantID) $results.Add("Succesfully terminated GDAP relationship $($_.displayName) from tenant $TenantFilter") + Write-LogMessage -user $ExecutingUser -API $APIName -message "GDAP Relationship $($_.displayName) has been terminated" -Sev "Info" -tenant $TenantFilter } catch { #$results.Add("Failed to terminate GDAP relationship $($_.displayName): $($_.Exception.message)") $errors.Add("Failed to terminate GDAP relationship $($_.displayName): $($_.Exception.message)") @@ -147,6 +151,7 @@ Function Invoke-ExecOffboardTenant { try { $terminate = (New-GraphPostRequest -type 'PATCH' -body '{ "relationshipToPartner": "none" }' -Uri "https://api.partnercenter.microsoft.com/v1/customers/$TenantFilter" -ContentType 'application/json' -scope 'https://api.partnercenter.microsoft.com/user_impersonation' -tenantid $env:TenantID) $results.Add('Succesfully terminated contract relationship') + Write-LogMessage -user $ExecutingUser -API $APIName -message "Contract relationship terminated" -Sev "Info" -tenant $TenantFilter } catch { #$results.Add("Failed to terminate contract relationship: $($_.Exception.message)") $errors.Add("Failed to terminate contract relationship: $($_.Exception.message)") diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOnboardTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOnboardTenant.ps1 index c67be5c4de8f..e9e23c2a174e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOnboardTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOnboardTenant.ps1 @@ -53,9 +53,11 @@ function Invoke-ExecOnboardTenant { Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop Push-OutputBinding -Name QueueItem -Value ([pscustomobject]@{ - FunctionName = 'ExecOnboardTenantQueue' - id = $Id - Roles = $Request.Body.gdapRoles + FunctionName = 'ExecOnboardTenantQueue' + id = $Id + Roles = $Request.Body.gdapRoles + AddMissingGroups = $Request.Body.addMissingGroups + AutoMapRoles = $Request.Body.autoMapRoles }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphExplorerPresets.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphExplorerPresets.ps1 new file mode 100644 index 000000000000..100aa6a450ba --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphExplorerPresets.ps1 @@ -0,0 +1,42 @@ +using namespace System.Net + +Function Invoke-ListGraphExplorerPresets { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + $Username = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($request.headers.'x-ms-client-principal')) | ConvertFrom-Json).userDetails + # Write to the Azure Functions log stream. + Write-Host 'PowerShell HTTP trigger function processed a request.' + try { + $Table = Get-CIPPTable -TableName 'GraphPresets' + $Presets = Get-CIPPAzDataTableEntity @Table -Filter "Owner eq '$Username' or IsShared eq true" + $Results = foreach ($Preset in $Presets) { + [PSCustomObject]@{ + id = $Preset.Id + name = $Preset.name + IsShared = $Preset.IsShared + IsMyPreset = $Preset.Owner -eq $Username + params = ConvertFrom-Json -InputObject $Preset.Params + } + } + } catch { + $Presets = @() + } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @{ + Results = @($Results) + Metadata = @{ + Count = ($Presets | Measure-Object).Count + } + } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListWebhookAlert.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListWebhookAlert.ps1 index 6eb63f8a57ae..5ee5feb2924f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListWebhookAlert.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListWebhookAlert.ps1 @@ -10,10 +10,9 @@ Function Invoke-ListWebhookAlert { $APIName = $TriggerMetadata.FunctionName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - $WebhookTable = Get-CIPPTable -TableName webhookTable - $WebhookRow = Get-CIPPAzDataTableEntity @WebhookTable - - # Associate values to output bindings by calling 'Push-OutputBinding'. + $Table = get-cipptable -TableName 'SchedulerConfig' + $WebhookRow = Get-CIPPAzDataTableEntity @Table | Where-Object -Property PartitionKey -EQ 'WebhookAlert' + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = @($WebhookRow) diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAdminPassword.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAdminPassword.ps1 new file mode 100644 index 000000000000..af03360c1973 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAdminPassword.ps1 @@ -0,0 +1,19 @@ + +function Push-CIPPAlertAdminPassword { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [pscustomobject]$QueueItem, + $TriggerMetadata + ) + try { + New-GraphGETRequest -uri "https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '62e90394-69f5-4237-9190-012177145e10'&`$expand=principal" -tenantid $($QueueItem.tenant) | Where-Object { ($_.principalOrganizationId -EQ $QueueItem.tenantid) -and ($_.principal.'@odata.type' -eq '#microsoft.graph.user') } | ForEach-Object { + $LastChanges = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/users/$($_.principalId)?`$select=UserPrincipalName,lastPasswordChangeDateTime" -tenant $($QueueItem.tenant) + if ($LastChanges.LastPasswordChangeDateTime -gt (Get-Date).AddDays(-1)) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Admin password has been changed for $($LastChanges.UserPrincipalName) in last 24 hours" + } + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Could not get admin password changes for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 new file mode 100644 index 000000000000..07571db760aa --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 @@ -0,0 +1,30 @@ +function Push-CIPPAlertApnCertExpiry { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + $LastRunTable = Get-CIPPTable -Table AlertLastRun + + try { + $Filter = "RowKey eq 'ApnCertExpiry' and PartitionKey eq '{0}'" -f $QueueItem.tenantid + $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter + $Yesterday = (Get-Date).AddDays(-1) + if (-not $LastRun.Timestamp.DateTime -or ($LastRun.Timestamp.DateTime -le $Yesterday)) { + try { + $Apn = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/applePushNotificationCertificate' -tenantid $QueueItem.tenant + if ($Apn.expirationDateTime -lt (Get-Date).AddDays(30) -and $Apn.expirationDateTime -gt (Get-Date).AddDays(-7)) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message ('Intune: Apple Push Notification certificate for {0} is expiring on {1}' -f $Apn.appleIdentifier, $Apn.expirationDateTime) + } + } catch {} + } + $LastRun = @{ + RowKey = 'ApnCertExpiry' + PartitionKey = $QueueItem.tenantid + } + Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force + } catch { + # Error handling + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 new file mode 100644 index 000000000000..e0936bb9c69c --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 @@ -0,0 +1,37 @@ +function Push-CIPPAlertAppSecretExpiry { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + $LastRunTable = Get-CIPPTable -Table AlertLastRun + + + try { + $Filter = "RowKey eq 'AppSecretExpiry' and PartitionKey eq '{0}'" -f $QueueItem.tenantid + $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter + $Yesterday = (Get-Date).AddDays(-1) + if (-not $LastRun.Timestamp.DateTime -or ($LastRun.Timestamp.DateTime -le $Yesterday)) { + New-GraphGetRequest -uri "https://graph.microsoft.com/beta/applications?`$select=appId,displayName,passwordCredentials" -tenantid $QueueItem.tenant | ForEach-Object { + foreach ($App in $_) { + if ($App.passwordCredentials) { + foreach ($Credential in $App.passwordCredentials) { + if ($Credential.endDateTime -lt (Get-Date).AddDays(30) -and $Credential.endDateTime -gt (Get-Date).AddDays(-7)) { + ("Application '{0}' has secrets expiring on {1}" -f $App.displayName, $Credential.endDateTime) + } + } + } + } + } + $LastRun = @{ + RowKey = 'AppSecretExpiry' + PartitionKey = $QueueItem.tenantid + } + Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force + } + } catch { + throw $_ + } +} + diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDefenderMalware.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDefenderMalware.ps1 new file mode 100644 index 000000000000..b69ed1b50ab2 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDefenderMalware.ps1 @@ -0,0 +1,16 @@ + +function Push-CIPPAlertDefenderMalware { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + try { + New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/windowsDeviceMalwareStates?`$top=999&`$filter=tenantId eq '$($QueueItem.tenantid)'" | Where-Object { $_.malwareThreatState -eq 'Active' } | ForEach-Object { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "$($_.managedDeviceName): Malware found and active. Severity: $($_.MalwareSeverity). Malware name: $($_.MalwareDisplayName)" + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Could not get malware data for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDefenderStatus.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDefenderStatus.ps1 new file mode 100644 index 000000000000..e9d4e06adae0 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDefenderStatus.ps1 @@ -0,0 +1,16 @@ + +function Push-CIPPAlertDefenderStatus { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + try { + New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/windowsProtectionStates?`$top=999&`$filter=tenantId eq '$($QueueItem.tenantid)'" | Where-Object { $_.realTimeProtectionEnabled -eq $false -or $_.MalwareprotectionEnabled -eq $false } | ForEach-Object { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "$($_.managedDeviceName) - Real Time Protection: $($_.realTimeProtectionEnabled) & Malware Protection: $($_.MalwareprotectionEnabled)" + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Could not get defender status for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDepTokenExpiry.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDepTokenExpiry.ps1 new file mode 100644 index 000000000000..c41d62c1f9b5 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDepTokenExpiry.ps1 @@ -0,0 +1,34 @@ +function Push-CIPPAlertDepTokenExpiry { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + $LastRunTable = Get-CIPPTable -Table AlertLastRun + + + + try { + $Filter = "RowKey eq 'DepTokenExpiry' and PartitionKey eq '{0}'" -f $QueueItem.tenantid + $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter + $Yesterday = (Get-Date).AddDays(-1) + if (-not $LastRun.Timestamp.DateTime -or ($LastRun.Timestamp.DateTime -le $Yesterday)) { + try { + $DepTokens = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings' -tenantid $QueueItem.tenant).value + foreach ($Dep in $DepTokens) { + if ($Dep.tokenExpirationDateTime -lt (Get-Date).AddDays(30) -and $Dep.tokenExpirationDateTime -gt (Get-Date).AddDays(-7)) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message ('Apple Device Enrollment Program token expiring on {0}' -f $Dep.tokenExpirationDateTime) + } + } + } catch {} + $LastRun = @{ + RowKey = 'DepTokenExpiry' + PartitionKey = $QueueItem.tenantid + } + Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force + } + } catch { + # Error handling + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertExpiringLicenses.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertExpiringLicenses.ps1 new file mode 100644 index 000000000000..e1f94df1fa8d --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertExpiringLicenses.ps1 @@ -0,0 +1,16 @@ +function Push-CIPPAlertExpiringLicenses { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + try { + Get-CIPPLicenseOverview -TenantFilter $QueueItem.tenant | Where-Object -Property 'TimeUntilRenew' -LT 29 | ForEach-Object { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "$($_.License) will expire in $($_.TimeUntilRenew) days. The estimated term is $($_.EstTerm)" + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Error occurred: $(Get-NormalizedError -message $_.Exception.message)" + } +} + diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAdmins.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAdmins.ps1 new file mode 100644 index 000000000000..7f8c83fad40b --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAdmins.ps1 @@ -0,0 +1,39 @@ +function Push-CIPPAlertMFAAdmins { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + try { + $StrongMFAMethods = '#microsoft.graph.fido2AuthenticationMethod', '#microsoft.graph.phoneAuthenticationMethod', '#microsoft.graph.passwordlessmicrosoftauthenticatorauthenticationmethod', '#microsoft.graph.softwareOathAuthenticationMethod', '#microsoft.graph.microsoftAuthenticatorAuthenticationMethod' + $AdminList = (New-GraphGETRequest -uri "https://graph.microsoft.com/beta/directoryRoles?`$expand=members" -tenantid $($QueueItem.tenant) | Where-Object -Property roleTemplateId -NE 'd29b2b05-8046-44ba-8758-1e26182fcf32').members | Where-Object { $_.userPrincipalName -ne $null -and $_.Usertype -eq 'Member' -and $_.accountEnabled -eq $true } | Sort-Object UserPrincipalName -Unique + $CAPolicies = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies' -tenantid $QueueItem.tenant -ErrorAction Stop) + foreach ($Policy in $CAPolicies) { + if ($policy.grantControls.customAuthenticationFactors -eq 'RequireDuoMfa') { + $DuoActive = $true + } + } + if (!$DuoActive) { + $AdminList | ForEach-Object { + $CARegistered = $null + try { + New-GraphGETRequest -uri "https://graph.microsoft.com/beta/users/$($_.ID)/authentication/Methods" -tenantid $($QueueItem.tenant) | ForEach-Object { + if ($_.'@odata.type' -in $StrongMFAMethods) { + $CARegistered = $true + } + } + if ($CARegistered -ne $true) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Admin $($_.UserPrincipalName) is enabled but does not have any form of MFA configured." + } + } catch { + # Error handling here if needed + } + } + } else { + Write-LogMessage -message 'Potentially using Duo for MFA, could not check MFA status for Admins with 100% accuracy' -API 'MFA Alerts - Informational' -tenant $QueueItem.tenant -sev Info + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Could not get MFA status for admins for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAlertUsers.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAlertUsers.ps1 new file mode 100644 index 000000000000..b7bf8bc6d304 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAlertUsers.ps1 @@ -0,0 +1,51 @@ +function Push-CIPPAlertMFAAlertUsers { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + try { + $users = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/users?`$select=userPrincipalName,id,accountEnabled,userType&`$filter=userType eq 'Member' and accountEnabled eq true" -tenantid $($QueueItem.tenant) + Write-Host "found $($users.count) users" + $StrongMFAMethods = '#microsoft.graph.fido2AuthenticationMethod', '#microsoft.graph.phoneAuthenticationMethod', '#microsoft.graph.passwordlessmicrosoftauthenticatorauthenticationmethod', '#microsoft.graph.softwareOathAuthenticationMethod', '#microsoft.graph.microsoftAuthenticatorAuthenticationMethod' + + $UserBatches = [System.Collections.Generic.List[Object]]@() + for ($i = 0; $i -lt $users.count; $i += 20) { + $UserBatches.Add($users[$i..($i + 19)]) + } + + $UserBatches | ForEach-Object -Parallel { + Import-Module CippCore + Import-Module AzBobbyTables + $UserBatch = $_ + Write-Host "processing batch of $($UserBatch.count) users" + $BatchRequests = $UserBatch | ForEach-Object { + @{ + id = $_.id + method = 'GET' + url = "users/$($_.ID)/authentication/Methods" + } + } + $BatchResponses = New-GraphBulkRequest -tenantid $using:QueueItem.tenant -Requests $BatchRequests + foreach ($response in $BatchResponses) { + $UPN = ($UserBatch | Where-Object { $_.id -eq $response.id }).UserPrincipalName + $CARegistered = $false + + foreach ($method in $response.body.value) { + if ($method.'@odata.type' -in $using:StrongMFAMethods) { + $CARegistered = $true + break + } + } + + if (-not $CARegistered) { + Write-AlertMessage -tenant $using:QueueItem.tenant -message "User $UPN is enabled but does not have any form of MFA configured." + } + } + } -ThrottleLimit 25 + + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Could not get MFA status for users for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertNewRole.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertNewRole.ps1 new file mode 100644 index 000000000000..504bb3ea3153 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertNewRole.ps1 @@ -0,0 +1,37 @@ +function Push-CIPPAlertNewRole { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + $Deltatable = Get-CIPPTable -Table DeltaCompare + try { + $Filter = "PartitionKey eq 'AdminDelta' and RowKey eq '{0}'" -f $QueueItem.tenantid + $AdminDelta = (Get-CIPPAzDataTableEntity @Deltatable -Filter $Filter).delta | ConvertFrom-Json -ErrorAction SilentlyContinue + $NewDelta = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/directoryRoles?`$expand=members" -tenantid $QueueItem.tenant) | Select-Object displayname, Members | ForEach-Object { + @{ + GroupName = $_.displayname + Members = $_.Members.UserPrincipalName + } + } + $NewDeltatoSave = $NewDelta | ConvertTo-Json -Depth 10 -Compress -ErrorAction SilentlyContinue | Out-String + $DeltaEntity = @{ + PartitionKey = 'AdminDelta' + RowKey = [string]$QueueItem.tenantid + delta = "$NewDeltatoSave" + } + Add-CIPPAzDataTableEntity @DeltaTable -Entity $DeltaEntity -Force + + if ($AdminDelta) { + foreach ($Group in $NewDelta) { + $OldDelta = $AdminDelta | Where-Object { $_.GroupName -eq $Group.GroupName } + $Group.members | Where-Object { $_ -notin $OldDelta.members } | ForEach-Object { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "$_ has been added to the $($Group.GroupName) Role" + } + } + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Could not get get role changes for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertNoCAConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertNoCAConfig.ps1 new file mode 100644 index 000000000000..72bd6d90b151 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertNoCAConfig.ps1 @@ -0,0 +1,21 @@ +function Push-CIPPAlertNoCAConfig { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + + try { + $CAAvailable = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $QueueItem.Tenant -erroraction stop).serviceplans + if ('AAD_PREMIUM' -in $CAAvailable.servicePlanName) { + $CAPolicies = (New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies' -tenantid $QueueItem.Tenant) + if (!$CAPolicies.id) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message 'Conditional Access is available, but no policies could be found.' + } + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Error occurred: $(Get-NormalizedError -message $_.Exception.message)" + } + +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertOverusedLicenses.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertOverusedLicenses.ps1 new file mode 100644 index 000000000000..bd96b03266cd --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertOverusedLicenses.ps1 @@ -0,0 +1,27 @@ +function Push-CIPPAlertOverusedLicenses { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + + + try { + $LicenseTable = Get-CIPPTable -TableName ExcludedLicenses + $ExcludedSkuList = Get-CIPPAzDataTableEntity @LicenseTable + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $QueueItem.tenant | ForEach-Object { + $skuid = $_ + foreach ($sku in $skuid) { + if ($sku.skuId -in $ExcludedSkuList.GUID) { continue } + $PrettyName = ($ConvertTable | Where-Object { $_.GUID -eq $sku.skuid }).'Product_Display_Name' | Select-Object -Last 1 + if (!$PrettyName) { $PrettyName = $sku.skuPartNumber } + if ($sku.prepaidUnits.enabled - $sku.consumedUnits -lt 0) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "$PrettyName has Overused licenses. Using $($_.consumedUnits) of $($_.prepaidUnits.enabled)." + } + } + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Error occurred: $(Get-NormalizedError -message $_.Exception.message)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertQuotaUsed.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertQuotaUsed.ps1 new file mode 100644 index 000000000000..9adaa80ceca6 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertQuotaUsed.ps1 @@ -0,0 +1,20 @@ +function Push-CIPPAlertQuotaUsed { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + + + try { + New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getMailboxUsageDetail(period='D7')?`$format=application/json" -tenantid $QueueItem.tenant | ForEach-Object { + $PercentLeft = [math]::round($_.StorageUsedInBytes / $_.prohibitSendReceiveQuotaInBytes * 100) + if ($PercentLeft -gt 90) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "$($_.UserPrincipalName): Mailbox has less than 10% space left. Mailbox is $PercentLeft% full" + } + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Error occurred: $(Get-NormalizedError -message $_.Exception.message)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertSecDefaultsUpsell.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertSecDefaultsUpsell.ps1 new file mode 100644 index 000000000000..1380b73b4233 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertSecDefaultsUpsell.ps1 @@ -0,0 +1,32 @@ +function Push-CIPPAlertSecDefaultsUpsell { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + $LastRunTable = Get-CIPPTable -Table AlertLastRun + + + try { + $Filter = "RowKey eq 'SecDefaultsUpsell' and PartitionKey eq '{0}'" -f $QueueItem.tenantid + $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter + $Yesterday = (Get-Date).AddDays(-1) + if (-not $LastRun.Timestamp.DateTime -or ($LastRun.Timestamp.DateTime -le $Yesterday)) { + try { + $SecDefaults = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -tenantid $QueueItem.tenant) + if ($SecDefaults.isEnabled -eq $false -and $SecDefaults.securityDefaultsUpsell.action -in @('autoEnable', 'autoEnabledNotify')) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message ('Security Defaults will be automatically enabled on {0}' -f $SecDefaults.securityDefaultsUpsell.dueDateTime) + } + } catch {} + $LastRun = @{ + RowKey = 'SecDefaultsUpsell' + PartitionKey = $QueueItem.tenantid + } + Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force + } + } catch { + # Error handling + } +} + diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertSharepointQuota.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertSharepointQuota.ps1 new file mode 100644 index 000000000000..1c5754b621ef --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertSharepointQuota.ps1 @@ -0,0 +1,23 @@ + +function Push-CIPPAlertSharepointQuota { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + Try { + $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains' -tenantid $QueueItem.Tenant | Where-Object { $_.isInitial -eq $true }).id.Split('.')[0] + $sharepointToken = (Get-GraphToken -scope "https://$($tenantName)-admin.sharepoint.com/.default" -tenantid $QueueItem.Tenant) + $sharepointToken.Add('accept', 'application/json') + $sharepointQuota = (Invoke-RestMethod -Method 'GET' -Headers $sharepointToken -Uri "https://$($tenantName)-admin.sharepoint.com/_api/StorageQuotas()?api-version=1.3.2" -ErrorAction Stop).value + if ($sharepointQuota) { + $UsedStoragePercentage = [int](($sharepointQuota.GeoUsedStorageMB / $sharepointQuota.TenantStorageMB) * 100) + if ($UsedStoragePercentage -gt 90) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "SharePoint Storage is at $($UsedStoragePercentage)%" + } + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Could not get SharePoint quota for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertUnusedLicenses.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertUnusedLicenses.ps1 new file mode 100644 index 000000000000..c2e7beeb6522 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertUnusedLicenses.ps1 @@ -0,0 +1,27 @@ +function Push-CIPPAlertUnusedLicenses { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + + + try { + $LicenseTable = Get-CIPPTable -TableName ExcludedLicenses + $ExcludedSkuList = Get-CIPPAzDataTableEntity @LicenseTable + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $QueueItem.tenant | ForEach-Object { + $skuid = $_ + foreach ($sku in $skuid) { + if ($sku.skuId -in $ExcludedSkuList.GUID) { continue } + $PrettyName = ($ConvertTable | Where-Object { $_.GUID -eq $sku.skuid }).'Product_Display_Name' | Select-Object -Last 1 + if (!$PrettyName) { $PrettyName = $sku.skuPartNumber } + if ($sku.prepaidUnits.enabled - $sku.consumedUnits -gt 0) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "$PrettyName has unused licenses. Using $($_.consumedUnits) of $($_.prepaidUnits.enabled)." + } + } + } + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Error occurred: $(Get-NormalizedError -message $_.Exception.message)" + } + } diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertVppTokenExpiry.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertVppTokenExpiry.ps1 new file mode 100644 index 000000000000..d18dd28dd11d --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertVppTokenExpiry.ps1 @@ -0,0 +1,36 @@ +function Push-CIPPAlertVppTokenExpiry { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $QueueItem, + $TriggerMetadata + ) + $LastRunTable = Get-CIPPTable -Table AlertLastRun + + + try { + $Filter = "RowKey eq 'VppTokenExpiry' and PartitionKey eq '{0}'" -f $QueueItem.tenantid + $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter + $Yesterday = (Get-Date).AddDays(-1) + if (-not $LastRun.Timestamp.DateTime -or ($LastRun.Timestamp.DateTime -le $Yesterday)) { + try { + $VppTokens = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceAppManagement/vppTokens' -tenantid $QueueItem.tenant).value + foreach ($Vpp in $VppTokens) { + if ($Vpp.state -ne 'valid') { + Write-AlertMessage -tenant $($QueueItem.tenant) -message 'Apple Volume Purchase Program Token is not valid, new token required' + } + if ($Vpp.expirationDateTime -lt (Get-Date).AddDays(30) -and $Vpp.expirationDateTime -gt (Get-Date).AddDays(-7)) { + Write-AlertMessage -tenant $($QueueItem.tenant) -message ('Apple Volume Purchase Program token expiring on {0}' -f $Vpp.expirationDateTime) + } + } + } catch {} + $LastRun = @{ + RowKey = 'VppTokenExpiry' + PartitionKey = $QueueItem.tenantid + } + Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force + } + } catch { + # Error handling + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 new file mode 100644 index 000000000000..687af6d40b87 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 @@ -0,0 +1,334 @@ +Function Push-ExecOnboardTenantQueue { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($QueueItem, $TriggerMetadata) + try { + $DateFormat = '%Y-%m-%d %H:%M:%S' + $Id = $QueueItem.id + #Write-Host ($QueueItem.Roles | ConvertTo-Json) + $Logs = [System.Collections.Generic.List[object]]::new() + $OnboardTable = Get-CIPPTable -TableName 'TenantOnboarding' + $TenantOnboarding = Get-CIPPAzDataTableEntity @OnboardTable -Filter "RowKey eq '$Id'" + + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Starting onboarding for relationship $Id" }) + $OnboardingSteps = $TenantOnboarding.OnboardingSteps | ConvertFrom-Json + $OnboardingSteps.Step1.Status = 'running' + $OnboardingSteps.Step1.Message = 'Checking GDAP invite status' + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Status = 'running' + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress -AsArray) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + + try { + $Relationship = $TenantOnboarding.Relationship | ConvertFrom-Json -ErrorAction Stop + } catch { + $Relationship = '' + } + + $ExpectedRoles = @( + @{ Name = 'Application Administrator'; Id = '9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3' }, + @{ Name = 'User Administrator'; Id = 'fe930be7-5e62-47db-91af-98c3a49a38b1' }, + @{ Name = 'Intune Administrator'; Id = '3a2c62db-5318-420d-8d74-23affee5d9d5' }, + @{ Name = 'Exchange Administrator'; Id = '29232cdf-9323-42fd-ade2-1d097af3e4de' }, + @{ Name = 'Security Administrator'; Id = '194ae4cb-b126-40b2-bd5b-6091b380977d' }, + @{ Name = 'Cloud App Security Administrator'; Id = '892c5842-a9a6-463a-8041-72aa08ca3cf6' }, + @{ Name = 'Cloud Device Administrator'; Id = '7698a772-787b-4ac8-901f-60d6b08affd2' }, + @{ Name = 'Teams Administrator'; Id = '69091246-20e8-4a56-aa4d-066075b2a7a8' }, + @{ Name = 'Sharepoint Administrator'; Id = 'f28a1f50-f6e7-4571-818b-6a12f2af6b6c' }, + @{ Name = 'Authentication Policy Administrator'; Id = '0526716b-113d-4c15-b2c8-68e3c22b9f80' }, + @{ Name = 'Privileged Role Administrator'; Id = 'e8611ab8-c189-46e8-94e1-60213ab1f814' }, + @{ Name = 'Privileged Authentication Administrator'; Id = '7be44c8a-adaf-4e2a-84d6-ab2649e08a13' } + ) + + if ($OnboardingSteps.Step1.Status -ne 'succeeded') { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Checking relationship status' }) + $x = 0 + do { + $Relationship = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$Id" + $x++ + Start-Sleep -Seconds 30 + } while ($Relationship.status -ne 'active' -and $x -lt 4) + + if ($Relationship.status -eq 'active') { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'GDAP Invite Accepted' }) + $OnboardingSteps.Step1.Status = 'succeeded' + $OnboardingSteps.Step1.Message = "GDAP Invite accepted for $($Relationship.customer.displayName)" + $TenantOnboarding.CustomerId = $Relationship.customer.tenantId + } else { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'GDAP Invite Failed' }) + $OnboardingSteps.Step1.Status = 'failed' + $OnboardingSteps.Step1.Message = 'GDAP Invite timeout, retry onboarding after accepting the invite with a GA account in the customer tenant.' + $TenantOnboarding.Status = 'failed' + } + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Relationship = [string](ConvertTo-Json -InputObject $Relationship -Compress -Depth 10) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + } + + if ($OnboardingSteps.Step1.Status -eq 'succeeded') { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Starting role check' }) + $OnboardingSteps.Step2.Status = 'running' + $OnboardingSteps.Step2.Message = 'Checking role mapping' + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + + $MissingRoles = [System.Collections.Generic.List[string]]::new() + foreach ($Role in $ExpectedRoles) { + $RoleFound = $false + foreach ($AvailableRole in $Relationship.accessDetails.unifiedRoles) { + if ($AvailableRole.roleDefinitionId -eq $Role.Id) { + $RoleFound = $true + break + } + } + if (!$RoleFound) { + $MissingRoles.Add($Role.Name) + } + } + if (($MissingRoles | Measure-Object).Count -gt 0) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Missing roles for relationship' }) + $TenantOnboarding.Status = 'failed' + $OnboardingSteps.Step2.Status = 'failed' + $OnboardingSteps.Step2.Message = "Your GDAP relationship is missing the following roles: $($MissingRoles -join ', ')" + } else { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Required roles found' }) + $OnboardingSteps.Step2.Status = 'succeeded' + $OnboardingSteps.Step2.Message = 'Your GDAP relationship has the required roles' + } + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + } + + if ($OnboardingSteps.Step2.Status -eq 'succeeded') { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Checking group mapping' }) + $AccessAssignments = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$Id/accessAssignments" + if ($AccessAssignments.id -and $QueueItem.AutoMapRoles -ne $true) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Groups mapped' }) + $OnboardingSteps.Step3.Status = 'succeeded' + $OnboardingSteps.Step3.Message = 'Your GDAP relationship already has mapped security groups' + } else { + $GroupSuccess = $false + $OnboardingSteps.Step3.Status = 'running' + $OnboardingSteps.Step3.Message = 'Mapping security groups' + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + + $InviteTable = Get-CIPPTable -TableName 'GDAPInvites' + $Invite = Get-CIPPAzDataTableEntity @InviteTable -Filter "RowKey eq '$Id'" + + if ($AccessAssignments.id -and !$Invite) { + $MissingRoles = [System.Collections.Generic.List[object]]::new() + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Relationship has existing access assignments, checking for missing mappings' }) + #Write-Host ($AccessAssignments | ConvertTo-Json -Depth 5) + if ($QueueItem.Roles -and $QueueItem.AutoMapRoles -eq $true) { + foreach ($Role in $QueueItem.Roles) { + if ($AccessAssignments.accessContainer.accessContainerid -notcontains $Role.GroupId -and $Relationship.accessDetails.unifiedRoles.roleDefinitionId -contains $Role.roleDefinitionId) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Adding missing group to relationship: $($Role.GroupName)" }) + $MissingRoles.Add([PSCustomObject]$Role) + } + } + + if (($MissingRoles | Measure-Object).Count -gt 0) { + $Invite = [PSCustomObject]@{ + 'PartitionKey' = 'invite' + 'RowKey' = $Id + 'InviteUrl' = 'https://admin.microsoft.com/AdminPortal/Home#/partners/invitation/granularAdminRelationships/{0}' -f $Id + 'RoleMappings' = [string](@($MissingRoles) | ConvertTo-Json -Depth 10 -Compress) + } + Add-CIPPAzDataTableEntity @InviteTable -Entity $Invite + } else { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'All roles have been mapped to the M365 GDAP security groups' }) + $OnboardingSteps.Step3.Status = 'succeeded' + $OnboardingSteps.Step3.Message = 'Groups mapped successfully' + $GroupSuccess = $true + } + } + } + + if (!$AccessAssignments.id -and !$Invite -and $QueueItem.Roles) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'No access assignments found, using defined role mapping.' }) + $MatchingRoles = [System.Collections.Generic.List[object]]::new() + foreach ($Role in $QueueItem.Roles) { + if ($Relationship.accessDetails.unifiedRoles.roleDefinitionId -contains $Role.roleDefinitionId) { + $MatchingRoles.Add([PSCustomObject]$Role) + } + } + + if (($MatchingRoles | Measure-Object).Count -gt 0 -and $QueueItem.AutoMapRoles -eq $true) { + $Invite = [PSCustomObject]@{ + 'PartitionKey' = 'invite' + 'RowKey' = $Id + 'InviteUrl' = 'https://admin.microsoft.com/AdminPortal/Home#/partners/invitation/granularAdminRelationships/{0}' -f $Id + 'RoleMappings' = [string](@($MatchingRoles) | ConvertTo-Json -Depth 10 -Compress) + } + Add-CIPPAzDataTableEntity @InviteTable -Entity $Invite + $GroupSuccess = $true + } else { + $TenantOnboarding.Status = 'failed' + $OnboardingSteps.Step3.Status = 'failed' + $OnboardingSteps.Step3.Message = 'No matching roles found, check the relationship and try again.' + } + } + + if ($Invite) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'GDAP invite found, starting group/role mapping' }) + $GroupMapStatus = Set-CIPPGDAPInviteGroups -Relationship $Relationship + if ($GroupMapStatus) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Groups mapped successfully' }) + $OnboardingSteps.Step3.Message = 'Groups mapped successfully, checking access assignment status' + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + + } else { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Group mapping failed' }) + $TenantOnboarding.Status = 'failed' + $OnboardingSteps.Step3.Status = 'failed' + $OnboardingSteps.Step3.Message = 'Group mapping failed, check the log book for details.' + } + } elseif (!$GroupSuccess) { + $TenantOnboarding.Status = 'failed' + $OnboardingSteps.Step3.Status = 'failed' + $OnboardingSteps.Step3.Message = 'Failed to map security groups, no pending invite available' + } + + $x = 0 + do { + $x++ + $AccessAssignments = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$Id/accessAssignments" + Start-Sleep -Seconds 10 + } while ($AccessAssignments.status -contains 'pending' -and $x -le 12) + + if ($AccessAssignments.status -notcontains 'pending') { + $OnboardingSteps.Step3.Message = 'Group check: Access assignments are mapped and active' + $OnboardingSteps.Step3.Status = 'succeeded' + } else { + $OnboardingSteps.Step3.Message = 'Group check: Access assignments are still pending, try again later' + $OnboardingSteps.Step3.Status = 'failed' + } + } + if ($QueueItem.AddMissingGroups -eq $true) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Checking for missing groups for SAM user' }) + $SamUserId = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me?`$select=id").id + $CurrentMemberships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me/transitiveMemberOf?`$select=id,displayName" + foreach ($Role in $QueueItem.Roles) { + if ($CurrentMemberships.id -notcontains $Role.GroupId) { + $PostBody = @{ + '@odata.id' = 'https://graph.microsoft.com/v1.0/directoryObjects/{0}' -f $SamUserId + } | ConvertTo-Json -Compress + try { + New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($Role.GroupId)/members/`$ref" -body $PostBody -AsApp $true -NoAuthCheck $true + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Added SAM user to $($Role.GroupName)" }) + } catch { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Failed to add SAM user to $($Role.GroupName) - $($_.Exception.Message)" }) + } + } + } + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'SAM user group check completed' }) + } + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + } + + if ($OnboardingSteps.Step3.Status -eq 'succeeded') { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Refreshing CPV permissions' }) + $OnboardingSteps.Step4.Status = 'running' + $OnboardingSteps.Step4.Message = 'Refreshing CPV permissions' + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + + try { + Remove-CIPPCache -tenantsOnly $true + } catch {} + + $Tenant = Get-Tenants | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 + if ($Tenant) { + $y = 0 + $Refreshing = $true + $CPVSuccess = $false + do { + try { + Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Tenant.defaultDomainName + Add-CIPPDelegatedPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Tenant.defaultDomainName + $CPVSuccess = $true + $Refreshing = $false + } catch { + $y++ + Start-Sleep -Seconds 30 + } + } while ($Refreshing -and $y -lt 4) + + if ($CPVSuccess) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'CPV permissions refreshed' }) + $OnboardingSteps.Step4.Status = 'succeeded' + $OnboardingSteps.Step4.Message = 'CPV permissions refreshed' + } else { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'CPV permissions failed to refresh' }) + $TenantOnboarding.Status = 'failed' + $OnboardingSteps.Step4.Status = 'failed' + $OnboardingSteps.Step4.Message = 'CPV permissions failed to refresh, try again later' + } + } else { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Tenant not found' }) + $TenantOnboarding.Status = 'failed' + $OnboardingSteps.Step4.Status = 'failed' + $OnboardingSteps.Step4.Message = 'Tenant not found in customer list, try again later' + } + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + } + + if ($OnboardingSteps.Step4.Status -eq 'succeeded') { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Testing API access for $($Tenant.defaultDomainName)" }) + $OnboardingSteps.Step5.Status = 'running' + $OnboardingSteps.Step5.Message = 'Testing API access' + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + + try { + #Write-Host ($Tenant | ConvertTo-Json) + $UserCount = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$count=true&`$top=1" -ComplexFilter -tenantid $Tenant.defaultDomainName -CountOnly + } catch { + $UserCount = 0 + $ApiError = $_.Exception.Message + } + + if ($UserCount -gt 0) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'API test successful' }) + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Onboarding complete' }) + $OnboardingSteps.Step5.Status = 'succeeded' + $OnboardingSteps.Step5.Message = 'API Test Successful: {0} users found' -f $UserCount + $TenantOnboarding.Status = 'succeeded' + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + } else { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'API Test failed: {0}' -f $ApiError }) + $OnboardingSteps.Step5.Status = 'failed' + $OnboardingSteps.Step5.Message = 'API Test failed: {0}' -f $ApiError + $TenantOnboarding.Status = 'succeeded' + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + } + } + } catch { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Onboarding failed. Exception: {0}' -f $_.Exception.Message }) + $TenantOnboarding.Status = 'failed' + $TenantOnboarding.Exception = [string]('{0} - Line {1} - {2}' -f $_.Exception.Message, $_.InvocationInfo.ScriptLineNumber, $_.InvocationInfo.ScriptName) + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + } +} diff --git a/Modules/CIPPCore/Public/Get-CIPPGeoIPLocation.ps1 b/Modules/CIPPCore/Public/Get-CIPPGeoIPLocation.ps1 index 11efe5083e4a..59877c61f9da 100644 --- a/Modules/CIPPCore/Public/Get-CIPPGeoIPLocation.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPGeoIPLocation.ps1 @@ -3,32 +3,7 @@ function Get-CIPPGeoIPLocation { param ( [string]$IP ) - - if ($IP -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$') { - $IP = $IP -replace ':\d+$', '' # Remove the port number if present - } - - $partitionKey = "GeoIP" - $IPAsint = [System.Numerics.BigInteger]::Zero - $ipAddress = [System.Net.IPAddress]::Parse($IP) - if ($ipAddress.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6) { - $partitionKey = "GeoIPv6" - $bytes = $ipAddress.GetAddressBytes() - for ($i = 0; $i -lt $bytes.Length; $i++) { - $IPAsint = $IPAsint -shl 8 -bor $bytes[$i] - } - } - else { - $IP.Split(".") | ForEach-Object { - $IPAddressByte = 0 - [int]::TryParse($_, [ref] $IPAddressByte) | Out-Null - $IPAsint = ([long]($IPAsint -shl 8)) -bor [byte]$_ - } - } - - $CTX = New-AzDataTableContext -TableName geoipdb -ConnectionString 'TableEndpoint=https://cyberdraingeoipdb.table.core.windows.net/;SharedAccessSignature=sv=2022-11-02&ss=t&srt=o&sp=rl&se=2025-08-08T21:05:23Z&st=2023-08-08T13:05:23Z&spr=https&sig=89Bmk2Un89xqNzZPLkryFnLRCjHs9rCWGUJjhvf5mso%3D' - $GeoTable = @{ Context = $CTX } - $location = (Get-CIPPAzDataTableEntity @GeoTable -Filter "PartitionKey eq '$partitionKey' and RowKey le '$IPAsint' and ipTo ge '$IPAsint'") | Select-Object -Last 1 - + $location = Invoke-RestMethod "https://geoipdb.azurewebsites.net/api/GetIPInfo?IP=$IP" + if ($location.status -eq 'FAIL') { throw "Could not get location for $IP" } return $location } diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 index 98ce8bdbf8dc..7a8044bc1c5f 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 @@ -25,6 +25,8 @@ function Get-NormalizedError { '*One or more added object references already exist for the following modified properties:*' { 'This user is already a member of this group.' } '*Microsoft.Exchange.Management.Tasks.MemberAlreadyExistsException*' { 'This user is already a member of this group.' } '*The property value exceeds the maximum allowed size (64KB)*' { 'One of the values exceeds the maximum allowed size (64KB).' } + '*Unable to initialize the authorization context*' { 'Your GDAP configuration does not allow us to write to this tenant, please check your group mappings and tenant onboarding.' } + '*Providers.Common.V1.CoreException*' { '403 (Access Denied) - We cannot connect to this tenant.' } Default { $message } } diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 index 9b4848253d98..533b4144c9cd 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 @@ -19,13 +19,19 @@ function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp, $N try { $ReturnedData = (Invoke-RestMethod -Uri $($uri) -Method $TYPE -Body $body -Headers $headers -ContentType 'application/json; charset=utf-8') } catch { - $ErrorMess = $($_.Exception.Message) $Message = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue).error.message - if (!$Message) { $Message = $ErrorMess } + if ($Message -eq $null) { + try { + $Message = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue).message + } catch { + $Message = $($_.Exception.Message) + } + } throw $Message } return $ReturnedData } else { Write-Error 'Not allowed. You cannot manage your own tenant or tenants not under your scope' + } } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/GraphHelper/Write-AlertMessage.ps1 b/Modules/CIPPCore/Public/GraphHelper/Write-AlertMessage.ps1 new file mode 100644 index 000000000000..567f1c05cb77 --- /dev/null +++ b/Modules/CIPPCore/Public/GraphHelper/Write-AlertMessage.ps1 @@ -0,0 +1,16 @@ +function Write-AlertMessage($message, $tenant = 'None', $tenantId = $null) { + <# + .FUNCTIONALITY + Internal + #> + $Table = Get-CIPPTable -tablename cachealerts + $PartitionKey = (Get-Date -UFormat '%Y%m%d').ToString() + $TableRow = @{ + 'Tenant' = [string]$tenant + 'Message' = [string]$message + 'PartitionKey' = $PartitionKey + 'RowKey' = ([guid]::NewGuid()).ToString() + } + $Table.Entity = $TableRow + Add-CIPPAzDataTableEntity @Table | Out-Null +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 index 922b8307edcd..65991c17a452 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 @@ -5,15 +5,17 @@ function Invoke-CippWebhookProcessing { $Data, $Resource, $Operations, - $AllowedLocations, $CIPPPURL, $APIName = 'Process webhook', $ExecutingUser ) + $ConfigTable = get-cipptable -TableName 'SchedulerConfig' $LocationTable = Get-CIPPTable -TableName 'knownlocationdb' + $Alertconfig = Get-CIPPAzDataTableEntity @ConfigTable -Filter "Tenant eq '$tenantfilter'" + if (!$Alertconfig) { + $Alertconfig = Get-CIPPAzDataTableEntity @ConfigTable -Filter "Tenant eq 'AllTenants'" + } - - $AllowedLocations = $AllowedLocations -split ',' if ($data.userId -eq 'Not Available') { $data.userId = $data.userKey } if ($data.Userkey -eq 'Not Available') { $data.Userkey = $data.userId } if ($data.clientip) { @@ -24,12 +26,20 @@ function Invoke-CippWebhookProcessing { Write-Host 'Using known location' $Country = $Location.CountryOrRegion $City = $Location.City - } - else { + $Proxy = $Location.Proxy + $hosting = $Location.Hosting + $ASName = $Location.ASName + } else { Write-Host 'We have to do a lookup' + if ($data.clientip -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$') { + $data.clientip = $data.clientip -replace ':\d+$', '' # Remove the port number if present + } $Location = Get-CIPPGeoIPLocation -IP $data.clientip - $Country = if ($Location.countryCode) { $Location.CountryCode } else { 'Unknown' } - $City = if ($Location.cityName) { $Location.cityName } else { 'Unknown' } + $Country = if ($Location.CountryCode) { $Location.CountryCode } else { 'Unknown' } + $City = if ($Location.City) { $Location.City } else { 'Unknown' } + $Proxy = if ($Location.Proxy -ne $null) { $Location.Proxy } else { 'Unknown' } + $hosting = if ($Location.Hosting -ne $null) { $Location.Hosting } else { 'Unknown' } + $ASName = if ($Location.ASName) { $Location.ASName } else { 'Unknown' } } } $TableObj = [PSCustomObject]::new() @@ -46,155 +56,163 @@ function Invoke-CippWebhookProcessing { Write-Host 'No need to process this operation.' return '' } + + $AllowedLocations = ($Alertconfig.if | ConvertFrom-Json).allowedcountries.value + Write-Host "These are the allowed locations: $($AllowedLocations)" + Write-Host "Operation: $($data.operation)" switch ($data.operation) { - { 'UserLoggedIn' -eq $data.operation -and $Country -notin $AllowedLocations -and $data.ResultStatus -eq 'Success' -and $TableObj.ResultStatusDetail -eq 'Success' } { $data.operation = 'UserLoggedInFromUnknownLocation' } + { 'UserLoggedIn' -eq $data.operation -and $proxy -eq $true } { $data.operation = 'BadRepIP' } + { 'UserLoggedIn' -eq $data.operation -and $hosting -eq $true } { $data.operation = 'HostedIP' } + { 'UserLoggedIn' -eq $data.operation -and $Country -notin $AllowedLocations -and $data.ResultStatus -eq 'Success' -and $TableObj.ResultStatusDetail -eq 'Success' } { + Write-Host "$($country) is not in $($AllowedLocations)" + $data.operation = 'UserLoggedInFromUnknownLocation' + } { 'UserloggedIn' -eq $data.operation -and $data.UserType -eq 2 -and $data.ResultStatus -eq 'Success' -and $TableObj.ResultStatusDetail -eq 'Success' } { $data.operation = 'AdminLoggedIn' } default { break } } - - #Check if the operation is allowed for this webhook. - if ($data.operation -notin $Operations) { - Write-Host "No need to process this operation, but we're saving the IP for future" - Write-Host 'Add IP and potential location to knownlocation db for this specific user' - - if ($data.ClientIP) { - $IP = $data.ClientIP - if ($IP -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$') { - $IP = $IP -replace ':\d+$', '' # Remove the port number if present + Write-Host "Rewrote to operation: $($data.operation)" + #Check if we actually need to do anything, and if not, break away. + foreach ($AlertSetting in $Alertconfig) { + $ifs = $AlertSetting.If | ConvertFrom-Json + $Dos = $AlertSetting.execution | ConvertFrom-Json + if ($data.operation -notin $Ifs.selection -and $ifs.selection -ne 'AnyAlert' -and ($ifs.count -le 1 -and $ifs.selection -ne 'customField')) { + Write-Host 'Not an operation to do anything for. storing IP info' + if ($data.ClientIP -and $data.operation -like '*LoggedIn*') { + Write-Host 'Add IP and potential location to knownlocation db for this specific user.' + $IP = $data.ClientIP + if ($IP -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$') { + $IP = $IP -replace ':\d+$', '' # Remove the port number if present + } + $LocationInfo = @{ + RowKey = [string]$ip + PartitionKey = [string]$data.UserId + Tenant = [string]$TenantFilter + CountryOrRegion = "$Country" + City = "$City" + Proxy = "$Proxy" + Hosting = "$hosting" + ASName = "$ASName" + } + $null = Add-CIPPAzDataTableEntity @LocationTable -Entity $LocationInfo -Force } - $LocationInfo = @{ - RowKey = [string]$ip - PartitionKey = [string]$data.UserId - Tenant = [string]$TenantFilter - CountryOrRegion = "$Country" - City = "$City" + Continue + } else { + $ConditionMet = $true + } + foreach ($field in $ifs.field) { + $parts = $field -split ' ', 3 + $key = $parts[0] + $operator = $parts[1] + $value = $parts[2] + if (!$value) { + Write-Host 'blank value, skip' + continue + } + if ($value -contains ',') { + $valueArray = "(@($value -split ','))" + $dynamicIf = "`$data.$key -$operator $valueArray" + } else { + $dynamicIf = "`$data.$key -$operator '$value'" + } + if (Invoke-Expression $dynamicIf) { + $ConditionMet = $true + } else { + $ConditionMet = $false } - $null = Add-CIPPAzDataTableEntity @LocationTable -Entity $LocationInfo -Force - } - return '' - } - - Set-Location (Get-Item $PSScriptRoot).FullName - $Appname = '[{"Application Name":"ACOM Azure Website","Application IDs":"23523755-3a2b-41ca-9315-f81f3f566a95"},{"Application Name":"AEM-DualAuth","Application IDs":"69893ee3-dd10-4b1c-832d-4870354be3d8"},{"Application Name":"ASM Campaign Servicing","Application IDs":"0cb7b9ec-5336-483b-bc31-b15b5788de71"},{"Application Name":"Azure Advanced Threat Protection","Application IDs":"7b7531ad-5926-4f2d-8a1d-38495ad33e17"},{"Application Name":"Azure Data Lake","Application IDs":"e9f49c6b-5ce5-44c8-925d-015017e9f7ad"},{"Application Name":"Azure Lab Services Portal","Application IDs":"835b2a73-6e10-4aa5-a979-21dfda45231c"},{"Application Name":"Azure Portal","Application IDs":"c44b4083-3bb0-49c1-b47d-974e53cbdf3c"},{"Application Name":"AzureSupportCenter","Application IDs":"37182072-3c9c-4f6a-a4b3-b3f91cacffce"},{"Application Name":"Bing","Application IDs":"9ea1ad79-fdb6-4f9a-8bc3-2b70f96e34c7"},{"Application Name":"CPIM Service","Application IDs":"bb2a2e3a-c5e7-4f0a-88e0-8e01fd3fc1f4"},{"Application Name":"CRM Power BI Integration","Application IDs":"e64aa8bc-8eb4-40e2-898b-cf261a25954f"},{"Application Name":"Dataverse","Application IDs":"00000007-0000-0000-c000-000000000000"},{"Application Name":"Enterprise Roaming and Backup","Application IDs":"60c8bde5-3167-4f92-8fdb-059f6176dc0f"},{"Application Name":"IAM Supportability","Application IDs":"a57aca87-cbc0-4f3c-8b9e-dc095fdc8978"},{"Application Name":"IrisSelectionFrontDoor","Application IDs":"16aeb910-ce68-41d1-9ac3-9e1673ac9575"},{"Application Name":"MCAPI Authorization Prod","Application IDs":"d73f4b35-55c9-48c7-8b10-651f6f2acb2e"},{"Application Name":"Media Analysis and Transformation Service","Application IDs":"944f0bd1-117b-4b1c-af26-804ed95e767e
0cd196ee-71bf-4fd6-a57c-b491ffd4fb1e"},{"Application Name":"Microsoft 365 Support Service","Application IDs":"ee272b19-4411-433f-8f28-5c13cb6fd407"},{"Application Name":"Microsoft App Access Panel","Application IDs":"0000000c-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Approval Management","Application IDs":"65d91a3d-ab74-42e6-8a2f-0add61688c74
38049638-cc2c-4cde-abe4-4479d721ed44"},{"Application Name":"Microsoft Authentication Broker","Application IDs":"29d9ed98-a469-4536-ade2-f981bc1d605e"},{"Application Name":"Microsoft Azure CLI","Application IDs":"04b07795-8ddb-461a-bbee-02f9e1bf7b46"},{"Application Name":"Microsoft Azure PowerShell","Application IDs":"1950a258-227b-4e31-a9cf-717495945fc2"},{"Application Name":"Microsoft Bing Search","Application IDs":"cf36b471-5b44-428c-9ce7-313bf84528de"},{"Application Name":"Microsoft Bing Search for Microsoft Edge","Application IDs":"2d7f3606-b07d-41d1-b9d2-0d0c9296a6e8"},{"Application Name":"Microsoft Bing Default Search Engine","Application IDs":"1786c5ed-9644-47b2-8aa0-7201292175b6"},{"Application Name":"Microsoft Defender for Cloud Apps","Application IDs":"3090ab82-f1c1-4cdf-af2c-5d7a6f3e2cc7"},{"Application Name":"Microsoft Docs","Application IDs":"18fbca16-2224-45f6-85b0-f7bf2b39b3f3"},{"Application Name":"Microsoft Dynamics ERP","Application IDs":"00000015-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Edge Insider Addons Prod","Application IDs":"6253bca8-faf2-4587-8f2f-b056d80998a7"},{"Application Name":"Microsoft Exchange Online Protection","Application IDs":"00000007-0000-0ff1-ce00-000000000000"},{"Application Name":"Microsoft Forms","Application IDs":"c9a559d2-7aab-4f13-a6ed-e7e9c52aec87"},{"Application Name":"Microsoft Graph","Application IDs":"00000003-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Intune Web Company Portal","Application IDs":"74bcdadc-2fdc-4bb3-8459-76d06952a0e9"},{"Application Name":"Microsoft Intune Windows Agent","Application IDs":"fc0f3af4-6835-4174-b806-f7db311fd2f3"},{"Application Name":"Microsoft Learn","Application IDs":"18fbca16-2224-45f6-85b0-f7bf2b39b3f3"},{"Application Name":"Microsoft Office","Application IDs":"d3590ed6-52b3-4102-aeff-aad2292ab01c"},{"Application Name":"Microsoft Office 365 Portal","Application IDs":"00000006-0000-0ff1-ce00-000000000000"},{"Application Name":"Microsoft Office Web Apps Service","Application IDs":"67e3df25-268a-4324-a550-0de1c7f97287"},{"Application Name":"Microsoft Online Syndication Partner Portal","Application IDs":"d176f6e7-38e5-40c9-8a78-3998aab820e7"},{"Application Name":"Microsoft password reset service","Application IDs":"93625bc8-bfe2-437a-97e0-3d0060024faa"},{"Application Name":"Microsoft Power BI","Application IDs":"871c010f-5e61-4fb1-83ac-98610a7e9110"},{"Application Name":"Microsoft Storefronts","Application IDs":"28b567f6-162c-4f54-99a0-6887f387bbcc"},{"Application Name":"Microsoft Stream Portal","Application IDs":"cf53fce8-def6-4aeb-8d30-b158e7b1cf83"},{"Application Name":"Microsoft Substrate Management","Application IDs":"98db8bd6-0cc0-4e67-9de5-f187f1cd1b41"},{"Application Name":"Microsoft Support","Application IDs":"fdf9885b-dd37-42bf-82e5-c3129ef5a302"},{"Application Name":"Microsoft Teams","Application IDs":"1fec8e78-bce4-4aaf-ab1b-5451cc387264"},{"Application Name":"Microsoft Teams Services","Application IDs":"cc15fd57-2c6c-4117-a88c-83b1d56b4bbe"},{"Application Name":"Microsoft Teams Web Client","Application IDs":"5e3ce6c0-2b1f-4285-8d4b-75ee78787346"},{"Application Name":"Microsoft Whiteboard Services","Application IDs":"95de633a-083e-42f5-b444-a4295d8e9314"},{"Application Name":"O365 Suite UX","Application IDs":"4345a7b9-9a63-4910-a426-35363201d503"},{"Application Name":"Office 365 Exchange Online","Application IDs":"00000002-0000-0ff1-ce00-000000000000"},{"Application Name":"Office 365 Management","Application IDs":"00b41c95-dab0-4487-9791-b9d2c32c80f2"},{"Application Name":"Office 365 Search Service","Application IDs":"66a88757-258c-4c72-893c-3e8bed4d6899"},{"Application Name":"Office 365 SharePoint Online","Application IDs":"00000003-0000-0ff1-ce00-000000000000"},{"Application Name":"Office Delve","Application IDs":"94c63fef-13a3-47bc-8074-75af8c65887a"},{"Application Name":"Office Online Add-in SSO","Application IDs":"93d53678-613d-4013-afc1-62e9e444a0a5"},{"Application Name":"Office Online Client AAD- Augmentation Loop","Application IDs":"2abdc806-e091-4495-9b10-b04d93c3f040"},{"Application Name":"Office Online Client AAD- Loki","Application IDs":"b23dd4db-9142-4734-867f-3577f640ad0c"},{"Application Name":"Office Online Client AAD- Maker","Application IDs":"17d5e35f-655b-4fb0-8ae6-86356e9a49f5"},{"Application Name":"Office Online Client MSA- Loki","Application IDs":"b6e69c34-5f1f-4c34-8cdf-7fea120b8670"},{"Application Name":"Office Online Core SSO","Application IDs":"243c63a3-247d-41c5-9d83-7788c43f1c43"},{"Application Name":"Office Online Search","Application IDs":"a9b49b65-0a12-430b-9540-c80b3332c127"},{"Application Name":"Office.com","Application IDs":"4b233688-031c-404b-9a80-a4f3f2351f90"},{"Application Name":"Office365 Shell WCSS-Client","Application IDs":"89bee1f7-5e6e-4d8a-9f3d-ecd601259da7"},{"Application Name":"OfficeClientService","Application IDs":"0f698dd4-f011-4d23-a33e-b36416dcb1e6"},{"Application Name":"OfficeHome","Application IDs":"4765445b-32c6-49b0-83e6-1d93765276ca"},{"Application Name":"OfficeShredderWacClient","Application IDs":"4d5c2d63-cf83-4365-853c-925fd1a64357"},{"Application Name":"OMSOctopiPROD","Application IDs":"62256cef-54c0-4cb4-bcac-4c67989bdc40"},{"Application Name":"OneDrive SyncEngine","Application IDs":"ab9b8c07-8f02-4f72-87fa-80105867a763"},{"Application Name":"OneNote","Application IDs":"2d4d3d8e-2be3-4bef-9f87-7875a61c29de"},{"Application Name":"Outlook Mobile","Application IDs":"27922004-5251-4030-b22d-91ecd9a37ea4"},{"Application Name":"Partner Customer Delegated Admin Offline Processor","Application IDs":"a3475900-ccec-4a69-98f5-a65cd5dc5306"},{"Application Name":"Password Breach Authenticator","Application IDs":"bdd48c81-3a58-4ea9-849c-ebea7f6b6360"},{"Application Name":"Power BI Service","Application IDs":"00000009-0000-0000-c000-000000000000"},{"Application Name":"SharedWithMe","Application IDs":"ffcb16e8-f789-467c-8ce9-f826a080d987"},{"Application Name":"SharePoint Online Web Client Extensibility","Application IDs":"08e18876-6177-487e-b8b5-cf950c1e598c"},{"Application Name":"Signup","Application IDs":"b4bddae8-ab25-483e-8670-df09b9f1d0ea"},{"Application Name":"Skype for Business Online","Application IDs":"00000004-0000-0ff1-ce00-000000000000"},{"Application Name":"Sway","Application IDs":"905fcf26-4eb7-48a0-9ff0-8dcc7194b5ba"},{"Application Name":"Universal Store Native Client","Application IDs":"268761a2-03f3-40df-8a8b-c3db24145b6b"},{"Application Name":"Vortex [wsfed enabled]","Application IDs":"5572c4c0-d078-44ce-b81c-6cbf8d3ed39e"},{"Application Name":"Windows Azure Active Directory","Application IDs":"00000002-0000-0000-c000-000000000000"},{"Application Name":"Windows Azure Service Management API","Application IDs":"797f4846-ba00-4fd7-ba43-dac1f8f63013"},{"Application Name":"WindowsDefenderATP Portal","Application IDs":"a3b79187-70b2-4139-83f9-6016c58cd27b"},{"Application Name":"Windows Search","Application IDs":"26a7ee05-5602-4d76-a7ba-eae8b7b67941"},{"Application Name":"Windows Spotlight","Application IDs":"1b3c667f-cde3-4090-b60b-3d2abd0117f0"},{"Application Name":"Windows Store for Business","Application IDs":"45a330b1-b1ec-4cc1-9161-9f03992aa49f"},{"Application Name":"Yammer","Application IDs":"00000005-0000-0ff1-ce00-000000000000"},{"Application Name":"Yammer Web","Application IDs":"c1c74fed-04c9-4704-80dc-9f79a2e515cb"},{"Application Name":"Yammer Web Embed","Application IDs":"e1ef36fd-b883-4dbf-97f0-9ece4b576fc6"}]' | ConvertFrom-Json | Where-Object -Property 'Application IDs' -EQ $data.applicationId - $HTML = Get-Content 'TemplateEmail.HTML' -Raw | Out-String - switch ($data.Operation) { - 'New-InboxRule' { - $Title = "$($TenantFilter) - New Rule Detected for $($data.UserId)" - $RuleTable = ($TableObj | ConvertTo-Html -Fragment | Out-String).Replace('', '
') - $ParameterName - $IntroText = "

A new rule has been created for the user $($data.UserId). You should check if this rule is not malicious. The rule information can be found in the table below.

$RuleTable" - $ButtonUrl = "$CIPPPURL/identity/administration/ViewBec?userId=$($data.UserId)&tenantDomain=$($data.OrganizationId)" - $ButtonText = 'Start BEC Investigation' - $AfterButtonText = '

If you believe this is a suspect rule, you can click the button above to start the investigation.

' - } - 'Set-inboxrule' { - $Title = "$($TenantFilter) - Rule Edit Detected for $($data.UserId)" - $RuleTable = ($TableObj | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') - $ParameterName - $IntroText = "

A rule has been edited for the user $($data.UserId). You should check if this rule is not malicious. The rule information can be found in the table below.

$RuleTable" - $ButtonUrl = "$CIPPPURL/identity/administration/ViewBec?userId=$($data.UserId)&tenantDomain=$($data.OrganizationId)" - $ButtonText = 'Start BEC Investigation' - $AfterButtonText = '

If you believe this is a suspect rule, you can click the button above to start the investigation.

' - } - 'Add member to role.' { - $Title = "$($TenantFilter) - Role change detected for $($data.ObjectId)" - $Table = ($data.ModifiedProperties | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') - $IntroText = "

$($data.UserId) has added $($data.ObjectId) to the $(($data.ModifiedProperties | Where-Object -Property Name -EQ 'Role.DisplayName').NewValue) role. The information about the role can be found in the table below.

$Table" - $ButtonUrl = "$CIPPPURL/identity/administration/roles?customerId=$($data.OrganizationId)" - $ButtonText = 'Role Management' - $AfterButtonText = '

If this role is incorrect, or you need more information, use the button to jump to the Role Management page.

' - - } - 'Disable account.' { - $Title = "$($TenantFilter) - $($data.ObjectId) has been disabled" - $IntroText = "$($data.ObjectId) has been disabled by $($data.UserId)." - $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" - $ButtonText = 'User Management' - $AfterButtonText = '

If this is incorrect, use the user management screen to unblock the users sign-in

' - } - 'Enable account.' { - $Title = "$($TenantFilter) - $($data.ObjectId) has been enabled" - $IntroText = "$($data.ObjectId) has been enabled by $($data.UserId)." - $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" - $ButtonText = 'User Management' - $AfterButtonText = '

If this is incorrect, use the user management screen to unblock the users sign-in

' - } - 'Update StsRefreshTokenValidFrom Timestamp.' { - $Title = "$($TenantFilter) - $($data.ObjectId) has had all sessions revoked" - $IntroText = "$($data.ObjectId) has had their sessions revoked by $($data.UserId)." - $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" - $ButtonText = 'User Management' - $AfterButtonText = '

If this is incorrect, use the user management screen to unblock the users sign-in

' - } - 'Disable Strong Authentication.' { - $Title = "$($TenantFilter) - $($data.ObjectId) has been MFA disabled" - $IntroText = "$($data.ObjectId) MFA has been disabled by $($data.UserId)." - $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" - $ButtonText = 'User Management' - $AfterButtonText = '

If this is incorrect, use the user management screen to reenable MFA

' - } - 'Remove Member from a role.' { - $Title = "$($TenantFilter) - Role change detected for $($data.ObjectId)" - $Table = ($data.ModifiedProperties | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') - $IntroText = "

$($data.UserId) has removed $($data.ObjectId) to the $(($data.ModifiedProperties | Where-Object -Property Name -EQ 'Role.DisplayName').NewValue) role. The information about the role can be found in the table below.

$Table" - $ButtonUrl = "$CIPPPURL/identity/administration/roles?customerId=$($data.OrganizationId)" - $ButtonText = 'Role Management' - $AfterButtonText = '

If this role change is incorrect, or you need more information, use the button to jump to the Role Management page.

' - } - 'Reset user password.' { - $Title = "$($TenantFilter) - $($data.ObjectId) has had their password reset" - $IntroText = "$($data.ObjectId) has had their password reset by $($data.userId)." - $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" - $ButtonText = 'User Management' - $AfterButtonText = '

If this is incorrect, use the user management screen to unblock the users sign-in

' + if ($ConditionMet) { + #we're doing two loops, one first to collect the results of any action taken, then the second to pass those results via email etc. + $ActionResults = foreach ($action in $dos) { + Write-Host "this is our action: $($action | ConvertTo-Json -Depth 15 -Compress))" + switch ($action.execute) { + 'disableUser' { + Set-CIPPSignInState -TenantFilter $TenantFilter -User $data.UserId -AccountEnabled $false -APIName 'Alert Engine' -ExecutingUser 'Alert Engine' + } + 'becremediate' { + $username = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($data.UserId)" -tenantid $TenantFilter).UserPrincipalName + Set-CIPPResetPassword -userid $username -tenantFilter $TenantFilter -APIName 'Alert Engine' -ExecutingUser 'Alert Engine' + Set-CIPPSignInState -userid $username -AccountEnabled $false -tenantFilter $TenantFilter -APIName 'Alert Engine' -ExecutingUser 'Alert Engine' + Revoke-CIPPSessions -userid $username -username $username -ExecutingUser 'Alert Engine' -APIName 'Alert Engine' -tenantFilter $TenantFilter + $RuleDisabled = 0 + New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'get-inboxrule' -cmdParams @{Mailbox = $username } | ForEach-Object { + $null = New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Disable-InboxRule' -cmdParams @{Confirm = $false; Identity = $_.Identity } + "Disabled Inbox Rule $($_.Identity) for $username" + $RuleDisabled ++ + } + if ($RuleDisabled) { + "Disabled $RuleDisabled Inbox Rules for $username" + } else { + "No Inbox Rules found for $username. We have not disabled any rules." + } + "Completed BEC Remediate for $username" + Write-LogMessage -API 'BECRemediate' -tenant $tenantfilter -message "Executed Remediation for $username" -sev 'Info' + } + 'store' { + Write-Host "Using $($action.connectionstring) as connectionstring to ship data" + $Context = New-AzDataTableContext -ConnectionString $action.ConnectionString -TableName 'AuditLog' + Write-Host 'Creating table if it does not exist' + New-AzDataTable -Context $Context | Out-Null + Write-Host 'Uploading data to table' + $TableObj = @{ + RowKey = [string]$data.id + PartitionKey = [string]$TenantFilter + Tenant = [string]$tenantfilter + Operation = [string]$data.operation + RawData = [string]($data | ConvertTo-Json -Depth 15 -Compress) + IP = [string]$data.clientip + Country = [string]$Country + City = [string]$City + Proxy = [string]$Proxy + Hosting = [string]$hosting + ASName = [string]$ASName + } + Add-CIPPAzDataTableEntity -Context $Context -Entity $TableObj + 'Succesfully stored log' + } + 'cippcommand' { + $CommandSplat = @{} + $action.parameters.psobject.properties | ForEach-Object { $CommandSplat.Add($_.name, $_.value) } + if ($CommandSplat['userid']) { $CommandSplat['userid'] = $data.userid } + if ($CommandSplat['tenantfilter']) { $CommandSplat['tenantfilter'] = $tenantfilter } + if ($CommandSplat['tenant']) { $CommandSplat['tenant'] = $tenantfilter } + if ($CommandSplat['user']) { $CommandSplat['user'] = $data.userid } + if ($CommandSplat['username']) { $CommandSplat['username'] = $data.userid } + & $action.command.value @CommandSplat + } + } + } + foreach ($action in $dos) { + switch ($action.execute) { + 'generatemail' { + $GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -LocationInfo $Location -ActionResults $ActionResults + Send-CIPPAlert -Type 'email' -Title $GenerateEmail.title -HTMLContent $GenerateEmail.htmlcontent -TenantFilter $TenantFilter + } + 'generatePSA' { + $GenerateEmail = New-CIPPAlertTemplate -format 'html'-data $Data -LocationInfo $Location -ActionResults $ActionResults + Send-CIPPAlert -Type 'psa' -Title $GenerateEmail.title -HTMLContent $GenerateEmail.htmlcontent -TenantFilter $TenantFilter + } + 'generateWebhook' { + $GenerateJSON = New-CIPPAlertTemplate -format 'json' -data $Data -ActionResults $ActionResults + $JsonContent = @{ + Title = $GenerateJSON.Title + ActionUrl = $GenerateJSON.ButtonUrl + RawData = $Data + IP = $data.ClientIP + PotentialCountry = $Country + PotentialCity = $City + PotentialProxy = $Proxy + PotentialHosting = $hosting + PotentialASName = $ASName + ActionsTaken = [string]($ActionResults | ConvertTo-Json -Depth 15 -Compress) + } | ConvertTo-Json -Depth 15 -Compress + Send-CIPPAlert -Type 'webhook' -Title $GenerateJSON.Title -JSONContent $JsonContent -TenantFilter $TenantFilter + } + } + } } - 'AdminLoggedIn' { - $Table = ($TableObj | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') - if ($Appname) { $AppName = $AppName.'Application Name' } else { $appName = $data.ApplicationId } - $Title = "$($TenantFilter) - an admin account has logged on" - $IntroText = "$($data.UserId) ($($data.Userkey)) has logged on from IP $($data.ClientIP) to the application $($Appname). See the table below for more information. $Table" - $ButtonUrl = "$CIPPPURL/identity/administration/ViewBec?userId=$($data.UserKey)&tenantDomain=$($data.OrganizationId)" - $ButtonText = 'User Management' - $AfterButtonText = '

If this is incorrect, use the user management screen to block the user and revoke the sessions

' - - } - 'UserLoggedInFromUnknownLocation' { - $Table = ($TableObj | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') - if ($Appname) { $AppName = $AppName.'Application Name' } else { $appName = $data.ApplicationId } - $Title = "$($TenantFilter) - a user has logged on from a potentially unsafe location" - $IntroText = "$($data.UserId) ($($data.Userkey)) has logged on from IP $($data.ClientIP) to the application $($Appname). According to our database this is located in $($Country) - $($City).

You have set up alerts to be notified when this happens. See the table below for more info.$Table" - $ButtonUrl = "$CIPPPURL/identity/administration/ViewBec?userId=$($data.ObjectId)&tenantDomain=$($data.OrganizationId)" - $ButtonText = 'User Management' - $AfterButtonText = '

If this is incorrect, use the user management screen to block the user and revoke the sessions

' - } - 'Add service principal.' { - if ($Appname) { $AppName = $AppName.'Application Name' } else { $appName = $data.ApplicationId } - $Title = "$($TenantFilter) - Service Principal $($data.ObjectId) has been added." - $Table = ($data.ModifiedProperties | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') - $IntroText = "$($data.ObjectId) has been added by $($data.UserId)." - $ButtonUrl = "$CIPPPURL/tenant/administration/enterprise-apps?customerId=?customerId=$($data.OrganizationId)" - $ButtonText = 'Enterprise Apps' - } - 'Remove service principal.' { - if ($Appname) { $AppName = $AppName.'Application Name' } else { $appName = $data.ApplicationId } - $Title = "$($TenantFilter) - Service Principal $($data.ObjectId) has been removed." - $Table = ($data.ModifiedProperties | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') - $IntroText = "$($data.ObjectId) has been added by $($data.UserId)." - $ButtonUrl = "$CIPPPURL/tenant/administration/enterprise-apps?customerId=?customerId=$($data.OrganizationId)" - $ButtonText = 'Enterprise Apps' - } - - default { - break - } - } - $HTML = "$HTML" -f $Title, $IntroText, $ButtonUrl, $ButtonText, $AfterButtonText - - Write-Host 'Add IP and potential location to knownlocation db for this specific user' if ($data.ClientIP) { $IP = $data.ClientIP @@ -207,23 +225,10 @@ function Invoke-CippWebhookProcessing { Tenant = [string]$TenantFilter CountryOrRegion = "$Country" City = "$City" + Proxy = "$Proxy" + Hosting = "$hosting" + ASName = "$ASName" } $null = Add-CIPPAzDataTableEntity @LocationTable -Entity $LocationInfo -Force } - $JsonContent = @{ - Title = $Title - ActionUrl = $ButtonUrl - RawData = $Data - IP = $data.ClientIP - PotentialCountry = $Country - PotentialCity = $City - } | ConvertTo-Json -Depth 15 -Compress - if ($Title) { - Write-Host 'Sending alert to email' - Send-CIPPAlert -Type 'email' -Title $title -HTMLContent $HTML -TenantFilter $TenantFilter - Write-Host 'Sending alert to webhook' - Send-CIPPAlert -Type 'webhook' -Title $title -JSONContent $JsonContent -TenantFilter $TenantFilter - Write-Host 'Sending alert to PSA' - Send-CIPPAlert -Type 'psa' -Title $title -HTMLContent $HTML -TenantFilter $TenantFilter - } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveWebhookAlert.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveWebhookAlert.ps1 index e54f283123de..0e9a92ac7868 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveWebhookAlert.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveWebhookAlert.ps1 @@ -10,9 +10,41 @@ Function Invoke-RemoveWebhookAlert { $APIName = $TriggerMetadata.FunctionName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $Table = get-cipptable -TableName 'SchedulerConfig' try { - $Results = Remove-CIPPGraphSubscription -TenantFilter $Request.query.TenantFilter -CIPPID $Request.query.CIPPID + $WebhookTable = Get-CIPPTable -TableName SchedulerConfig + $WebhookRow = Get-CIPPAzDataTableEntity @WebhookTable -Filter "PartitionKey eq 'WebhookAlert'" | Where-Object -Property Tenant -EQ $Request.query.TenantFilter + Write-Host "The webhook count is $($WebhookRow.count)" + if ($WebhookRow.count -gt 1) { + $Entity = $WebhookRow | Where-Object -Property RowKey -EQ $Request.query.ID + Remove-AzDataTableEntity @WebhookTable -Entity $Entity | Out-Null + $Results = "Removed Alert Rule for $($Request.query.TenantFilter)" + } else { + if ($Request.query.TenantFilter -eq 'AllTenants') { + $Tenants = Get-Tenants -IncludeAll -IncludeErrors | Select-Object -ExpandProperty defaultDomainName + try { + $CompleteObject = @{ + tenant = 'AllTenants' + type = 'webhookcreation' + RowKey = 'AllTenantsWebhookCreation' + PartitionKey = 'webhookcreation' + } + Remove-AzDataTableEntity @Table -Entity $CompleteObject -ErrorAction SilentlyContinue | Out-Null + } catch { + # + } + } else { + $Tenants = $Request.query.TenantFilter + } + + $Results = foreach ($Tenant in $Tenants) { + Remove-CIPPGraphSubscription -TenantFilter $Tenant -Type 'AuditLog' + $Entity = $WebhookRow | Where-Object -Property RowKey -EQ $Request.query.ID + Remove-AzDataTableEntity @WebhookTable -Entity $Entity | Out-Null + "Removed Alert Rule for $($Request.query.TenantFilter)" + } + } $body = [pscustomobject]@{'Results' = $Results } } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove webhook alert. $($_.Exception.Message)" -Sev 'Error' @@ -25,6 +57,4 @@ Function Invoke-RemoveWebhookAlert { StatusCode = [HttpStatusCode]::OK Body = $body }) - - -} +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 new file mode 100644 index 000000000000..3c8c48347d2a --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 @@ -0,0 +1,250 @@ +function New-CIPPAlertTemplate { + param( + [Parameter(Mandatory = $true)] + $Data, + [Parameter(Mandatory = $true)] + [ValidateSet('html', 'json')] + $Format, + $LocationInfo, + $ActionResults + ) + + $Appname = '[{"Application Name":"ACOM Azure Website","Application IDs":"23523755-3a2b-41ca-9315-f81f3f566a95"},{"Application Name":"AEM-DualAuth","Application IDs":"69893ee3-dd10-4b1c-832d-4870354be3d8"},{"Application Name":"ASM Campaign Servicing","Application IDs":"0cb7b9ec-5336-483b-bc31-b15b5788de71"},{"Application Name":"Azure Advanced Threat Protection","Application IDs":"7b7531ad-5926-4f2d-8a1d-38495ad33e17"},{"Application Name":"Azure Data Lake","Application IDs":"e9f49c6b-5ce5-44c8-925d-015017e9f7ad"},{"Application Name":"Azure Lab Services Portal","Application IDs":"835b2a73-6e10-4aa5-a979-21dfda45231c"},{"Application Name":"Azure Portal","Application IDs":"c44b4083-3bb0-49c1-b47d-974e53cbdf3c"},{"Application Name":"AzureSupportCenter","Application IDs":"37182072-3c9c-4f6a-a4b3-b3f91cacffce"},{"Application Name":"Bing","Application IDs":"9ea1ad79-fdb6-4f9a-8bc3-2b70f96e34c7"},{"Application Name":"CPIM Service","Application IDs":"bb2a2e3a-c5e7-4f0a-88e0-8e01fd3fc1f4"},{"Application Name":"CRM Power BI Integration","Application IDs":"e64aa8bc-8eb4-40e2-898b-cf261a25954f"},{"Application Name":"Dataverse","Application IDs":"00000007-0000-0000-c000-000000000000"},{"Application Name":"Enterprise Roaming and Backup","Application IDs":"60c8bde5-3167-4f92-8fdb-059f6176dc0f"},{"Application Name":"IAM Supportability","Application IDs":"a57aca87-cbc0-4f3c-8b9e-dc095fdc8978"},{"Application Name":"IrisSelectionFrontDoor","Application IDs":"16aeb910-ce68-41d1-9ac3-9e1673ac9575"},{"Application Name":"MCAPI Authorization Prod","Application IDs":"d73f4b35-55c9-48c7-8b10-651f6f2acb2e"},{"Application Name":"Media Analysis and Transformation Service","Application IDs":"944f0bd1-117b-4b1c-af26-804ed95e767e
0cd196ee-71bf-4fd6-a57c-b491ffd4fb1e"},{"Application Name":"Microsoft 365 Support Service","Application IDs":"ee272b19-4411-433f-8f28-5c13cb6fd407"},{"Application Name":"Microsoft App Access Panel","Application IDs":"0000000c-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Approval Management","Application IDs":"65d91a3d-ab74-42e6-8a2f-0add61688c74
38049638-cc2c-4cde-abe4-4479d721ed44"},{"Application Name":"Microsoft Authentication Broker","Application IDs":"29d9ed98-a469-4536-ade2-f981bc1d605e"},{"Application Name":"Microsoft Azure CLI","Application IDs":"04b07795-8ddb-461a-bbee-02f9e1bf7b46"},{"Application Name":"Microsoft Azure PowerShell","Application IDs":"1950a258-227b-4e31-a9cf-717495945fc2"},{"Application Name":"Microsoft Bing Search","Application IDs":"cf36b471-5b44-428c-9ce7-313bf84528de"},{"Application Name":"Microsoft Bing Search for Microsoft Edge","Application IDs":"2d7f3606-b07d-41d1-b9d2-0d0c9296a6e8"},{"Application Name":"Microsoft Bing Default Search Engine","Application IDs":"1786c5ed-9644-47b2-8aa0-7201292175b6"},{"Application Name":"Microsoft Defender for Cloud Apps","Application IDs":"3090ab82-f1c1-4cdf-af2c-5d7a6f3e2cc7"},{"Application Name":"Microsoft Docs","Application IDs":"18fbca16-2224-45f6-85b0-f7bf2b39b3f3"},{"Application Name":"Microsoft Dynamics ERP","Application IDs":"00000015-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Edge Insider Addons Prod","Application IDs":"6253bca8-faf2-4587-8f2f-b056d80998a7"},{"Application Name":"Microsoft Exchange Online Protection","Application IDs":"00000007-0000-0ff1-ce00-000000000000"},{"Application Name":"Microsoft Forms","Application IDs":"c9a559d2-7aab-4f13-a6ed-e7e9c52aec87"},{"Application Name":"Microsoft Graph","Application IDs":"00000003-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Intune Web Company Portal","Application IDs":"74bcdadc-2fdc-4bb3-8459-76d06952a0e9"},{"Application Name":"Microsoft Intune Windows Agent","Application IDs":"fc0f3af4-6835-4174-b806-f7db311fd2f3"},{"Application Name":"Microsoft Learn","Application IDs":"18fbca16-2224-45f6-85b0-f7bf2b39b3f3"},{"Application Name":"Microsoft Office","Application IDs":"d3590ed6-52b3-4102-aeff-aad2292ab01c"},{"Application Name":"Microsoft Office 365 Portal","Application IDs":"00000006-0000-0ff1-ce00-000000000000"},{"Application Name":"Microsoft Office Web Apps Service","Application IDs":"67e3df25-268a-4324-a550-0de1c7f97287"},{"Application Name":"Microsoft Online Syndication Partner Portal","Application IDs":"d176f6e7-38e5-40c9-8a78-3998aab820e7"},{"Application Name":"Microsoft password reset service","Application IDs":"93625bc8-bfe2-437a-97e0-3d0060024faa"},{"Application Name":"Microsoft Power BI","Application IDs":"871c010f-5e61-4fb1-83ac-98610a7e9110"},{"Application Name":"Microsoft Storefronts","Application IDs":"28b567f6-162c-4f54-99a0-6887f387bbcc"},{"Application Name":"Microsoft Stream Portal","Application IDs":"cf53fce8-def6-4aeb-8d30-b158e7b1cf83"},{"Application Name":"Microsoft Substrate Management","Application IDs":"98db8bd6-0cc0-4e67-9de5-f187f1cd1b41"},{"Application Name":"Microsoft Support","Application IDs":"fdf9885b-dd37-42bf-82e5-c3129ef5a302"},{"Application Name":"Microsoft Teams","Application IDs":"1fec8e78-bce4-4aaf-ab1b-5451cc387264"},{"Application Name":"Microsoft Teams Services","Application IDs":"cc15fd57-2c6c-4117-a88c-83b1d56b4bbe"},{"Application Name":"Microsoft Teams Web Client","Application IDs":"5e3ce6c0-2b1f-4285-8d4b-75ee78787346"},{"Application Name":"Microsoft Whiteboard Services","Application IDs":"95de633a-083e-42f5-b444-a4295d8e9314"},{"Application Name":"O365 Suite UX","Application IDs":"4345a7b9-9a63-4910-a426-35363201d503"},{"Application Name":"Office 365 Exchange Online","Application IDs":"00000002-0000-0ff1-ce00-000000000000"},{"Application Name":"Office 365 Management","Application IDs":"00b41c95-dab0-4487-9791-b9d2c32c80f2"},{"Application Name":"Office 365 Search Service","Application IDs":"66a88757-258c-4c72-893c-3e8bed4d6899"},{"Application Name":"Office 365 SharePoint Online","Application IDs":"00000003-0000-0ff1-ce00-000000000000"},{"Application Name":"Office Delve","Application IDs":"94c63fef-13a3-47bc-8074-75af8c65887a"},{"Application Name":"Office Online Add-in SSO","Application IDs":"93d53678-613d-4013-afc1-62e9e444a0a5"},{"Application Name":"Office Online Client AAD- Augmentation Loop","Application IDs":"2abdc806-e091-4495-9b10-b04d93c3f040"},{"Application Name":"Office Online Client AAD- Loki","Application IDs":"b23dd4db-9142-4734-867f-3577f640ad0c"},{"Application Name":"Office Online Client AAD- Maker","Application IDs":"17d5e35f-655b-4fb0-8ae6-86356e9a49f5"},{"Application Name":"Office Online Client MSA- Loki","Application IDs":"b6e69c34-5f1f-4c34-8cdf-7fea120b8670"},{"Application Name":"Office Online Core SSO","Application IDs":"243c63a3-247d-41c5-9d83-7788c43f1c43"},{"Application Name":"Office Online Search","Application IDs":"a9b49b65-0a12-430b-9540-c80b3332c127"},{"Application Name":"Office.com","Application IDs":"4b233688-031c-404b-9a80-a4f3f2351f90"},{"Application Name":"Office365 Shell WCSS-Client","Application IDs":"89bee1f7-5e6e-4d8a-9f3d-ecd601259da7"},{"Application Name":"OfficeClientService","Application IDs":"0f698dd4-f011-4d23-a33e-b36416dcb1e6"},{"Application Name":"OfficeHome","Application IDs":"4765445b-32c6-49b0-83e6-1d93765276ca"},{"Application Name":"OfficeShredderWacClient","Application IDs":"4d5c2d63-cf83-4365-853c-925fd1a64357"},{"Application Name":"OMSOctopiPROD","Application IDs":"62256cef-54c0-4cb4-bcac-4c67989bdc40"},{"Application Name":"OneDrive SyncEngine","Application IDs":"ab9b8c07-8f02-4f72-87fa-80105867a763"},{"Application Name":"OneNote","Application IDs":"2d4d3d8e-2be3-4bef-9f87-7875a61c29de"},{"Application Name":"Outlook Mobile","Application IDs":"27922004-5251-4030-b22d-91ecd9a37ea4"},{"Application Name":"Partner Customer Delegated Admin Offline Processor","Application IDs":"a3475900-ccec-4a69-98f5-a65cd5dc5306"},{"Application Name":"Password Breach Authenticator","Application IDs":"bdd48c81-3a58-4ea9-849c-ebea7f6b6360"},{"Application Name":"Power BI Service","Application IDs":"00000009-0000-0000-c000-000000000000"},{"Application Name":"SharedWithMe","Application IDs":"ffcb16e8-f789-467c-8ce9-f826a080d987"},{"Application Name":"SharePoint Online Web Client Extensibility","Application IDs":"08e18876-6177-487e-b8b5-cf950c1e598c"},{"Application Name":"Signup","Application IDs":"b4bddae8-ab25-483e-8670-df09b9f1d0ea"},{"Application Name":"Skype for Business Online","Application IDs":"00000004-0000-0ff1-ce00-000000000000"},{"Application Name":"Sway","Application IDs":"905fcf26-4eb7-48a0-9ff0-8dcc7194b5ba"},{"Application Name":"Universal Store Native Client","Application IDs":"268761a2-03f3-40df-8a8b-c3db24145b6b"},{"Application Name":"Vortex [wsfed enabled]","Application IDs":"5572c4c0-d078-44ce-b81c-6cbf8d3ed39e"},{"Application Name":"Windows Azure Active Directory","Application IDs":"00000002-0000-0000-c000-000000000000"},{"Application Name":"Windows Azure Service Management API","Application IDs":"797f4846-ba00-4fd7-ba43-dac1f8f63013"},{"Application Name":"WindowsDefenderATP Portal","Application IDs":"a3b79187-70b2-4139-83f9-6016c58cd27b"},{"Application Name":"Windows Search","Application IDs":"26a7ee05-5602-4d76-a7ba-eae8b7b67941"},{"Application Name":"Windows Spotlight","Application IDs":"1b3c667f-cde3-4090-b60b-3d2abd0117f0"},{"Application Name":"Windows Store for Business","Application IDs":"45a330b1-b1ec-4cc1-9161-9f03992aa49f"},{"Application Name":"Yammer","Application IDs":"00000005-0000-0ff1-ce00-000000000000"},{"Application Name":"Yammer Web","Application IDs":"c1c74fed-04c9-4704-80dc-9f79a2e515cb"},{"Application Name":"Yammer Web Embed","Application IDs":"e1ef36fd-b883-4dbf-97f0-9ece4b576fc6"}]' | ConvertFrom-Json | Where-Object -Property 'Application IDs' -EQ $data.applicationId + $HTMLTemplate = Get-Content 'TemplateEmail.HTML' -Raw | Out-String + $Title = '' + $IntroText = '' + $ButtonUrl = '' + $ButtonText = '' + $AfterButtonText = '' + $RuleTable = '' + $Table = '' + + switch ($Data.Operation) { + 'New-InboxRule' { + $Title = "$($TenantFilter) - New Rule Detected for $($data.UserId)" + $RuleTable = ($TableObj | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') + $ParameterName + $IntroText = "

A new rule has been created for the user $($data.UserId). You should check if this rule is not malicious. The rule information can be found in the table below.

$RuleTable" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/ViewBec?userId=$($data.UserId)&tenantDomain=$($data.OrganizationId)" + $ButtonText = 'Start BEC Investigation' + $AfterButtonText = '

If you believe this is a suspect rule, you can click the button above to start the investigation.

' + } + 'Set-inboxrule' { + $Title = "$($TenantFilter) - Rule Edit Detected for $($data.UserId)" + $RuleTable = ($TableObj | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') + $ParameterName + $IntroText = "

A rule has been edited for the user $($data.UserId). You should check if this rule is not malicious. The rule information can be found in the table below.

$RuleTable" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/ViewBec?userId=$($data.UserId)&tenantDomain=$($data.OrganizationId)" + $ButtonText = 'Start BEC Investigation' + $AfterButtonText = '

If you believe this is a suspect rule, you can click the button above to start the investigation.

' + } + 'Add member to role.' { + $Title = "$($TenantFilter) - Role change detected for $($data.ObjectId)" + $Table = ($data.ModifiedProperties | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') + $IntroText = "

$($data.UserId) has added $($data.ObjectId) to the $(($data.ModifiedProperties | Where-Object -Property Name -EQ 'Role.DisplayName').NewValue) role. The information about the role can be found in the table below.

$Table" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/roles?customerId=$($data.OrganizationId)" + $ButtonText = 'Role Management' + $AfterButtonText = '

If this role is incorrect, or you need more information, use the button to jump to the Role Management page.

' + + } + 'Disable account.' { + $Title = "$($TenantFilter) - $($data.ObjectId) has been disabled" + $IntroText = "$($data.ObjectId) has been disabled by $($data.UserId)." + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" + $ButtonText = 'User Management' + $AfterButtonText = '

If this is incorrect, use the user management screen to unblock the users sign-in

' + } + 'Enable account.' { + $Title = "$($TenantFilter) - $($data.ObjectId) has been enabled" + $IntroText = "$($data.ObjectId) has been enabled by $($data.UserId)." + $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonText = 'User Management' + $AfterButtonText = '

If this is incorrect, use the user management screen to unblock the users sign-in

' + } + 'Update StsRefreshTokenValidFrom Timestamp.' { + $Title = "$($TenantFilter) - $($data.ObjectId) has had all sessions revoked" + $IntroText = "$($data.ObjectId) has had their sessions revoked by $($data.UserId)." + $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonText = 'User Management' + $AfterButtonText = '

If this is incorrect, use the user management screen to unblock the users sign-in

' + } + 'Disable Strong Authentication.' { + $Title = "$($TenantFilter) - $($data.ObjectId) has been MFA disabled" + $IntroText = "$($data.ObjectId) MFA has been disabled by $($data.UserId)." + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" + $ButtonText = 'User Management' + $AfterButtonText = '

If this is incorrect, use the user management screen to reenable MFA

' + } + 'Remove Member from a role.' { + $Title = "$($TenantFilter) - Role change detected for $($data.ObjectId)" + $Table = ($data.ModifiedProperties | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') + $IntroText = "

$($data.UserId) has removed $($data.ObjectId) to the $(($data.ModifiedProperties | Where-Object -Property Name -EQ 'Role.DisplayName').NewValue) role. The information about the role can be found in the table below.

$Table" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/roles?customerId=$($data.OrganizationId)" + $ButtonText = 'Role Management' + $AfterButtonText = '

If this role change is incorrect, or you need more information, use the button to jump to the Role Management page.

' + + } + + 'Reset user password.' { + $Title = "$($TenantFilter) - $($data.ObjectId) has had their password reset" + $IntroText = "$($data.ObjectId) has had their password reset by $($data.userId)." + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" + $ButtonText = 'User Management' + $AfterButtonText = '

If this is incorrect, use the user management screen to unblock the users sign-in

' + + } + 'AdminLoggedIn' { + $Table = ($TableObj | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + if ($Appname) { $AppName = $AppName.'Application Name' } else { $appName = $data.ApplicationId } + $Title = "$($TenantFilter) - an admin account has logged on" + $IntroText = "$($data.UserId) ($($data.Userkey)) has logged on from IP $($data.ClientIP) to the application $($Appname). See the table below for more information. $Table" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/ViewBec?userId=$($data.UserKey)&tenantDomain=$($data.OrganizationId)" + $ButtonText = 'User Management' + $AfterButtonText = '

If this is incorrect, use the user management screen to block the user and revoke the sessions

' + + } + 'UserLoggedInFromUnknownLocation' { + $Table = ($TableObj | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + if ($Appname) { $AppName = $AppName.'Application Name' } else { $appName = $data.ApplicationId } + $Title = "$($TenantFilter) - a user has logged on from a potentially unsafe location" + $IntroText = "$($data.UserId) ($($data.Userkey)) has logged on from IP $($data.ClientIP) to the application $($Appname). According to our database this is located in $($LocationInfo.Country) - $($LocationInfo.City).

You have set up alerts to be notified when this happens. See the table below for more info.$Table" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/ViewBec?userId=$($data.ObjectId)&tenantDomain=$($data.OrganizationId)" + $ButtonText = 'User Management' + $AfterButtonText = '

If this is incorrect, use the user management screen to block the user and revoke the sessions

' + } + 'BadRepIP' { + $Table = ($TableObj | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + if ($Appname) { $AppName = $AppName.'Application Name' } else { $appName = $data.ApplicationId } + $Title = "$($TenantFilter) - a user has logged on from a potentially unsafe location" + $IntroText = "$($data.UserId) ($($data.Userkey)) has logged on from IP $($data.ClientIP) to the application $($Appname). According to our database this is located in $($LocationInfo.Country) - $($LocationInfo.City), but is a VPN, Proxy, or IP anonimizing service.

You have set up alerts to be notified when this happens. See the table below for more info.$Table" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/ViewBec?userId=$($data.ObjectId)&tenantDomain=$($data.OrganizationId)" + $ButtonText = 'User Management' + $AfterButtonText = '

If this is incorrect, use the user management screen to block the user and revoke the sessions

' + } + 'HostedIP' { + $Table = ($TableObj | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + if ($Appname) { $AppName = $AppName.'Application Name' } else { $appName = $data.ApplicationId } + $Title = "$($TenantFilter) - a user has logged on from a potentially unsafe location" + $IntroText = "$($data.UserId) ($($data.Userkey)) has logged on from IP $($data.ClientIP) to the application $($Appname). According to our database this is located in $($LocationInfo.Country) - $($LocationInfo.City), but this IP is also belonging to a Hosting Provider, such as Microsoft, Google, or other cloud service.

You have set up alerts to be notified when this happens. See the table below for more info.$Table" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/ViewBec?userId=$($data.ObjectId)&tenantDomain=$($data.OrganizationId)" + $ButtonText = 'User Management' + $AfterButtonText = '

If this is incorrect, use the user management screen to block the user and revoke the sessions

' + } + 'Add service principal.' { + if ($Appname) { $AppName = $AppName.'Application Name' } else { $appName = $data.ApplicationId } + $Title = "$($TenantFilter) - Service Principal $($data.ObjectId) has been added." + $Table = ($data.ModifiedProperties | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $IntroText = "$($data.ObjectId) has been added by $($data.UserId)." + $ButtonUrl = "$CIPPPURL/tenant/administration/enterprise-apps?customerId=?customerId=$($data.OrganizationId)" + $ButtonText = 'Enterprise Apps' + } + 'Remove service principal.' { + if ($Appname) { $AppName = $AppName.'Application Name' } else { $appName = $data.ApplicationId } + $Title = "$($TenantFilter) - Service Principal $($data.ObjectId) has been removed." + $Table = ($data.ModifiedProperties | ConvertTo-Html -Fragment | Out-String).Replace('
', '
') + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $IntroText = "$($data.ObjectId) has been added by $($data.UserId)." + $ButtonUrl = "$CIPPPURL/tenant/administration/enterprise-apps?customerId=?customerId=$($data.OrganizationId)" + $ButtonText = 'Enterprise Apps' + } + + default { + $Title = 'A custom alert has occured' + $Table = ($data | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = "

You have setup CIPP to send you a custom alert for the event $($Data.operation)

$Table" + if ($ActionResults) { $IntroText = $IntroText + "

Based on the rule, the following actions have been taken: $($ActionResults -join '
' )

" } + if ($LocationInfo) { + $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') + $IntroText = $IntroText + "

The location information for this IP is as follows:

$LocationTable" + } + $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" + $ButtonText = 'User Management' + } + } + + if ($Format -eq 'html') { + return [pscustomobject]@{ + title = $Title + htmlcontent = $HTMLTemplate -f $Title, $IntroText, $ButtonUrl, $ButtonText, $AfterButtonText + } + } elseif ($Format -eq 'json') { + return [pscustomobject]@{ + title = $Title + buttonurl = $ButtonUrl + } + } +} diff --git a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 index 649a50c452df..6af3225124cb 100644 --- a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 @@ -9,7 +9,7 @@ function New-CIPPGraphSubscription { $operations, $Resource, $EventType, - $APIName = "Create Webhook", + $APIName = 'Create Webhook', $ExecutingUser, [Switch]$Recreate ) @@ -18,27 +18,42 @@ function New-CIPPGraphSubscription { try { if ($auditLogAPI) { - $AuditLogParams = @{ - webhook = @{ - "address" = "$BaseURL/API/Publicwebhooks?EventType=$EventType&CIPPID=$CIPPID" + $EventTypes = @('Audit.AzureActiveDirectory', 'Audit.Exchange', 'Audit.SharePoint', 'Audit.General') + foreach ($EventType in $EventTypes) { + $CIPPID = (New-Guid).GUID + $Resource = $EventType + $AuditLogParams = @{ + webhook = @{ + 'address' = "$BaseURL/API/Publicwebhooks?EventType=$EventType&CIPPID=$CIPPID" + } + } | ConvertTo-Json + #List existing webhook subscriptions in table + $WebhookFilter = "PartitionKey eq '$($TenantFilter)'" + $ExistingWebhooks = Get-CIPPAzDataTableEntity @WebhookTable -Filter $WebhookFilter + $MatchedWebhook = $ExistingWebhooks | Where-Object { $_.Resource -eq $Resource } + try { + if (!$MatchedWebhook) { + $AuditLog = New-GraphPOSTRequest -uri "https://manage.office.com/api/v1.0/$($TenantFilter)/activity/feed/subscriptions/start?contentType=$EventType&PublisherIdentifier=$($TenantFilter)" -tenantid $TenantFilter -type POST -scope 'https://manage.office.com/.default' -body $AuditLogparams -verbose + $WebhookRow = @{ + PartitionKey = [string]$TenantFilter + RowKey = [string]$CIPPID + Resource = $Resource + Expiration = 'Does Not Expire' + WebhookNotificationUrl = [string]$Auditlog.webhook.address + } + $null = Add-CIPPAzDataTableEntity @WebhookTable -Entity $WebhookRow + Write-LogMessage -user $ExecutingUser -API $APIName -message "Created Webhook subscription for $($TenantFilter) for the log $($EventType)" -Sev 'Info' -tenant $TenantFilter + } else { + Write-LogMessage -user $ExecutingUser -API $APIName -message "No webhook creation required for $($TenantFilter). Already exists" -Sev 'Info' -tenant $TenantFilter + } + } catch { + if ($_.Exception.Message -like '*already exists*') { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Webhook subscription for $($TenantFilter) already exists" -Sev 'Info' -tenant $TenantFilter + } else { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to create Webhook Subscription for $($TenantFilter): $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter + } } - } | ConvertTo-Json - Write-Host ($AuditLogParams) - $AuditLog = New-GraphPOSTRequest -uri "https://manage.office.com/api/v1.0/$($TenantFilter)/activity/feed/subscriptions/start?contentType=$EventType&PublisherIdentifier=$($TenantFilter)" -tenantid $TenantFilter -type POST -scope "https://manage.office.com/.default" -body $AuditLogparams -verbose - $WebhookRow = @{ - PartitionKey = [string]$TenantFilter - RowKey = [string]$CIPPID - EventType = [string]$EventType - Resource = "M365AuditLogs" - Operations = [string]$operations - AllowedLocations = [string]$AllowedLocations - Expiration = "None" - WebhookNotificationUrl = [string]$Auditlog.webhook.address } - - $null = Add-CIPPAzDataTableEntity @WebhookTable -Entity $WebhookRow - Write-LogMessage -user $ExecutingUser -API $APIName -message "Created Webhook subscription for $($TenantFilter)" -Sev "Info" -tenant $TenantFilter - } else { # First check if there is an exsiting Webhook in place $WebhookFilter = "PartitionKey eq '$($TenantFilter)'" @@ -46,7 +61,7 @@ function New-CIPPGraphSubscription { $MatchedWebhook = $ExistingWebhooks | Where-Object { $_.Resource -eq $Resource } if (($MatchedWebhook | Measure-Object).count -eq 0 -or $Recreate) { - $expiredate = (Get-Date).AddDays(1).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + $expiredate = (Get-Date).AddDays(1).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') $params = @{ changeType = $TypeofSubscription notificationUrl = "https://$BaseURL/API/PublicWebhooks?EventType=$EventType&CIPPID=$($CIPPID)&Type=GraphSubscription" @@ -55,7 +70,7 @@ function New-CIPPGraphSubscription { } | ConvertTo-Json - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/subscriptions" -tenantid $TenantFilter -type POST -body $params -verbose + $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/subscriptions' -tenantid $TenantFilter -type POST -body $params -verbose #If creation is succesfull, we store the GUID in the storage table webhookTable to make sure we can check against this later on. #We store the GUID as rowkey, the event type, the resource, and the expiration date as properties, we also add the Tenant name so we can easily find this later on. #We don't store the return, because Ms decided that a renewal or re-authenticate does not change the url, but does change the id... @@ -72,14 +87,14 @@ function New-CIPPGraphSubscription { $null = Add-CIPPAzDataTableEntity @WebhookTable -Entity $WebhookRow #todo: add remove webhook function, add check webhook function, add list webhooks function #add refresh webhook function based on table. - Write-LogMessage -user $ExecutingUser -API $APIName -message "Created Graph Webhook subscription for $($TenantFilter)" -Sev "Info" -tenant $TenantFilter + Write-LogMessage -user $ExecutingUser -API $APIName -message "Created Graph Webhook subscription for $($TenantFilter)" -Sev 'Info' -tenant $TenantFilter } else { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Existing Graph Webhook subscription for $($TenantFilter) found" -Sev "Info" -tenant $TenantFilter + Write-LogMessage -user $ExecutingUser -API $APIName -message "Existing Graph Webhook subscription for $($TenantFilter) found" -Sev 'Info' -tenant $TenantFilter } } return "Created Webhook subscription for $($TenantFilter)" } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to create Webhook Subscription: $($_.Exception.Message)" -Sev "Error" -tenant $TenantFilter + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to create Webhook Subscription: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter Return "Failed to create Webhook Subscription for $($TenantFilter): $($_.Exception.Message)" } diff --git a/Modules/CIPPCore/Public/Remove-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/Remove-CIPPGraphSubscription.ps1 index e0ff1f4ef9ad..2847bdb24e99 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPGraphSubscription.ps1 @@ -4,17 +4,24 @@ function Remove-CIPPGraphSubscription { $TenantFilter, $CIPPID, $APIName = 'Remove Graph Webhook', + $Type, $ExecutingUser ) try { $WebhookTable = Get-CIPPTable -TableName webhookTable - $WebhookRow = Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $_.RowKey -eq $CIPPID } + if ($type -eq 'AuditLog') { + $WebhookRow = Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $_.PartitionKey -eq $TenantFilter } + } else { + $WebhookRow = Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $_.RowKey -eq $CIPPID } + } $Entity = $WebhookRow | Select-Object PartitionKey, RowKey - if ($WebhookRow.Resource -eq 'M365AuditLogs') { + if ($Type -eq 'AuditLog') { try { - $AuditLog = New-GraphPOSTRequest -uri "https://manage.office.com/api/v1.0/$($TenantFilter)/activity/feed/subscriptions/stop?contentType=$($WebhookRow.EventType)" -scope 'https://manage.office.com/.default' -tenantid $TenantFilter -type POST -body '{}' -verbose + foreach ($EventType in $WebhookRow.EventType) { + $AuditLog = New-GraphPOSTRequest -uri "https://manage.office.com/api/v1.0/$($TenantFilter)/activity/feed/subscriptions/stop?contentType=$($EventType)" -scope 'https://manage.office.com/.default' -tenantid $TenantFilter -type POST -body '{}' -verbose + } } catch { - #allowed to fail if the subscription is already removed + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to remove webhook subscription at Microsoft's side: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter } $null = Remove-AzDataTableEntity @WebhookTable -Entity $Entity } else { diff --git a/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 new file mode 100644 index 000000000000..ecdb1bdfc904 --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 @@ -0,0 +1,120 @@ +function Set-CIPPAuthenticationPolicy { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)]$Tenant, + [Parameter(Mandatory = $true)][ValidateSet('FIDO2', 'MicrosoftAuthenticator', 'SMS', 'TemporaryAccessPass', 'HardwareOATH', 'softwareOath', 'Voice', 'Email', 'x509Certificate')]$AuthenticationMethodId, + [Parameter(Mandatory = $true)][bool]$Enabled, # true = enabled or false = disabled + $MicrosoftAuthenticatorSoftwareOathEnabled, + $TAPMinimumLifetime = 60, #Minutes + $TAPMaximumLifetime = 480, #minutes + $TAPDefaultLifeTime = 60, #minutes + $TAPDefaultLength = 8, #TAP password generated length in chars + [bool]$TAPisUsableOnce = $true, + $APIName = 'Set Authentication Policy', + $ExecutingUser + ) + + # Convert bool input to usable string + $State = if ($Enabled) { 'enabled' } else { 'disabled' } + # Get current state of the called authentication method and Set state of authentication method to input state + try { + $CurrentInfo = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/$AuthenticationMethodId" -tenantid $Tenant + $CurrentInfo.state = $State + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message "Could not get CurrentInfo for $AuthenticationMethodId. Error:$($_.exception.message)" -sev Error + Return "Could not get CurrentInfo for $AuthenticationMethodId. Error:$($_.exception.message)" + } + + switch ($AuthenticationMethodId) { + + # FIDO2 + 'FIDO2' { + if ($State -eq 'enabled') { + $CurrentInfo.isAttestationEnforced = $true + $CurrentInfo.isSelfServiceRegistrationAllowed = $true + } + } + + # Microsoft Authenticator + 'MicrosoftAuthenticator' { + # Remove numberMatchingRequiredState property if it exists + $CurrentInfo.featureSettings.PSObject.Properties.Remove('numberMatchingRequiredState') + + if ($State -eq 'enabled') { + $CurrentInfo.featureSettings.displayAppInformationRequiredState.state = $State + $CurrentInfo.featureSettings.displayLocationInformationRequiredState.state = $State + # Set MS authenticator OTP state if parameter is passed in + if ($null -ne $MicrosoftAuthenticatorSoftwareOathEnabled ) { + $CurrentInfo.isSoftwareOathEnabled = $MicrosoftAuthenticatorSoftwareOathEnabled + $OptionalLogMessage = "and MS Authenticator software OTP to $MicrosoftAuthenticatorSoftwareOathEnabled" + } + } + } + + # SMS + 'SMS' { + if ($State -eq 'enabled') { + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message "Setting $AuthenticationMethodId to enabled is not allowed" -sev Error + return "Setting $AuthenticationMethodId to enabled is not allowed" + } + } + + # Temporary Access Pass + 'TemporaryAccessPass' { + if ($State -eq 'enabled') { + $CurrentInfo.isUsableOnce = $TAPisUsableOnce + $CurrentInfo.minimumLifetimeInMinutes = $TAPMinimumLifetime + $CurrentInfo.maximumLifetimeInMinutes = $TAPMaximumLifetime + $CurrentInfo.defaultLifetimeInMinutes = $TAPDefaultLifeTime + $CurrentInfo.defaultLength = $TAPDefaultLength + } + } + + # Hardware OATH tokens (Preview) + 'HardwareOATH' { + # Nothing special to do here + } + + # Third-party software OATH tokens + 'softwareOath' { + # Nothing special to do here + } + + # Voice call + 'Voice' { + # Disallow enabling voice + if ($State -eq 'enabled') { + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message "Setting $AuthenticationMethodId to enabled is not allowed" -sev Error + return "Setting $AuthenticationMethodId to enabled is not allowed" + } + } + + # Email OTP + 'Email' { + if ($State -eq 'enabled') { + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message "Setting $AuthenticationMethodId to enabled is not allowed" -sev Error + return "Setting $AuthenticationMethodId to enabled is not allowed" + } + } + + # Certificate-based authentication + 'x509Certificate' { + # Nothing special to do here + } + Default { + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message 'Somehow you hit the default case. You probably made a typo in the input for AuthenticationMethodId. It''s case sensitive' -sev Error + return 'Somehow you hit the default case. You probably made a typo in the input for AuthenticationMethodId. It''s case sensitive.' + } + } + # Set state of the authentication method + try { + # Convert body to JSON and send request + $body = ConvertTo-Json -Compress -Depth 10 -InputObject $CurrentInfo + New-GraphPostRequest -tenantid $Tenant -Uri "https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/$AuthenticationMethodId" -Type patch -Body $body -ContentType 'application/json' + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message "Set $AuthenticationMethodId state to $State $OptionalLogMessage" -sev Info + return "Set $AuthenticationMethodId state to $State $OptionalLogMessage" + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message "Failed to $State $AuthenticationMethodId Support: $($_.exception.message)" -sev Error + return "Failed to $State $AuthenticationMethodId Support: $($_.exception.message)" + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 b/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 index d4c06d0e904f..8de89293b96c 100644 --- a/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 @@ -15,19 +15,26 @@ function Set-CIPPCPVConsent { try { $DeleteSP = New-GraphpostRequest -Type DELETE -noauthcheck $true -uri "https://api.partnercenter.microsoft.com/v1/customers/$($TenantFilter)/applicationconsents/$($ENV:applicationId)" -scope 'https://api.partnercenter.microsoft.com/.default' -tenantid $env:TenantID $Results.add("Deleted Service Principal from $TenantName") - } - catch { + } catch { $Results.add("Error deleting SP - $($_.Exception.Message)") } } try { - $AppBody = @" -{ - "ApplicationGrants":[ {"EnterpriseApplicationId":"00000003-0000-0000-c000-000000000000","Scope":"Application.ReadWrite.all,DelegatedPermissionGrant.ReadWrite.All,Directory.ReadWrite.All"}], - "ApplicationId": "$($ENV:applicationId)" -} -"@ + $AppBody = @{ + ApplicationId = $($ENV:applicationId) + ApplicationGrants = @( + @{ + EnterpriseApplicationId = '00000003-0000-0000-c000-000000000000' + Scope = @( + 'DelegatedPermissionGrant.ReadWrite.All', + 'Directory.ReadWrite.All', + 'AppRoleAssignment.ReadWrite.All' + ) -Join ',' + } + ) + } | ConvertTo-Json + $CPVConsent = New-GraphpostRequest -body $AppBody -Type POST -noauthcheck $true -uri "https://api.partnercenter.microsoft.com/v1/customers/$($TenantFilter)/applicationconsents" -scope 'https://api.partnercenter.microsoft.com/.default' -tenantid $env:TenantID $Table = Get-CIPPTable -TableName cpvtenants $unixtime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds @@ -42,13 +49,11 @@ function Set-CIPPCPVConsent { $Results.add("Successfully added CPV Application to tenant $($TenantName)") | Out-Null Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Added our Service Principal to $($TenantName): $($_.Exception.message)" -Sev 'Info' -tenant $TenantName -tenantId $TenantFilter - } - catch { - $ErrorMessage = $_.Exception.Message - if ($ErrorMessage -like '*409 (Conflict)*') { return @("We've already added our Service Principal to $($TenantName)") } - + } catch { + $ErrorMessage = Get-NormalizedError -message $_.Exception.Message + if ($ErrorMessage -like '*Permission entry already exists*') { return @("We've already added our Service Principal to $($TenantName)") } Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Could not add our Service Principal to the client tenant $($TenantName): $($_.Exception.message)" -Sev 'Error' -tenant $TenantName -tenantId $TenantFilter - return @("Could not add our Service Principal to the client tenant $($TenantName): $($_.Exception.message)") + return @("Could not add our Service Principal to the client tenant $($TenantName): $ErrorMessage") } return $Results } diff --git a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 index f3c488a4e805..f465c0f76d8c 100644 --- a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 @@ -39,9 +39,20 @@ function Set-CIPPDefaultAPDeploymentProfile { } } $Body = ConvertTo-Json -InputObject $ObjBody - Write-Host $Body - $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles' -body $body -tenantid $tenantfilter - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $($tenantfilter) -message "Added Autopilot profile $($Displayname)" -Sev 'Info' + + $Profiles = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles' -tenantid $tenantfilter | Where-Object -Property displayName -EQ $displayname + if ($Profiles.count -gt 1) { + $Profiles | ForEach-Object { + if ($_.id -ne $Profiles[0].id) { + $Delete = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($_.id)" -tenantid $tenantfilter -type DELETE + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $($tenantfilter) -message "Deleted duplicate Autopilot profile $($displayname)" -Sev 'Info' + } + } + } + if (!$Profiles) { + $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles' -body $body -tenantid $tenantfilter + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $($tenantfilter) -message "Added Autopilot profile $($Displayname)" -Sev 'Info' + } if ($AssignTo) { $AssignBody = '{"target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}}' $assign = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($GraphRequest.id)/assignments" -tenantid $tenantfilter -type POST -body $AssignBody diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 index e16637950b76..470e5498a1dd 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 @@ -5,23 +5,42 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP { #> param($Tenant, $Settings) If ($Settings.remediate) { + + # Disable SMTP Basic Authentication for the tenant try { $Request = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-TransportConfig' -cmdParams @{ SmtpClientAuthenticationDisabled = $true } Write-LogMessage -API 'Standards' -tenant $tenant -message 'Disabled SMTP Basic Authentication' -sev Info } catch { Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable SMTP Basic Authentication: $($_.exception.message)" -sev Error } + + # Disable SMTP Basic Authentication for all users + $SMTPusers = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-CASMailbox' -cmdParams @{ ResultSize = 'Unlimited' } | Where-Object { ($null -ne $_.SmtpClientAuthenticationDisabled) } + $SMTPusers | ForEach-Object { + try { + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-CASMailbox' -cmdParams @{ Identity = $_.Identity; SmtpClientAuthenticationDisabled = $null } -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -tenant $tenant -message "Disabled SMTP Basic Authentication for $($_.DisplayName), $($_.PrimarySmtpAddress)" -sev Info + } catch { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable SMTP Basic Authentication for $($_.DisplayName), $($_.PrimarySmtpAddress). Error: $($_.exception.message)" -sev Error + + } + } } - if ($Settings.alert) { + + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-TransportConfig' - if ($CurrentInfo.SmtpClientAuthenticationDisabled) { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'SMTP Basic Authentication is disabled' -sev Info - } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'SMTP Basic Authentication is not disabled' -sev Alert + + if ($Settings.alert) { + if ($CurrentInfo.SmtpClientAuthenticationDisabled) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'SMTP Basic Authentication is disabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'SMTP Basic Authentication is not disabled' -sev Alert + } + } + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'DisableBasicAuthSMTP' -FieldValue [bool]$CurrentInfo.SmtpClientAuthenticationDisabled -StoreAs bool -Tenant $tenant } - } - if ($Settings.report) { - Add-CIPPBPAField -FieldName 'DisableBasicAuthSMTP' -FieldValue [bool]$CurrentInfo.SmtpClientAuthenticationDisabled -StoreAs bool -Tenant $tenant } } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 new file mode 100644 index 000000000000..dcd995600259 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 @@ -0,0 +1,28 @@ +function Invoke-CIPPStandardDisableEmail { + <# + .FUNCTIONALITY + Internal + #> + param($Tenant, $Settings) + + If ($Settings.remediate) { + Set-CIPPAuthenticationPolicy -Tenant $tenant -APIName 'Standards' -AuthenticationMethodId 'Email' -Enabled $false + } + + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { + $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/Email' -tenantid $Tenant + $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } + + if ($Settings.alert) { + if ($State) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Email Support is enabled' -sev Alert + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Email Support is not enabled' -sev Info + } + } + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'DisableEmail' -FieldValue [bool]$State -StoreAs bool -Tenant $tenant + } + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 index bcb286cc7677..e328f3d99afc 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 @@ -6,7 +6,7 @@ function Invoke-CIPPStandardDisableGuests { param($Tenant, $Settings) $lookup = (Get-Date).AddDays(-90).ToUniversalTime().ToString('o') $GraphRequest = New-GraphgetRequest -uri "https://graph.microsoft.com/beta/users?`$filter=(signInActivity/lastSignInDateTime le $lookup)&`$select=id,UserPrincipalName,signInActivity,mail,userType,accountEnabled" -scope 'https://graph.microsoft.com/.default' -tenantid $Tenant | Where-Object { $_.userType -EQ 'Guest' -and $_.AccountEnabled -EQ $true } - + If ($Settings.remediate) { try { foreach ($guest in $GraphRequest) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 new file mode 100644 index 000000000000..d133c34deef5 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 @@ -0,0 +1,28 @@ +function Invoke-CIPPStandardDisableSMS { + <# + .FUNCTIONALITY + Internal + #> + param($Tenant, $Settings) + + If ($Settings.remediate) { + Set-CIPPAuthenticationPolicy -Tenant $tenant -APIName 'Standards' -AuthenticationMethodId 'SMS' -Enabled $false + } + + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { + $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/SMS' -tenantid $Tenant + $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } + + if ($Settings.alert) { + if ($State) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'SMS Support is enabled' -sev Alert + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'SMS Support is not enabled' -sev Info + } + } + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'DisableSMS' -FieldValue [bool]$State -StoreAs bool -Tenant $tenant + } + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 new file mode 100644 index 000000000000..50d18e51c43f --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 @@ -0,0 +1,28 @@ +function Invoke-CIPPStandardDisableVoice { + <# + .FUNCTIONALITY + Internal + #> + param($Tenant, $Settings) + + If ($Settings.remediate) { + Set-CIPPAuthenticationPolicy -Tenant $tenant -APIName 'Standards' -AuthenticationMethodId 'Voice' -Enabled $false + } + + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { + $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/Voice' -tenantid $Tenant + $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } + + if ($Settings.alert) { + if ($State) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Voice Support is enabled' -sev Alert + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Voice Support is not enabled' -sev Info + } + } + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'DisableVoice' -FieldValue [bool]$State -StoreAs bool -Tenant $tenant + } + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 new file mode 100644 index 000000000000..94a36d9b9125 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 @@ -0,0 +1,28 @@ +function Invoke-CIPPStandardDisablex509Certificate { + <# + .FUNCTIONALITY + Internal + #> + param($Tenant, $Settings) + + If ($Settings.remediate) { + Set-CIPPAuthenticationPolicy -Tenant $tenant -APIName 'Standards' -AuthenticationMethodId 'x509Certificate' -Enabled $false + } + + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { + $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/x509Certificate' -tenantid $Tenant + $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } + + if ($Settings.alert) { + if ($State) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'x509Certificate Support is enabled' -sev Alert + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'x509Certificate Support is not enabled' -sev Info + } + } + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'Disablex509Certificate' -FieldValue [bool]$State -StoreAs bool -Tenant $tenant + } + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 index 1c7ff1337f4e..ff80b7f264f8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 @@ -4,28 +4,24 @@ function Invoke-CIPPStandardEnableFIDO2 { Internal #> param($Tenant, $Settings) + If ($Settings.remediate) { - - - try { - $body = '{"@odata.type":"#microsoft.graph.fido2AuthenticationMethodConfiguration","id":"Fido2","includeTargets":[{"id":"all_users","isRegistrationRequired":false,"targetType":"group","displayName":"All users"}],"excludeTargets":[],"isAttestationEnforced":true,"isSelfServiceRegistrationAllowed":true,"keyRestrictions":{"aaGuids":[],"enforcementType":"block","isEnforced":false},"state":"enabled"}' - New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/Fido2' -Type patch -Body $body -ContentType 'application/json' - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Enabled FIDO2 Support' -sev Info - } catch { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to enable FIDO2 Support: $($_.exception.message)" -sev Error - } + Set-CIPPAuthenticationPolicy -Tenant $tenant -APIName 'Standards' -AuthenticationMethodId 'Fido2' -Enabled $true } - - if ($Settings.alert) { + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/Fido2' -tenantid $Tenant - if ($CurrentInfo.state -eq 'enabled') { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'FIDO2 Support is enabled' -sev Info - } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'FIDO2 Support is not enabled' -sev Alert + + if ($Settings.alert) { + if ($CurrentInfo.state -eq 'enabled') { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'FIDO2 Support is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'FIDO2 Support is not enabled' -sev Alert + } + } + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'EnableFIDO2' -FieldValue [bool]$CurrentInfo.state -StoreAs bool -Tenant $tenant } - } - if ($Settings.report) { - Add-CIPPBPAField -FieldName 'EnableFIDO2' -FieldValue [bool]$CurrentInfo.state -StoreAs bool -Tenant $tenant } } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 new file mode 100644 index 000000000000..605a5188a47d --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 @@ -0,0 +1,27 @@ +function Invoke-CIPPStandardEnableHardwareOAuth { + <# + .FUNCTIONALITY + Internal + #> + param($Tenant, $Settings) + + If ($Settings.remediate) { + Set-CIPPAuthenticationPolicy -Tenant $tenant -APIName 'Standards' -AuthenticationMethodId 'HardwareOath' -Enabled $true + } + + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { + $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/HardwareOath' -tenantid $Tenant + + if ($Settings.alert) { + if ($CurrentInfo.state -eq 'enabled') { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'HardwareOAuth Support is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'HardwareOAuth Support is not enabled' -sev Alert + } + } + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'EnableHardwareOAuth' -FieldValue [bool]$CurrentInfo.state -StoreAs bool -Tenant $tenant + } + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 index 73e59b351a78..738420ebe73a 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 @@ -18,7 +18,7 @@ function Invoke-CIPPStandardMailContacts { { $Contacts.GeneralContact } { $body | Add-Member -NotePropertyName privacyProfile -NotePropertyValue @{contactEmail = $Contacts.GeneralContact } } } Write-Host (ConvertTo-Json -InputObject $body) - New-GraphPostRequest -tenantid $tenant -Uri "https://graph.microsoft.com/beta/organization/$($TenantID.id)" -Type patch -Body (ConvertTo-Json -InputObject $body) -ContentType 'application/json' + New-GraphPostRequest -tenantid $tenant -Uri "https://graph.microsoft.com/beta/organization/$($TenantID.id)" -asApp $true -Type patch -Body (ConvertTo-Json -InputObject $body) -ContentType 'application/json' Write-LogMessage -API 'Standards' -tenant $tenant -message "Contact email's set." -sev Info } catch { Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set contact emails: $($_.exception.message)" -sev Error diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 index a3ecdb371327..e9889299cdfd 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 @@ -3,48 +3,47 @@ function Invoke-CIPPStandardOauthConsent { .FUNCTIONALITY Internal #> - param($tenant, $settings) { - $State = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $tenant) + param($tenant, $settings) + $State = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $tenant - If ($Settings.remediate) { - $AllowedAppIdsForTenant = $Settings.AllowedApps -split ',' - try { - if ($State.permissionGrantPolicyIdsAssignedToDefaultUserRole -notin @('ManagePermissionGrantsForSelf.cipp-1sent-policy')) { - $Existing = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies/' -tenantid $tenant) | Where-Object -Property id -EQ 'cipp-consent-policy' - if (!$Existing) { - New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies' -Type POST -Body '{ "id":"cipp-consent-policy", "displayName":"Application Consent Policy", "description":"This policy controls the current application consent policies."}' -ContentType 'application/json' - New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies/cipp-consent-policy/includes' -Type POST -Body '{"permissionClassification":"all","permissionType":"delegated","clientApplicationIds":["d414ee2d-73e5-4e5b-bb16-03ef55fea597"]}' -ContentType 'application/json' - } - try { - foreach ($AllowedApp in $AllowedAppIdsForTenant) { - Write-Host "$AllowedApp" - New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies/cipp-consent-policy/includes' -Type POST -Body ('{"permissionType": "delegated","clientApplicationIds": ["' + $AllowedApp + '"]}') -ContentType 'application/json' - New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies/cipp-consent-policy/includes' -Type POST -Body ('{ "permissionType": "Application", "clientApplicationIds": ["' + $AllowedApp + '"] }') -ContentType 'application/json' - } - } catch { - "Could not add exclusions, probably already exist: $($_)" - } - New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -Type PATCH -Body '{"permissionGrantPolicyIdsAssignedToDefaultUserRole":["managePermissionGrantsForSelf.cipp-consent-policy"]}' -ContentType 'application/json' + If ($Settings.remediate) { + $AllowedAppIdsForTenant = $Settings.AllowedApps -split ',' + try { + if ($State.permissionGrantPolicyIdsAssignedToDefaultUserRole -notin @('ManagePermissionGrantsForSelf.cipp-1sent-policy')) { + $Existing = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies/' -tenantid $tenant) | Where-Object -Property id -EQ 'cipp-consent-policy' + if (!$Existing) { + New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies' -Type POST -Body '{ "id":"cipp-consent-policy", "displayName":"Application Consent Policy", "description":"This policy controls the current application consent policies."}' -ContentType 'application/json' + New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies/cipp-consent-policy/includes' -Type POST -Body '{"permissionClassification":"all","permissionType":"delegated","clientApplicationIds":["d414ee2d-73e5-4e5b-bb16-03ef55fea597"]}' -ContentType 'application/json' } - if ($AllowedAppIdsForTenant) { + try { + foreach ($AllowedApp in $AllowedAppIdsForTenant) { + Write-Host "$AllowedApp" + New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies/cipp-consent-policy/includes' -Type POST -Body ('{"permissionType": "delegated","clientApplicationIds": ["' + $AllowedApp + '"]}') -ContentType 'application/json' + New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies/cipp-consent-policy/includes' -Type POST -Body ('{ "permissionType": "Application", "clientApplicationIds": ["' + $AllowedApp + '"] }') -ContentType 'application/json' + } + } catch { + "Could not add exclusions, probably already exist: $($_)" } - - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Application Consent Mode has been enabled.' -sev Info - } catch { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to apply Application Consent Mode Error: $($_.exception.message)" -sev Error + New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -Type PATCH -Body '{"permissionGrantPolicyIdsAssignedToDefaultUserRole":["managePermissionGrantsForSelf.cipp-consent-policy"]}' -ContentType 'application/json' } - } - if ($Settings.alert) { - - if ($State.permissionGrantPolicyIdsAssignedToDefaultUserRole -eq 'managePermissionGrantsForSelf.cipp-consent-policy') { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Application Consent Mode is enabled.' -sev Info - } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Application Consent Mode is not enabled.' -sev Alert + if ($AllowedAppIdsForTenant) { } + + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Application Consent Mode has been enabled.' -sev Info + } catch { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to apply Application Consent Mode Error: $($_.exception.message)" -sev Error } - if ($Settings.report) { - if ($State.permissionGrantPolicyIdsAssignedToDefaultUserRole -eq 'managePermissionGrantsForSelf.cipp-consent-policy') { $UserQuota = $true } else { $UserQuota = $false } - Add-CIPPBPAField -FieldName 'OauthConsent' -FieldValue [bool]$UserQuota -StoreAs bool -Tenant $tenant + } + if ($Settings.alert) { + + if ($State.permissionGrantPolicyIdsAssignedToDefaultUserRole -eq 'managePermissionGrantsForSelf.cipp-consent-policy') { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Application Consent Mode is enabled.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Application Consent Mode is not enabled.' -sev Alert } } -} \ No newline at end of file + if ($Settings.report) { + if ($State.permissionGrantPolicyIdsAssignedToDefaultUserRole -eq 'managePermissionGrantsForSelf.cipp-consent-policy') { $UserQuota = $true } else { $UserQuota = $false } + Add-CIPPBPAField -FieldName 'OauthConsent' -FieldValue [bool]$UserQuota -StoreAs bool -Tenant $tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 index aecda655774b..4bf9ebcd3426 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 @@ -4,29 +4,24 @@ function Invoke-CIPPStandardPWdisplayAppInformationRequiredState { Internal #> param($Tenant, $Settings) - $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -tenantid $Tenant - + If ($Settings.remediate) { - try { - $body = @' -{"@odata.type":"#microsoft.graph.microsoftAuthenticatorAuthenticationMethodConfiguration","id":"MicrosoftAuthenticator","includeTargets":[{"id":"all_users","isRegistrationRequired":false,"targetType":"group","authenticationMode":"any"}],"excludeTargets":[],"state":"enabled","isSoftwareOathEnabled":false,"featureSettings":{"displayLocationInformationRequiredState":{"state":"enabled","includeTarget":{"id":"all_users","targetType":"group","displayName":"All users"}},"displayAppInformationRequiredState":{"state":"enabled","includeTarget":{"id":"all_users","targetType":"group","displayName":"All users"}},"companionAppAllowedState":{"state":"default","includeTarget":{"id":"all_users","targetType":"group","displayName":"All users"}}}} -'@ - (New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -Type patch -Body $body -ContentType 'application/json') - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Enabled passwordless with Information and Number Matching.' -sev Info - } catch { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to enable passwordless with Information and Number Matching. Error: $($_.exception.message)" -sev 'Error' - } + Set-CIPPAuthenticationPolicy -Tenant $tenant -APIName 'Standards' -AuthenticationMethodId 'MicrosoftAuthenticator' -Enabled $true } - if ($Settings.alert) { - - if ($CurrentInfo.featureSettings.displayAppInformationRequiredState.state -eq 'enabled') { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Passwordless with Information and Number Matching is enabled.' -sev Info - } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Passwordless with Information and Number Matching is not enabled.' -sev Alert + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { + $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -tenantid $Tenant + $State = if ($CurrentInfo.featureSettings.displayAppInformationRequiredState.state -eq 'enabled') { $true } else { $false } + + if ($Settings.alert) { + if ($State) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Passwordless with Information and Number Matching is enabled.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Passwordless with Information and Number Matching is not enabled.' -sev Alert + } + } + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'PWdisplayAppInformationRequiredState' -FieldValue [bool]$State -StoreAs bool -Tenant $tenant } } - if ($Settings.report) { - if ($CurrentInfo.featureSettings.displayAppInformationRequiredState.state -eq 'enabled') { $authstate = $true } else { $authstate = $false } - Add-CIPPBPAField -FieldName 'PWdisplayAppInformationRequiredState' -FieldValue [bool]$authstate -StoreAs bool -Tenant $tenant - } -} +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 index ed6559f29cf2..e98e7faddd01 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 @@ -4,40 +4,25 @@ function Invoke-CIPPStandardTAP { Internal #> param($Tenant, $Settings) - $CurrentInfo = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/TemporaryAccessPass' -tenantid $Tenant) - + If ($Settings.remediate) { - try { - - $CurrentInfo.state = 'enabled' - $CurrentInfo.isUsableOnce = $Settings.config - $CurrentInfo.minimumLifetimeInMinutes = '60' - $CurrentInfo.maximumLifetimeInMinutes = '480' - $CurrentInfo.defaultLifetimeInMinutes = '60' - $CurrentInfo.defaultLength = '8' - $body = ConvertTo-Json -Depth 10 -InputObject $CurrentInfo - Write-Host "Sending body $body" - New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/TemporaryAccessPass' -Type patch -asApp $true -Body $body -ContentType 'application/json' - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Enabled Temporary Access Passwords.' -sev Info - } catch { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to enable TAP. Error: $($_.exception.message)" -sev Error - } + Set-CIPPAuthenticationPolicy -Tenant $tenant -APIName 'Standards' -AuthenticationMethodId 'TemporaryAccessPass' -Enabled $true -TAPisUsableOnce $Settings.config } - if ($Settings.alert) { - if ($CurrentInfo.state -eq 'enabled') { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Temporary Access Passwords is enabled.' -sev Info - } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Temporary Access Passwords is not enabled.' -sev Alert + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { + $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/TemporaryAccessPass' -tenantid $Tenant + $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } + + if ($Settings.alert) { + if ($State) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Temporary Access Passwords is enabled.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Temporary Access Passwords is not enabled.' -sev Alert + } } - } - if ($Settings.report) { - if ($CurrentInfo.state -eq 'enabled') { - $CurrentInfo.state = $true - } else { - $CurrentInfo.state = $false + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'TemporaryAccessPass' -FieldValue [bool]$State -StoreAs bool -Tenant $tenant } - Add-CIPPBPAField -FieldName 'TemporaryAccessPass' -FieldValue [bool]$CurrentInfo.state -StoreAs bool -Tenant $tenant } - -} +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 index 00be3942e2db..8455c93208f5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 @@ -4,33 +4,26 @@ function Invoke-CIPPStandardallowOAuthTokens { Internal #> param($Tenant, $Settings) - $CurrentInfo = new-graphgetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/softwareOath' -tenantid $Tenant - If ($Settings.remediate) { - - try { - $CurrentInfo.state = 'enabled' - $body = ($CurrentInfo | ConvertTo-Json -Depth 10) - (New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/softwareOath' -Type patch -Body $body -ContentType 'application/json') - - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Enabled software OTP/oAuth tokens' -sev Info - } catch { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to enable software OTP/oAuth tokens. Error: $($_.exception.message)" -sev 'Error' - } + + If ($Settings.remediate) { + Set-CIPPAuthenticationPolicy -Tenant $tenant -APIName 'Standards' -AuthenticationMethodId 'softwareOath' -Enabled $true } - if ($Settings.alert) { - if ($CurrentInfo.state -eq 'enabled') { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'software OTP/oAuth tokens is enabled' -sev Info - } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'software OTP/oAuth tokens is not enabled' -sev Alert + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { + $CurrentInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/softwareOath' -tenantid $Tenant + $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } + + if ($Settings.alert) { + if ($State) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Software OTP/oAuth tokens is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Software OTP/oAuth tokens is not enabled' -sev Alert + } } - } - if ($Settings.report) { - if ($CurrentInfo.state -eq 'enabled') { - $CurrentInfo.state = $true - } else { - $CurrentInfo.state = $false + + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'softwareOath' -FieldValue [bool]$State -StoreAs bool -Tenant $tenant } - Add-CIPPBPAField -FieldName 'softwareOath' -FieldValue [bool]$CurrentInfo.state -StoreAs bool -Tenant $tenant } -} +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 index c7a5958a45d8..cca5dc8ae8e2 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 @@ -4,30 +4,25 @@ function Invoke-CIPPStandardallowOTPTokens { Internal #> param($Tenant, $Settings) - $CurrentInfo = new-graphgetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -tenantid $Tenant - + If ($Settings.remediate) { - - try { - $CurrentInfo.featureSettings.PSObject.Properties.Remove('numberMatchingRequiredState') - $CurrentInfo.isSoftwareOathEnabled = $true - $body = ($CurrentInfo | ConvertTo-Json -Depth 10) - (New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -Type patch -Body $body -ContentType 'application/json') + Set-CIPPAuthenticationPolicy -Tenant $tenant -APIName 'Standards' -AuthenticationMethodId 'MicrosoftAuthenticator' -Enabled $true -MicrosoftAuthenticatorSoftwareOathEnabled $true + } - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Enabled MS authenticator OTP/oAuth tokens' -sev Info - } catch { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to enable MS authenticator OTP/oAuth tokens. Error: $($_.exception.message)" -sev Error + # This is ugly but done to avoid a second call to the Graph API + if ($Settings.alert -or $Settings.report) { + $CurrentInfo = new-graphgetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -tenantid $Tenant + if ($Settings.alert) { + + if ($CurrentInfo.isSoftwareOathEnabled) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'MS authenticator OTP/oAuth tokens is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'MS authenticator OTP/oAuth tokens is not enabled' -sev Alert + } } - } - if ($Settings.alert) { - if ($CurrentInfo.isSoftwareOathEnabled) { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'MS authenticator OTP/oAuth tokens is enabled' -sev Info - } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'MS authenticator OTP/oAuth tokens is not enabled' -sev Alert + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'MSAuthenticator' -FieldValue [bool]$CurrentInfo.isSoftwareOathEnabled -StoreAs bool -Tenant $tenant } } - if ($Settings.report) { - Add-CIPPBPAField -FieldName 'MSAuthenticator' -FieldValue [bool]$CurrentInfo.isSoftwareOathEnabled -StoreAs bool -Tenant $tenant - } -} +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 index 9a2ccfb0e298..f05a9350a705 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 @@ -6,20 +6,60 @@ function Invoke-CIPPStandardcalDefault { param($Tenant, $Settings) If ($Settings.remediate) { $Mailboxes = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' - foreach ($Mailbox in $Mailboxes) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Started setting default calendar permissions for $($Mailboxes.Count) mailboxes." -sev Info + + # Thread safe counter + $UserSuccesses = [HashTable]::Synchronized(@{Counter = 0 }) + + # Set default calendar permissions for each mailbox. Run in parallel to speed up the process + $Mailboxes | ForEach-Object -ThrottleLimit 25 -Parallel { + Import-Module CIPPcore + $Tenant = $Using:Tenant + $Settings = $Using:Settings + $Mailbox = $_ + $UserSuccesses = $Using:UserSuccesses + try { - New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MailboxFolderStatistics' -cmdParams @{identity = $Mailbox.UserPrincipalName; FolderScope = 'Calendar' } -Anchor $Mailbox.UserPrincipalName | Where-Object { $_.FolderType -eq 'Calendar' } | ForEach-Object { - New-ExoRequest -tenantid $Tenant -cmdlet 'Set-MailboxFolderPermission' -cmdparams @{Identity = "$($Mailbox.UserPrincipalName):$($_.FolderId)"; User = 'Default'; AccessRights = $Settings.permissionlevel } -Anchor $Mailbox.UserPrincipalName - Write-LogMessage -API 'Standards' -tenant $tenant -message "Set default folder permission for $($Mailbox.UserPrincipalName):\$($_.Name) to $($Settings.permissionlevel)" -sev Info - } - } - catch { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Could not set default calendar permissions for $($Mailbox.UserPrincipalName). Error: $($_.exception.message)" -sev Error - } - - } - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Done setting default calendar permissions.' -sev Info + $GetRetryCount = 0 + + do { + # Get all calendars for the mailbox, retry if it fails + try { + New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MailboxFolderStatistics' -cmdParams @{identity = $Mailbox.UserPrincipalName; FolderScope = 'Calendar' } -Anchor $Mailbox.UserPrincipalName | Where-Object { $_.FolderType -eq 'Calendar' } | + # Set permissions for each calendar found + ForEach-Object { + $SetRetryCount = 0 + do { + try { + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-MailboxFolderPermission' -cmdparams @{Identity = "$($Mailbox.UserPrincipalName):$($_.FolderId)"; User = 'Default'; AccessRights = $Settings.permissionlevel } -Anchor $Mailbox.UserPrincipalName + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Set default folder permission for $($Mailbox.UserPrincipalName):\$($_.Name) to $($Settings.permissionlevel)" -sev Debug + $Success = $true + $UserSuccesses.Counter++ + } catch { + # Retry Set-MailboxFolderStatistics + Start-Sleep -Milliseconds (Get-Random -Minimum 200 -Maximum 300) + $SetRetryCount++ + + # Log error if it fails 3 times + if ($SetRetryCount -ge 3) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not set default calendar permissions for $($Mailbox.UserPrincipalName). Error: $($_.exception.message)" -sev Error + } + } + } Until ($SetRetryCount -ge 3 -or $Success -eq $true) + } + $Success = $true + } catch { + # Retry Get-MailboxFolderStatistics + Start-Sleep -Milliseconds (Get-Random -Minimum 250 -Maximum 500) + $GetRetryCount++ + } - } + } until ($GetRetryCount -ge 3 -or $Success -eq $true) + } catch { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not set default calendar permissions for $($Mailbox.UserPrincipalName). Error: $($_.exception.message)" -sev Error + } + } + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully set default calendar permissions for $($UserSuccesses.Counter) out of $($Mailboxes.Count) mailboxes." -sev Info -} + } + } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 index 6c6a969d906f..d527bdd0ff3b 100644 --- a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 @@ -66,8 +66,9 @@ function Test-CIPPGDAPRelationships { } } if (-not $GroupFound) { + if ($Group -eq 'AdminAgents') { $Type = 'Error' } else { $Type = 'Warning' } $GDAPissues.add([PSCustomObject]@{ - Type = 'Warning' + Type = $Type Issue = "$($Group) is not assigned to the SAM user $me. If you have migrated outside of CIPP this is to be expected. Please perform an access check to make sure you have the correct set of permissions." Tenant = '*Partner Tenant' Relationship = 'None' diff --git a/PublicWebhooks/run.ps1 b/PublicWebhooks/run.ps1 index 232c44458572..ad13d0546ef7 100644 --- a/PublicWebhooks/run.ps1 +++ b/PublicWebhooks/run.ps1 @@ -6,22 +6,22 @@ param($Request, $TriggerMetadata) $WebhookTable = Get-CIPPTable -TableName webhookTable $Webhooks = Get-CIPPAzDataTableEntity @WebhookTable -Write-Host "Received request" +Write-Host 'Received request' Write-Host "CIPPID: $($request.Query.CIPPID)" $url = ($request.headers.'x-ms-original-url').split('/API') | Select-Object -First 1 Write-Host $url -if ($Request.CIPPID -in $Webhooks.CIPPID) { - Write-Host "Found matching CIPPID" +if ($Request.Query.CIPPID -in $Webhooks.RowKey) { + Write-Host 'Found matching CIPPID' if ($Request.query.ValidationToken -or $Request.body.validationCode) { - Write-Host "Validation token received" + Write-Host 'Validation token received' $body = $request.query.ValidationToken } else { Push-OutputBinding -Name QueueWebhook -Value $Request $Body = 'Webhook Recieved' } } else { - $body = "This webhook is not authorized." + $body = 'This webhook is not authorized.' } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/PublicWebhooksProcess/run.ps1 b/PublicWebhooksProcess/run.ps1 index 8c5f91227ceb..1f404c867df2 100644 --- a/PublicWebhooksProcess/run.ps1 +++ b/PublicWebhooksProcess/run.ps1 @@ -7,15 +7,15 @@ $Request = $QueueItem $WebhookTable = Get-CIPPTable -TableName webhookTable $Webhooks = Get-AzDataTableEntity @WebhookTable -Write-Host "Received request" +Write-Host 'Received request' Write-Host "CIPPID: $($request.Query.CIPPID)" $url = ($request.headers.'x-ms-original-url').split('/API') | Select-Object -First 1 Write-Host $url if ($Request.query.CIPPID -in $Webhooks.RowKey) { - Write-Host "Found matching CIPPID" + Write-Host 'Found matching CIPPID' $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID - if ($Request.Query.Type = 'GraphSubscription') { + if ($Request.Query.Type -eq 'GraphSubscription') { # Graph Subscriptions [pscustomobject]$ReceivedItem = $Request.Body.value Invoke-CippGraphWebhookProcessing -Data $ReceivedItem -CIPPID $request.Query.CIPPID -WebhookInfo $Webhookinfo @@ -23,25 +23,16 @@ if ($Request.query.CIPPID -in $Webhooks.RowKey) { } else { # Auditlog Subscriptions $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID - $operations = $Webhookinfo.Operations -split ',' foreach ($ReceivedItem In ($Request.body)) { $ReceivedItem = [pscustomobject]$ReceivedItem $TenantFilter = (Get-Tenants | Where-Object -Property customerId -EQ $ReceivedItem.TenantId).defaultDomainName Write-Host "TenantFilter: $TenantFilter" - $Data = New-GraphPostRequest -type GET -uri "https://manage.office.com/api/v1.0/$($ReceivedItem.tenantId)/activity/feed/audit/$($ReceivedItem.contentid)" -tenantid $TenantFilter -scope "https://manage.office.com/.default" + $Data = New-GraphPostRequest -type GET -uri "https://manage.office.com/api/v1.0/$($ReceivedItem.tenantId)/activity/feed/audit/$($ReceivedItem.contentid)" -tenantid $TenantFilter -scope 'https://manage.office.com/.default' Write-Host "Data to process found: $(($ReceivedItem.operation).count) items" Write-Host "Operations to process for this client: $($Webhookinfo.Operations)" foreach ($Item in $Data) { Write-Host "Processing $($item.operation)" - if ($item.operation -in $operations) { - Invoke-CippWebhookProcessing -TenantFilter $TenantFilter -Data $Item -CIPPPURL $url -allowedlocations $Webhookinfo.AllowedLocations -Operations $operations - } - if ($item.operation -eq "UserLoggedIn" -and "UserLoggedInFromUnknownLocation" -in $operations) { - Invoke-CippWebhookProcessing -TenantFilter $TenantFilter -Data $Item -CIPPPURL $url -allowedlocations $Webhookinfo.AllowedLocations -Operations $operations - } - if ($item.operation -eq "UserLoggedIn" -and "AdminLoggedIn" -in $operations) { - Invoke-CippWebhookProcessing -TenantFilter $TenantFilter -Data $Item -CIPPPURL $url -allowedlocations $Webhookinfo.AllowedLocations -Operations $operations - } + Invoke-CippWebhookProcessing -TenantFilter $TenantFilter -Data $Item -CIPPPURL $url } } } diff --git a/Scheduler_Alert/function.json b/Scheduler_Alert/function.json index 2d4ea9094b24..b8758df62b9d 100644 --- a/Scheduler_Alert/function.json +++ b/Scheduler_Alert/function.json @@ -4,6 +4,12 @@ "name": "tenant", "direction": "in", "type": "activityTrigger" + }, + { + "type": "queue", + "direction": "out", + "name": "QueueItem", + "queueName": "CIPPGenericQueue" } ] -} \ No newline at end of file +} diff --git a/Scheduler_Alert/run.ps1 b/Scheduler_Alert/run.ps1 index 21f2bc61d55d..c35df4f9d1a1 100644 --- a/Scheduler_Alert/run.ps1 +++ b/Scheduler_Alert/run.ps1 @@ -5,396 +5,39 @@ try { $Table = Get-CIPPTable -Table SchedulerConfig if ($Tenant.tag -eq 'AllTenants') { $Filter = "RowKey eq 'AllTenants' and PartitionKey eq 'Alert'" - } - else { + } else { $Filter = "RowKey eq '{0}' and PartitionKey eq 'Alert'" -f $Tenant.tenantid } $Alerts = Get-CIPPAzDataTableEntity @Table -Filter $Filter - $ConfigFilter = "RowKey eq 'CippNotifications' and PartitionKey eq 'CippNotifications'" - $Config = [pscustomobject](Get-CIPPAzDataTableEntity @Table -Filter $ConfigFilter) - - $DeltaTable = Get-CIPPTable -Table DeltaCompare - $LastRunTable = Get-CIPPTable -Table AlertLastRun - - $ShippedAlerts = switch ($Alerts) { - { $_.'AdminPassword' -eq $true } { - try { - New-GraphGETRequest -uri "https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '62e90394-69f5-4237-9190-012177145e10'&`$expand=principal" -tenantid $($tenant.tenant) | Where-Object { ($_.principalOrganizationId -EQ $tenant.tenantid) -and ($_.principal.'@odata.type' -eq '#microsoft.graph.user') } | ForEach-Object { - $LastChanges = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/users/$($_.principalId)?`$select=UserPrincipalName,lastPasswordChangeDateTime" -tenant $($tenant.tenant) - if ($LastChanges.LastPasswordChangeDateTime -gt (Get-Date).AddDays(-1)) { "Admin password has been changed for $($LastChanges.UserPrincipalName) in last 24 hours" } - } - - } - catch { - "Could not get admin password changes for $($Tenant.tenant): $(Get-NormalizedError -message $_.Exception.message)" - } - } - { $_.'DefenderMalware' -eq $true } { - try { - New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/windowsDeviceMalwareStates?`$top=999&`$filter=tenantId eq '$($Tenant.tenantid)'" | Where-Object { $_.malwareThreatState -eq 'Active' } | ForEach-Object { - "$($_.managedDeviceName): Malware found and active. Severity: $($_.MalwareSeverity). Malware name: $($_.MalwareDisplayName)" - } - } - catch { - - } - } - - { $_.'DefenderStatus' -eq $true } { - try { - New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/windowsProtectionStates?`$top=999&`$filter=tenantId eq '$($Tenant.tenantid)'" | Where-Object { $_.realTimeProtectionEnabled -eq $false -or $_.MalwareprotectionEnabled -eq $false } | ForEach-Object { - "$($_.managedDeviceName) - Real Time Protection: $($_.realTimeProtectionEnabled) & Malware Protection: $($_.MalwareprotectionEnabled)" - } - } - catch { - - } - } - { $_.'SharepointQuota' -eq $true } { - Try { - $tenantName = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/domains" -tenantid $Tenant.Tenant | Where-Object { $_.isInitial -eq $true }).id.Split(".")[0] - $sharepointToken = (Get-GraphToken -scope "https://$($tenantName)-admin.sharepoint.com/.default" -tenantid $Tenant.Tenant) - $sharepointToken.Add('accept', 'application/json') - $sharepointQuota = (Invoke-RestMethod -Method "GET" -Headers $sharepointToken -Uri "https://$($tenantName)-admin.sharepoint.com/_api/StorageQuotas()?api-version=1.3.2" -ErrorAction Stop).value - if ($sharepointQuota) { - $UsedStoragePercentage = [int](($sharepointQuota.GeoUsedStorageMB / $sharepointQuota.TenantStorageMB) * 100) - if ($UsedStoragePercentage -gt 90) { - "SharePoint Storage is at $($UsedStoragePercentage)%" - } - } - } - catch { - } - } - { $_.'MFAAdmins' -eq $true } { - try { - $StrongMFAMethods = '#microsoft.graph.fido2AuthenticationMethod', '#microsoft.graph.phoneAuthenticationMethod', '#microsoft.graph.passwordlessmicrosoftauthenticatorauthenticationmethod', '#microsoft.graph.softwareOathAuthenticationMethod', '#microsoft.graph.microsoftAuthenticatorAuthenticationMethod' - $AdminList = (New-GraphGETRequest -uri "https://graph.microsoft.com/beta/directoryRoles?`$expand=members" -tenantid $($tenant.tenant) | Where-Object -Property roleTemplateId -NE 'd29b2b05-8046-44ba-8758-1e26182fcf32').members | Where-Object { $_.userPrincipalName -ne $null -and $_.Usertype -eq 'Member' -and $_.accountEnabled -eq $true } | Sort-Object UserPrincipalName -Unique - $CAPolicies = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies' -tenantid $Tenant.tenant -ErrorAction Stop) - foreach ($Policy in $CAPolicies) { - if ($policy.grantControls.customAuthenticationFactors -eq 'RequireDuoMfa') { - $DuoActive = $true - } - } - if (!$DuoActive) { - $AdminList | ForEach-Object { - $CARegistered = $null - try { - (New-GraphGETRequest -uri "https://graph.microsoft.com/beta/users/$($_.ID)/authentication/Methods" -tenantid $($tenant.tenant)) | ForEach-Object { - if ($_.'@odata.type' -in $StrongMFAMethods) { - $CARegistered = $true; - } - } - if ($_.StrongAuthenticationRequirements.StrongAuthenticationRequirement.state -eq $null -and $CARegistered -ne $true) { "Admin $($_.UserPrincipalName) is enabled but does not have any form of MFA configured." } - } - catch { - } - } - } - else { - Write-LogMessage -message "Potentially using Duo for MFA, could not check MFA status for Admins with 100% accuracy" -API 'MFA Alerts - Informational' -tenant $tenant.tenant -sev Info - - } - } - catch { - "Could not get MFA status for admins for $($Tenant.tenant): $(Get-NormalizedError -message $_.Exception.message)" - } - } - { $_.'MFAAlertUsers' -eq $true } { - try { - $users = Get-CIPPMSolUsers -tenant $tenant.tenant - $StrongMFAMethods = '#microsoft.graph.fido2AuthenticationMethod', '#microsoft.graph.phoneAuthenticationMethod', '#microsoft.graph.passwordlessmicrosoftauthenticatorauthenticationmethod', '#microsoft.graph.softwareOathAuthenticationMethod', '#microsoft.graph.microsoftAuthenticatorAuthenticationMethod' - - $users | Where-Object { $_.Usertype -eq 'Member' -and $_.BlockCredential -eq $false } | ForEach-Object { - try { - (New-GraphGETRequest -uri "https://graph.microsoft.com/beta/users/$($_.ObjectID)/authentication/Methods" -tenantid $($tenant.tenant)) | ForEach-Object { - if ($_.'@odata.type' -in $StrongMFAMethods -and !$CARegistered) { - $CARegistered = $true; - } } - if ($_.StrongAuthenticationRequirements.StrongAuthenticationRequirement.state -eq $null -and $CARegistered -eq $false) { "User $($_.UserPrincipalName) is enabled but does not have any form of MFA configured." } - } - catch { - $CARegistered = $false - } - } - - } - catch { - "Could not get MFA status for users for $($Tenant.tenant): $(Get-NormalizedError -message $_.Exception.message)" - - } - } - - { $_.'NewRole' -eq $true } { - try { - $Filter = "PartitionKey eq 'AdminDelta' and RowKey eq '{0}'" -f $Tenant.tenantid - $AdminDelta = (Get-CIPPAzDataTableEntity @Deltatable -Filter $Filter).delta | ConvertFrom-Json -ErrorAction SilentlyContinue - $NewDelta = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/directoryRoles?`$expand=members" -tenantid $Tenant.tenant) | Select-Object displayname, Members | ForEach-Object { - @{ - GroupName = $_.displayname - Members = $_.Members.UserPrincipalName - } - } - $NewDeltatoSave = $NewDelta | ConvertTo-Json -Depth 10 -Compress -ErrorAction SilentlyContinue | Out-String - $DeltaEntity = @{ - PartitionKey = 'AdminDelta' - RowKey = [string]$Tenant.tenantid - delta = "$NewDeltatoSave" - } - Add-CIPPAzDataTableEntity @DeltaTable -Entity $DeltaEntity -Force - - if ($AdminDelta) { - foreach ($Group in $NewDelta) { - $OldDelta = $AdminDelta | Where-Object { $_.GroupName -eq $Group.GroupName } - $Group.members | Where-Object { $_ -notin $OldDelta.members } | ForEach-Object { - "$_ has been added to the $($Group.GroupName) Role" - } - } - } - } - catch { - "Could not get get role changes for $($Tenant.tenant): $(Get-NormalizedError -message $_.Exception.message)" - - } - } - { $_.'QuotaUsed' -eq $true } { - try { - New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getMailboxUsageDetail(period='D7')?`$format=application/json" -tenantid $Tenant.tenant | ForEach-Object { - $PercentLeft = [math]::round($_.StorageUsedInBytes / $_.prohibitSendReceiveQuotaInBytes * 100) - if ($PercentLeft -gt 90) { "$($_.UserPrincipalName): Mailbox has less than 10% space left. Mailbox is $PercentLeft% full" } - } - } - catch { - - } - } - { $_.'ExpiringLicenses' -eq $true } { - try { - Get-CIPPLicenseOverview -TenantFilter $Tenant.tenant | Where-Object -Property [int]TimeUntilRenew -LT 29 | ForEach-Object { - "$($_.License) will expire in $($_.TimeUntilRenew) days" - } - } - catch { - - } - } - { $_.'NoCAConfig' -eq $true } { - try { - $CAAvailable = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $Tenant.Tenant -erroraction stop).serviceplans - if ('AAD_PREMIUM' -in $CAAvailable.servicePlanName) { - $CAPolicies = (New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies' -tenantid $Tenant.Tenant) - if (!$CAPolicies.id) { - 'Conditional Access is available, but no policies could be found.' - } - } - } - catch { - } - } - { $_.'UnusedLicenses' -eq $true } { - try { - #$ConvertTable = Import-Csv Conversiontable.csv - $LicenseTable = Get-CIPPTable -TableName ExcludedLicenses - $ExcludedSkuList = Get-CIPPAzDataTableEntity @LicenseTable - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $Tenant.tenant | ForEach-Object { - $skuid = $_ - foreach ($sku in $skuid) { - if ($sku.skuId -in $ExcludedSkuList.GUID) { continue } - $PrettyName = ($ConvertTable | Where-Object { $_.GUID -eq $_.skuid }).'Product_Display_Name' | Select-Object -Last 1 - if (!$PrettyName) { $PrettyName = $sku.skuPartNumber } - if ($sku.prepaidUnits.enabled - $sku.consumedUnits -gt 0) { - "$PrettyName has unused licenses. Using $($_.consumedUnits) of $($_.prepaidUnits.enabled)." - } - } - } - } - catch { - - } - } - { $_.'OverusedLicenses' -eq $true } { - try { - #$ConvertTable = Import-Csv Conversiontable.csv - $LicenseTable = Get-CIPPTable -TableName ExcludedLicenses - $ExcludedSkuList = Get-CIPPAzDataTableEntity @LicenseTable - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $Tenant.tenant | ForEach-Object { - $skuid = $_ - foreach ($sku in $skuid) { - if ($sku.skuId -in $ExcludedSkuList.GUID) { continue } - $PrettyName = ($ConvertTable | Where-Object { $_.GUID -eq $sku.skuid }).'Product_Display_Name' | Select-Object -Last 1 - if (!$PrettyName) { $PrettyName = $sku.skuPartNumber } - if ($sku.prepaidUnits.enabled - $sku.consumedUnits -lt 0) { - "$PrettyName has Overused licenses. Using $($_.consumedUnits) of $($_.prepaidUnits.enabled)." - } - } - } - } - catch { - - } - } - - { $_.'AppSecretExpiry' -eq $true } { - try { - $Filter = "RowKey eq 'AppSecretExpiry' and PartitionKey eq '{0}'" -f $Tenant.tenantid - $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter - $Yesterday = (Get-Date).AddDays(-1) - if (-not $LastRun.Timestamp.DateTime -or ($LastRun.Timestamp.DateTime -le $Yesterday)) { - New-GraphGetRequest -uri "https://graph.microsoft.com/beta/applications?`$select=appId,displayName,passwordCredentials" -tenantid $Tenant.tenant | ForEach-Object { - foreach ($App in $_) { - if ($App.passwordCredentials) { - foreach ($Credential in $App.passwordCredentials) { - if ($Credential.endDateTime -lt (Get-Date).AddDays(30) -and $Credential.endDateTime -gt (Get-Date).AddDays(-7)) { - "Application '{0}' has secrets expiring on {1}" -f $App.displayName, $Credential.endDateTime - } - } - } - } - } - $LastRun = @{ - RowKey = 'AppSecretExpiry' - PartitionKey = $Tenant.tenantid - } - Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force - } - } - catch { - #$Message = 'Exception on line {0} - {1}' -f $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - #Write-LogMessage -message $Message -API 'Alerts' -tenant $tenant.tenant -sev Error - } - } - { $_.'ApnCertExpiry' -eq $true } { - try { - $Filter = "RowKey eq 'ApnCertExpiry' and PartitionKey eq '{0}'" -f $Tenant.tenantid - $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter - $Yesterday = (Get-Date).AddDays(-1) - if (-not $LastRun.Timestamp.DateTime -or ($LastRun.Timestamp.DateTime -le $Yesterday)) { - try { - $Apn = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/applePushNotificationCertificate' -tenantid $Tenant.tenant - if ($Apn.expirationDateTime -lt (Get-Date).AddDays(30) -and $Apn.expirationDateTime -gt (Get-Date).AddDays(-7)) { - 'Intune: Apple Push Notification certificate for {0} is expiring on {1}' -f $Apn.appleIdentifier, $Apn.expirationDateTime - } - } - catch {} - } - $LastRun = @{ - RowKey = 'ApnCertExpiry' - PartitionKey = $Tenant.tenantid - } - Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force - } - catch { - #$Message = 'Exception on line {0} - {1}' -f $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - #Write-LogMessage -message $Message -API 'Alerts' -tenant $tenant.tenant -sev Error - } - } - { $_.'VppTokenExpiry' -eq $true } { - try { - $Filter = "RowKey eq 'VppTokenExpiry' and PartitionKey eq '{0}'" -f $Tenant.tenantid - $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter - $Yesterday = (Get-Date).AddDays(-1) - if (-not $LastRun.Timestamp.DateTime -or ($LastRun.Timestamp.DateTime -le $Yesterday)) { - try { - $VppTokens = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceAppManagement/vppTokens' -tenantid $Tenant.tenant).value - foreach ($Vpp in $VppTokens) { - if ($Vpp.state -ne 'valid') { - 'Apple Volume Purchase Program Token is not valid, new token required' - } - if ($Vpp.expirationDateTime -lt (Get-Date).AddDays(30) -and $Vpp.expirationDateTime -gt (Get-Date).AddDays(-7)) { - 'Apple Volume Purchase Program token expiring on {0}' -f $Vpp.expirationDateTime - } - } - } - catch {} - $LastRun = @{ - RowKey = 'VppTokenExpiry' - PartitionKey = $Tenant.tenantid - } - Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force - } - } - catch { - #$Message = 'Exception on line {0} - {1}' -f $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - #Write-LogMessage -message $Message -API 'Alerts' -tenant $tenant.tenant -sev Error - } - } - { $_.'DepTokenExpiry' -eq $true } { - try { - $Filter = "RowKey eq 'DepTokenExpiry' and PartitionKey eq '{0}'" -f $Tenant.tenantid - $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter - $Yesterday = (Get-Date).AddDays(-1) - if (-not $LastRun.Timestamp.DateTime -or ($LastRun.Timestamp.DateTime -le $Yesterday)) { - try { - $DepTokens = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings' -tenantid $Tenant.tenant).value - foreach ($Dep in $DepTokens) { - if ($Dep.tokenExpirationDateTime -lt (Get-Date).AddDays(30) -and $Dep.tokenExpirationDateTime -gt (Get-Date).AddDays(-7)) { - 'Apple Device Enrollment Program token expiring on {0}' -f $Dep.tokenExpirationDateTime - } - } - } - catch {} - $LastRun = @{ - RowKey = 'DepTokenExpiry' - PartitionKey = $Tenant.tenantid - } - Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force - } - } - catch { - #$Message = 'Exception on line {0} - {1}' -f $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - #Write-LogMessage -message $Message -API 'Alerts' -tenant $tenant.tenant -sev Error - } - } - { $_.'SecDefaultsUpsell' -eq $true } { - try { - $Filter = "RowKey eq 'SecDefaultsUpsell' and PartitionKey eq '{0}'" -f $Tenant.tenantid - $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter - $Yesterday = (Get-Date).AddDays(-1) - if (-not $LastRun.Timestamp.DateTime -or ($LastRun.Timestamp.DateTime -le $Yesterday)) { - try { - $SecDefaults = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -tenantid $Tenant.tenant) - if ($SecDefaults.isEnabled -eq $false -and $SecDefaults.securityDefaultsUpsell.action -in @('autoEnable', 'autoEnabledNotify')) { - 'Security Defaults will be automatically enabled on {0}' -f $SecDefaults.securityDefaultsUpsell.dueDateTime - } - } - catch {} - $LastRun = @{ - RowKey = 'SecDefaultsUpsell' - PartitionKey = $Tenant.tenantid - } - Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force - } - } - catch { - #$Message = 'Exception on line {0} - {1}' -f $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - #Write-LogMessage -message $Message -API 'Alerts' -tenant $tenant.tenant -sev Error - } - } + $IgnoreList = @('Etag', 'PartitionKey', 'Timestamp', 'RowKey', 'tenantid', 'tenant', 'type') + $alertList = $Alerts | Select-Object * -ExcludeProperty $IgnoreList + foreach ($task in ($AlertList.psobject.members | Where-Object { $_.MemberType -EQ 'NoteProperty' -and $_.value -eq $True }).name) { + $QueueItem = [pscustomobject]@{ + tenant = $tenant.tenant + tenantid = $tenant.tenantid + FunctionName = "CIPPAlert$($Task)" + } + Push-OutputBinding -Name QueueItem -Value $QueueItem } $Table = Get-CIPPTable $PartitionKey = Get-Date -UFormat '%Y%m%d' $Filter = "PartitionKey eq '{0}' and Tenant eq '{1}'" -f $PartitionKey, $tenant.tenant - Write-Host $Filter $currentlog = Get-CIPPAzDataTableEntity @Table -Filter $Filter - $ShippedAlerts | ForEach-Object { - if ($_ -notin $currentlog.Message) { - if ($Config.includeTenantId) { - Write-LogMessage -message $_ -API 'Alerts' -tenant $tenant.tenant -sev Alert -tenantid $Tenant.tenantid - } - else { - Write-LogMessage -message $_ -API 'Alerts' -tenant $tenant.tenant -sev Alert - } - } + $AlertsTable = Get-CIPPTable -Table cachealerts + $CurrentAlerts = (Get-CIPPAzDataTableEntity @AlertsTable -Filter $Filter) + $CurrentAlerts | ForEach-Object { + if ($_.Message -notin $currentlog.Message) { Write-LogMessage -message $_.Message -API 'Alerts' -tenant $tenant.tenant -sev Alert -tenantid $Tenant.tenantid } + Remove-AzDataTableEntity @AlertsTable -Entity $_ } + [PSCustomObject]@{ ReturnedValues = $true } -} -catch { +} catch { $Message = 'Exception on line {0} - {1}' -f $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message Write-LogMessage -message $Message -API 'Alerts' -tenant $tenant.tenant -sev Error [PSCustomObject]@{ diff --git a/Scheduler_GetQueue/run.ps1 b/Scheduler_GetQueue/run.ps1 index 9714b8421eb9..3d495bd85264 100644 --- a/Scheduler_GetQueue/run.ps1 +++ b/Scheduler_GetQueue/run.ps1 @@ -1,7 +1,7 @@ param($name) $Table = Get-CIPPTable -TableName SchedulerConfig -$Tenants = Get-CIPPAzDataTableEntity @Table +$Tenants = Get-CIPPAzDataTableEntity @Table | Where-Object -Property PartitionKey -NE 'WebhookAlert' $object = foreach ($Tenant in $Tenants) { if ($Tenant.tenant -ne 'AllTenants') { @@ -11,8 +11,7 @@ $object = foreach ($Tenant in $Tenants) { TenantID = $Tenant.tenantid Type = $Tenant.type } - } - else { + } else { Write-Host 'All tenants, doing them all' $TenantList = Get-Tenants foreach ($t in $TenantList) { diff --git a/Scheduler_Standards/function.json b/Scheduler_Standards/function.json index 88d7d049eb48..35ec29f027f7 100644 --- a/Scheduler_Standards/function.json +++ b/Scheduler_Standards/function.json @@ -2,7 +2,7 @@ "bindings": [ { "name": "Timer", - "schedule": "0 0 */3 * * *", + "schedule": "0 0 */4 * * *", "direction": "in", "type": "timerTrigger" }, diff --git a/Scheduler_Timer/function.json b/Scheduler_Timer/function.json index f30537d11b34..1d19ac7d6733 100644 --- a/Scheduler_Timer/function.json +++ b/Scheduler_Timer/function.json @@ -2,7 +2,7 @@ "bindings": [ { "name": "Timer", - "schedule": "0 */15 * * * *", + "schedule": "0 */20 * * * *", "direction": "in", "type": "timerTrigger" }, diff --git a/Modules/CIPPCore/Public/TemplateEmail.html b/TemplateEmail.html similarity index 100% rename from Modules/CIPPCore/Public/TemplateEmail.html rename to TemplateEmail.html diff --git a/profile.ps1 b/profile.ps1 index 5b932cfda949..f30159db12cb 100644 --- a/profile.ps1 +++ b/profile.ps1 @@ -13,11 +13,11 @@ # Remove this if you are not planning on using MSI or Azure PowerShell. # Import modules -@('CippCore','CippExtensions','Az.KeyVault','Az.Accounts') | ForEach-Object { +@('CippCore', 'CippExtensions', 'Az.KeyVault', 'Az.Accounts') | ForEach-Object { try { Import-Module -Name $_ -ErrorAction Stop } catch { - Write-LogMessage -message "Failed to import module $($_): $_.Exception.Message" -Sev 'CRITICAL' + Write-LogMessage -message "Failed to import module $($_): $_.Exception.Message" -Sev 'debug' $_.Exception.Message } } @@ -32,7 +32,7 @@ try { $Auth = Get-CIPPAuthentication } } catch { - Write-LogMessage -message "Could not retrieve keys from Keyvault: $($_.Exception.Message)" -Sev 'CRITICAL' + Write-LogMessage -message "Could not retrieve keys from Keyvault: $($_.Exception.Message)" -Sev 'debug' } # Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell. diff --git a/version_latest.txt b/version_latest.txt index 490375502c2a..1f1ac7c2f330 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -4.8.4 \ No newline at end of file +4.9.1 \ No newline at end of file