diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddAlert.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddAlert.ps1 index ad22d9aef8b1..6cc440842493 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddAlert.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddAlert.ps1 @@ -43,6 +43,7 @@ Function Invoke-AddAlert { SecDefaultsUpsell = [bool]$Request.body.SecDefaultsUpsell SharePointQuota = [int]$Request.body.SharePointQuotaQuota ExpiringLicenses = [bool]$Request.body.ExpiringLicenses + NewAppApproval = [bool]$Request.body.NewAppApproval type = 'Alert' RowKey = $TenantID PartitionKey = 'Alert' diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddGroup.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddGroup.ps1 index 9794b0d51f3c..2a9d176a365e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddGroup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddGroup.ps1 @@ -45,13 +45,23 @@ Function Invoke-AddGroup { } $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/groups' -tenantid $tenant -type POST -body (ConvertTo-Json -InputObject $BodyToship -Depth 10) -verbose } else { - $Params = @{ - Name = $groupobj.Displayname - Alias = $groupobj.username - Description = $groupobj.Description - PrimarySmtpAddress = $email - Type = $groupobj.groupType - RequireSenderAuthenticationEnabled = [bool]!$groupobj.AllowExternal + if ($groupobj.groupType -eq 'dynamicdistribution') { + $Params = @{ + Name = $groupobj.Displayname + RecipientFilter = $groupobj.membershipRules + PrimarySmtpAddress = $email + } + $GraphRequest = New-ExoRequest -tenantid $tenant -cmdlet 'New-DynamicDistributionGroup' -cmdParams $params + } else { + $Params = @{ + Name = $groupobj.Displayname + Alias = $groupobj.username + Description = $groupobj.Description + PrimarySmtpAddress = $email + Type = $groupobj.groupType + RequireSenderAuthenticationEnabled = [bool]!$groupobj.AllowExternal + } + $GraphRequest = New-ExoRequest -tenantid $tenant -cmdlet 'New-DistributionGroup' -cmdParams $params } $GraphRequest = New-ExoRequest -tenantid $tenant -cmdlet 'New-DistributionGroup' -cmdParams $params # At some point add logic to use AddOwner/AddMember for New-DistributionGroup, but idk how we're going to brr that - rvdwegen diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddIntuneTemplate.ps1 index d8e651ae36ad..ca7815ec778b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddIntuneTemplate.ps1 @@ -35,13 +35,23 @@ Function Invoke-AddIntuneTemplate { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created intune policy template named $($Request.body.displayname) with GUID $GUID" -Sev 'Debug' $body = [pscustomobject]@{'Results' = 'Successfully added template' } - } - else { + } else { $TenantFilter = $request.query.TenantFilter $URLName = $Request.query.URLName $ID = $request.query.id switch ($URLName) { - + 'deviceCompliancePolicies' { + $Type = 'deviceCompliancePolicies' + $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)/$($ID)?`$expand=scheduledActionsForRule(`$expand=scheduledActionConfigurations)" -tenantid $tenantfilter + $DisplayName = $template.displayName + $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 10 -Compress + } + 'managedAppPolicies' { + $Type = 'AppProtection' + $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$($urlname)('$($ID)')" -tenantid $tenantfilter + $DisplayName = $template.displayName + $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 10 -Compress + } 'configurationPolicies' { $Type = 'Catalog' $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$($ID)')?`$expand=settings" -tenantid $tenantfilter | Select-Object name, description, settings, platforms, technologies, templateReference @@ -112,8 +122,7 @@ Function Invoke-AddIntuneTemplate { $body = [pscustomobject]@{'Results' = 'Successfully added template' } } - } - catch { + } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Intune Template Deployment failed: $($_.Exception.Message)" -Sev 'Error' $body = [pscustomobject]@{'Results' = "Intune Template Deployment failed: $($_.Exception.Message)" } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddPolicy.ps1 index ca5240804f3b..3cf7126bd8b4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddPolicy.ps1 @@ -24,6 +24,27 @@ Function Invoke-AddPolicy { } try { switch ($Request.body.TemplateType) { + 'AppProtection' { + $TemplateType = ($RawJSON | ConvertFrom-Json).'@odata.type' -replace '#microsoft.graph.', '' + $TemplateTypeURL = "$($TemplateType)s" + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenant + if ($displayname -in $CheckExististing.displayName) { + Throw "Policy with Display Name $($Displayname) Already exists" + } + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $RawJSON + } + 'deviceCompliancePolicies' { + $TemplateTypeURL = 'deviceCompliancePolicies' + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant + if ($displayname -in $CheckExististing.displayName) { + Throw "Policy with Display Name $($Displayname) Already exists" + } + $JSON = $RawJSON | ConvertFrom-Json | Select-Object * -ExcludeProperty id, createdDateTime, lastModifiedDateTime, version, 'scheduledActionsForRule@odata.context', '@odata.context' + $JSON.scheduledActionsForRule = @($JSON.scheduledActionsForRule | Select-Object * -ExcludeProperty 'scheduledActionConfigurations@odata.context') + $RawJSON = ConvertTo-Json -InputObject $JSON -Depth 20 -Compress + Write-Host $RawJSON + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $RawJson + } 'Admin' { $TemplateTypeURL = 'groupPolicyConfigurations' $CreateBody = '{"description":"' + $description + '","displayName":"' + $displayname + '","roleScopeTagIds":["0"]}' diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddUserBulk.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddUserBulk.ps1 new file mode 100644 index 000000000000..14d52620943d --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddUserBulk.ps1 @@ -0,0 +1,52 @@ +using namespace System.Net + +Function Invoke-AddUserBulk { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = 'AddUserBulk' + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $TenantFilter = $Request.body.TenantFilter + $Results = [System.Collections.ArrayList]@() + foreach ($userobj in $request.body.BulkUser) { + Write-Host 'PowerShell HTTP trigger function processed a request.' + try { + $password = if ($userobj.password) { $userobj.password } else { New-passwordString } + $UserprincipalName = "$($UserObj.mailNickName)@$($UserObj.domain)" + $BodyToship = $userobj + #Remove domain from body to ship + $BodyToship = $BodyToship | Select-Object * -ExcludeProperty password, domain + $BodyToship | Add-Member -NotePropertyName accountEnabled -NotePropertyValue $true -Force + $BodyToship | Add-Member -NotePropertyName userPrincipalName -NotePropertyValue $UserprincipalName -Force + $BodyToship | Add-Member -NotePropertyName passwordProfile -NotePropertyValue @{'password' = $password; 'forceChangePasswordNextSignIn' = $true } -Force + Write-Host "body is now: $($BodyToship | ConvertTo-Json -Depth 10 -Compress)" + if ($userobj.businessPhones) { $bodytoShip.businessPhones = @($userobj.businessPhones) } + $bodyToShip = ConvertTo-Json -Depth 10 -InputObject $BodyToship -Compress + Write-Host "Our body to ship is $bodyToShip" + $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $TenantFilter -type POST -body $BodyToship -verbose + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message "Created user $($userobj.displayname) with id $($GraphRequest.id) " -Sev 'Info' + $results.add("Created user $($UserprincipalName). Password is $password") + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message "Failed to create user. Error:$($_.Exception.Message)" -Sev 'Error' + $body = $results.add("Failed to create user. $($_.Exception.Message)" ) + } + } + $body = [pscustomobject] @{ + 'Results' = @($results) + 'Username' = $UserprincipalName + 'Password' = $password + 'CopyFrom' = $copyFromResults + } + + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + }) + +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-EditUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-EditUser.ps1 index 7c4674317d06..5a29285bd695 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-EditUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-EditUser.ps1 @@ -42,7 +42,6 @@ Function Invoke-EditUser { 'displayName' = $UserObj.Displayname 'postalCode' = $userobj.postalCode 'companyName' = $userobj.companyName - 'mailNickname' = $UserObj.username 'jobTitle' = $UserObj.JobTitle 'userPrincipalName' = $Email 'usageLocation' = $UserObj.usageLocation diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCPVPermissions.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCPVPermissions.ps1 index 7573e9fd209c..4703972094e4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCPVPermissions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCPVPermissions.ps1 @@ -11,14 +11,14 @@ Function Invoke-ExecCPVPermissions { $APIName = $TriggerMetadata.FunctionName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - # Write to the Azure Functions log stream. Write-Host 'PowerShell HTTP trigger function processed a request.' - $TenantFilter = (get-tenants -IncludeAll -IncludeErrors | Where-Object -Property customerId -EQ $Request.query.Tenantfilter).defaultDomainName - Write-Host "Our Tenantfilter is $TenantFilter" + $Tenant = Get-Tenants -IncludeAll | Where-Object -Property customerId -EQ $Request.Query.TenantFilter | Select-Object -First 1 + + Write-Host "Our tenant is $($Tenant.displayName) - $($Tenant.defaultDomainName)" $CPVConsentParams = @{ - Tenantfilter = $TenantFilter + TenantFilter = $Request.Query.TenantFilter } if ($Request.Query.ResetSP -eq 'true') { $CPVConsentParams.ResetSP = $true @@ -26,15 +26,15 @@ Function Invoke-ExecCPVPermissions { $GraphRequest = try { Set-CIPPCPVConsent @CPVConsentParams - Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $TenantFilter - Add-CIPPDelegatedPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $TenantFilter + Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Request.Query.TenantFilter + Add-CIPPDelegatedPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Request.Query.TenantFilter $Success = $true } catch { - "Failed to update permissions for $($TenantFilter): $($_.Exception.Message)" + "Failed to update permissions for $($Tenant.displayName): $($_.Exception.Message)" $Success = $false } - $Tenant = Get-Tenants -IncludeAll -IncludeErrors | Where-Object -Property defaultDomainName -EQ $Tenantfilter + $Tenant = Get-Tenants -IncludeAll | Where-Object -Property customerId -EQ $TenantFilter # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecMailTest.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecMailTest.ps1 new file mode 100644 index 000000000000..7bc6c3f17f90 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecMailTest.ps1 @@ -0,0 +1,82 @@ +using namespace System.Net +Function Invoke-ExecMailTest { + <# + .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' + + # Write to the Azure Functions log stream. + Write-Host 'PowerShell HTTP trigger function processed a request.' + + try { + switch ($Request.Query.Action) { + 'CheckConfig' { + $GraphToken = Get-GraphToken -returnRefresh $true -SkipCache $true + $AccessTokenDetails = Read-JwtAccessDetails -Token $GraphToken.access_token + $Me = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/me?$select=displayName,proxyAddresses' -NoAuthCheck $true + if ($AccessTokenDetails.Scope -contains 'Mail.Read') { + $Message = 'Mail.Read - Delegated was found in the token scope.' + $HasMailRead = $true + } else { + $Message = 'Please add Mail.Read - Delegated to the API permissions for CIPP-SAM.' + $HasMailRead = $false + } + + $Body = [PSCustomObject]@{ + Message = $Message + HasMailRead = $HasMailRead + MailUser = $Me.displayName + MailAddresses = $Me.proxyAddresses | Select-Object @{n = 'Address'; exp = { ($_ -split ':')[1] } }, @{n = 'IsPrimary'; exp = { $_ -cmatch 'SMTP' } } + } + } + default { + $Messages = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me/mailFolders/Inbox/messages?`$select=receivedDateTime,subject,sender,internetMessageHeaders,webLink" -NoAuthCheck $true + $Results = foreach ($Message in $Messages) { + $AuthResult = ($Message.internetMessageHeaders | Where-Object -Property name -EQ 'Authentication-Results').value + $AuthResult = $AuthResult -split ';\s*' + $AuthResult = $AuthResult | ForEach-Object { + if ($_ -match '^(?.+?)=\s*(?.+?)\s(?.+)$') { + [PSCustomObject]@{ + Name = $Matches.Name + Status = $Matches.Status + Info = $Matches.Info + } + } + } + [PSCustomObject]@{ + Received = $Message.receivedDateTime + Subject = $Message.subject + Sender = $Message.sender.emailAddress.name + From = $Message.sender.emailAddress.address + Link = $Message.webLink + Headers = $Message.internetMessageHeaders + AuthResult = $AuthResult + } + } + $Body = [PSCustomObject]@{ + Results = @($Results) + Metadata = [PSCustomObject]@{ + Count = ($Results | Measure-Object).Count + } + } + } + } + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $StatusCode = [HttpStatusCode]::BadRequest + $Body = [PSCustomObject]@{ + Results = @($ErrorMessage) + } + } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $Body + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOffboardUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOffboardUser.ps1 index 2391bd643995..02516f3148bf 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOffboardUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOffboardUser.ps1 @@ -7,42 +7,44 @@ Function Invoke-ExecOffboardUser { #> [CmdletBinding()] param($Request, $TriggerMetadata) - try { - $APIName = 'ExecOffboardUser' - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - $Username = $request.body.user - $Tenantfilter = $request.body.tenantfilter - $Results = if ($Request.body.Scheduled.enabled) { - $taskObject = [PSCustomObject]@{ - TenantFilter = $Tenantfilter - Name = "Offboarding: $Username" - Command = @{ - value = 'Invoke-CIPPOffboardingJob' - } - Parameters = @{ - Username = $Username - APIName = 'Scheduled Offboarding' - options = $request.body - } - ScheduledTime = $Request.body.scheduled.date - PostExecution = @{ - Webhook = [bool]$Request.Body.PostExecution.webhook - Email = [bool]$Request.Body.PostExecution.email - PSA = [bool]$Request.Body.PostExecution.psa + if ($Request.body.user.value) { $AllUsers = $Request.body.user.value } else { $AllUsers = @($Request.body.user) } + $Results = foreach ($username in $AllUsers) { + try { + $APIName = 'ExecOffboardUser' + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + $Tenantfilter = $request.body.tenantfilter + if ($Request.body.Scheduled.enabled) { + $taskObject = [PSCustomObject]@{ + TenantFilter = $Tenantfilter + Name = "Offboarding: $Username" + Command = @{ + value = 'Invoke-CIPPOffboardingJob' + } + Parameters = @{ + Username = $Username + APIName = 'Scheduled Offboarding' + options = $request.body + } + ScheduledTime = $Request.body.scheduled.date + PostExecution = @{ + Webhook = [bool]$Request.Body.PostExecution.webhook + Email = [bool]$Request.Body.PostExecution.email + PSA = [bool]$Request.Body.PostExecution.psa + } } + Add-CIPPScheduledTask -Task $taskObject -hidden $false + } else { + Invoke-CIPPOffboardingJob -Username $Username -TenantFilter $Tenantfilter -Options $Request.body -APIName $APIName -ExecutingUser $request.headers.'x-ms-client-principal' } - - Add-CIPPScheduledTask -Task $taskObject -hidden $false - } else { - Invoke-CIPPOffboardingJob -Username $Username -TenantFilter $Tenantfilter -Options $Request.body -APIName $APIName -ExecutingUser $request.headers.'x-ms-client-principal' + $StatusCode = [HttpStatusCode]::OK + + } catch { + $StatusCode = [HttpStatusCode]::Forbidden + $body = $_.Exception.message } - $StatusCode = [HttpStatusCode]::OK - $body = [pscustomobject]@{'Results' = @($results) } - } catch { - $StatusCode = [HttpStatusCode]::Forbidden - $body = $_.Exception.message } - $Request.Body.PostExecution + $body = [pscustomobject]@{'Results' = @($results) } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode Body = $Body diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListAlertsQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListAlertsQueue.ps1 index 8d12a5c8f94d..cb26d77ec876 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListAlertsQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListAlertsQueue.ps1 @@ -20,26 +20,29 @@ Function Invoke-ListAlertsQueue { $CurrentStandards = foreach ($QueueFile in $QueuedApps) { [PSCustomObject]@{ - tenantName = $QueueFile.tenant - AdminPassword = [bool]$QueueFile.AdminPassword - DefenderMalware = [bool]$QueueFile.DefenderMalware - DefenderStatus = [bool]$QueueFile.DefenderStatus - MFAAdmins = [bool]$QueueFile.MFAAdmins - MFAAlertUsers = [bool]$QueueFile.MFAAlertUsers - NewGA = [bool]$QueueFile.NewGA - NewRole = [bool]$QueueFile.NewRole - QuotaUsed = [bool]$QueueFile.QuotaUsed - UnusedLicenses = [bool]$QueueFile.UnusedLicenses - OverusedLicenses = [bool]$QueueFile.OverusedLicenses - AppSecretExpiry = [bool]$QueueFile.AppSecretExpiry - ApnCertExpiry = [bool]$QueueFile.ApnCertExpiry - VppTokenExpiry = [bool]$QueueFile.VppTokenExpiry - DepTokenExpiry = [bool]$QueueFile.DepTokenExpiry - NoCAConfig = [bool]$QueueFile.NoCAConfig - SecDefaultsUpsell = [bool]$QueueFile.SecDefaultsUpsell - SharePointQuota = [bool]$QueueFile.SharePointQuota - ExpiringLicenses = [bool]$QueueFile.ExpiringLicenses - tenantId = $QueueFile.tenantid + tenantName = $QueueFile.tenant + AdminPassword = [bool]$QueueFile.AdminPassword + DefenderMalware = [bool]$QueueFile.DefenderMalware + DefenderStatus = [bool]$QueueFile.DefenderStatus + MFAAdmins = [bool]$QueueFile.MFAAdmins + MFAAlertUsers = [bool]$QueueFile.MFAAlertUsers + NewGA = [bool]$QueueFile.NewGA + NewRole = [bool]$QueueFile.NewRole + QuotaUsed = [bool]$QueueFile.QuotaUsed + UnusedLicenses = [bool]$QueueFile.UnusedLicenses + OverusedLicenses = [bool]$QueueFile.OverusedLicenses + AppSecretExpiry = [bool]$QueueFile.AppSecretExpiry + ApnCertExpiry = [bool]$QueueFile.ApnCertExpiry + VppTokenExpiry = [bool]$QueueFile.VppTokenExpiry + DepTokenExpiry = [bool]$QueueFile.DepTokenExpiry + NoCAConfig = [bool]$QueueFile.NoCAConfig + SecDefaultsUpsell = [bool]$QueueFile.SecDefaultsUpsell + SharePointQuota = [bool]$QueueFile.SharePointQuota + ExpiringLicenses = [bool]$QueueFile.ExpiringLicenses + NewAppApproval = [bool]$QueueFile.NewAppApproval + SharePointQuotaQuota = [int]$QueueFile.SharePointQuota + QuotaUsedQuota = [int]$QueueFile.QuotaUsed + tenantId = $QueueFile.tenantid } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListConditionalAccessPolicies.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListConditionalAccessPolicies.ps1 index affec8e72291..c093a8c4f009 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListConditionalAccessPolicies.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListConditionalAccessPolicies.ps1 @@ -243,7 +243,7 @@ function Get-ApplicationNameFromId { 4660504c-45b3-4674-a709-71951a6b0763 { $return = 'Microsoft Invitation Acceptance Portal' } ba23cd2a-306c-48f2-9d62-d3ecd372dfe4 { $return = 'OfficeGraph' } d52485ee-4609-4f6b-b3a3-68b6f841fa23 { $return = 'On-Premises Data Gateway Connector' } - 996def3d-b36c-4153-8607-a6fd3c01b89f { $return = 'Dynamics 365 for Financials' } + 996def3d-b36c-4153-8607-a6fd3c01b89f { $return = 's 365 for Financials' } b6b84568-6c01-4981-a80f-09da9a20bbed { $return = 'Microsoft Invoicing' } 9d3e55ba-79e0-4b7c-af50-dc460b81dca1 { $return = 'Microsoft Azure Data Catalog' } 4345a7b9-9a63-4910-a426-35363201d503 { $return = 'O365 Suite UX' } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListDomainAnalyser.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListDomainAnalyser.ps1 index ef10d6a3505d..76fe08536529 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListDomainAnalyser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListDomainAnalyser.ps1 @@ -8,28 +8,8 @@ Function Invoke-ListDomainAnalyser { #> [CmdletBinding()] param($Request, $TriggerMetadata) - $DomainTable = Get-CIPPTable -Table 'Domains' - - # Get all the things - - if ($Request.Query.tenantFilter -ne 'AllTenants') { - $DomainTable.Filter = "TenantId eq '{0}'" -f $Request.Query.tenantFilter - } - - try { - # Extract json from table results - $Results = foreach ($DomainAnalyserResult in (Get-CIPPAzDataTableEntity @DomainTable).DomainAnalyser) { - try { - if (![string]::IsNullOrEmpty($DomainAnalyserResult)) { - $Object = $DomainAnalyserResult | ConvertFrom-Json -ErrorAction SilentlyContinue - $Object - } - } catch {} - } - } catch { - $Results = @() - } + $Results = Get-CIPPDomainAnalyser -TenantFilter $Request.query.tenantFilter # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExternalTenantInfo.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExternalTenantInfo.ps1 index 2bec443954b5..55fb22f3f99e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExternalTenantInfo.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExternalTenantInfo.ps1 @@ -58,20 +58,20 @@ Function Invoke-ListExternalTenantInfo { # Invoke $response = Invoke-RestMethod -UseBasicParsing -Method Post -Uri 'https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc' -Body $body -Headers $headers - + # Return $TenantDomains = $response.Envelope.body.GetFederationInformationResponseMessage.response.Domains.Domain | Sort-Object } $results = [PSCustomObject]@{ GraphRequest = $GraphRequest - Domains = $TenantDomains + Domains = @($TenantDomains) } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = $StatusCode - Body = $results + StatusCode = $StatusCode + Body = $results }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListScheduledItems.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListScheduledItems.ps1 index d23de674c779..17a650f11196 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListScheduledItems.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListScheduledItems.ps1 @@ -11,7 +11,10 @@ Function Invoke-ListScheduledItems { # Write to the Azure Functions log stream. Write-Host 'PowerShell HTTP trigger function processed a request.' $Table = Get-CIPPTable -TableName 'ScheduledTasks' - $ScheduledTasks = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'ScheduledTask' and Hidden ne 'True'" + $ScheduledTasks = foreach ($Task in Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'ScheduledTask' and Hidden ne 'True'") { + $Task.Parameters = $Task.Parameters | ConvertFrom-Json + $Task + } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListUsers.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListUsers.ps1 index 50c2d13f4525..8431d441a320 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListUsers.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListUsers.ps1 @@ -10,7 +10,7 @@ Function Invoke-ListUsers { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - $selectlist = 'id', 'accountEnabled', 'displayName', 'userPrincipalName', 'userType', 'createdDateTime', 'companyName', 'country', 'department', 'businessPhones', 'city', 'faxNumber', 'givenName', 'isResourceAccount', 'jobTitle', 'mobilePhone', 'officeLocation', 'postalCode', 'preferredDataLocation', 'preferredLanguage', 'mail', 'mailNickname', 'proxyAddresses', 'Aliases', 'otherMails', 'showInAddressList', 'state', 'streetAddress', 'surname', 'usageLocation', 'LicJoined', 'assignedLicenses', 'onPremisesSyncEnabled', 'OnPremisesImmutableId', 'onPremisesDistinguishedName', 'onPremisesLastSyncDateTime', 'primDomain', 'Tenant', 'CippStatus' + $selectlist = 'id', 'accountEnabled', 'displayName', 'userPrincipalName', 'username', 'userType', 'createdDateTime', 'companyName', 'country', 'department', 'businessPhones', 'city', 'faxNumber', 'givenName', 'isResourceAccount', 'jobTitle', 'mobilePhone', 'officeLocation', 'postalCode', 'preferredDataLocation', 'preferredLanguage', 'mail', 'mailNickname', 'proxyAddresses', 'Aliases', 'otherMails', 'showInAddressList', 'state', 'streetAddress', 'surname', 'usageLocation', 'LicJoined', 'assignedLicenses', 'onPremisesSyncEnabled', 'OnPremisesImmutableId', 'onPremisesDistinguishedName', 'onPremisesLastSyncDateTime', 'primDomain', 'Tenant', 'CippStatus' # Write to the Azure Functions log stream. Write-Host 'PowerShell HTTP trigger function processed a request.' $ConvertTable = Import-Csv Conversiontable.csv | Sort-Object -Property 'guid' -Unique @@ -22,6 +22,7 @@ Function Invoke-ListUsers { $GraphRequest = if ($TenantFilter -ne 'AllTenants') { New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($userid)?`$top=999&`$select=$($selectlist -join ',')&`$filter=$GraphFilter&`$count=true" -tenantid $TenantFilter -ComplexFilter | Select-Object $selectlist | ForEach-Object { $_.onPremisesSyncEnabled = [bool]($_.onPremisesSyncEnabled) + $_.UserName = $_.userPrincipalName -split '@' | Select-Object -First 1 $_.Aliases = $_.Proxyaddresses -join ', ' $SkuID = $_.AssignedLicenses.skuid $_.LicJoined = ($ConvertTable | Where-Object { $_.guid -in $skuid }).'Product_Display_Name' -join ', ' diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListWebhookAlert.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListWebhookAlert.ps1 index 5ee5feb2924f..d585504f4254 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListWebhookAlert.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListWebhookAlert.ps1 @@ -11,12 +11,14 @@ Function Invoke-ListWebhookAlert { $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' - $WebhookRow = Get-CIPPAzDataTableEntity @Table | Where-Object -Property PartitionKey -EQ 'WebhookAlert' - + $WebhookRow = foreach ($Webhook in Get-CIPPAzDataTableEntity @Table | Where-Object -Property PartitionKey -EQ 'WebhookAlert') { + $Webhook.If = $Webhook.If | ConvertFrom-Json + $Webhook.execution = $Webhook.execution | ConvertFrom-Json + $Webhook + } + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = @($WebhookRow) }) - - } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 index 68442e76a7b4..37136c230bdd 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 @@ -25,7 +25,13 @@ Function Invoke-PublicPhishingCheck { Write-Host 'Not being Phished, no issue' } else { $bytes = [Convert]::FromBase64String('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') - Write-AlertMessage -message "Potential Phishing page detected. Detected Information: $($request.headers | ConvertTo-Json -Depth 5)" -sev 'Alert' -tenant $Request.query.TenantId + + $AlertMessage = If ($Request.headers.referer) { + "Potential Phishing page detected. Detected Information: Hosted at $($Request.headers.referer). Access by IP $($request.headers.'x-forwarded-for')" + } else { + "Potential Phishing page detected. Detected Information: Access by IP $($request.headers.'x-forwarded-for')" + } + Write-AlertMessage -message $AlertMessage -sev 'Alert' -tenant $Request.query.TenantId } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicWebhooks.ps1 index 97c135235cd6..38d6f00157bc 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicWebhooks.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicWebhooks.ps1 @@ -11,110 +11,118 @@ function Invoke-PublicWebhooks { 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) { + if ($Webhooks.Resource -eq 'M365AuditLogs') { + Write-Host "Found M365AuditLogs - This is an old entry, we'll deny so Microsoft stops sending it." + $body = 'This webhook is not authorized, its an old entry.' + $StatusCode = [HttpStatusCode]::Forbidden + } + if ($Request.query.ValidationToken) { + Write-Host 'Validation token received - query ValidationToken' + $body = $request.query.ValidationToken + $StatusCode = [HttpStatusCode]::OK + } elseif ($Request.body.validationCode) { + Write-Host 'Validation token received - body validationCode' + $body = $request.body.validationCode + $StatusCode = [HttpStatusCode]::OK + } elseif ($Request.query.validationCode) { + Write-Host 'Validation token received - query validationCode' + $body = $request.query.validationCode + $StatusCode = [HttpStatusCode]::OK + } elseif ($Request.Query.CIPPID -in $Webhooks.RowKey) { Write-Host 'Found matching CIPPID' - if ($Webhooks.Resource -eq 'M365AuditLogs') { - Write-Host "Found M365AuditLogs - This is an old entry, we'll deny so Microsoft stops sending it." - $body = 'This webhook is not authorized, its an old entry.' - $StatusCode = [HttpStatusCode]::Forbidden - } - if ($Request.query.ValidationToken -or $Request.body.validationCode) { - Write-Host 'Validation token received' - $body = $request.query.ValidationToken - $StatusCode = [HttpStatusCode]::OK - } else { - 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 - $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID + 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.Type -eq 'GraphSubscription') { - # Graph Subscriptions - [pscustomobject]$ReceivedItem = $Request.Body.value - $Entity = [PSCustomObject]@{ - PartitionKey = 'Webhook' - RowKey = [string](New-Guid).Guid - Type = $Request.Query.Type - Data = [string]($ReceivedItem | ConvertTo-Json -Depth 10) - CIPPID = $Request.Query.CIPPID - WebhookInfo = [string]($WebhookInfo | ConvertTo-Json -Depth 10) - FunctionName = 'PublicWebhookProcess' - } - Add-CIPPAzDataTableEntity @WebhookIncoming -Entity $Entity - ## Push webhook data to queue - #Invoke-CippGraphWebhookProcessing -Data $ReceivedItem -CIPPID $request.Query.CIPPID -WebhookInfo $Webhookinfo + $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID - } else { - # Auditlog Subscriptions - try { - foreach ($ReceivedItem In ($Request.body)) { - $ReceivedItem = [pscustomobject]$ReceivedItem - Write-Host "Received Item: $($ReceivedItem | ConvertTo-Json -Depth 15 -Compress))" - $TenantFilter = (Get-Tenants | Where-Object -Property customerId -EQ $ReceivedItem.TenantId).defaultDomainName - Write-Host "Webhook TenantFilter: $TenantFilter" - $ConfigTable = get-cipptable -TableName 'SchedulerConfig' - $Alertconfig = Get-CIPPAzDataTableEntity @ConfigTable | Where-Object { $_.Tenant -eq $TenantFilter -or $_.Tenant -eq 'AllTenants' } - $Operations = @(($AlertConfig.if | ConvertFrom-Json -ErrorAction SilentlyContinue).selection) + 'UserLoggedIn' - $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID - #Increased download efficiency: only download the data we need for processing. Todo: Change this to load from table or dynamic source. - $MappingTable = [pscustomobject]@{ - 'UserLoggedIn' = 'Audit.AzureActiveDirectory' - 'Add member to role.' = 'Audit.AzureActiveDirectory' - 'Disable account.' = 'Audit.AzureActiveDirectory' - 'Update StsRefreshTokenValidFrom Timestamp.' = 'Audit.AzureActiveDirectory' - 'Enable account.' = 'Audit.AzureActiveDirectory' - 'Disable Strong Authentication.' = 'Audit.AzureActiveDirectory' - 'Reset user password.' = 'Audit.AzureActiveDirectory' - 'Add service principal.' = 'Audit.AzureActiveDirectory' - 'HostedIP' = 'Audit.AzureActiveDirectory' - 'badRepIP' = 'Audit.AzureActiveDirectory' - 'UserLoggedInFromUnknownLocation' = 'Audit.AzureActiveDirectory' - 'customfield' = 'AnyLog' - 'anyAlert' = 'AnyLog' - 'New-InboxRule' = 'Audit.Exchange' - 'Set-InboxRule' = 'Audit.Exchange' - } - #Compare $Operations to $MappingTable. If there is a match, we make a new variable called $LogsToDownload - #Example: $Operations = 'UserLoggedIn', 'Set-InboxRule' makes : $LogsToDownload = @('Audit.AzureActiveDirectory',Audit.Exchange) - $LogsToDownload = $Operations | Where-Object { $MappingTable.$_ } | ForEach-Object { $MappingTable.$_ } - Write-Host "Our operations: $Operations" - Write-Host "Logs to download: $LogsToDownload" - if ($ReceivedItem.ContentType -in $LogsToDownload -or 'AnyLog' -in $LogsToDownload) { - $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' - } else { - Write-Host "No data to download for $($ReceivedItem.ContentType)" - continue - } - Write-Host "Data found: $($data.count) items" - $DataToProcess = if ('anylog' -NotIn $LogsToDownload) { $Data | Where-Object -Property Operation -In $Operations } else { $Data } - Write-Host "Data to process found: $($DataToProcess.count) items" - foreach ($Item in $DataToProcess) { - Write-Host "Processing $($item.operation)" + if ($Request.Query.Type -eq 'GraphSubscription') { + # Graph Subscriptions + [pscustomobject]$ReceivedItem = $Request.Body.value + $Entity = [PSCustomObject]@{ + PartitionKey = 'Webhook' + RowKey = [string](New-Guid).Guid + Type = $Request.Query.Type + Data = [string]($ReceivedItem | ConvertTo-Json -Depth 10) + CIPPID = $Request.Query.CIPPID + WebhookInfo = [string]($WebhookInfo | ConvertTo-Json -Depth 10) + FunctionName = 'PublicWebhookProcess' + } + Add-CIPPAzDataTableEntity @WebhookIncoming -Entity $Entity + ## Push webhook data to queue + #Invoke-CippGraphWebhookProcessing -Data $ReceivedItem -CIPPID $request.Query.CIPPID -WebhookInfo $Webhookinfo - ## Push webhook data to table - $Entity = [PSCustomObject]@{ - PartitionKey = 'Webhook' - RowKey = [string](New-Guid).Guid - Type = 'AuditLog' - Data = [string]($Item | ConvertTo-Json -Depth 10) - CIPPURL = $CIPPURL - TenantFilter = $TenantFilter - FunctionName = 'PublicWebhookProcess' - } - Add-CIPPAzDataTableEntity @WebhookIncoming -Entity $Entity -Force - #Invoke-CippWebhookProcessing -TenantFilter $TenantFilter -Data $Item -CIPPPURL $url + } else { + # Auditlog Subscriptions + try { + foreach ($ReceivedItem In ($Request.body)) { + $ReceivedItem = [pscustomobject]$ReceivedItem + Write-Host "Received Item: $($ReceivedItem | ConvertTo-Json -Depth 15 -Compress))" + $TenantFilter = (Get-Tenants | Where-Object -Property customerId -EQ $ReceivedItem.TenantId).defaultDomainName + Write-Host "Webhook TenantFilter: $TenantFilter" + $ConfigTable = get-cipptable -TableName 'SchedulerConfig' + $Alertconfig = Get-CIPPAzDataTableEntity @ConfigTable | Where-Object { $_.Tenant -eq $TenantFilter -or $_.Tenant -eq 'AllTenants' } + $Operations = @(($AlertConfig.if | ConvertFrom-Json -ErrorAction SilentlyContinue).selection) + 'UserLoggedIn' + $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID + #Increased download efficiency: only download the data we need for processing. Todo: Change this to load from table or dynamic source. + $MappingTable = [pscustomobject]@{ + 'UserLoggedIn' = 'Audit.AzureActiveDirectory' + 'Add member to role.' = 'Audit.AzureActiveDirectory' + 'Disable account.' = 'Audit.AzureActiveDirectory' + 'Update StsRefreshTokenValidFrom Timestamp.' = 'Audit.AzureActiveDirectory' + 'Enable account.' = 'Audit.AzureActiveDirectory' + 'Disable Strong Authentication.' = 'Audit.AzureActiveDirectory' + 'Reset user password.' = 'Audit.AzureActiveDirectory' + 'Add service principal.' = 'Audit.AzureActiveDirectory' + 'HostedIP' = 'Audit.AzureActiveDirectory' + 'badRepIP' = 'Audit.AzureActiveDirectory' + 'UserLoggedInFromUnknownLocation' = 'Audit.AzureActiveDirectory' + 'customfield' = 'AnyLog' + 'anyAlert' = 'AnyLog' + 'New-InboxRule' = 'Audit.Exchange' + 'Set-InboxRule' = 'Audit.Exchange' + } + #Compare $Operations to $MappingTable. If there is a match, we make a new variable called $LogsToDownload + #Example: $Operations = 'UserLoggedIn', 'Set-InboxRule' makes : $LogsToDownload = @('Audit.AzureActiveDirectory',Audit.Exchange) + $LogsToDownload = $Operations | Where-Object { $MappingTable.$_ } | ForEach-Object { $MappingTable.$_ } + Write-Host "Our operations: $Operations" + Write-Host "Logs to download: $LogsToDownload" + if ($ReceivedItem.ContentType -in $LogsToDownload -or 'AnyLog' -in $LogsToDownload) { + $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' + } else { + Write-Host "No data to download for $($ReceivedItem.ContentType)" + continue + } + Write-Host "Data found: $($data.count) items" + $DataToProcess = if ('anylog' -NotIn $LogsToDownload) { $Data | Where-Object -Property Operation -In $Operations } else { $Data } + Write-Host "Data to process found: $($DataToProcess.count) items" + foreach ($Item in $DataToProcess) { + Write-Host "Processing $($item.operation)" + + ## Push webhook data to table + $Entity = [PSCustomObject]@{ + PartitionKey = 'Webhook' + RowKey = [string](New-Guid).Guid + Type = 'AuditLog' + Data = [string]($Item | ConvertTo-Json -Depth 10) + CIPPURL = $CIPPURL + TenantFilter = $TenantFilter + FunctionName = 'PublicWebhookProcess' } + Add-CIPPAzDataTableEntity @WebhookIncoming -Entity $Entity -Force + #Invoke-CippWebhookProcessing -TenantFilter $TenantFilter -Data $Item -CIPPPURL $url } - } catch { - Write-Host "Webhook Failed: $($_.Exception.Message). Line number $($_.InvocationInfo.ScriptLineNumber)" } + } catch { + Write-Host "Webhook Failed: $($_.Exception.Message). Line number $($_.InvocationInfo.ScriptLineNumber)" } - - $Body = 'Webhook Recieved' - $StatusCode = [HttpStatusCode]::OK } + + $Body = 'Webhook Recieved' + $StatusCode = [HttpStatusCode]::OK + } else { $Body = 'This webhook is not authorized.' $StatusCode = [HttpStatusCode]::Forbidden diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 index 8054acdc8902..13a411f105e9 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 @@ -11,6 +11,7 @@ function Push-CIPPAlertApnCertExpiry { Write-AlertMessage -tenant $($Item.tenant) -message ('Intune: Apple Push Notification certificate for {0} is expiring on {1}' -f $Apn.appleIdentifier, $Apn.expirationDateTime) } } catch { - Write-AlertMessage -tenant $($Item.Tenant) -message "Failed to check APN certificate expiry for $($Item.Tenant): $(Get-NormalizedError -message $_.Exception.message)" + #no error because if a tenant does not have an APN, it'll error anyway. + #Write-AlertMessage -tenant $($Item.Tenant) -message "Failed to check APN certificate expiry for $($Item.Tenant): $(Get-NormalizedError -message $_.Exception.message)" } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 index 739f6953dfce..7b1b8b12fd4f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 @@ -21,7 +21,7 @@ function Push-CIPPAlertAppSecretExpiry { } } } catch { - Write-AlertMessage -tenant $($Item.Tenant) -message "Failed to check App registration expiry for $($Item.Tenant): $(Get-NormalizedError -message $_.Exception.message)" + #Write-AlertMessage -tenant $($Item.Tenant) -message "Failed to check App registration expiry for $($Item.Tenant): $(Get-NormalizedError -message $_.Exception.message)" } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertNewAppApproval.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertNewAppApproval.ps1 new file mode 100644 index 000000000000..438fd62739d6 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertNewAppApproval.ps1 @@ -0,0 +1,15 @@ + +function Push-CIPPAlertNewAppApproval { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [pscustomobject]$Item + ) + try { + $Approvals = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/identityGovernance/appConsent/appConsentRequests' -tenantid $item.tenant + if ($Approvals.count -gt 1) { + Write-AlertMessage -tenant $($Item.tenant) -message "There is are $($Approvals.count) App Approvals waiting." + } + } catch { + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 index 24d206b069c7..b6938cc1389c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 @@ -263,72 +263,87 @@ Function Push-ExecOnboardTenantQueue { $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Clearing tenant cache' }) - $y = 0 - do { - try { - Remove-CIPPCache -tenantsOnly $true - } catch {} + $IsExcluded = (Get-Tenants -SkipList | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Measure-Object).Count -gt 0 + if ($IsExcluded) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Tenant is excluded from CIPP, onboarding cannot continue.' }) + $TenantOnboarding.Status = 'failed' + $OnboardingSteps.Step4.Status = 'failed' + $OnboardingSteps.Step4.Message = 'Tenant excluded from CIPP, remove the exclusion and retry onboarding.' + } else { - $Tenant = Get-Tenants | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 - $y++ - Start-Sleep -Seconds 20 - } while (!$Tenant -and $y -le 4) + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Clearing tenant cache' }) + $y = 0 + do { + try { + Remove-CIPPCache -tenantsOnly $true + } catch {} - if ($Tenant) { - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Tenant found in customer list' }) - try { - $CPVConsentParams = @{ - TenantFilter = $Tenant.defaultDomainName - } - $Consent = Set-CIPPCPVConsent @CPVConsentParams - if ($Consent -match 'Could not add our Service Principal to the client tenant') { - throw + $Tenant = Get-Tenants -IncludeAll | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 + $y++ + Start-Sleep -Seconds 20 + } while (!$Tenant -and $y -le 4) + + if ($Tenant) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Tenant found in customer list' }) + try { + $CPVConsentParams = @{ + TenantFilter = $Relationship.customer.tenantId + } + $Consent = Set-CIPPCPVConsent @CPVConsentParams + if ($Consent -match 'Could not add our Service Principal to the client tenant') { + throw + } + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Added initial CPV consent permissions' }) + } catch { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'CPV Consent Failed' }) + $TenantOnboarding.Status = 'failed' + $OnboardingSteps.Step4.Status = 'failed' + $OnboardingSteps.Step4.Message = 'CPV Consent failed, check the App Registration in your partner tenant for missing admin consent.' + $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + return } - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Added initial CPV consent permissions' }) - } catch { - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'CPV Consent Failed' }) - $TenantOnboarding.Status = 'failed' - $OnboardingSteps.Step4.Status = 'failed' - $OnboardingSteps.Step4.Message = 'CPV Consent failed, check the App Registration in your partner tenant for missing admin consent.' + $Refreshing = $true + $CPVSuccess = $false + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Refreshing CPV permissions' }) + $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 - return - } - $Refreshing = $true - $CPVSuccess = $false - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Refreshing CPV permissions' }) - $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 - 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 { - Start-Sleep -Seconds 30 - } - } while ($Refreshing -and (Get-Date) -lt $Start.AddMinutes(8)) + do { + try { + Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Relationship.customer.tenantId + Add-CIPPDelegatedPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Relationship.customer.tenantId + $CPVSuccess = $true + $Refreshing = $false + } catch { + Start-Sleep -Seconds 30 + } + } while ($Refreshing -and (Get-Date) -lt $Start.AddMinutes(8)) - if ($CPVSuccess) { - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'CPV permissions refreshed' }) - $OnboardingSteps.Step4.Status = 'succeeded' - $OnboardingSteps.Step4.Message = 'CPV permissions refreshed' + if ($CPVSuccess) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'CPV permissions refreshed' }) + $OnboardingSteps.Step4.Status = 'succeeded' + $OnboardingSteps.Step4.Message = 'CPV permissions refreshed' + if ($Tenant.defaultDomainName -match 'Domain Error') { + try { + Remove-CIPPCache -tenantsOnly $true + } catch {} + $Tenant = Get-Tenants -IncludeAll | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 + } + } 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 = 'CPV permissions failed to refresh' }) + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Tenant not found' }) $TenantOnboarding.Status = 'failed' $OnboardingSteps.Step4.Status = 'failed' - $OnboardingSteps.Step4.Message = 'CPV permissions failed to refresh, try again later' + $OnboardingSteps.Step4.Message = 'Tenant not found in customer list, 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) diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-GetPendingWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-GetPendingWebhooks.ps1 new file mode 100644 index 000000000000..11e518c782bd --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-GetPendingWebhooks.ps1 @@ -0,0 +1,8 @@ +function Push-GetPendingWebhooks { + $Table = Get-CIPPTable -TableName WebhookIncoming + $Webhooks = Get-CIPPAzDataTableEntity @Table + $WebhookCount = ($Webhooks | Measure-Object).Count + $Message = 'Processing {0} webhooks' -f $WebhookCount + Write-LogMessage -API 'Webhooks' -message $Message -sev Info + return $Webhooks +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-SchedulerAlert.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-SchedulerAlert.ps1 index 40fb4f9c404f..43a1433ce573 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-SchedulerAlert.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-SchedulerAlert.ps1 @@ -14,17 +14,18 @@ function Push-SchedulerAlert { $IgnoreList = @('Etag', 'PartitionKey', 'Timestamp', 'RowKey', 'tenantid', 'tenant', 'type') $AlertList = $Alerts | Select-Object * -ExcludeProperty $IgnoreList - $Batch = foreach ($task in ($AlertList.psobject.members | Where-Object { $_.MemberType -EQ 'NoteProperty' -and $_.value -ne $false })) { + foreach ($task in ($AlertList.psobject.members | Where-Object { $_.MemberType -EQ 'NoteProperty' -and $_.value -ne $false })) { $Table = Get-CIPPTable -TableName AlertRunCheck $Filter = "PartitionKey eq '{0}' and RowKey eq '{1}' and Timestamp ge datetime'{2}'" -f $Item.Tenant, $task.Name, (Get-Date).AddMinutes(-10).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss') $ExistingMessage = Get-CIPPAzDataTableEntity @Table -Filter $Filter if (!$ExistingMessage) { - [pscustomobject]@{ - Tenant = $Item.Tenant - Tenantid = $Item.Tenantid - FunctionName = "CIPPAlert$($Task.Name)" - value = $Task.value + $Item = [pscustomobject]@{ + Tenant = $Item.Tenant + Tenantid = $Item.Tenantid + value = $Task.value } + $Function = "Push-CIPPAlert$($Task.Name)" + & $Function -Item $Item #Push-OutputBinding -Name QueueItemOut -Value $Item $Item | Add-Member -MemberType NoteProperty -Name 'RowKey' -Value $task.Name -Force $Item | Add-Member -MemberType NoteProperty -Name 'PartitionKey' -Value $Item.Tenant -Force @@ -41,9 +42,8 @@ function Push-SchedulerAlert { } else { Write-Host ('ALERTS: Duplicate run found. Ignoring. Tenant: {0}, Task: {1}' -f $Item.Tenant, $task.Name) } - } - if (($Batch | Measure-Object).Count -gt 0) { + <#if (($Batch | Measure-Object).Count -gt 0) { $InputObject = [PSCustomObject]@{ OrchestratorName = 'AlertsOrchestrator' SkipLog = $true @@ -55,7 +55,7 @@ function Push-SchedulerAlert { #$Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId } else { Write-Host 'No alerts to process' - } + }#> } catch { $Message = 'Exception on line {0} - {1}' -f $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message Write-LogMessage -message $Message -API 'Alerts' -tenant $Item.tenant -sev Error diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-UpdatePermissionsQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-UpdatePermissionsQueue.ps1 index e1d72b14e867..672f1f196b65 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-UpdatePermissionsQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-UpdatePermissionsQueue.ps1 @@ -7,11 +7,11 @@ function Push-UpdatePermissionsQueue { if (!$CPVRows -or $ENV:ApplicationID -notin $CPVRows.applicationId) { Write-LogMessage -tenant $Item.defaultDomainName -tenantId $Item.customerId -message 'A New tenant has been added, or a new CIPP-SAM Application is in use' -Sev 'Warn' -API 'NewTenant' Write-Host 'Adding CPV permissions' - Set-CIPPCPVConsent -Tenantfilter $Item.defaultDomainName + Set-CIPPCPVConsent -Tenantfilter $Item.customerId } - Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Item.defaultDomainName - Add-CIPPDelegatedPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Item.defaultDomainName + Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Item.customerId + Add-CIPPDelegatedPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Item.customerId - Write-LogMessage -tenant $Item.defaultDomainName -tenantId $Item.customerId -message "Updated permissions for $($Item.defaultDomainName)" -Sev 'Info' -API 'UpdatePermissionsQueue' + Write-LogMessage -tenant $Item.defaultDomainName -tenantId $Item.customerId -message "Updated permissions for $($Item.displayName)" -Sev 'Info' -API 'UpdatePermissionsQueue' } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 b/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 new file mode 100644 index 000000000000..e39e6d5953ba --- /dev/null +++ b/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 @@ -0,0 +1,26 @@ +function Get-CIPPDomainAnalyser { + [CmdletBinding()] + Param($TenantFilter) + $DomainTable = Get-CIPPTable -Table 'Domains' + + # Get all the things + + if ($TenantFilter -ne 'AllTenants') { + $DomainTable.Filter = "TenantId eq '{0}'" -f $TenantFilter + } + + try { + # Extract json from table results + $Results = foreach ($DomainAnalyserResult in (Get-CIPPAzDataTableEntity @DomainTable).DomainAnalyser) { + try { + if (![string]::IsNullOrEmpty($DomainAnalyserResult)) { + $Object = $DomainAnalyserResult | ConvertFrom-Json -ErrorAction SilentlyContinue + $Object + } + } catch {} + } + } catch { + $Results = @() + } + return $Results +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-ClassicAPIToken.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-ClassicAPIToken.ps1 index f887e8ce850e..ce7e48fa5247 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-ClassicAPIToken.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-ClassicAPIToken.ps1 @@ -5,10 +5,10 @@ function Get-ClassicAPIToken($tenantID, $Resource) { #> $TokenKey = '{0}-{1}' -f $TenantID, $Resource if ($script:classictoken.$TokenKey -and [int](Get-Date -UFormat %s -Millisecond 0) -lt $script:classictoken.$TokenKey.expires_on) { - Write-Host 'Classic: cached token' + #Write-Host 'Classic: cached token' return $script:classictoken.$TokenKey } else { - Write-Host 'Using classic' + #Write-Host 'Using classic' $uri = "https://login.microsoftonline.com/$($TenantID)/oauth2/token" $Body = @{ client_id = $env:ApplicationID diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-GraphToken.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-GraphToken.ps1 index 05b1b7f9c8fc..b0021973a701 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-GraphToken.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-GraphToken.ps1 @@ -36,10 +36,10 @@ function Get-GraphToken($tenantid, $scope, $AsApp, $AppID, $refreshToken, $Retur try { if ($script:AccessTokens.$TokenKey -and [int](Get-Date -UFormat %s -Millisecond 0) -lt $script:AccessTokens.$TokenKey.expires_on -and $SkipCache -ne $true) { - Write-Host 'Graph: cached token' + #Write-Host 'Graph: cached token' $AccessToken = $script:AccessTokens.$TokenKey } else { - Write-Host 'Graph: new token' + #Write-Host 'Graph: new token' $AccessToken = (Invoke-RestMethod -Method post -Uri "https://login.microsoftonline.com/$($tenantid)/oauth2/v2.0/token" -Body $Authbody -ErrorAction Stop) $ExpiresOn = [int](Get-Date -UFormat %s -Millisecond 0) + $AccessToken.expires_in Add-Member -InputObject $AccessToken -NotePropertyName 'expires_on' -NotePropertyValue $ExpiresOn diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 index e44eccbea5d9..fcf350f72774 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 @@ -36,41 +36,61 @@ function Get-Tenants { $LastRefresh = $false } if (!$LastRefresh -or $LastRefresh -lt (Get-Date).Addhours(-24).ToUniversalTime()) { - try { - Write-Host "Renewing. Cache not hit. $LastRefresh" - $TenantList = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/tenants?`$top=999" -tenantid $env:TenantID ) | Select-Object id, @{l = 'customerId'; e = { $_.tenantId } }, @{l = 'DefaultdomainName'; e = { [string]($_.contract.defaultDomainName) } } , @{l = 'MigratedToNewTenantAPI'; e = { $true } }, DisplayName, domains, @{n = 'delegatedPrivilegeStatus'; exp = { $_.tenantStatusInformation.delegatedPrivilegeStatus } } | Where-Object { $_.defaultDomainName -NotIn $SkipListCache.defaultDomainName -and $_.defaultDomainName -ne $null } - - } catch { - Write-Host "Get-Tenants - Lighthouse Error, using contract/delegatedAdminRelationship calls. Error: $($_.Exception.Message)" - [System.Collections.Generic.List[PSCustomObject]]$BulkRequests = @( - @{ - id = 'Contracts' - method = 'GET' - url = "/contracts?`$top=999" - }, - @{ - id = 'GDAPRelationships' - method = 'GET' - url = '/tenantRelationships/delegatedAdminRelationships' - } - ) - $BulkResults = New-GraphBulkRequest -Requests $BulkRequests -tenantid $TenantFilter -NoAuthCheck:$true - $Contracts = Get-GraphBulkResultByID -Results $BulkResults -ID 'Contracts' -Value - $GDAPRelationships = Get-GraphBulkResultByID -Results $BulkResults -ID 'GDAPRelationships' -Value + # Query for active relationships + $GDAPRelationships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active'&`$select=customer,autoExtendDuration,endDateTime" - $ContractList = $Contracts | Select-Object id, customerId, DefaultdomainName, DisplayName, domains, @{l = 'MigratedToNewTenantAPI'; e = { $true } }, @{ n = 'delegatedPrivilegeStatus'; exp = { $CustomerId = $_.customerId; if (($GDAPRelationships | Where-Object { $_.customer.tenantId -EQ $CustomerId -and $_.status -EQ 'active' } | Measure-Object).Count -gt 0) { 'delegatedAndGranularDelegetedAdminPrivileges' } else { 'delegatedAdminPrivileges' } } } | Where-Object -Property defaultDomainName -NotIn $SkipListCache.defaultDomainName + # Flatten gdap relationship + $GDAPList = foreach ($Relationship in $GDAPRelationships) { + [PSCustomObject]@{ + customerId = $Relationship.customer.tenantId + displayName = $Relationship.customer.displayName + autoExtend = ($Relationship.autoExtendDuration -ne 'PT0S') + relationshipEnd = $Relationship.endDateTime + } + } - $GDAPOnlyList = $GDAPRelationships | Where-Object { $_.status -eq 'active' -and $Contracts.customerId -notcontains $_.customer.tenantId } | Select-Object id, @{l = 'customerId'; e = { $($_.customer.tenantId) } }, @{l = 'defaultDomainName'; e = { (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/findTenantInformationByTenantId(tenantId='$($_.customer.tenantId)')" -noauthcheck $true -asApp:$true -tenant $env:TenantId).defaultDomainName } }, @{l = 'MigratedToNewTenantAPI'; e = { $true } }, @{n = 'displayName'; exp = { $_.customer.displayName } }, domains, @{n = 'delegatedPrivilegeStatus'; exp = { 'granularDelegatedAdminPrivileges' } } | Where-Object { $_.defaultDomainName -NotIn $SkipListCache.defaultDomainName -and $_.defaultDomainName -ne $null } | Sort-Object -Property customerId -Unique + # Group relationships, build object for adding to tables + $ActiveRelationships = $GDAPList | Where-Object { $_.customerId -notin $SkipListCache.customerId } + $TenantList = $ActiveRelationships | Group-Object -Property customerId | ForEach-Object -Parallel { + Import-Module .\Modules\CIPPCore + $LatestRelationship = $_.Group | Sort-Object -Property relationshipEnd | Select-Object -Last 1 + $AutoExtend = ($_.Group | Where-Object { $_.autoExtend -eq $true } | Measure-Object).Count -gt 0 - $TenantList = @($ContractList) + @($GDAPOnlyList) + # Query domains to get default/initial + try { + $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains' -tenantid $LatestRelationship.customerId -NoAuthCheck:$true -ErrorAction Stop + $defaultDomainName = ($Domains | Where-Object { $_.isDefault -eq $true }).id + $initialDomainName = ($Domains | Where-Object { $_.isInitial -eq $true }).id + } catch { + $defaultDomainName = 'Domain Error, check permissions' + $initialDomainName = 'Domain Error, check permissions' + } + [PSCustomObject]@{ + PartitionKey = 'Tenants' + RowKey = $_.Name + customerId = $_.Name + displayName = $LatestRelationship.displayName + relationshipEnd = $LatestRelationship.relationshipEnd + relationshipCount = $_.Count + defaultDomainName = $defaultDomainName + initialDomainName = $initialDomainName + hasAutoExtend = $AutoExtend + delegatedPrivilegeStatus = 'granularDelegatedAdminPrivileges' + domains = '' + Excluded = $false + ExcludeUser = '' + ExcludeDate = '' + GraphErrorCount = 0 + LastGraphError = '' + LastRefresh = (Get-Date).ToUniversalTime() + } } - <#if (!$TenantList.customerId) { - $TenantList = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/contracts?`$top=999" -tenantid $env:TenantID ) | Select-Object id, customerId, DefaultdomainName, DisplayName, domains | Where-Object -Property defaultDomainName -NotIn $SkipListCache.defaultDomainName - }#> - $IncludedTenantsCache = [system.collections.generic.list[hashtable]]::new() + + $IncludedTenantsCache = [system.collections.generic.list[object]]::new() if ($env:PartnerTenantAvailable) { - $IncludedTenantsCache.Add(@{ + # Add partner tenant if env is set + $IncludedTenantsCache.Add([PSCustomObject]@{ RowKey = $env:TenantID PartitionKey = 'Tenants' customerId = $env:TenantID @@ -87,21 +107,7 @@ function Get-Tenants { } foreach ($Tenant in $TenantList) { if ($Tenant.defaultDomainName -eq 'Invalid' -or !$Tenant.defaultDomainName) { continue } - $IncludedTenantsCache.Add(@{ - RowKey = [string]$Tenant.customerId - PartitionKey = 'Tenants' - customerId = [string]$Tenant.customerId - defaultDomainName = [string]$Tenant.defaultDomainName - displayName = [string]$Tenant.DisplayName - delegatedPrivilegeStatus = [string]$Tenant.delegatedPrivilegeStatus - domains = '' - Excluded = $false - ExcludeUser = '' - ExcludeDate = '' - GraphErrorCount = 0 - LastGraphError = '' - LastRefresh = (Get-Date).ToUniversalTime() - }) | Out-Null + $IncludedTenantsCache.Add($Tenant) | Out-Null } if ($IncludedTenantsCache) { diff --git a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 index cfe78a4af8c6..4a05cf778fe7 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 @@ -5,7 +5,8 @@ function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anc #> if ((Get-AuthorisedRequest -TenantID $tenantid) -or $NoAuthCheck -eq $True) { $token = Get-ClassicAPIToken -resource 'https://outlook.office365.com' -Tenantid $tenantid - $tenant = (get-tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $tenantid -or $_.customerId -eq $tenantid }).customerId + $Tenant = Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $tenantid -or $_.customerId -eq $tenantid } + if ($cmdParams) { $Params = $cmdParams } else { @@ -23,11 +24,12 @@ function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anc if ($cmdparams.User) { $Anchor = $cmdparams.User } if (!$Anchor -or $useSystemMailbox) { - $OnMicrosoft = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains?$top=999' -tenantid $tenantid -NoAuthCheck $NoAuthCheck | Where-Object -Property isInitial -EQ $true).id - + if (!$Tenant.initialDomainName) { + $OnMicrosoft = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains?$top=999' -tenantid $tenantid -NoAuthCheck $NoAuthCheck | Where-Object -Property isInitial -EQ $true).id + } else { + $OnMicrosoft = $Tenant.initialDomainName + } $anchor = "UPN:SystemMailbox{8cc370d3-822a-4ab8-a926-bb94bd0641a9}@$($OnMicrosoft)" - - } } Write-Host "Using $Anchor" @@ -40,9 +42,9 @@ function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anc } try { if ($Select) { $Select = "`$select=$Select" } - $URL = "https://outlook.office365.com/adminapi/beta/$($tenant)/InvokeCommand?$Select" - - $ReturnedData = + $URL = "https://outlook.office365.com/adminapi/beta/$($tenant.customerId)/InvokeCommand?$Select" + + $ReturnedData = do { $Return = Invoke-RestMethod $URL -Method POST -Body $ExoBody -Headers $Headers -ContentType 'application/json; charset=utf-8' $URL = $Return.'@odata.nextLink' diff --git a/Modules/CIPPCore/Public/Invoke-CIPPStandardsRun.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPStandardsRun.ps1 index 1c7e177e3555..8dd8055c6c93 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPStandardsRun.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPStandardsRun.ps1 @@ -78,7 +78,7 @@ function Invoke-CIPPStandardsRun { #For each item in our object, run the queue. - $Batch = foreach ($task in $object | Where-Object -Property Standard -NotLike 'v2*') { + $Batch = foreach ($task in $object | Where-Object { $_.Standard -NotLike 'v2*' -and ($_.Settings.remediate -eq $true -or $_.Settings.alert -eq $true -or $_.Settings.report -eq $true) }) { [PSCustomObject]@{ Tenant = $task.Tenant Standard = $task.Standard diff --git a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 index 77139f1ddaba..db62dae8b160 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 @@ -67,6 +67,7 @@ function Invoke-CippWebhookProcessing { 'OAuth2:Token' 'SAS:EndAuth' 'SAS:ProcessAuth' + 'Login:reprocess' ) if ($TableObj.RequestType -in $ExtendedPropertiesIgnoreList) { Write-Host 'No need to process this operation.' diff --git a/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 b/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 index 35a90d7f9e90..d508471a08a4 100644 --- a/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 @@ -1,23 +1,25 @@ function Set-CIPPCPVConsent { [CmdletBinding()] param( - $Tenantfilter, + $TenantFilter, $APIName = 'CPV Consent', $ExecutingUser, [bool]$ResetSP = $false ) $Results = [System.Collections.Generic.List[string]]::new() - $Tenant = Get-Tenants -IncludeAll -IncludeErrors | Where-Object -Property defaultDomainName -EQ $Tenantfilter - $TenantName = $Tenant.defaultDomainName - $TenantFilter = $Tenant.customerId + $Tenant = Get-Tenants -IncludeAll | Where-Object -Property customerId -EQ $TenantFilter | Select-Object -First 1 + $TenantName = $Tenant.displayName - if ($Tenantfilter -eq $env:TenantID) { + if ($TenantFilter -eq $env:TenantID) { return @('Cannot modify CPV consent on partner tenant') } + if ($Tenant.customerId -ne $TenantFilter) { + return @('Not a valid tenant') + } if ($ResetSP) { 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 + $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 { $Results.add("Error deleting SP - $($_.Exception.Message)") @@ -51,7 +53,7 @@ function Set-CIPPCPVConsent { } Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force $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 + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Added our Service Principal to $($TenantName): $($_.Exception.message)" -Sev 'Info' -tenant $Tenant.defaultDomainName -tenantId $TenantFilter } catch { $ErrorMessage = Get-NormalizedError -message $_.Exception.Message @@ -68,7 +70,7 @@ function Set-CIPPCPVConsent { Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force 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 + 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 $Tenant.defaultDomainName -tenantId $TenantFilter return @("Could not add our Service Principal to the client tenant $($TenantName): $ErrorMessage") } return $Results diff --git a/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 b/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 index e2ae2dba708d..a4c87ab9eb5c 100644 --- a/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 @@ -6,8 +6,10 @@ function Set-CIPPGDAPInviteGroups { $Invite = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$($Relationship.id)'" $APINAME = 'GDAPInvites' $RoleMappings = $Invite.RoleMappings | ConvertFrom-Json - - foreach ($role in $RoleMappings) { + $AccessAssignments = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$($Relationship.id)/accessAssignments" + foreach ($Role in $RoleMappings) { + # Skip mapping if group is present in relationship + if ($AccessAssignments.id -and $AccessAssignments.accessContainer.accessContainerid -contains $Role.GroupId ) { continue } try { $Mappingbody = ConvertTo-Json -Depth 10 -InputObject @{ 'accessContainer' = @{ diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 new file mode 100644 index 000000000000..1bd6e70bebf0 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -0,0 +1,80 @@ +function Invoke-CIPPStandardAntiPhishPolicy { + <# + .FUNCTIONALITY + Internal + #> + + param($Tenant, $Settings) + $AntiPhishPolicyState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishPolicy' | + Where-Object -Property Name -eq $PolicyName | + Select-Object Name, Enabled, PhishThresholdLevel, EnableMailboxIntelligence, EnableMailboxIntelligenceProtection, EnableSpoofIntelligence, EnableFirstContactSafetyTips, EnableSimilarUsersSafetyTips, EnableSimilarDomainsSafetyTips, EnableUnusualCharactersSafetyTips, EnableUnauthenticatedSender, EnableViaTag, MailboxIntelligenceProtectionAction, MailboxIntelligenceQuarantineTag + + $PolicyName = "Default Anti-Phishing Policy" + $StateIsCorrect = if ( + ($AntiPhishPolicyState.Name -eq $PolicyName) -and + ($AntiPhishPolicyState.Enabled -eq $true) -and + ($AntiPhishPolicyState.PhishThresholdLevel -eq $Settings.PhishThresholdLevel) -and + ($AntiPhishPolicyState.EnableMailboxIntelligence -eq $true) -and + ($AntiPhishPolicyState.EnableMailboxIntelligenceProtection -eq $true) -and + ($AntiPhishPolicyState.EnableSpoofIntelligence -eq $true) -and + ($AntiPhishPolicyState.EnableFirstContactSafetyTips -eq $Settings.EnableFirstContactSafetyTips) -and + ($AntiPhishPolicyState.EnableSimilarUsersSafetyTips -eq $Settings.EnableSimilarUsersSafetyTips) -and + ($AntiPhishPolicyState.EnableSimilarDomainsSafetyTips -eq $Settings.EnableSimilarDomainsSafetyTips) -and + ($AntiPhishPolicyState.EnableUnusualCharactersSafetyTips -eq $Settings.EnableUnusualCharactersSafetyTips) -and + ($AntiPhishPolicyState.EnableUnauthenticatedSender -eq $true) -and + ($AntiPhishPolicyState.EnableViaTag -eq $true) -and + ($AntiPhishPolicyState.MailboxIntelligenceProtectionAction -eq $Settings.MailboxIntelligenceProtectionAction) -and + ($AntiPhishPolicyState.MailboxIntelligenceQuarantineTag -eq $Settings.MailboxIntelligenceQuarantineTag) + ) { $true } else { $false } + + if ($Settings.remediate) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Anti-phishing Policy already exists.' -sev Info + } else { + $cmdparams = @{ + Enabled = $true + PhishThresholdLevel = $Settings.PhishThresholdLevel + EnableMailboxIntelligence = $true + EnableMailboxIntelligenceProtection = $true + EnableSpoofIntelligence = $true + EnableFirstContactSafetyTips = $Settings.EnableFirstContactSafetyTips + EnableSimilarUsersSafetyTips = $Settings.EnableSimilarUsersSafetyTips + EnableSimilarDomainsSafetyTips = $Settings.EnableSimilarDomainsSafetyTips + EnableUnusualCharactersSafetyTips = $Settings.EnableUnusualCharactersSafetyTips + EnableUnauthenticatedSender = $true + EnableViaTag = $true + MailboxIntelligenceProtectionAction = $Settings.MailboxIntelligenceProtectionAction + MailboxIntelligenceQuarantineTag = $Settings.MailboxIntelligenceQuarantineTag + } + + try { + if ($AntiPhishPolicyState.Name -eq $PolicyName) { + $cmdparams.Add("Identity", $PolicyName) + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-AntiPhishPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Anti-phishing Policy' -sev Info + } else { + $cmdparams.Add("Name", $PolicyName) + New-ExoRequest -tenantid $Tenant -cmdlet 'New-AntiPhishPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Created Anti-phishing Policy' -sev Info + } + } catch { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create Anti-phishing Policy. Error: $($_.exception.message)" -sev Error + } + } + } + + + if ($Settings.alert) { + + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Anti-phishing Policy is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Anti-phishing Policy is not enabled' -sev Alert + } + } + + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'AntiPhishPolicy' -FieldValue [bool]$StateIsCorrect -StoreAs bool -Tenant $tenant + } + +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 new file mode 100644 index 000000000000..75d78bc395b0 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 @@ -0,0 +1,49 @@ +function Invoke-CIPPStandardAtpPolicyForO365 { + <# + .FUNCTIONALITY + Internal + #> + + param($Tenant, $Settings) + $AtpPolicyForO365State = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AtpPolicyForO365' | + Select-Object EnableATPForSPOTeamsODB, EnableSafeDocs, AllowSafeDocsOpen + + $StateIsCorrect = if ( + ($AtpPolicyForO365State.EnableATPForSPOTeamsODB -eq $true) -and + ($AtpPolicyForO365State.EnableSafeDocs -eq $true) -and + ($AtpPolicyForO365State.AllowSafeDocsOpen -eq $Settings.AllowSafeDocsOpen) + ) { $true } else { $false } + + if ($Settings.remediate) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Atp Policy For O365 already set.' -sev Info + } else { + $cmdparams = @{ + EnableATPForSPOTeamsODB = $true + EnableSafeDocs = $true + AllowSafeDocsOpen = $Settings.AllowSafeDocsOpen + } + + try { + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-AntiPhishPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Atp Policy For O365' -sev Info + } catch { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set Atp Policy For O365. Error: $($_.exception.message)" -sev Error + } + } + } + + if ($Settings.alert) { + + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Atp Policy For O365 is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Atp Policy For O365 is not enabled' -sev Alert + } + } + + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'AtpPolicyForO365' -FieldValue [bool]$StateIsCorrect -StoreAs bool -Tenant $tenant + } + +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 new file mode 100644 index 000000000000..0d1d2d2a0af6 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 @@ -0,0 +1,37 @@ +function Invoke-CIPPStandardDisableAppCreation { + <# + .FUNCTIONALITY + Internal + #> + param($Tenant, $Settings) + $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy?$select=defaultUserRolePermissions' -tenantid $Tenant + + If ($Settings.remediate) { + if ($CurrentInfo.defaultUserRolePermissions.allowedToCreateApps -eq $false) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Users are already not allowed to create App registrations.' -sev Info + } else { + try { + $body = '{"defaultUserRolePermissions":{"allowedToCreateApps":false}}' + $null = New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -Type patch -Body $body -ContentType 'application/json' + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Disabled users from creating App registrations.' -sev Info + $CurrentInfo.defaultUserRolePermissions.allowedToCreateApps = $false + } catch { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable users from creating App registrations: $($_.exception.message)" -sev Error + } + } + } + + if ($Settings.alert) { + + if ($CurrentInfo.defaultUserRolePermissions.allowedToCreateApps -eq $false) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Users are not allowed to create App registrations.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Users are allowed to create App registrations.' -sev Alert + } + } + + if ($Settings.report) { + $State = -not $CurrentInfo.defaultUserRolePermissions.allowedToCreateApps + Add-CIPPBPAField -FieldName 'UserAppCreationDisabled' -FieldValue [bool]$State -StoreAs bool -Tenant $tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 new file mode 100644 index 000000000000..3b9df5cfb779 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 @@ -0,0 +1,44 @@ +function Invoke-CIPPStandardExternalMFATrusted { + <# + .FUNCTIONALITY + Internal + #> + param($Tenant, $Settings) + + $ExternalMFATrusted = (New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/default?$select=inboundTrust' -tenantid $Tenant) + $WantedState = if ($Settings.state -eq 'true') { $true } else { $false } + $StateMessage = if ($WantedState) { 'enabled' } else { 'disabled' } + + if ($Settings.remediate) { + + Write-Host 'Remediate External MFA Trusted' + if ($ExternalMFATrusted.inboundTrust.isMfaAccepted -eq $WantedState ) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "External MFA Trusted is already $StateMessage." -sev Info + } else { + try { + $NewBody = $ExternalMFATrusted + $NewBody.inboundTrust.isMfaAccepted = $WantedState + $NewBody = ConvertTo-Json -Depth 10 -InputObject $NewBody -Compress + $null = New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/default' -Type patch -Body $NewBody -ContentType 'application/json' + Write-LogMessage -API 'Standards' -tenant $tenant -message "Set External MFA Trusted to $StateMessage." -sev Info + } catch { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set External MFA Trusted to $StateMessage. Error: $($_.exception.message)" -sev Error + } + } + } + + if ($Settings.alert) { + + if ($ExternalMFATrusted.inboundTrust.isMfaAccepted -eq $WantedState) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "External MFA Trusted is $StateMessage." -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message "External MFA Trusted is not $StateMessage." -sev Alert + } + + } + + if ($Settings.report) { + + Add-CIPPBPAField -FieldName 'ExternalMFATrusted' -FieldValue [bool]$ExternalMFATrusted.inboundTrust.isMfaAccepted -StoreAs bool -Tenant $tenant + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 index f07e54320d72..e2809c6f4bd9 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 @@ -32,15 +32,24 @@ function Invoke-CIPPStandardGroupTemplate { } $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/groups' -tenantid $tenant -type POST -body (ConvertTo-Json -InputObject $BodyToship -Depth 10) -verbose } else { - $Params = @{ - Name = $groupobj.Displayname - Alias = $groupobj.username - Description = $groupobj.Description - PrimarySmtpAddress = $email - Type = $groupobj.groupType - RequireSenderAuthenticationEnabled = [bool]!$groupobj.AllowExternal + if ($groupobj.groupType -eq 'dynamicdistribution') { + $Params = @{ + Name = $groupobj.Displayname + RecipientFilter = $groupobj.membershipRules + PrimarySmtpAddress = $email + } + $GraphRequest = New-ExoRequest -tenantid $tenant -cmdlet 'New-DynamicDistributionGroup' -cmdParams $params + } else { + $Params = @{ + Name = $groupobj.Displayname + Alias = $groupobj.username + Description = $groupobj.Description + PrimarySmtpAddress = $email + Type = $groupobj.groupType + RequireSenderAuthenticationEnabled = [bool]!$groupobj.AllowExternal + } + $GraphRequest = New-ExoRequest -tenantid $tenant -cmdlet 'New-DistributionGroup' -cmdParams $params } - $GraphRequest = New-ExoRequest -tenantid $tenant -cmdlet 'New-DistributionGroup' -cmdParams $params } Write-LogMessage -user $request.headers.'x-ms-client-principal' -API 'Standards' -tenant $tenant -message "Created group $($groupobj.displayname) with id $($GraphRequest.id) " -Sev 'Info' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 index d6676fc437ec..8ab1793fec2f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 @@ -75,6 +75,7 @@ function Invoke-CIPPStandardIntuneTemplate { if ($Settings.AssignTo) { Write-Host "Assigning Policy to $($Settings.AssignTo) the create ID is $($CreateRequest)" + if ($Settings.AssignTo -eq 'customGroup') { $Settings.AssignTo = $Settings.customGroup } Set-CIPPAssignedPolicy -PolicyId $CreateRequest.id -TenantFilter $tenant -GroupName $Settings.AssignTo -Type $TemplateTypeURL } Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully added Intune Template policy for $($Tenant)" -sev 'Info' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 new file mode 100644 index 000000000000..40ed853dc1d2 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 @@ -0,0 +1,70 @@ +function Invoke-CIPPStandardMalwareFilterPolicy { + <# + .FUNCTIONALITY + Internal + #> + + param($Tenant, $Settings) + $MalwareFilterState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MalwareFilterPolicy' | + Where-Object -Property Name -eq $PolicyName | + Select-Object Name, EnableFileFilter, FileTypeAction, ZapEnabled, QuarantineTag, EnableInternalSenderAdminNotifications, InternalSenderAdminAddress, EnableExternalSenderAdminNotifications, ExternalSenderAdminAddress + + $PolicyName = "Default Malware Policy" + $StateIsCorrect = if ( + ($MalwareFilterState.Name -eq $PolicyName) -and + ($MalwareFilterState.EnableFileFilter -eq $true) -and + ($MalwareFilterState.FileTypeAction -eq $Settings.FileTypeAction) -and + ($MalwareFilterState.ZapEnabled -eq $true) -and + ($MalwareFilterState.QuarantineTag -eq $Settings.QuarantineTag) -and + ($MalwareFilterState.EnableInternalSenderAdminNotifications -eq $Settings.EnableInternalSenderAdminNotifications) -and + ($MalwareFilterState.InternalSenderAdminAddress -eq $Settings.InternalSenderAdminAddress) -and + ($MalwareFilterState.EnableExternalSenderAdminNotifications -eq $Settings.EnableExternalSenderAdminNotifications) -and + ($MalwareFilterState.ExternalSenderAdminAddress -eq $Settings.ExternalSenderAdminAddress) + ) { $true } else { $false } + + if ($Settings.remediate) { + + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Malware Filter Policy already exists.' -sev Info + } else { + $cmdparams = @{ + EnableFileFilter = $true + FileTypeAction = $Settings.FileTypeAction + ZapEnabled = $true + QuarantineTag = $Settings.QuarantineTag + EnableInternalSenderAdminNotifications = $Settings.EnableInternalSenderAdminNotifications + InternalSenderAdminAddress = $Settings.InternalSenderAdminAddress + EnableExternalSenderAdminNotifications = $Settings.EnableExternalSenderAdminNotifications + ExternalSenderAdminAddress = $Settings.ExternalSenderAdminAddress + } + + try { + if ($MalwareFilterState.Name -eq $PolicyName) { + $cmdparams.Add("Identity", $PolicyName) + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-MalwareFilterPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Malware Filter Policy' -sev Info + } else { + $cmdparams.Add("Name", $PolicyName) + New-ExoRequest -tenantid $Tenant -cmdlet 'New-MalwareFilterPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Created Malware Filter Policy' -sev Info + } + } catch { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create Malware Filter Policy. Error: $($_.exception.message)" -sev Error + } + } + } + + if ($Settings.alert) { + + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Malware Filter Policy is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Malware Filter Policy is not enabled' -sev Alert + } + } + + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'MalwareFilterPolicy' -FieldValue [bool]$StateIsCorrect -StoreAs bool -Tenant $tenant + } + +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 new file mode 100644 index 000000000000..784e221b659f --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 @@ -0,0 +1,62 @@ +function Invoke-CIPPStandardSafeAttachmentPolicy { + <# + .FUNCTIONALITY + Internal + #> + + param($Tenant, $Settings) + $SafeAttachmentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeAttachmentPolicy' | + Where-Object -Property Name -eq $PolicyName | + Select-Object Name, Enable, Action, QuarantineTag, Redirect, RedirectAddress + + $PolicyName = "Default Safe Attachment Policy" + $StateIsCorrect = if ( + ($SafeAttachmentState.Name -eq $PolicyName) -and + ($SafeAttachmentState.Enable -eq $true) -and + ($SafeAttachmentState.QuarantineTag -eq $Settings.QuarantineTag) -and + ($SafeAttachmentState.Redirect -eq $Settings.Redirect) -and + ($SafeAttachmentState.RedirectAddress -eq $Settings.RedirectAddress) + ) { $true } else { $false } + + if ($Settings.remediate) { + + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Safe Attachment Policy already exists.' -sev Info + } else { + $cmdparams = @{ + Enable = $true + QuarantineTag = $Settings.QuarantineTag + Redirect = $Settings.Redirect + RedirectAddress = $Settings.RedirectAddress + } + + try { + if ($SafeAttachmentState.Name -eq $PolicyName) { + $cmdparams.Add("Identity", $PolicyName) + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-SafeAttachmentPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Safe Attachment Policy' -sev Info + } else { + $cmdparams.Add("Name", $PolicyName) + New-ExoRequest -tenantid $Tenant -cmdlet 'New-SafeAttachmentPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Created Safe Attachment Policy' -sev Info + } + } catch { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create Safe Attachment Policy. Error: $($_.exception.message)" -sev Error + } + } + } + + if ($Settings.alert) { + + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Safe Attachment Policy is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Safe Attachment Policy is not enabled' -sev Alert + } + } + + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'SafeAttachmentPolicy' -FieldValue [bool]$StateIsCorrect -StoreAs bool -Tenant $tenant + } + +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 new file mode 100644 index 000000000000..7233a4e92fc9 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 @@ -0,0 +1,74 @@ +function Invoke-CIPPStandardSafeLinksPolicy { + <# + .FUNCTIONALITY + Internal + #> + + param($Tenant, $Settings) + $SafeLinkState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeLinksPolicy' | + Where-Object -Property Name -eq $PolicyName | + Select-Object Name, EnableSafeLinksForEmail, EnableSafeLinksForTeams, EnableSafeLinksForOffice, TrackClicks, AllowClickThrough, ScanUrls, EnableForInternalSenders, DeliverMessageAfterScan, DisableUrlRewrite, EnableOrganizationBranding + + $PolicyName = "Default SafeLinks Policy" + $StateIsCorrect = if ( + ($SafeLinkState.Name -eq $PolicyName) -and + ($SafeLinkState.EnableSafeLinksForEmail -eq $true) -and + ($SafeLinkState.EnableSafeLinksForTeams -eq $true) -and + ($SafeLinkState.EnableSafeLinksForOffice -eq $true) -and + ($SafeLinkState.TrackClicks -eq $true) -and + ($SafeLinkState.ScanUrls -eq $true) -and + ($SafeLinkState.EnableForInternalSenders -eq $true) -and + ($SafeLinkState.DeliverMessageAfterScan -eq $true) -and + ($SafeLinkState.AllowClickThrough -eq $Settings.AllowClickThrough) -and + ($SafeLinkState.DisableUrlRewrite -eq $Settings.DisableUrlRewrite) -and + ($SafeLinkState.EnableOrganizationBranding -eq $Settings.EnableOrganizationBranding) + ) { $true } else { $false } + + if ($Settings.remediate) { + + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'SafeLink Policy already exists.' -sev Info + } else { + $cmdparams = @{ + EnableSafeLinksForEmail = $true + EnableSafeLinksForTeams = $true + EnableSafeLinksForOffice = $true + TrackClicks = $true + ScanUrls = $true + EnableForInternalSenders = $true + DeliverMessageAfterScan = $true + AllowClickThrough = $Settings.AllowClickThrough + DisableUrlRewrite = $Settings.DisableUrlRewrite + EnableOrganizationBranding = $Settings.EnableOrganizationBranding + } + + try { + if ($SafeLinkState.Name -eq $PolicyName) { + $cmdparams.Add("Identity", $PolicyName) + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-SafeLinksPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated SafeLink Policy' -sev Info + } else { + $cmdparams.Add("Name", $PolicyName) + New-ExoRequest -tenantid $Tenant -cmdlet 'New-SafeLinksPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Created SafeLink Policy' -sev Info + } + } catch { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create SafeLink Policy. Error: $($_.exception.message)" -sev Error + } + } + } + + if ($Settings.alert) { + + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'SafeLink Policy is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'SafeLink Policy is not enabled' -sev Alert + } + } + + if ($Settings.report) { + Add-CIPPBPAField -FieldName 'SafeLinksPolicy' -FieldValue [bool]$StateIsCorrect -StoreAs bool -Tenant $tenant + } + +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 index 971753f68d40..f82b937d9e57 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 @@ -26,7 +26,7 @@ function Test-CIPPAccessPermissions { Set-Location (Get-Item $PSScriptRoot).FullName $ExpectedPermissions = Get-Content '.\SAMManifest.json' | ConvertFrom-Json - $GraphToken = Get-GraphToken -returnRefresh $true + $GraphToken = Get-GraphToken -returnRefresh $true -SkipCache $true if ($GraphToken) { $GraphPermissions = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/myorganization/applications?`$filter=appId eq '$env:ApplicationID'" -NoAuthCheck $true } diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 index 6e9d85fd7a74..014218b3cc16 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 @@ -25,19 +25,11 @@ function Test-CIPPAccessTenant { $TenantIds = foreach ($Tenant in $Tenants) { ($TenantList | Where-Object { $_.defaultDomainName -eq $Tenant }).customerId } - try { - $MyRoles = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/myRoles?`$filter=tenantId in ('$($TenantIds -join "','")')" - } catch { - $MyRoles = @() - $AddedText = 'but could not retrieve GDAP roles from Lighthouse API' - } + $results = foreach ($tenant in $Tenants) { $AddedText = '' try { $TenantId = ($TenantList | Where-Object { $_.defaultDomainName -eq $tenant }).customerId - $Assignments = ($MyRoles | Where-Object { $_.tenantId -eq $TenantId }).assignments - $SAMUserRoles = $Assignments.roles - $BulkRequests = $ExpectedRoles | ForEach-Object { @( @{ id = "roleManagement_$($_.id)" @@ -49,10 +41,12 @@ function Test-CIPPAccessTenant { $GDAPRolesGraph = New-GraphBulkRequest -tenantid $tenant -Requests $BulkRequests $GDAPRoles = [System.Collections.Generic.List[object]]::new() $MissingRoles = [System.Collections.Generic.List[object]]::new() + + #Write-Host ($GDAPRolesGraph.body.value | ConvertTo-Json -Depth 10) foreach ($RoleId in $ExpectedRoles) { $GraphRole = $GDAPRolesGraph.body.value | Where-Object -Property roleDefinitionId -EQ $RoleId.Id $Role = $GraphRole.principal | Where-Object -Property organizationId -EQ $ENV:tenantid - $SAMRole = $SAMUserRoles | Where-Object -Property templateId -EQ $RoleId.Id + if (!$Role) { $MissingRoles.Add( [PSCustomObject]@{ @@ -62,16 +56,10 @@ function Test-CIPPAccessTenant { ) $AddedText = 'but missing GDAP roles' } else { - $GDAPRoles.Add([PSCustomObject]$RoleId) - } - if (!$SAMRole) { - $MissingRoles.Add( - [PSCustomObject]@{ - Name = $RoleId.Name - Type = 'SAM User' - } - ) - $AddedText = 'but missing GDAP roles' + $GDAPRoles.Add([PSCustomObject]@{ + Role = $RoleId.Name + Group = $Role.displayName + }) } } if (!($MissingRoles | Measure-Object).Count -gt 0) { @@ -82,7 +70,6 @@ function Test-CIPPAccessTenant { Status = "Successfully connected $($AddedText)" GDAPRoles = $GDAPRoles MissingRoles = $MissingRoles - SAMUserRoles = $SAMUserRoles } Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message 'Tenant access check executed successfully' -Sev 'Info' diff --git a/Scheduler_GetWebhooks/run.ps1 b/Scheduler_GetWebhooks/run.ps1 index 9b890b878588..a36890b001df 100644 --- a/Scheduler_GetWebhooks/run.ps1 +++ b/Scheduler_GetWebhooks/run.ps1 @@ -1,13 +1,15 @@ param($Timer) -$Table = Get-CIPPTable -TableName WebhookIncoming -$Webhooks = Get-CIPPAzDataTableEntity @Table -$InputObject = [PSCustomObject]@{ - OrchestratorName = 'WebhookOrchestrator' - Batch = @($Webhooks) - SkipLog = $true +try { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'WebhookOrchestrator' + QueueFunction = @{ + FunctionName = 'GetPendingWebhooks' + } + SkipLog = $true + } + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started orchestration with ID = '$InstanceId'" +} catch { + Write-LogMessage -API 'Webhooks' -message "Error processing webhooks - $($_.Exception.Message)" -sev Error } -#Write-Host ($InputObject | ConvertTo-Json) -$InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) -Write-Host "Started orchestration with ID = '$InstanceId'" -#$Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId \ No newline at end of file diff --git a/Scheduler_UserTasks/run.ps1 b/Scheduler_UserTasks/run.ps1 index 8ad06065a2fa..2585ee499be6 100644 --- a/Scheduler_UserTasks/run.ps1 +++ b/Scheduler_UserTasks/run.ps1 @@ -3,7 +3,8 @@ param($Timer) $Table = Get-CippTable -tablename 'ScheduledTasks' $Filter = "TaskState eq 'Planned' or TaskState eq 'Failed - Planned'" $tasks = Get-CIPPAzDataTableEntity @Table -Filter $Filter -$Batch = foreach ($task in $tasks) { +$Batch = [System.Collections.Generic.List[object]]::new() +foreach ($task in $tasks) { $tenant = $task.Tenant $currentUnixTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds if ($currentUnixTime -ge $task.ScheduledTime) { @@ -26,15 +27,20 @@ $Batch = foreach ($task in $tasks) { } if ($task.Tenant -eq 'AllTenants') { - Get-Tenants | ForEach-Object { - $ScheduledCommand.Parameters['TenantFilter'] = $_.defaultDomainName - $ScheduledCommand - #Push-OutputBinding -Name Msg -Value $ScheduledCommand + $AllTenantCommands = foreach ($Tenant in Get-Tenants) { + $NewParams = $task.Parameters.Clone() + $NewParams.TenantFilter = $Tenant.defaultDomainName + [pscustomobject]@{ + Command = $task.Command + Parameters = $NewParams + TaskInfo = $task + FunctionName = 'ExecScheduledCommand' + } } + $Batch.AddRange($AllTenantCommands) } else { $ScheduledCommand.Parameters['TenantFilter'] = $task.Tenant - $ScheduledCommand - #$Results = Push-OutputBinding -Name Msg -Value $ScheduledCommand + $Batch.Add($ScheduledCommand) } } catch { $errorMessage = $_.Exception.Message @@ -56,7 +62,8 @@ if (($Batch | Measure-Object).Count -gt 0) { Batch = @($Batch) SkipLog = $true } - #Write-Host ($InputObject | ConvertTo-Json) - $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + #Write-Host ($InputObject | ConvertTo-Json -Depth 10) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 10) + Write-Host "Started orchestration with ID = '$InstanceId'" } \ No newline at end of file diff --git a/version_latest.txt b/version_latest.txt index 7d3cdbf0dd04..8ae03c11904c 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.3.1 \ No newline at end of file +5.4.2