diff --git a/Modules/CIPPCore/Public/Add-CIPPAlias.ps1 b/Modules/CIPPCore/Public/Add-CIPPAlias.ps1 new file mode 100644 index 000000000000..68f297e4440b --- /dev/null +++ b/Modules/CIPPCore/Public/Add-CIPPAlias.ps1 @@ -0,0 +1,26 @@ +function Add-CIPPAlias { + [CmdletBinding()] + param ( + $user, + $Aliases, + $UserprincipalName, + $TenantFilter, + $APIName = 'Set Manager', + $ExecutingUser + ) + + try { + foreach ($Alias in $Aliases) { + Write-Host "Adding alias $Alias to $user" + New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$user" -tenantid $TenantFilter -type 'patch' -body "{`"mail`": `"$Alias`"}" -verbose + } + Write-Host "Resetting primary alias to $User" + New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($user)" -tenantid $TenantFilter -type 'patch' -body "{`"mail`": `"$User`"}" -verbose + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message "Added alias $($Alias) to $($UserprincipalName)" -Sev 'Info' + return ("Added Aliases: $($Aliases -join ',')") + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message "Failed to set alias. Error:$($_.Exception.Message)" -Sev 'Error' + throw "Failed to set alias: $($_.Exception.Message)" + } +} + diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertDeviceCompliance.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertDeviceCompliance.ps1 new file mode 100644 index 000000000000..a8c3ff745fd5 --- /dev/null +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertDeviceCompliance.ps1 @@ -0,0 +1,22 @@ + +function Get-CIPPAlertDeviceCompliance { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $false)] + [Alias('input')] + $InputValue, + $TenantFilter + ) + try { + $AlertData = New-GraphGETRequest -uri "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$top=999" -tenantid $TenantFilter | Where-Object -Property complianceState -NE 'compliant' | ForEach-Object { + $_ | Select-Object -Property id, deviceName, deviceType, complianceState, lastReportedDateTime + } + Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData + } catch { + Write-AlertMessage -tenant $($TenantFilter) -message "Could not get compliance state for $($TenantFilter): $(Get-NormalizedError -message $_.Exception.message)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 index bd9635c01630..ff311a85737e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 @@ -217,6 +217,12 @@ function Push-DomainAnalyserDomain { if (![string]::IsNullOrEmpty($DomainObject.DkimSelectors)) { $DkimParams.Selectors = $DomainObject.DkimSelectors | ConvertFrom-Json } + # Check if its a onmicrosoft.com domain and add special selectors for these + if ($Domain -match 'onmicrosoft.com' -and $Domain -notmatch 'mail.onmicrosoft.com') { + $DKIMSelector1Value = "selector1-$($Domain -replace '\.', '-' )" + $DKIMSelector2Value = "selector2-$($Domain -replace '\.', '-' )" + $DkimParams.Add('Selectors', @("$DKIMSelector1Value", "$DKIMSelector2Value")) + } $DkimRecord = Read-DkimRecord @DkimParams -ErrorAction Stop diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Graph Requests/Push-ListGraphRequestQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Graph Requests/Push-ListGraphRequestQueue.ps1 index 827307d8c31f..b3f88c0b9e73 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Graph Requests/Push-ListGraphRequestQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Graph Requests/Push-ListGraphRequestQueue.ps1 @@ -41,7 +41,7 @@ function Push-ListGraphRequestQueue { Get-GraphRequestList @GraphRequestParams } catch { [PSCustomObject]@{ - Tenant = $Item.Tenant + Tenant = $Item.TenantFilter CippStatus = "Could not connect to tenant. $($_.Exception.message)" } } @@ -62,4 +62,4 @@ function Push-ListGraphRequestQueue { Write-Information "Queue Error: $($_.Exception.Message)" throw $_ } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 index 0e3d211d6718..f5316ac76a0e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 @@ -135,7 +135,7 @@ Function Push-ExecOnboardTenantQueue { if ($AccessAssignments.id -and !$Invite) { $MissingRoles = [System.Collections.Generic.List[object]]::new() $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Relationship has existing access assignments, checking for missing mappings' }) - #Write-Host ($AccessAssignments | ConvertTo-Json -Depth 5) + if ($Item.Roles -and $Item.AutoMapRoles -eq $true) { foreach ($Role in $Item.Roles) { if ($AccessAssignments.accessContainer.accessContainerid -notcontains $Role.GroupId -and $Relationship.accessDetails.unifiedRoles.roleDefinitionId -contains $Role.roleDefinitionId) { @@ -161,7 +161,7 @@ Function Push-ExecOnboardTenantQueue { } } - if (!$AccessAssignments.id -and !$Invite -and $Item.Roles) { + if (!$AccessAssignments.id -and $Item.Roles) { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'No access assignments found, using defined role mapping.' }) $MatchingRoles = [System.Collections.Generic.List[object]]::new() foreach ($Role in $Item.Roles) { @@ -177,7 +177,7 @@ Function Push-ExecOnboardTenantQueue { 'InviteUrl' = 'https://admin.microsoft.com/AdminPortal/Home#/partners/invitation/granularAdminRelationships/{0}' -f $Id 'RoleMappings' = [string](@($MatchingRoles) | ConvertTo-Json -Depth 10 -Compress) } - Add-CIPPAzDataTableEntity @InviteTable -Entity $Invite + Add-CIPPAzDataTableEntity @InviteTable -Entity $Invite -Force $GroupSuccess = $true } else { $TenantOnboarding.Status = 'failed' @@ -292,10 +292,10 @@ Function Push-ExecOnboardTenantQueue { } $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' }) + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = ('CPV Consent Failed, error: {0}' -f $Consent) }) $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.' + $OnboardingSteps.Step4.Message = 'CPV Consent failed, check the logs for more details.' $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop @@ -309,6 +309,7 @@ Function Push-ExecOnboardTenantQueue { $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + $LastCPVError = '' do { try { Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Relationship.customer.tenantId @@ -316,6 +317,7 @@ Function Push-ExecOnboardTenantQueue { $CPVSuccess = $true $Refreshing = $false } catch { + $LastCPVError = $_.Exception.Message Start-Sleep -Seconds 30 } } while ($Refreshing -and (Get-Date) -lt $Start.AddMinutes(8)) @@ -328,10 +330,10 @@ Function Push-ExecOnboardTenantQueue { $Tenant = Get-Tenants -TriggerRefresh -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' }) + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'CPV permissions failed to refresh. {0}' -f $LastCPVError }) $TenantOnboarding.Status = 'failed' $OnboardingSteps.Step4.Status = 'failed' - $OnboardingSteps.Step4.Message = 'CPV permissions failed to refresh, try again later' + $OnboardingSteps.Step4.Message = 'CPV permissions failed to refresh, check the logs for more details.' } } else { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Tenant not found' }) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ListMFAUsersQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ListMFAUsersQueue.ps1 index 37ac2186d265..1ff7acc513d7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ListMFAUsersQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ListMFAUsersQueue.ps1 @@ -9,7 +9,7 @@ function Push-ListMFAUsersQueue { Write-Host "PowerShell queue trigger function processed work item: $($Item.defaultDomainName)" try { - Update-CippQueueEntry -RowKey $Item.QueueId -Status 'Running' -Name $Item.displayName + #Update-CippQueueEntry -RowKey $Item.QueueId -Status 'Running' -Name $Item.displayName $domainName = $Item.defaultDomainName $Table = Get-CIPPTable -TableName cachemfa Try { @@ -29,6 +29,12 @@ function Push-ListMFAUsersQueue { RowKey = [string]"$domainName" PartitionKey = 'users' } + } else { + $GraphRequest = foreach ($Request in $GraphRequest) { + $Request.CAPolicies = try { [string](@($Request.CAPolicies) | ConvertTo-Json -Compress -Depth 5) } catch { [string]$Request.CAPolicies } + $Request.MFAMethods = try { [string](@($Request.MFAMethods) | ConvertTo-Json -Compress -Depth 5) } catch { [string]$Request.MFAMethods } + $Request + } } Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null @@ -47,7 +53,7 @@ function Push-ListMFAUsersQueue { } Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null } finally { - Update-CippQueueEntry -RowKey $QueueItem -Status 'Completed' + #Update-CippQueueEntry -RowKey $QueueItem -Status 'Completed' } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-UpdatePermissionsQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-UpdatePermissionsQueue.ps1 index abc0fd0b7814..165b652c006b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-UpdatePermissionsQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-UpdatePermissionsQueue.ps1 @@ -4,17 +4,50 @@ function Push-UpdatePermissionsQueue { Entrypoint #> param($Item) - Write-Host "Applying permissions for $($Item.defaultDomainName)" - $Table = Get-CIPPTable -TableName cpvtenants - $CPVRows = Get-CIPPAzDataTableEntity @Table | Where-Object -Property Tenant -EQ $Item.customerId - 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.customerId - } - Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Item.customerId - Add-CIPPDelegatedPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Item.customerId + try { + $DomainRefreshRequired = $false + + if (!$Item.defaultDomainName) { + $DomainRefreshRequired = $true + } + + Write-Information "Applying permissions for $($Item.displayName)" + $Table = Get-CIPPTable -TableName cpvtenants + $CPVRows = Get-CIPPAzDataTableEntity @Table | Where-Object -Property Tenant -EQ $Item.customerId + + 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-Information 'Adding CPV permissions' + Set-CIPPCPVConsent -Tenantfilter $Item.customerId + $DomainRefreshRequired = $true + } + Write-Information 'Updating permissions' + 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.displayName)" -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 + Write-Information 'Pushing CIPP-SAM admin roles' + Set-CIPPSAMAdminRoles -TenantFilter $Item.customerId + + $Table = Get-CIPPTable -TableName cpvtenants + $unixtime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds + $GraphRequest = @{ + LastApply = "$unixtime" + applicationId = "$($ENV:applicationId)" + Tenant = "$($Item.customerId)" + PartitionKey = 'Tenant' + RowKey = "$($Item.customerId)" + } + Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force + + if ($DomainRefreshRequired) { + $UpdatedTenant = Get-Tenants -TenantFilter $Item.customerId -TriggerRefresh + if ($UpdatedTenant.defaultDomainName) { + Write-Information "Updated tenant domains $($UpdatedTenant.defaultDomainName)" + } + } + } catch { + Write-Information "Error updating permissions for $($Item.displayName)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 index 873af42e2e43..b16829c6a3a7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 @@ -49,7 +49,7 @@ function Push-AuditLogTenant { } if (($NewBundles | Measure-Object).Count -gt 0) { - Add-CIPPAzDataTableEntity @AuditBundleTable -Entity $NewBundles + Add-CIPPAzDataTableEntity @AuditBundleTable -Entity $NewBundles -Force Write-Information ($NewBundles | ConvertTo-Json -Depth 5 -Compress) $Batch = $NewBundles | Select-Object @{Name = 'ContentId'; Expression = { $_.RowKey } }, @{Name = 'TenantFilter'; Expression = { $_.PartitionKey } }, @{Name = 'FunctionName'; Expression = { 'AuditLogBundleProcessing' } } @@ -62,4 +62,4 @@ function Push-AuditLogTenant { Write-Host "Started orchestration with ID = '$InstanceId'" } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-Schedulerwebhookcreation.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-Schedulerwebhookcreation.ps1 index e893aac710ba..bba8630e91c5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-Schedulerwebhookcreation.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-Schedulerwebhookcreation.ps1 @@ -22,6 +22,13 @@ function Push-Schedulerwebhookcreation { if ($Row.tenantid -ne 'AllTenants') { Remove-AzDataTableEntity @Table -Entity $Row } + if (($Webhook | Measure-Object).Count -gt 1) { + $Webhook = $Webhook | Select-Object -First 1 + $WebhooksToRemove = $ExistingWebhooks | Where-Object { $_.RowKey -ne $Webhook.RowKey } + foreach ($RemoveWebhook in $WebhooksToRemove) { + Remove-AzDataTableEntity @WebhookTable -Entity $RemoveWebhook + } + } } else { Write-Information "No existing webhook for $Tenant - $($Row.webhookType) - Time to create." try { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionsConfig.ps1 index 2484fd9885dd..be740805a61e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionsConfig.ps1 @@ -19,8 +19,13 @@ Function Invoke-ExecExtensionsConfig { Write-Information 'PowerShell HTTP trigger function processed a request.' $results = try { if ($Request.Body.CIPPAPI.Enabled) { - $APIConfig = New-CIPPAPIConfig -ExecutingUser $Request.Headers.'x-ms-client-principal' -resetpassword $Request.Body.CIPPAPI.ResetPassword - $AddedText = $APIConfig.Results + try { + $APIConfig = New-CIPPAPIConfig -ExecutingUser $Request.Headers.'x-ms-client-principal' -resetpassword $Request.Body.CIPPAPI.ResetPassword + $AddedText = $APIConfig.Results + } catch { + $AddedText = ' Could not enable CIPP-API. Check the CIPP documentation for API requirements.' + $Request.Body = $Request.Body | Select-Object * -ExcludeProperty CIPPAPI + } } # Check if NinjaOne URL is set correctly and the instance has at least version 5.6 @@ -31,7 +36,7 @@ Function Invoke-ExecExtensionsConfig { throw "Failed to connect to NinjaOne check your Instance is set correctly eg 'app.ninjarmmm.com'" } if ($Version -lt [version]'5.6.0.0') { - throw 'NinjaOne 5.6.0.0 is required. This will be rolling out regionally between the end of November and mid-December. Please try again at a later date.' + throw 'NinjaOne 5.6.0.0 is required.' } } @@ -84,9 +89,9 @@ Function Invoke-ExecExtensionsConfig { Add-AzDataTableEntity @ConfigTable -Entity $AddObject -Force Register-CIPPExtensionScheduledTasks - "Successfully set the configuration. $AddedText" + "Successfully saved the extension configuration. $AddedText" } catch { - "Failed to set configuration: $($_.Exception.message) Linenumber: $($_.InvocationInfo.ScriptLineNumber)" + "Failed to save the extensions configuration: $($_.Exception.message) Linenumber: $($_.InvocationInfo.ScriptLineNumber)" } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCPVPermissions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCPVPermissions.ps1 index db0bae59c71b..61b32f4bdcf5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCPVPermissions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCPVPermissions.ps1 @@ -19,6 +19,7 @@ Function Invoke-ExecCPVPermissions { Write-Host "Our tenant is $($Tenant.displayName) - $($Tenant.defaultDomainName)" + $TenantFilter = $Request.Query.TenantFilter $CPVConsentParams = @{ TenantFilter = $Request.Query.TenantFilter } @@ -27,16 +28,21 @@ Function Invoke-ExecCPVPermissions { } $GraphRequest = try { - Set-CIPPCPVConsent @CPVConsentParams - Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Request.Query.TenantFilter - Add-CIPPDelegatedPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Request.Query.TenantFilter + if ($TenantFilter -ne 'PartnerTenant') { + Set-CIPPCPVConsent @CPVConsentParams + } else { + $TenantFilter = $env:TenantId + } + Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $TenantFilter + Add-CIPPDelegatedPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $TenantFilter + Set-CIPPSAMAdminRoles -TenantFilter $TenantFilter $Success = $true } catch { "Failed to update permissions for $($Tenant.displayName): $($_.Exception.Message)" $Success = $false } - $Tenant = Get-Tenants -IncludeAll | Where-Object -Property customerId -EQ $TenantFilter + $Tenant = Get-Tenants -IncludeAll | Where-Object -Property customerId -EQ $TenantFilter | Select-Object -First 1 # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ @@ -44,7 +50,7 @@ Function Invoke-ExecCPVPermissions { Body = @{ Results = $GraphRequest Metadata = @{ - Heading = 'CPV Permission - {0} ({1})' -f $Tenant.displayName, $Tenant.defaultDomainName + Heading = ('CPV Permission - {0} ({1})' -f $Tenant.displayName, $Tenant.defaultDomainName) Success = $Success } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMRoles.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMRoles.ps1 new file mode 100644 index 000000000000..b001693e4cef --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMRoles.ps1 @@ -0,0 +1,42 @@ +function Invoke-ExecSAMRoles { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.SuperAdmin.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $SAMRolesTable = Get-CIPPTable -tablename 'SAMRoles' + switch ($Request.Query.Action) { + 'Update' { + $Entity = [pscustomobject]@{ + PartitionKey = 'SAMRoles' + RowKey = 'SAMRoles' + Roles = [string](ConvertTo-Json -Depth 5 -Compress -InputObject $Request.Body.Roles) + Tenants = [string](ConvertTo-Json -Depth 5 -Compress -InputObject $Request.Body.Tenants) + } + $null = Add-CIPPAzDataTableEntity @SAMRolesTable -Entity $Entity -Force + $Body = [pscustomobject]@{'Results' = 'Successfully updated SAM roles' } + } + default { + $SAMRoles = Get-CIPPAzDataTableEntity @SAMRolesTable + $Roles = @($SAMRoles.Roles | ConvertFrom-Json) + $Tenants = @($SAMRoles.Tenants | ConvertFrom-Json) + $Body = @{ + 'Roles' = $Roles + 'Tenants' = $Tenants + 'Metadata' = @{ + 'RoleCount' = ($Roles | Measure-Object).Count + 'TenantCount' = ($Tenants | Measure-Object).Count + } + } | ConvertTo-Json -Depth 5 + } + } + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 index 9f38b50965a0..490b7c6d892a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 @@ -166,6 +166,11 @@ Function Invoke-ExecSAMSetup { } catch { Write-Host "didn't deploy spn for Teams, probably already there." } + try { + $SPNO365Manage = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/servicePrincipals' -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method POST -Body "{ `"appId`": `"c5393580-f805-4401-95e8-94b7a6ef2fc2`" }" -ContentType 'application/json') + } catch { + Write-Host "didn't deploy spn for O365 Management, probably already there." + } try { $SPNPartnerCenter = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/servicePrincipals' -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method POST -Body "{ `"appId`": `"fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd`" }" -ContentType 'application/json') } catch { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAPDevice.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAPDevice.ps1 index b9ff114c5ffd..d74d69cc0074 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAPDevice.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAPDevice.ps1 @@ -23,9 +23,26 @@ Function Invoke-AddAPDevice { $Devices = ConvertTo-Json @($rawDevices) $Result = try { $CurrentStatus = (New-GraphgetRequest -uri "https://api.partnercenter.microsoft.com/v1/customers/$tenantfilter/DeviceBatches" -scope 'https://api.partnercenter.microsoft.com/user_impersonation') - if ($groupname -in $CurrentStatus.items.id) { throw 'This device batch name already exists. The batch name must be unique.' } - $body = '{"batchId":"' + $($GroupName) + '","devices":' + $Devices + '}' - $GraphRequest = (New-GraphPostRequest -returnHeaders $true -uri "https://api.partnercenter.microsoft.com/v1/customers/$TenantFilter/DeviceBatches" -body $body -scope 'https://api.partnercenter.microsoft.com/user_impersonation') + if ($groupname -in $CurrentStatus.items.id) { + Write-Host 'Gonna do an update!' + $body = $request.body.autopilotData | ForEach-Object { + $Device = $_ + [pscustomobject]@{ + deviceBatchId = $GroupName + hardwareHash = $Device.hardwareHash + serialNumber = $Device.SerialNumber + productKey = $Device.productKey + oemManufacturerName = $Device.oemManufacturerName + modelName = $Device.modelName + } + } + $body = ConvertTo-Json -Depth 10 -Compress -InputObject @($body) + Write-Host $body + $GraphRequest = (New-GraphPostRequest -returnHeaders $true -uri "https://api.partnercenter.microsoft.com/v1/$($CurrentStatus.items.deviceslink.uri)" -body $body -scope 'https://api.partnercenter.microsoft.com/user_impersonation') + } else { + $body = '{"batchId":"' + $($GroupName) + '","devices":' + $Devices + '}' + $GraphRequest = (New-GraphPostRequest -returnHeaders $true -uri "https://api.partnercenter.microsoft.com/v1/customers/$TenantFilter/DeviceBatches" -body $body -scope 'https://api.partnercenter.microsoft.com/user_impersonation') + } $Amount = 0 do { Write-Host "Checking status of import job for $GroupName" @@ -35,14 +52,15 @@ Function Invoke-AddAPDevice { } until ($Newstatus.status -eq 'finished' -or $amount -eq 4) if ($NewStatus.status -ne 'finished') { throw 'Could not retrieve status of import - This job might still be running. Check the autopilot device list in 10 minutes for the latest status.' } Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $($Request.body.TenantFilter) -message "Created Autopilot devices group. Group ID is $GroupName" -Sev 'Info' + [PSCustomObject]@{ Status = 'Import Job Completed' - Devices = @($NewStatus.devicesStatus) + Devices = @($NewStatus.devicesStatus) } } catch { [PSCustomObject]@{ Status = "$($Request.body.TenantFilter): Failed to create autopilot devices. $($_.Exception.Message)" - Devices = @() + Devices = @() } Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $($Request.body.TenantFilter) -message "Failed to create autopilot devices. $($_.Exception.Message)" -Sev 'Error' } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 index dea49aa6c1ab..9d6865355490 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 @@ -27,6 +27,7 @@ Function Invoke-AddPolicy { try { Write-Host 'Calling Adding policy' Set-CIPPIntunePolicy -TemplateType $Request.body.TemplateType -Description $description -DisplayName $displayname -RawJSON $RawJSON -AssignTo $AssignTo -tenantFilter $Tenant + "Added policy $($Displayname) to $($Tenant)" Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added policy $($Displayname)" -Sev 'Info' } catch { "Failed to add policy for $($Tenant): $($_.Exception.Message)" diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 index 56291e02341e..38b476922adc 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 @@ -13,134 +13,42 @@ Function Invoke-AddUser { $APIName = 'AddUser' Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - $Results = [System.Collections.Generic.List[string]]::new() $UserObj = $Request.body # Write to the Azure Functions log stream. Write-Host 'PowerShell HTTP trigger function processed a request.' - try { - $license = $UserObj.license - $Aliases = ($UserObj.AddedAliases) -split '\s' - $password = if ($UserObj.password) { $UserObj.password } else { New-passwordString } - $UserprincipalName = "$($UserObj.Username)@$($UserObj.Domain)" - $BodyToship = [pscustomobject] @{ - 'givenName' = $UserObj.FirstName - 'surname' = $UserObj.LastName - 'accountEnabled' = $true - 'displayName' = $UserObj.DisplayName - 'department' = $UserObj.Department - 'mailNickname' = $UserObj.Username - 'userPrincipalName' = $UserprincipalName - 'usageLocation' = $UserObj.usageLocation - 'city' = $UserObj.City - 'country' = $UserObj.Country - 'jobtitle' = $UserObj.Jobtitle - 'mobilePhone' = $UserObj.MobilePhone - 'streetAddress' = $UserObj.streetAddress - 'postalCode' = $UserObj.PostalCode - 'companyName' = $UserObj.CompanyName - 'passwordProfile' = @{ - 'forceChangePasswordNextSignIn' = [bool]$UserObj.MustChangePass - 'password' = $password + if ($UserObj.Scheduled.Enabled) { + $TaskBody = [pscustomobject]@{ + TenantFilter = 'AllTenants' + Name = "New user creation: $($UserObj.User)@$($UserObj.Domain)" + Command = @{ + value = 'New-CIPPUserTask' + label = 'New-CIPPUserTask' } - } - if ($userobj.businessPhone) { $bodytoShip | Add-Member -NotePropertyName businessPhones -NotePropertyValue @($UserObj.businessPhone) } - if ($UserObj.addedAttributes) { - Write-Host 'Found added attribute' - Write-Host "Added attributes: $($UserObj.addedAttributes | ConvertTo-Json)" - $UserObj.addedAttributes.GetEnumerator() | ForEach-Object { - $results.add("Added property $($_.Key) with value $($_.value)") - $bodytoShip | Add-Member -NotePropertyName $_.Key -NotePropertyValue $_.Value + Parameters = [pscustomobject]@{ userobj = $UserObj } + ScheduledTime = $UserObj.Scheduled.date + PostExecution = @{ + Webhook = [bool]$Request.Body.PostExecution.Webhook + Email = [bool]$Request.Body.PostExecution.Email + PSA = [bool]$Request.Body.PostExecution.PSA } } - $bodyToShip = ConvertTo-Json -Depth 10 -InputObject $BodyToship -Compress - $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $UserObj.tenantID -type POST -body $BodyToship -verbose - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($UserObj.tenantID) -message "Created user $($UserObj.displayname) with id $($GraphRequest.id) " -Sev 'Info' - - #PWPush - try { - $PasswordLink = New-PwPushLink -Payload $password - if ($PasswordLink) { - $password = $PasswordLink - } - } catch { - + Add-CIPPScheduledTask -Task $TaskBody -hidden $false -DisallowDuplicateName $true + $body = [pscustomobject] @{ + 'Results' = @("Successfully created scheduled task to create user $($UserObj.DisplayName)") } - $results.add('Created user.') - $results.add("Username: $($UserprincipalName)") - $results.add("Password: $password") - } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($UserObj.tenantID) -message "Failed to create user. Error:$($_.Exception.Message)" -Sev 'Error' - $body = $results.add("Failed to create user. $($_.Exception.Message)" ) - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = @{ Results = $Results } - }) - exit 1 - } - - try { - if ($license) { - Write-Host ($UserObj | ConvertTo-Json) - $licenses = (($UserObj | Select-Object 'License_*').psobject.properties | Where-Object { $_.value -EQ $true }).name -replace 'License_', '' - Write-Host "Lics are: $licences" - $LicenseBody = if ($licenses.count -ge 2) { - $liclist = foreach ($license in $Licenses) { '{"disabledPlans": [],"skuId": "' + $license + '" },' } - '{"addLicenses": [' + $LicList + '], "removeLicenses": [ ] }' - } else { - '{"addLicenses": [ {"disabledPlans": [],"skuId": "' + $licenses + '" }],"removeLicenses": [ ]}' + } else { + $CreationResults = New-CIPPUserTask -userobj $UserObj -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' + $body = [pscustomobject] @{ + 'Results' = $CreationResults.Results + 'Username' = $CreationResults.username + 'Password' = $CreationResults.password + 'CopyFrom' = @{ + 'Success' = $CreationResults.CopyFrom.Success + 'Error' = $CreationResults.CopyFrom.Error } - Write-Host $LicenseBody - $LicRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($GraphRequest.id)/assignlicense" -tenantid $UserObj.tenantID -type POST -body $LicenseBody -verbose - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($UserObj.tenantid) -message "Assigned user $($UserObj.DisplayName) license $($licences)" -Sev 'Info' - $body = $results.add('Assigned licenses.') } - } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($userobj.tenantID) -message "Failed to assign the license. Error:$($_.Exception.Message)" -Sev 'Error' - $body = $results.add("Failed to assign the license. $($_.Exception.Message)") - } - - try { - if ($Aliases) { - foreach ($Alias in $Aliases) { - Write-Host $Alias - New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($GraphRequest.id)" -tenantid $UserObj.tenantID -type 'patch' -body "{`"mail`": `"$Alias`"}" -verbose - } - New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($GraphRequest.id)" -tenantid $UserObj.tenantID -type 'patch' -body "{`"mail`": `"$UserprincipalName`"}" -verbose - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($UserObj.tenantID) -message "Added alias $($Alias) to $($UserObj.DisplayName)" -Sev 'Info' - $body = $results.add("Added Aliases: $($Aliases -join ',')") - } - } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($userobj.tenantID) -message "Failed to create the Aliases. Error:$($_.Exception.Message)" -Sev 'Error' - $body = $results.add("Failed to create the Aliases: $($_.Exception.Message)") } - if ($Request.body.CopyFrom -ne '') { - $CopyFrom = Set-CIPPCopyGroupMembers -ExecutingUser $request.headers.'x-ms-client-principal' -CopyFromId $Request.body.CopyFrom -UserID $UserprincipalName -TenantFilter $UserObj.tenantID - $CopyFrom.Success | ForEach-Object { $results.Add($_) } - $CopyFrom.Error | ForEach-Object { $results.Add($_) } - } - - if ($Request.body.setManager) { - $ManagerBody = [PSCustomObject]@{'@odata.id' = "https://graph.microsoft.com/beta/users/$($Request.body.setManager.value)" } - $ManagerBodyJSON = ConvertTo-Json -Compress -Depth 10 -InputObject $ManagerBody - New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($GraphRequest.id)/manager/`$ref" -tenantid $UserObj.tenantID -type PUT -body $ManagerBodyJSON -Verbose - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $UserObj.tenantID -message "Set $($UserObj.DisplayName)'s manager to $($Request.body.setManager.label)" -Sev 'Info' - $results.add("Success. Set $($UserObj.DisplayName)'s manager to $($Request.body.setManager.label)") - } - - $copyFromResults = @{ - 'Success' = $CopyFrom.Success - 'Error' = $CopyFrom.Error - } - - $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 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogs.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogs.ps1 new file mode 100644 index 000000000000..2582bc961e74 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogs.ps1 @@ -0,0 +1,84 @@ +function Invoke-ListAuditLogs { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.Alert.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = 'ListAuditLogs' + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + $TenantFilter = $Request.Query.TenantFilter + $FilterConditions = [System.Collections.Generic.List[string]]::new() + + if ($Request.Query.LogId) { + $FilterConditions.Add("RowKey eq '$($Request.Query.LogId)'") + } else { + if ($TenantFilter -and $TenantFilter -ne 'AllTenants') { + $FilterConditions.Add("Tenant eq '$TenantFilter'") + } + + if (!$Request.Query.StartDate -and !$Request.Query.EndDate -and !$Request.Query.RelativeTime) { + $Request.Query.StartDate = (Get-Date).AddDays(-1).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + $Request.Query.EndDate = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } + + if ($Request.Query.RelativeTime) { + $RelativeTime = $Request.Query.RelativeTime + + if ($RelativeTime -match '(\d+)([dhm])') { + $EndDate = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + $Interval = [Int32]$Matches[1] + switch ($Matches[2]) { + 'd' { $StartDate = (Get-Date).AddDays(-$Interval).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') } + 'h' { $StartDate = (Get-Date).AddHours(-$Interval).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') } + 'm' { $StartDate = (Get-Date).AddMinutes(-$Interval).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') } + } + } + $FilterConditions.Add("Timestamp ge datetime'$StartDate' and Timestamp le datetime'$EndDate'") + } else { + if ($Request.Query.StartDate) { + if ($Request.Query.StartDate -match '^\d+$') { + $Request.Query.StartDate = [DateTimeOffset]::FromUnixTimeSeconds($Request.Query.StartDate).DateTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } else { + $StartDate = (Get-Date $Request.Query.StartDate).ToString('yyyy-MM-ddTHH:mm:ssZ') + } + $FilterConditions.Add("Timestamp ge datetime'$StartDate'") + + if ($Request.Query.EndDate) { + if ($Request.Query.EndDate -match '^\d+$') { + $Request.Query.EndDate = [DateTimeOffset]::FromUnixTimeSeconds($Request.Query.EndDate).DateTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } else { + $EndDate = (Get-Date $Request.Query.EndDate).ToString('yyyy-MM-ddTHH:mm:ssZ') + } + $FilterConditions.Add("Timestamp le datetime'$EndDate'") + } + } + } + } + + $Table = Get-CIPPTable -TableName 'AuditLogs' + if ($FilterConditions) { + $Table.Filter = $FilterConditions -join ' and ' + } + $AuditLogs = Get-CIPPAzDataTableEntity @Table | ForEach-Object { + $_.Data = try { $_.Data | ConvertFrom-Json } catch { $_.AuditData } + $_ + } + + $Body = @{ + Results = @($AuditLogs) + Metadata = @{ + Count = $AuditLogs.Count + Filter = $Table.Filter ?? '' + } + } + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecNamedLocation.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecNamedLocation.ps1 new file mode 100644 index 000000000000..e1961e25313e --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecNamedLocation.ps1 @@ -0,0 +1,42 @@ +using namespace System.Net + +Function Invoke-ExecNamedLocation { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.ConditionalAccess.ReadWrite + #> + [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.' + + $TenantFilter = $Request.Body.TenantFilter + $NamedLocationId = $Request.Body.NamedLocationId + $change = $Request.Body.change + $content = $Request.Body.input + + try { + $results = Set-CIPPNamedLocation -NamedLocationId $NamedLocationId -TenantFilter $TenantFilter -change $change -content $content -ExecutingUser $request.headers.'x-ms-client-principal' + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -message "Failed to edit named location: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + $results = "Failed to edit named location. Error: $($ErrorMessage.NormalizedError)" + } + + + $body = [pscustomobject]@{'Results' = @($results) } + + # 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/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 index 91cfa3388bb2..0e737bb7f778 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 @@ -12,7 +12,7 @@ Function Invoke-ExecGDAPInvite { $APIName = 'ExecGDAPInvite' Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - $RoleMappings = $Request.body.gdapRoles + $RoleMappings = $Request.Body.gdapRoles if ($RoleMappings.roleDefinitionId -contains '62e90394-69f5-4237-9190-012177145e10') { $AutoExtendDuration = 'PT0S' @@ -49,9 +49,13 @@ Function Invoke-ExecGDAPInvite { if ($NewRelationshipRequest.action -eq 'lockForApproval') { $InviteUrl = "https://admin.microsoft.com/AdminPortal/Home#/partners/invitation/granularAdminRelationships/$($NewRelationship.id)" - $Uri = ([System.Uri]$TriggerMetadata.Headers.Referer) - $TableFilter = [System.Web.HttpUtility]::UrlEncode(('Complex: id eq {0}' -f $NewRelationship.id)) - $OnboardingUrl = $Uri.AbsoluteUri.Replace($Uri.PathAndQuery, "/tenant/administration/tenant-onboarding-wizard?tableFilter=$TableFilter") + try { + $Uri = ([System.Uri]$TriggerMetadata.Headers.Referer) + $TableFilter = [System.Web.HttpUtility]::UrlEncode(('Complex: id eq {0}' -f $NewRelationship.id)) + $OnboardingUrl = $Uri.AbsoluteUri.Replace($Uri.PathAndQuery, "/tenant/administration/tenant-onboarding-wizard?tableFilter=$TableFilter") + } catch { + $OnboardingUrl = $null + } $InviteEntity = [PSCustomObject]@{ 'PartitionKey' = 'invite' diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ListGDAPInvite.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ListGDAPInvite.ps1 index 218f0248f2cc..814cdf4ae693 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ListGDAPInvite.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ListGDAPInvite.ps1 @@ -21,7 +21,10 @@ Function Invoke-ListGDAPInvite { if (![string]::IsNullOrEmpty($Request.Query.RelationshipId)) { $Invite = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$($Request.Query.RelationshipId)'" } else { - $Invite = Get-CIPPAzDataTableEntity @Table + $Invite = Get-CIPPAzDataTableEntity @Table | ForEach-Object { + $_.RoleMappings = try { $_.RoleMappings | ConvertFrom-Json } catch { $_.RoleMappings } + $_ + } } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsers.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsers.ps1 index e73aa9205517..643900b2ccd8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsers.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsers.ps1 @@ -24,13 +24,14 @@ Function Invoke-ListMFAUsers { $Rows = Get-CIPPAzDataTableEntity @Table | Where-Object -Property Timestamp -GT (Get-Date).AddHours(-2) if (!$Rows) { - $Queue = New-CippQueueEntry -Name 'MFA Users - All Tenants' -Link '/identity/reports/mfa-report?customerId=AllTenants' + $TenantList = Get-Tenants -IncludeErrors + $Queue = New-CippQueueEntry -Name 'MFA Users - All Tenants' -Link '/identity/reports/mfa-report?customerId=AllTenants' -TotalTasks ($TenantList | Measure-Object).Count Write-Information ($Queue | ConvertTo-Json) #Push-OutputBinding -Name mfaqueue -Value $Queue.RowKey $GraphRequest = [PSCustomObject]@{ UPN = 'Loading data for all tenants. Please check back in a few minutes' } - $Batch = Get-Tenants -IncludeErrors | ForEach-Object { + $Batch = $TenantList | ForEach-Object { $_ | Add-Member -NotePropertyName FunctionName -NotePropertyValue 'ListMFAUsersQueue' $_ | Add-Member -NotePropertyName QueueId -NotePropertyValue $Queue.RowKey $_ @@ -46,6 +47,15 @@ Function Invoke-ListMFAUsers { Write-Host "Started permissions orchestration with ID = '$InstanceId'" } } else { + $Rows = foreach ($Row in $Rows) { + if ($Row.CAPolicies) { + $Row.CAPolicies = try { $Row.CAPolicies | ConvertFrom-Json } catch { $Row.CAPolicies } + } + if ($Row.MFAMethods) { + $Row.MFAMethods = try { $Row.MFAMethods | ConvertFrom-Json } catch { $Row.MFAMethods } + } + $Row + } $GraphRequest = $Rows } } diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 index fc635a188857..bae1da836afc 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 @@ -11,7 +11,8 @@ function Get-Tenants { [switch]$IncludeErrors, [switch]$SkipDomains, [switch]$TriggerRefresh, - [switch]$CleanOld + [switch]$CleanOld, + [string]$TenantFilter ) $TenantsTable = Get-CippTable -tablename 'Tenants' @@ -29,6 +30,24 @@ function Get-Tenants { } else { $Filter = "PartitionKey eq 'Tenants' and Excluded eq false and GraphErrorCount lt 50" } + + if ($TenantFilter) { + Write-Information "Getting tenant $TenantFilter" + if ($TenantFilter -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') { + $Filter = "{0} and customerId eq '{1}'" -f $Filter, $TenantFilter + # create where-object scriptblock + $IncludedTenantFilter = [scriptblock]::Create("`$_.customerId -eq '$TenantFilter'") + $RelationshipFilter = " and customer/tenantId eq '$TenantFilter'" + } else { + $Filter = "{0} and defaultDomainName eq '{1}'" -f $Filter, $TenantFilter + $IncludedTenantFilter = [scriptblock]::Create("`$_.defaultDomainName -eq '$TenantFilter'") + $RelationshipFilter = '' + } + } else { + $IncludedTenantFilter = [scriptblock]::Create('$true') + $RelationshipFilter = '' + } + $IncludedTenantsCache = Get-CIPPAzDataTableEntity @TenantsTable -Filter $Filter if (($IncludedTenantsCache | Measure-Object).Count -eq 0) { @@ -55,7 +74,7 @@ function Get-Tenants { if (($BuildRequired -or $TriggerRefresh.IsPresent) -and $PartnerTenantState.state -ne 'owntenant') { #get the full list of tenants - $GDAPRelationships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active' and not startsWith(displayName,'MLT_')&`$select=customer,autoExtendDuration,endDateTime&`$top=300" -NoAuthCheck:$true + $GDAPRelationships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active' and not startsWith(displayName,'MLT_')$RelationshipFilter&`$select=customer,autoExtendDuration,endDateTime&`$top=300" -NoAuthCheck:$true $GDAPList = foreach ($Relationship in $GDAPRelationships) { [PSCustomObject]@{ customerId = $Relationship.customer.tenantId @@ -65,7 +84,7 @@ function Get-Tenants { } } - $ActiveRelationships = $GDAPList | Where-Object { $_.customerId -notin $SkipListCache.customerId } + $ActiveRelationships = $GDAPList | Where-Object $IncludedTenantFilter | Where-Object { $_.customerId -notin $SkipListCache.customerId } $TenantList = $ActiveRelationships | Group-Object -Property customerId | ForEach-Object { #Write-Host "Processing $($_.Name) to add to tenant list." $ExistingTenantInfo = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and RowKey eq '$($_.Name)'" @@ -152,7 +171,7 @@ function Get-Tenants { }) | Out-Null } - foreach ($Tenant in $TenantList) { + foreach ($Tenant in $TenantList | Where-Object $IncludedTenantFilter) { if ($Tenant.defaultDomainName -eq 'Invalid' -or !$Tenant.defaultDomainName) { Write-LogMessage -API 'Get-Tenants' -message "We're skipping $($Tenant.displayName) as it has an invalid default domain name. Something is up with this instance." -level 'Critical' continue diff --git a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 index 89b36c348483..fdded4391dd3 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 @@ -106,9 +106,15 @@ function New-ExoRequest { $RedirectedHost = ([System.Uri]($ComplianceHeaders.Location | Select-Object -First 1)).Host $RedirectedHostname = '{0}.ps.compliance.protection.outlook.com' -f ($RedirectedHost -split '\.' | Select-Object -First 1) $Resource = "https://$($RedirectedHostname)" - $Tenant | Add-Member -MemberType NoteProperty -Name ComplianceUrl -Value $Resource - $TenantTable = Get-CIPPTable -tablename 'Tenants' - Add-CIPPAzDataTableEntity @TenantTable -Entity $Tenant -Force + try { + $null = [System.Uri]$Resource + $Tenant | Add-Member -MemberType NoteProperty -Name ComplianceUrl -Value $Resource + $TenantTable = Get-CIPPTable -tablename 'Tenants' + Add-CIPPAzDataTableEntity @TenantTable -Entity $Tenant -Force + } catch { + Write-Error "Failed to get the Compliance URL for $($tenant.defaultDomainName), invalid URL - check the Anchor and try again." + return + } } else { $Resource = $Tenant.ComplianceUrl } diff --git a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 index 0c0af71bc86e..ad18603f2862 100644 --- a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 +++ b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 @@ -176,10 +176,12 @@ function Get-GraphRequestList { } try { + $DefaultDomainName = $_.defaultDomainName + Write-Host "Default domain name is $DefaultDomainName" Get-GraphRequestList @GraphRequestParams | Select-Object *, @{l = 'Tenant'; e = { $_.defaultDomainName } }, @{l = 'CippStatus'; e = { 'Good' } } } catch { [PSCustomObject]@{ - Tenant = $_.defaultDomainName + Tenant = $DefaultDomainName CippStatus = "Could not connect to tenant. $($_.Exception.message)" } } diff --git a/Modules/CIPPCore/Public/New-CIPPTemplateRun.ps1 b/Modules/CIPPCore/Public/New-CIPPTemplateRun.ps1 new file mode 100644 index 000000000000..6265eafb927b --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPTemplateRun.ps1 @@ -0,0 +1,203 @@ +function New-CIPPTemplateRun { + [CmdletBinding()] + param ( + $TemplateSettings, + $TenantFilter + ) + $Table = Get-CippTable -tablename 'templates' + $ExistingTemplates = (Get-CIPPAzDataTableEntity @Table) | ForEach-Object { + $data = $_.JSON | ConvertFrom-Json -Depth 100 + $data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.RowKey -Force + $data | Add-Member -NotePropertyName 'PartitionKey' -NotePropertyValue $_.PartitionKey -Force + $data + } | Sort-Object -Property displayName + + + $Tasks = foreach ($key in $TemplateSettings.Keys) { + if ($TemplateSettings[$key] -eq $true) { + $key + } + } + + + foreach ($Task in $Tasks) { + Write-Host "Working on task $Task" + switch ($Task) { + 'ca' { + Write-Host "Template Conditional Access Policies for $TenantFilter" + $Policies = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter + Write-Host 'Creating templates for found Conditional Access Policies' + foreach ($policy in $policies) { + try { + $Template = New-CIPPCATemplate -TenantFilter $TenantFilter -JSON $policy + #check existing templates, if the displayName is the same, overwrite it. + $ExistingPolicy = $ExistingTemplates | Where-Object { $_.displayName -eq $policy.displayName } | Select-Object -First 1 + if ($ExistingPolicy -and $ExistingPolicy.PartitionKey -eq 'CATemplate') { + "Policy $($policy.displayName) found, updating template" + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$Template" + RowKey = $ExistingPolicy.GUID + PartitionKey = 'CATemplate' + GUID = $ExistingPolicy.GUID + } -Force + } else { + "Policy $($policy.displayName) not found in existing templates, creating new template" + $GUID = (New-Guid).GUID + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$Template" + RowKey = "$GUID" + PartitionKey = 'CATemplate' + GUID = "$GUID" + } + } + + } catch { + "Failed to create a template of the Conditional Access Policy with ID: $($policy.id). Error: $($_.Exception.Message)" + } + } + } + 'intuneconfig' { + Write-Host "Backup Intune Configuration Policies for $TenantFilter" + $GraphURLS = @("https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations?`$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,microsoft.graph.unsupportedDeviceConfiguration/originalEntityTypeName&`$expand=assignments&top=1000" + 'https://graph.microsoft.com/beta/deviceManagement/windowsDriverUpdateProfiles' + "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations?`$expand=assignments&top=999" + "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations?`$expand=assignments&`$filter=microsoft.graph.androidManagedStoreAppConfiguration/appSupportsOemConfig%20eq%20true" + 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' + ) + + $Policies = foreach ($url in $GraphURLS) { + try { + $Policies = New-GraphGetRequest -uri "$($url)" -tenantid $TenantFilter + $URLName = (($url).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', '' + foreach ($Policy in $Policies) { + try { + $Template = New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName $URLName -ID $Policy.ID + $ExistingPolicy = $ExistingTemplates | Where-Object { $_.displayName -eq $Template.DisplayName } | Select-Object -First 1 + if ($ExistingPolicy -and $ExistingPolicy.PartitionKey -eq 'IntuneTemplate') { + "Policy $($Template.DisplayName) found, updating template" + $object = [PSCustomObject]@{ + Displayname = $Template.DisplayName + Description = $Template.Description + RAWJson = $Template.TemplateJson + Type = $Template.Type + GUID = $ExistingPolicy.GUID + } | ConvertTo-Json + + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$object" + RowKey = $ExistingPolicy.GUID + PartitionKey = 'IntuneTemplate' + } -Force + } else { + "Policy $($Template.DisplayName) not found in existing templates, creating new template" + $GUID = (New-Guid).GUID + $object = [PSCustomObject]@{ + Displayname = $Template.DisplayName + Description = $Template.Description + RAWJson = $Template.TemplateJson + Type = $Template.Type + GUID = $GUID + } | ConvertTo-Json + + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$object" + RowKey = "$GUID" + PartitionKey = 'IntuneTemplate' + } -Force + } + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + "Failed to create a template of the Intune Configuration Policy with ID: $($Policy.id). Error: $ErrorMessage" + } + } + } catch { + Write-Host "Failed to backup $url" + } + } + } + 'intunecompliance' { + Write-Host "Backup Intune Compliance Policies for $TenantFilter" + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies?$top=999' -tenantid $TenantFilter | ForEach-Object { + $Template = New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'deviceCompliancePolicies' -ID $_.ID + $ExistingPolicy = $ExistingTemplates | Where-Object { $_.displayName -eq $Template.DisplayName } | Select-Object -First 1 + if ($ExistingPolicy -and $ExistingPolicy.PartitionKey -eq 'IntuneTemplate') { + "Policy $($Template.DisplayName) found, updating template" + $object = [PSCustomObject]@{ + Displayname = $Template.DisplayName + Description = $Template.Description + RAWJson = $Template.TemplateJson + Type = $Template.Type + GUID = $ExistingPolicy.GUID + } | ConvertTo-Json + + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$object" + RowKey = $ExistingPolicy.GUID + PartitionKey = 'IntuneTemplate' + } -Force + } else { + "Policy $($Template.DisplayName) not found in existing templates, creating new template" + $GUID = (New-Guid).GUID + $object = [PSCustomObject]@{ + Displayname = $Template.DisplayName + Description = $Template.Description + RAWJson = $Template.TemplateJson + Type = $Template.Type + GUID = $GUID + } | ConvertTo-Json + + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$object" + RowKey = "$GUID" + PartitionKey = 'IntuneTemplate' + } -Force + } + + } + } + + 'intuneprotection' { + Write-Host "Backup Intune Protection Policies for $TenantFilter" + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceAppManagement/managedAppPolicies?$top=999' -tenantid $TenantFilter | ForEach-Object { + $Template = New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'managedAppPolicies' -ID $_.ID + $ExistingPolicy = $ExistingTemplates | Where-Object { $_.displayName -eq $Template.DisplayName } | Select-Object -First 1 + if ($ExistingPolicy -and $ExistingPolicy.PartitionKey -eq 'IntuneTemplate') { + "Policy $($Template.DisplayName) found, updating template" + $object = [PSCustomObject]@{ + Displayname = $Template.DisplayName + Description = $Template.Description + RAWJson = $Template.TemplateJson + Type = $Template.Type + GUID = $ExistingPolicy.GUID + } | ConvertTo-Json + + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$object" + RowKey = $ExistingPolicy.GUID + PartitionKey = 'IntuneTemplate' + } -Force + } else { + "Policy $($Template.DisplayName) not found in existing templates, creating new template" + $GUID = (New-Guid).GUID + $object = [PSCustomObject]@{ + Displayname = $Template.DisplayName + Description = $Template.Description + RAWJson = $Template.TemplateJson + Type = $Template.Type + GUID = $GUID + } | ConvertTo-Json + + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$object" + RowKey = "$GUID" + PartitionKey = 'IntuneTemplate' + } -Force + } + } + } + + } + } + return $BackupData +} + diff --git a/Modules/CIPPCore/Public/New-CIPPUserTask.ps1 b/Modules/CIPPCore/Public/New-CIPPUserTask.ps1 new file mode 100644 index 000000000000..ea18ca891c16 --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPUserTask.ps1 @@ -0,0 +1,58 @@ +function New-CIPPUserTask { + [CmdletBinding()] + param ( + $userobj, + $APIName = 'New User Task', + $ExecutingUser + ) + $Results = [System.Collections.Generic.List[string]]::new() + + try { + $CreationResults = New-CIPPUser -userobj $UserObj -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' + $results.add('Created New User.') + $results.add("Username: $($CreationResults.username)") + $results.add("Password: $($CreationResults.password)") + } catch { + $results.add("Failed to create user. $($_.Exception.Message)" ) + return @{'Results' = $results } + } + + try { + $licenses = (($UserObj | Select-Object 'License_*').psobject.properties | Where-Object { $_.value -EQ $true }).name -replace 'License_', '' + if ($licenses) { + $LicenseResults = Set-CIPPUserLicense -userid $CreationResults.username -TenantFilter $UserObj.tenantID -Licenses $licenses + $Results.Add($LicenseResults) + } + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($userobj.tenantID) -message "Failed to assign the license. Error:$($_.Exception.Message)" -Sev 'Error' + $body = $results.add("Failed to assign the license. $($_.Exception.Message)") + } + + try { + if ($Userobj.AddedAliases) { + $AliasResults = Add-CIPPAlias -user $CreationResults.username -Aliases ($UserObj.AddedAliases -split '\s') -UserprincipalName $UserObj.UserprincipalName -TenantFilter $UserObj.tenantID -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' + $results.add($AliasResults) + } + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($userobj.tenantID) -message "Failed to create the Aliases. Error:$($_.Exception.Message)" -Sev 'Error' + $body = $results.add("Failed to create the Aliases: $($_.Exception.Message)") + } + if ($userobj.CopyFrom -ne '') { + $CopyFrom = Set-CIPPCopyGroupMembers -ExecutingUser $request.headers.'x-ms-client-principal' -CopyFromId $userObj.CopyFrom -UserID $UserObj.UserprincipalName -TenantFilter $UserObj.tenantID + $CopyFrom.Success | ForEach-Object { $results.Add($_) } + $CopyFrom.Error | ForEach-Object { $results.Add($_) } + } + + if ($userobj.setManager) { + $ManagerResult = Set-CIPPManager -user $CreationResults.username -Manager $userObj.setManager.value -TenantFilter $UserObj.tenantID -APIName 'Set Manager' -ExecutingUser $request.headers.'x-ms-client-principal' + $results.add($ManagerResult) + } + + return @{ + Results = $results + username = $CreationResults.username + password = $CreationResults.password + CopyFrom = $CopyFrom + } +} + diff --git a/Modules/CIPPCore/Public/New-CippUser.ps1 b/Modules/CIPPCore/Public/New-CippUser.ps1 new file mode 100644 index 000000000000..c45517f62d96 --- /dev/null +++ b/Modules/CIPPCore/Public/New-CippUser.ps1 @@ -0,0 +1,69 @@ +function New-CIPPUser { + [CmdletBinding()] + param ( + $userobj, + $Aliases = 'Scheduled', + $RestoreValues, + $APIName = 'New User', + $ExecutingUser + ) + + try { + $Aliases = ($UserObj.AddedAliases) -split '\s' + $password = if ($UserObj.password) { $UserObj.password } else { New-passwordString } + $UserprincipalName = "$($UserObj.Username)@$($UserObj.Domain)" + $BodyToship = [pscustomobject] @{ + 'givenName' = $UserObj.FirstName + 'surname' = $UserObj.LastName + 'accountEnabled' = $true + 'displayName' = $UserObj.DisplayName + 'department' = $UserObj.Department + 'mailNickname' = $UserObj.Username + 'userPrincipalName' = $UserprincipalName + 'usageLocation' = $UserObj.usageLocation + 'city' = $UserObj.City + 'country' = $UserObj.Country + 'jobtitle' = $UserObj.Jobtitle + 'mobilePhone' = $UserObj.MobilePhone + 'streetAddress' = $UserObj.streetAddress + 'postalCode' = $UserObj.PostalCode + 'companyName' = $UserObj.CompanyName + 'passwordProfile' = @{ + 'forceChangePasswordNextSignIn' = [bool]$UserObj.MustChangePass + 'password' = $password + } + } + if ($userobj.businessPhone) { $bodytoShip | Add-Member -NotePropertyName businessPhones -NotePropertyValue @($UserObj.businessPhone) } + if ($UserObj.addedAttributes) { + Write-Host 'Found added attribute' + Write-Host "Added attributes: $($UserObj.addedAttributes | ConvertTo-Json)" + $UserObj.addedAttributes.GetEnumerator() | ForEach-Object { + $results.add("Added property $($_.Key) with value $($_.value)") + $bodytoShip | Add-Member -NotePropertyName $_.Key -NotePropertyValue $_.Value + } + } + $bodyToShip = ConvertTo-Json -Depth 10 -InputObject $BodyToship -Compress + $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $UserObj.tenantID -type POST -body $BodyToship -verbose + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($UserObj.tenantID) -message "Created user $($UserObj.displayname) with id $($GraphRequest.id) " -Sev 'Info' + + try { + $PasswordLink = New-PwPushLink -Payload $password + if ($PasswordLink) { + $password = $PasswordLink + } + } catch { + + } + $Results = @{ + Results = ('Created New User.') + Username = $UserprincipalName + Password = $password + } + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($UserObj.tenantID) -message "Failed to create user. Error:$($_.Exception.Message)" -Sev 'Error' + $results = @{ Results = ("Failed to create user. $($_.Exception.Message)" ) } + throw "Failed to create user $($_.Exception.Message)" + } + return $Results +} + diff --git a/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 b/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 index 380431faa98f..54e0def2bb98 100644 --- a/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 +++ b/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 @@ -8,7 +8,8 @@ function Send-CIPPAlert { $JSONContent, $TenantFilter, $APIName = 'Send Alert', - $ExecutingUser + $ExecutingUser, + $TableName ) Write-Information 'Shipping Alert' $Table = Get-CIPPTable -TableName SchedulerConfig @@ -43,7 +44,26 @@ function Send-CIPPAlert { Write-Information "Could not send webhook alert to email: $($ErrorMessage.NormalizedError)" Write-LogMessage -API 'Webhook Alerts' -message "Could not send webhook alerts to email. $($ErrorMessage.NormalizedError)" -tenant $TenantFilter -sev Error -LogData $ErrorMessage } + } + if ($Type -eq 'table' -and $TableName) { + Write-Information 'Trying to send to Table' + try { + $Table = Get-CIPPTable -TableName $TableName + $Alert = @{ + PartitionKey = $TenantFilter ?? 'Alert' + RowKey = [string][guid]::NewGuid() + Title = $Title + Data = [string]$JSONContent + Tenant = $TenantFilter + } + Add-CIPPAzDataTableEntity @Table -Entity $Alert + return $Alert.RowKey + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-Information "Could not send alerts to table: $($ErrorMessage.NormalizedError)" + Write-LogMessage -API 'Webhook Alerts' -message "Could not send alerts to table: $($ErrorMessage.NormalizedError)" -tenant $TenantFilter -sev Error -LogData $ErrorMessage + } } if ($Type -eq 'webhook') { diff --git a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 index 5e94db51d179..c9f4cb5bbcff 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 @@ -69,9 +69,12 @@ function Set-CIPPAssignedPolicy { $assignmentsObject = [PSCustomObject]@{ assignments = @($assignmentsObject) } + + $AssignJSON = ($assignmentsObject | ConvertTo-Json -Depth 10 -Compress) + Write-Host "AssignJSON: $AssignJSON" if ($PSCmdlet.ShouldProcess($GroupName, "Assigning policy $PolicyId")) { Write-Host "https://graph.microsoft.com/beta/$($PlatformType)/$Type('$($PolicyId)')/assign" - $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/$($PlatformType)/$Type('$($PolicyId)')/assign" -tenantid $tenantFilter -type POST -body ($assignmentsObject | ConvertTo-Json -Depth 10) + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/$($PlatformType)/$Type('$($PolicyId)')/assign" -tenantid $tenantFilter -type POST -body $AssignJSON Write-LogMessage -user $ExecutingUser -API $APIName -message "Assigned $GroupName to Policy $PolicyId" -Sev 'Info' -tenant $TenantFilter } } catch { diff --git a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 index cb05c24df99f..2972dea4713f 100644 --- a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 @@ -13,7 +13,7 @@ function Set-CIPPIntunePolicy { switch ($TemplateType) { 'AppProtection' { $TemplateType = ($RawJSON | ConvertFrom-Json).'@odata.type' -replace '#microsoft.graph.', '' - $TemplateTypeURL = "$($TemplateType)s" + $TemplateTypeURL = if ($TemplateType -eq 'windowsInformationProtectionPolicy') { 'windowsInformationProtectionPolicies' } else { "$($TemplateType)s" } $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenantFilter if ($displayname -in $CheckExististing.displayName) { $PostType = 'edited' @@ -67,10 +67,12 @@ function Set-CIPPIntunePolicy { } 'Device' { $TemplateTypeURL = 'deviceConfigurations' - - $PolicyName = ($RawJSON | ConvertFrom-Json).displayName + $PolicyFile = $RawJSON | ConvertFrom-Json + $Null = $PolicyFile | Add-Member -MemberType NoteProperty -Name 'description' -Value $description -Force + $null = $PolicyFile | Add-Member -MemberType NoteProperty -Name 'displayName' -Value $displayname -Force + $RawJSON = ConvertTo-Json -InputObject $PolicyFile -Depth 20 $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter - if ($PolicyName -in $CheckExististing.displayName) { + if ($PolicyName -in $PolicyFile.displayName) { $PostType = 'edited' $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $PolicyName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON @@ -119,13 +121,11 @@ function Set-CIPPIntunePolicy { Write-Host "Assigning policy to $($AssignTo) with ID $($CreateRequest.id) and type $TemplateTypeURL for tenant $tenantFilter" Set-CIPPAssignedPolicy -GroupName $AssignTo -PolicyId $CreateRequest.id -Type $TemplateTypeURL -TenantFilter $tenantFilter } - "Successfully $($PostType) policy for $($tenantFilter) with display name $($Displayname)" + return "Successfully $($PostType) policy for $($tenantFilter) with display name $($Displayname)" } catch { $ErrorMessage = Get-CippException -Exception $_ - "Failed to add or set policy for $($tenantFilter) with display name $($Displayname): $($ErrorMessage.NormalizedError)" Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantFilter) -message "Failed $($PostType) policy $($Displayname). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage - continue + throw "Failed to add or set policy for $($tenantFilter) with display name $($Displayname): $($ErrorMessage.NormalizedError)" } - return $ReturnValue } diff --git a/Modules/CIPPCore/Public/Set-CIPPManager.ps1 b/Modules/CIPPCore/Public/Set-CIPPManager.ps1 new file mode 100644 index 000000000000..0b7c68c710fc --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPManager.ps1 @@ -0,0 +1,22 @@ +function Set-CIPPManager { + [CmdletBinding()] + param ( + $user, + $Manager, + $TenantFilter, + $APIName = 'Set Manager', + $ExecutingUser + ) + + try { + $ManagerBody = [PSCustomObject]@{'@odata.id' = "https://graph.microsoft.com/beta/users/$($Manager)" } + $ManagerBodyJSON = ConvertTo-Json -Compress -Depth 10 -InputObject $ManagerBody + New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($User)/manager/`$ref" -tenantid $TenantFilter -type PUT -body $ManagerBodyJSON -Verbose + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $UserObj.tenantID -message "Set $user's manager to $Manager" -Sev 'Info' + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($UserObj.tenantID) -message "Failed to Set Manager. Error:$($_.Exception.Message)" -Sev 'Error' + throw "Failed to set manager: $($_.Exception.Message)" + } + return "Set $user's manager to $Manager" +} + diff --git a/Modules/CIPPCore/Public/Set-CIPPNamedLocation.ps1 b/Modules/CIPPCore/Public/Set-CIPPNamedLocation.ps1 new file mode 100644 index 000000000000..2a5eb8477915 --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPNamedLocation.ps1 @@ -0,0 +1,47 @@ +function Set-CIPPNamedLocation { + [CmdletBinding(SupportsShouldProcess = $true)] + param( + $NamedLocationId, + $TenantFilter, + #$change should be one of 'addip','addlocation','removeip','removelocation' + [ValidateSet('addip', 'addlocation', 'removeip', 'removelocation')] + $change, + $content, + $APIName = 'Set Named Location', + $ExecutingUser + ) + + try { + $NamedLocations = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations/$NamedLocationId" -Tenantid $tenantfilter + switch ($change) { + 'addip' { + $NamedLocations.ipRanges = @($NamedLocations.ipRanges + @{ cidrAddress = $content; '@odata.type' = '#microsoft.graph.iPv4CidrRange' }) + } + 'addlocation' { + $NamedLocations.countriesAndRegions = $NamedLocations.countriesAndRegions + $content + } + 'removeip' { + $NamedLocations.ipRanges = @($NamedLocations.ipRanges | Where-Object -Property cidrAddress -NE $content) + } + 'removelocation' { + $NamedLocations.countriesAndRegions = @($NamedLocations.countriesAndRegions | Where-Object { $_ -NE $content }) + } + } + if ($PSCmdlet.ShouldProcess($GroupName, "Assigning Application $ApplicationId")) { + #Remove unneeded propertie + if ($change -like '*location*') { + $NamedLocations = $NamedLocations | Select-Object '@odata.type', 'displayName', 'countriesAndRegions', 'includeUnknownCountriesAndRegions' + } else { + $NamedLocations = $NamedLocations | Select-Object '@odata.type', 'displayName', 'ipRanges', 'isTrusted' + } + + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations/$NamedLocationId" -tenantid $TenantFilter -type PATCH -body $($NamedLocations | ConvertTo-Json -Compress -Depth 10) + Write-LogMessage -user $ExecutingUser -API $APIName -message "Edited named location. Change: $change with content $($content)" -Sev 'Info' -tenant $TenantFilter + } + return "Edited named location. Change: $change with content $($content)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to edit named location: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Failed to edit named location. Error: $($ErrorMessage.NormalizedError)" + } +} diff --git a/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 b/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 new file mode 100644 index 000000000000..a0a0808938e8 --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 @@ -0,0 +1,94 @@ +function Set-CIPPSAMAdminRoles { + <# + .SYNOPSIS + Set SAM roles + .DESCRIPTION + Set SAM roles on a tenant + .PARAMETER TenantFilter + Tenant to apply the SAM roles to + .FUNCTIONALITY + Internal + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$TenantFilter + ) + + $ActionLogs = [System.Collections.Generic.List[object]]::new() + + $SAMRolesTable = Get-CIPPTable -tablename 'SAMRoles' + $Roles = Get-CIPPAzDataTableEntity @SAMRolesTable + + $SAMRoles = $Roles.Roles | ConvertFrom-Json + $Tenants = $Roles.Tenants | ConvertFrom-Json + + if (($SAMRoles | Measure-Object).count -gt 0 -and $Tenants -contains $TenantFilter -or $Tenants -contains 'AllTenants') { + $AppMemberOf = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/servicePrincipals(appId='$($env:ApplicationId)')/memberOf/#microsoft.graph.directoryRole" -tenantid $TenantFilter -AsApp $true + + $sp = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/servicePrincipals(appId='$($env:ApplicationId)')?`$select=id,displayName" -tenantid $TenantFilter -AsApp $true) + $id = $sp.id + + $Requests = $SAMRoles | Where-Object { $AppMemberOf.roleTemplateId -notcontains $_.value } | ForEach-Object { + # Batch add service principal to directoryRole + [PSCustomObject]@{ + 'id' = $_.label + 'headers' = @{ + 'Content-Type' = 'application/json' + } + 'url' = "directoryRoles(roleTemplateId='$($_.value)')/members/`$ref" + 'method' = 'POST' + 'body' = @{ + '@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$($id)" + } + } + } + if (($Requests | Measure-Object).count -gt 0) { + $HasFailures = $false + try { + $null = New-ExoRequest -cmdlet 'New-ServicePrincipal' -cmdParams @{AppId = $env:ApplicationId; ObjectId = $id; DisplayName = 'CIPP-SAM' } -Compliance -tenantid $TenantFilter -useSystemMailbox $true -AsApp + $ActionLogs.Add('Added Service Principal to Compliance Center') + } catch { + $ActionLogs.Add('Service Principal already added to Compliance Center') + } + try { + $null = New-ExoRequest -cmdlet 'New-ServicePrincipal' -cmdParams @{AppId = $env:ApplicationId; ObjectId = $id; DisplayName = 'CIPP-SAM' } -tenantid $TenantFilter -useSystemMailbox $true -AsApp + $ActionLogs.Add('Added Service Principal to Exchange Online') + } catch { + $ActionLogs.Add('Service Principal already added to Exchange Online') + } + + Write-Verbose ($Requests | ConvertTo-Json -Depth 5) + $Results = New-GraphBulkRequest -tenantid $TenantFilter -Requests @($Requests) + $Results | ForEach-Object { + if ($_.status -eq 204) { + $ActionLogs.Add("Added service principal to directory role $($_.id)") + } else { + $ActionLogs.Add("Failed to add service principal to directoryRole $($_.id)") + Write-Verbose ($_ | ConvertTo-Json -Depth 5) + $HasFailures = $true + } + } + $LogMessage = @{ + 'API' = 'Set-CIPPSAMAdminRoles' + 'tenant' = $TenantFilter + 'tenantid' = (Get-Tenants -TenantFilter $TenantFilter -IncludeErrors).custom + 'message' = '' + 'LogData' = $ActionLogs + } + if ($HasFailures) { + $LogMessage.message = 'Errors occurred while setting Admin Roles for CIPP-SAM' + $LogMessage.sev = 'Error' + } else { + $LogMessage.message = 'Successfully set Admin Roles for CIPP-SAM' + $LogMessage.sev = 'Info' + } + Write-LogMessage @LogMessage + } else { + $ActionLogs.Add('Service principal already exists in all requested Admin Roles') + } + } else { + $ActionLogs.Add('No SAM roles found or tenant not added to CIPP-SAM roles') + } + $ActionLogs +} diff --git a/Modules/CIPPCore/Public/Set-CIPPUserLicense.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserLicense.ps1 new file mode 100644 index 000000000000..142eed413627 --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPUserLicense.ps1 @@ -0,0 +1,25 @@ +function Set-CIPPUserLicense { + [CmdletBinding()] + param ( + $userid, + $TenantFilter, + $Licenses + ) + + Write-Host "Lics are: $licences" + $LicenseBody = if ($licenses.count -ge 2) { + $liclist = foreach ($license in $Licenses) { '{"disabledPlans": [],"skuId": "' + $license + '" },' } + '{"addLicenses": [' + $LicList + '], "removeLicenses": [ ] }' + } else { + '{"addLicenses": [ {"disabledPlans": [],"skuId": "' + $licenses + '" }],"removeLicenses": [ ]}' + } + Write-Host $LicenseBody + try { + $LicRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserId)/assignlicense" -tenantid $TenantFilter -type POST -body $LicenseBody -verbose + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($UserObj.tenantid) -message "Failed to assign the license. Error:$($_.Exception.Message)" -Sev 'Error' + throw "Failed to assign the license. $($_.Exception.Message)" + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($UserObj.tenantid) -message "Assigned user $($UserObj.DisplayName) license $($licences)" -Sev 'Info' + return 'Assigned licenses.' +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 index cac25d4e75c7..98b55103c15f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 @@ -9,7 +9,7 @@ function Invoke-CIPPStandardIntuneTemplate { Write-Host 'starting template deploy' $APINAME = 'Standards' foreach ($Template in $Settings.TemplateList) { - Write-Host 'working on template deploy' + Write-Host "working on template deploy: $($Template | ConvertTo-Json)" try { $Table = Get-CippTable -tablename 'templates' $Filter = "PartitionKey eq 'IntuneTemplate'" @@ -112,7 +112,7 @@ function Invoke-CIPPStandardIntuneTemplate { } } - + #Legacy assign. if ($Settings.AssignTo) { Write-Host "Assigning Policy to $($Settings.AssignTo) the create ID is $($CreateRequest)" if ($Settings.AssignTo -eq 'customGroup') { $Settings.AssignTo = $Settings.customGroup } @@ -124,6 +124,17 @@ function Invoke-CIPPStandardIntuneTemplate { Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully created Intune Template $PolicyName policy for $($Tenant)" -sev 'Info' } } + + if ($Template.AssignedTo) { + Write-Host "New: Assigning Policy to $($Template.AssignedTo) the create ID is $($CreateRequest)" + if ($ExistingID) { + Set-CIPPAssignedPolicy -PolicyId $ExistingID.id -TenantFilter $tenant -GroupName $Template.AssignedTo -Type $TemplateTypeURL + Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully updated Intune Template $PolicyName policy for $($Tenant)" -sev 'Info' + } else { + Set-CIPPAssignedPolicy -PolicyId $CreateRequest.id -TenantFilter $tenant -GroupName $Template.AssignedTo -Type $TemplateTypeURL + Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully created Intune Template $PolicyName policy for $($Tenant)" -sev 'Info' + } + } } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update Intune Template $PolicyName, Error: $ErrorMessage" -sev 'Error' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 index 4357e0ae7ba0..8ca222457501 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 @@ -15,12 +15,16 @@ function Invoke-CIPPStandardSpamFilterPolicy { TAG "mediumimpact" ADDEDCOMPONENT - {"type":"Select","label":"Spam Action","name":"standards.SpamFilterPolicy.SpamAction","values":[{"label":"Move message to Junk Email folder","value":"MoveToJmf"},{"label":"Quarantine the message","value":"Quarantine"}]} - {"type":"Select","label":"Spam Quarantine Tag","name":"standards.SpamFilterPolicy.SpamQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} - {"type":"Select","label":"High Confidence Spam Quarantine Tag","name":"standards.SpamFilterPolicy.HighConfidenceSpamQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} - {"type":"Select","label":"Bulk Quarantine Tag","name":"standards.SpamFilterPolicy.BulkQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} - {"type":"Select","label":"Phish Quarantine Tag","name":"standards.SpamFilterPolicy.PhishQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} - {"type":"Select","label":"High Confidence Phish Quarantine Tag","name":"standards.SpamFilterPolicy.HighConfidencePhishQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + { "type": "number", "label": "Bulk email threshold (Default 7)", "name": "standards.SpamFilterPolicy.BulkThreshold", "default": 7 } + { "type": "Select", "label": "Spam Action", "name": "standards.SpamFilterPolicy.SpamAction", "values": [ { "label": "Move message to Junk Email folder", "value": "MoveToJmf" }, { "label": "Quarantine the message", "value": "Quarantine" } ] } + { "type": "Select", "label": "Spam Quarantine Tag", "name": "standards.SpamFilterPolicy.SpamQuarantineTag", "values": [ { "label": "AdminOnlyAccessPolicy", "value": "AdminOnlyAccessPolicy" }, { "label": "DefaultFullAccessPolicy", "value": "DefaultFullAccessPolicy" }, { "label": "DefaultFullAccessWithNotificationPolicy", "value": "DefaultFullAccessWithNotificationPolicy" } ] } + { "type": "Select", "label": "High Confidence Spam Action", "name": "standards.SpamFilterPolicy.HighConfidenceSpamAction", "values": [ { "label": "Quarantine the message", "value": "Quarantine" }, { "label": "Move message to Junk Email folder", "value": "MoveToJmf" } ] } + { "type": "Select", "label": "High Confidence Spam Quarantine Tag", "name": "standards.SpamFilterPolicy.HighConfidenceSpamQuarantineTag", "values": [ { "label": "AdminOnlyAccessPolicy", "value": "AdminOnlyAccessPolicy" }, { "label": "DefaultFullAccessPolicy", "value": "DefaultFullAccessPolicy" }, { "label": "DefaultFullAccessWithNotificationPolicy", "value": "DefaultFullAccessWithNotificationPolicy" } ] } + { "type": "Select", "label": "Bulk Spam Action", "name": "standards.SpamFilterPolicy.BulkSpamAction", "values": [ { "label": "Move message to Junk Email folder", "value": "MoveToJmf" }, { "label": "Quarantine the message", "value": "Quarantine" } ] } + { "type": "Select", "label": "Bulk Quarantine Tag", "name": "standards.SpamFilterPolicy.BulkQuarantineTag", "values": [ { "label": "AdminOnlyAccessPolicy", "value": "AdminOnlyAccessPolicy" }, { "label": "DefaultFullAccessPolicy", "value": "DefaultFullAccessPolicy" }, { "label": "DefaultFullAccessWithNotificationPolicy", "value": "DefaultFullAccessWithNotificationPolicy" } ] } + { "type": "Select", "label": "Phish Spam Action", "name": "standards.SpamFilterPolicy.PhishSpamAction", "values": [ { "label": "Quarantine the message", "value": "Quarantine" }, { "label": "Move message to Junk Email folder", "value": "MoveToJmf" } ] } + { "type": "Select", "label": "Phish Quarantine Tag", "name": "standards.SpamFilterPolicy.PhishQuarantineTag", "values": [ { "label": "AdminOnlyAccessPolicy", "value": "AdminOnlyAccessPolicy" }, { "label": "DefaultFullAccessPolicy", "value": "DefaultFullAccessPolicy" }, { "label": "DefaultFullAccessWithNotificationPolicy", "value": "DefaultFullAccessWithNotificationPolicy" } ] } + { "type": "Select", "label": "High Confidence Phish Quarantine Tag", "name": "standards.SpamFilterPolicy.HighConfidencePhishQuarantineTag", "values": [ { "label": "AdminOnlyAccessPolicy", "value": "AdminOnlyAccessPolicy" }, { "label": "DefaultFullAccessPolicy", "value": "DefaultFullAccessPolicy" }, { "label": "DefaultFullAccessWithNotificationPolicy", "value": "DefaultFullAccessWithNotificationPolicy" } ] } IMPACT Medium Impact POWERSHELLEQUIVALENT @@ -40,17 +44,17 @@ function Invoke-CIPPStandardSpamFilterPolicy { Select-Object -Property * $StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and - ($CurrentState.HighConfidenceSpamAction -eq 'Quarantine') -and - ($CurrentState.HighConfidenceSpamQuarantineTag -eq $Settings.HighConfidenceSpamQuarantineTag) -and ($CurrentState.SpamAction -eq $Settings.SpamAction) -and ($CurrentState.SpamQuarantineTag -eq $Settings.SpamQuarantineTag) -and - ($CurrentState.PhishSpamAction -eq 'MoveToJmf') -and - ($CurrentState.BulkSpamAction -eq 'MoveToJmf') -and + ($CurrentState.HighConfidenceSpamAction -eq $Settings.HighConfidenceSpamAction) -and + ($CurrentState.HighConfidenceSpamQuarantineTag -eq $Settings.HighConfidenceSpamQuarantineTag) -and + ($CurrentState.BulkSpamAction -eq $Settings.BulkSpamAction) -and ($CurrentState.BulkQuarantineTag -eq $Settings.BulkQuarantineTag) -and + ($CurrentState.PhishSpamAction -eq $Settings.PhishSpamAction) -and ($CurrentState.PhishQuarantineTag -eq $Settings.PhishQuarantineTag) -and ($CurrentState.HighConfidencePhishAction -eq 'Quarantine') -and ($CurrentState.HighConfidencePhishQuarantineTag -eq $Settings.HighConfidencePhishQuarantineTag) -and - ($CurrentState.BulkThreshold -eq 7) -and + ($CurrentState.BulkThreshold -eq $Settings.BulkThreshold) -and ($CurrentState.QuarantineRetentionPeriod -eq 30) -and ($CurrentState.IncreaseScoreWithNumericIps -eq 'On') -and ($CurrentState.IncreaseScoreWithRedirectToOtherPort -eq 'On') -and @@ -80,29 +84,29 @@ function Invoke-CIPPStandardSpamFilterPolicy { Write-LogMessage -API 'Standards' -Tenant $Tenant -message 'Spam Filter Policy already correctly configured' -sev Info } else { $cmdparams = @{ - HighConfidenceSpamAction = 'Quarantine' - HighConfidenceSpamQuarantineTag = $Settings.HighConfidenceSpamQuarantineTag - SpamAction = $Settings.SpamAction - SpamQuarantineTag = $Settings.SpamQuarantineTag - PhishSpamAction = 'MoveToJmf' - BulkSpamAction = 'MoveToJmf' - BulkQuarantineTag = $Settings.BulkQuarantineTag - PhishQuarantineTag = $Settings.PhishQuarantineTag - HighConfidencePhishAction = 'Quarantine' - HighConfidencePhishQuarantineTag = $Settings.HighConfidencePhishQuarantineTag - BulkThreshold = 7 - QuarantineRetentionPeriod = 30 - IncreaseScoreWithNumericIps = 'On' - IncreaseScoreWithRedirectToOtherPort= 'On' - MarkAsSpamEmptyMessages = 'On' - MarkAsSpamJavaScriptInHtml = 'On' - MarkAsSpamSpfRecordHardFail = 'On' - MarkAsSpamFromAddressAuthFail = 'On' - MarkAsSpamNdrBackscatter = 'On' - MarkAsSpamBulkMail = 'On' - InlineSafetyTipsEnabled = $true - PhishZapEnabled = $true - SpamZapEnabled = $true + SpamAction = $Settings.SpamAction + SpamQuarantineTag = $Settings.SpamQuarantineTag + HighConfidenceSpamAction = $Settings.HighConfidenceSpamAction + HighConfidenceSpamQuarantineTag = $Settings.HighConfidenceSpamQuarantineTag + BulkSpamAction = $Settings.BulkSpamAction + BulkQuarantineTag = $Settings.BulkQuarantineTag + PhishSpamAction = $Settings.PhishSpamAction + PhishQuarantineTag = $Settings.PhishQuarantineTag + HighConfidencePhishAction = 'Quarantine' + HighConfidencePhishQuarantineTag = $Settings.HighConfidencePhishQuarantineTag + BulkThreshold = $Settings.BulkThreshold + QuarantineRetentionPeriod = 30 + IncreaseScoreWithNumericIps = 'On' + IncreaseScoreWithRedirectToOtherPort = 'On' + MarkAsSpamEmptyMessages = 'On' + MarkAsSpamJavaScriptInHtml = 'On' + MarkAsSpamSpfRecordHardFail = 'On' + MarkAsSpamFromAddressAuthFail = 'On' + MarkAsSpamNdrBackscatter = 'On' + MarkAsSpamBulkMail = 'On' + InlineSafetyTipsEnabled = $true + PhishZapEnabled = $true + SpamZapEnabled = $true } if ($CurrentState.Name -eq $PolicyName) { @@ -114,7 +118,8 @@ function Invoke-CIPPStandardSpamFilterPolicy { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -API 'Standards' -Tenant $Tenant -message "Failed to update Spam Filter Policy. Error: $ErrorMessage" -sev Error } - } else { + } + else { try { $cmdparams.Add('Name', $PolicyName) New-ExoRequest -TenantId $Tenant -cmdlet 'New-HostedContentFilterPolicy' -cmdparams $cmdparams -UseSystemMailbox $true @@ -129,8 +134,8 @@ function Invoke-CIPPStandardSpamFilterPolicy { if ($RuleStateIsCorrect -eq $false) { $cmdparams = @{ HostedContentFilterPolicy = $PolicyName - Priority = 0 - RecipientDomainIs = $AcceptedDomains.Name + Priority = 0 + RecipientDomainIs = $AcceptedDomains.Name } if ($RuleState.Name -eq $PolicyName) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 new file mode 100644 index 000000000000..93b1d73b7418 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 @@ -0,0 +1,69 @@ +Function Invoke-CIPPStandardTeamsEmailIntegration { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) TeamsEmailIntegration + .SYNOPSIS + (Label) Disallow emails to be sent to channel email addresses + .DESCRIPTION + (Helptext) Should users be allowed to send emails directly to a channel email addresses? + (DocsDescription) Teams channel email addresses are an optional feature that allows users to email the Teams channel directly. + .NOTES + CAT + Teams Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"boolean","name":"standards.TeamsEmailIntegration.AllowEmailIntoChannel","label":"Allow channel emails"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-CsTeamsClientConfiguration -AllowEmailIntoChannel \$false + RECOMMENDEDBY + "CIS 3.0" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTeamsClientConfiguration' -CmdParams @{Identity = 'Global'} + | Select-Object AllowEmailIntoChannel + + if ($null -eq $Settings.AllowEmailIntoChannel) { $Settings.AllowEmailIntoChannel = $false } + + $StateIsCorrect = ($CurrentState.AllowEmailIntoChannel -eq $Settings.AllowEmailIntoChannel) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams Email Integration settings already set.' -sev Info + } else { + $cmdparams = @{ + Identity = 'Global' + AllowEmailIntoChannel = $Settings.AllowEmailIntoChannel + } + + try { + New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Set-CsTeamsClientConfiguration' -CmdParams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Teams Email Integration settings' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set Teams Email Integration settings. Error: $ErrorMessage" -sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams Email Integration settings is set correctly.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams Email Integration settings is not set correctly.' -sev Alert + } + } + + if ($Setings.report -eq $true) { + Add-CIPPBPAField -FieldName 'TeamsEmailIntoChannel' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 new file mode 100644 index 000000000000..9da84577511f --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 @@ -0,0 +1,76 @@ +Function Invoke-CIPPStandardTeamsExternalAccessPolicy { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) TeamsExternalAccessPolicy + .SYNOPSIS + (Label) External Access Settings for Microsoft Teams + .DESCRIPTION + (Helptext) Sets the properties of the Global external access policy. + (DocsDescription) Sets the properties of the Global external access policy. External access policies determine whether or not your users can: 1) communicate with users who have Session Initiation Protocol (SIP) accounts with a federated organization; 2) communicate with users who are using custom applications built with Azure Communication Services; 3) access Skype for Business Server over the Internet, without having to log on to your internal network; 4) communicate with users who have SIP accounts with a public instant messaging (IM) provider such as Skype; and, 5) communicate with people who are using Teams with an account that's not managed by an organization. + .NOTES + CAT + Teams Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + {"type":"boolean","name":"standards.TeamsExternalAccessPolicy.EnableFederationAccess","label":"Allow communication from trusted organizations"} + {"type":"boolean","name":"standards.TeamsExternalAccessPolicy.EnablePublicCloudAccess","label":"Allow user to communicate with Skype users"} + {"type":"boolean","name":"standards.TeamsExternalAccessPolicy.EnableTeamsConsumerAccess","label":"Allow communication with unmanaged Teams accounts"} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-CsExternalAccessPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsExternalAccessPolicy' -CmdParams @{Identity = 'Global'} + | Select-Object * + + if ($null -eq $Settings.EnableFederationAccess) { $Settings.EnableFederationAccess = $false } + if ($null -eq $Settings.EnablePublicCloudAccess) { $Settings.EnablePublicCloudAccess = $false } + if ($null -eq $Settings.EnableTeamsConsumerAccess) { $Settings.EnableTeamsConsumerAccess = $false } + + $StateIsCorrect = ($CurrentState.EnableFederationAccess -eq $Settings.EnableFederationAccess) -and + ($CurrentState.EnablePublicCloudAccess -eq $Settings.EnablePublicCloudAccess) -and + ($CurrentState.EnableTeamsConsumerAccess -eq $Settings.EnableTeamsConsumerAccess) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'External Access Policy already set.' -sev Info + } else { + $cmdparams = @{ + Identity = 'Global' + EnableFederationAccess = $Settings.EnableFederationAccess + EnablePublicCloudAccess = $Settings.EnablePublicCloudAccess + EnableTeamsConsumerAccess = $Settings.EnableTeamsConsumerAccess + } + + try { + New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Set-CsExternalAccessPolicy' -CmdParams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated External Access Policy' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set External Access Policy. Error: $ErrorMessage" -sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'External Access Policy is set correctly.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'External Access Policy is not set correctly.' -sev Alert + } + } + + if ($Setings.report -eq $true) { + Add-CIPPBPAField -FieldName 'TeamsExternalAccessPolicy' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 new file mode 100644 index 000000000000..97e2059f3687 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 @@ -0,0 +1,84 @@ +Function Invoke-CIPPStandardTeamsExternalFileSharing { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) TeamsExternalFileSharing + .SYNOPSIS + (Label) Define approved cloud storage services for external file sharing in Teams + .DESCRIPTION + (Helptext) Ensure external file sharing in Teams is enabled for only approved cloud storage services. + (DocsDescription) Ensure external file sharing in Teams is enabled for only approved cloud storage services. + .NOTES + CAT + Teams Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowGoogleDrive","label":"Allow Google Drive","default":false} + {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowShareFile","label":"Allow ShareFile","default":false} + {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowBox","label":"Allow Box","default":false} + {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowDropbox","label":"Allow Dropbox","default":false} + {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowEgnyte","label":"Allow Egnyte","default":false} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-CsTeamsClientConfiguration -AllowGoogleDrive \$false -AllowShareFile \$false -AllowBox \$false -AllowDroBbox \$false -AllowEgnyte \$false + RECOMMENDEDBY + "CIS 3.0" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTeamsClientConfiguration' + | Select-Object AllowGoogleDrive, AllowShareFile, AllowBox, AllowDropBox, AllowEgnyte + + if ($null -eq $Settings.AllowGoogleDrive) { $Settings.AllowGoogleDrive = $false } + if ($null -eq $Settings.AllowShareFile) { $Settings.AllowShareFile = $false } + if ($null -eq $Settings.AllowBox) { $Settings.AllowBox = $false } + if ($null -eq $Settings.AllowDropBox) { $Settings.AllowDropBox = $false } + if ($null -eq $Settings.AllowEgnyte) { $Settings.AllowEgnyte = $false } + + $StateIsCorrect = ($CurrentState.AllowGoogleDrive -eq $Settings.AllowGoogleDrive) -and + ($CurrentState.AllowShareFile -eq $Settings.AllowShareFile) -and + ($CurrentState.AllowBox -eq $Settings.AllowBox) -and + ($CurrentState.AllowDropBox -eq $Settings.AllowDropBox) -and + ($CurrentState.AllowEgnyte -eq $Settings.AllowEgnyte) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams External File Sharing already set.' -sev Info + } else { + $cmdparams = @{ + AllowGoogleDrive = $Settings.AllowGoogleDrive + AllowShareFile = $Settings.AllowShareFile + AllowBox = $Settings.AllowBox + AllowDropBox = $Settings.AllowDropBox + AllowEgnyte = $Settings.AllowEgnyte + } + + try { + New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Set-CsTeamsClientConfiguration' -CmdParams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Teams External File Sharing' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set Teams External File Sharing. Error: $ErrorMessage" -sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams External File Sharing is set correctly.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams External File Sharing is not set correctly.' -sev Alert + } + } + + if ($Setings.report -eq $true) { + Add-CIPPBPAField -FieldName 'TeamsExternalFileSharing' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 new file mode 100644 index 000000000000..415ded7983e8 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 @@ -0,0 +1,111 @@ +Function Invoke-CIPPStandardTeamsFederationConfiguration { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) TeamsFederationConfiguration + .SYNOPSIS + (Label) Federation Configuration for Microsoft Teams + .DESCRIPTION + (Helptext) Sets the properties of the Global federation configuration. + (DocsDescription) Sets the properties of the Global federation configuration. Federation configuration settings determine whether or not your users can communicate with users who have SIP accounts with a federated organization. + .NOTES + CAT + Teams Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + {"type":"boolean","name":"standards.TeamsFederationConfiguration.AllowTeamsConsumer","label":"Allow users to communicate with other organizations"} + {"type":"boolean","name":"standards.TeamsFederationConfiguration.AllowPublicUsers","label":"Allow users to communicate with Skype Users"} + {"type":"Select","name":"standards.TeamsFederationConfiguration.DomainControl","label":"Communication Mode","values":[{"label":"Allow all external domains","value":"AllowAllExternal"},{"label":"Block all external domains","value":"BlockAllExternal"},{"label":"Allow specific external domains","value":"AllowSpecificExternal"},{"label":"Block specific external domains","value":"BlockSpecificExternal"}]} + {"type":"input","name":"standards.TeamsFederationConfiguration.DomainList","label":"Domains, Comma separated"} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-CsTenantFederationConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTenantFederationConfiguration' -CmdParams @{Identity = 'Global'} + | Select-Object * + + Switch ($Settings.DomainControl) { + 'AllowAllExternal' { + $AllowFederatedUsers = $true + $AllowedDomainsAsAList = 'AllowAllKnownDomains' + $BlockedDomains = @() + } + 'BlockAllExternal' { + $AllowFederatedUsers = $false + $AllowedDomainsAsAList = 'AllowAllKnownDomains' + $BlockedDomains = @() + } + 'AllowSpecificExternal' { + $AllowFederatedUsers = $true + $BlockedDomains = @() + if ($null -ne $Settings.DomainList) { + $AllowedDomainsAsAList = @($Settings.DomainList).Split(',').Trim() + } else { + $AllowedDomainsAsAList = @() + } + } + 'BlockSpecificExternal' { + $AllowFederatedUsers = $true + $AllowedDomainsAsAList = 'AllowAllKnownDomains' + if ($null -ne $Settings.DomainList) { + $BlockedDomains = @($Settings.DomainList).Split(',').Trim() + } else { + $BlockedDomains = @() + } + } + } + + # TODO : Add proper validation for the domain list + # $CurrentState.AllowedDomains returns a PSObject System.Object and adds a Domain= for each allowed domain, ex {Domain=example.com, Domain=example2.com} + + $StateIsCorrect = ($CurrentState.AllowTeamsConsumer -eq $Settings.AllowTeamsConsumer) -and + ($CurrentState.AllowPublicUsers -eq $Settings.AllowPublicUsers) -and + ($CurrentState.AllowFederatedUsers -eq $AllowFederatedUsers) -and + ($CurrentState.AllowedDomains -eq $AllowedDomainsAsAList) -and + ($CurrentState.BlockedDomains -eq $BlockedDomains) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Federation Configuration already set.' -sev Info + } else { + $cmdparams = @{ + Identity = 'Global' + AllowTeamsConsumer = $Settings.AllowTeamsConsumer + AllowPublicUsers = $Settings.AllowPublicUsers + AllowFederatedUsers = $AllowFederatedUsers + AllowedDomainsAsAList = $AllowedDomainsAsAList + BlockedDomains = $BlockedDomains + } + + try { + New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Set-CsTenantFederationConfiguration' -CmdParams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Federation Configuration Policy' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set Federation Configuration Policy. Error: $ErrorMessage" -sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Federation Configuration is set correctly.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Federation Configuration is not set correctly.' -sev Alert + } + } + + if ($Setings.report -eq $true) { + Add-CIPPBPAField -FieldName 'FederationConfiguration' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 new file mode 100644 index 000000000000..48795f929cf5 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 @@ -0,0 +1,81 @@ +Function Invoke-CIPPStandardTeamsGlobalMeetingPolicy { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) TeamsGlobalMeetingPolicy + .SYNOPSIS + (Label) Define Global Meeting Policy for Teams + .DESCRIPTION + (Helptext) Defines the CIS recommended global meeting policy for Teams. This includes AllowAnonymousUsersToJoinMeeting, AllowAnonymousUsersToStartMeeting, AutoAdmittedUsers, AllowPSTNUsersToBypassLobby, MeetingChatEnabledType, DesignatedPresenterRoleMode, AllowExternalParticipantGiveRequestControl + (DocsDescription) Defines the CIS recommended global meeting policy for Teams. This includes AllowAnonymousUsersToJoinMeeting, AllowAnonymousUsersToStartMeeting, AutoAdmittedUsers, AllowPSTNUsersToBypassLobby, MeetingChatEnabledType, DesignatedPresenterRoleMode, AllowExternalParticipantGiveRequestControl + .NOTES + CAT + Teams Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"Select","name":"standards.TeamsGlobalMeetingPolicy.DesignatedPresenterRoleMode","label":"Default value of the `Who can present?`","values":[{"label":"EveryoneUserOverride","value":"EveryoneUserOverride"},{"label":"EveryoneInCompanyUserOverride","value":"EveryoneInCompanyUserOverride"},{"label":"EveryoneInSameAndFederatedCompanyUserOverride","value":"EveryoneInSameAndFederatedCompanyUserOverride"},{"label":"OrganizerOnlyUserOverride","value":"OrganizerOnlyUserOverride"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-CsTeamsMeetingPolicy -AllowAnonymousUsersToJoinMeeting \$false -AllowAnonymousUsersToStartMeeting \$false -AutoAdmittedUsers EveryoneInCompanyExcludingGuests -AllowPSTNUsersToBypassLobby \$false -MeetingChatEnabledType EnabledExceptAnonymous -DesignatedPresenterRoleMode \$DesignatedPresenterRoleMode -AllowExternalParticipantGiveRequestControl \$false + RECOMMENDEDBY + "CIS 3.0" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTeamsMeetingPolicy' -CmdParams @{Identity = 'Global'} + | Select-Object AllowAnonymousUsersToJoinMeeting, AllowAnonymousUsersToStartMeeting, AutoAdmittedUsers, AllowPSTNUsersToBypassLobby, MeetingChatEnabledType, DesignatedPresenterRoleMode, AllowExternalParticipantGiveRequestControl + + if ($null -eq $Settings.DesignatedPresenterRoleMode) { $Settings.DesignatedPresenterRoleMode = $CurrentState.DesignatedPresenterRoleMode } + + $StateIsCorrect = ($CurrentState.AllowAnonymousUsersToJoinMeeting -eq $false) -and + ($CurrentState.AllowAnonymousUsersToStartMeeting -eq $false) -and + ($CurrentState.AutoAdmittedUsers -eq 'EveryoneInCompanyExcludingGuests') -and + ($CurrentState.AllowPSTNUsersToBypassLobby -eq $false) + ($CurrentState.MeetingChatEnabledType -eq 'EnabledExceptAnonymous') + ($CurrentState.DesignatedPresenterRoleMode -eq $Settings.DesignatedPresenterRoleMode) + ($CurrentState.AllowExternalParticipantGiveRequestControl -eq $false) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams Global Policy already set.' -sev Info + } else { + $cmdparams = @{ + Identity = 'Global' + AllowAnonymousUsersToJoinMeeting = $false + AllowAnonymousUsersToStartMeeting = $false + AutoAdmittedUsers = 'EveryoneInCompanyExcludingGuests' + AllowPSTNUsersToBypassLobby = $false + MeetingChatEnabledType = 'EnabledExceptAnonymous' + DesignatedPresenterRoleMode = $Settings.DesignatedPresenterRoleMode + AllowExternalParticipantGiveRequestControl = $false + } + + try { + New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Set-CsTeamsMeetingPolicy' -CmdParams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Teams Global Policy' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set Teams Global Policy. Error: $ErrorMessage" -sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams Global Policy is set correctly.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams Global Policy is not set correctly.' -sev Alert + } + } + + if ($Setings.report -eq $true) { + Add-CIPPBPAField -FieldName 'TeamsGlobalMeetingPolicy' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 index b7a40e7b3128..df5e5291df95 100644 --- a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 @@ -10,7 +10,7 @@ function Invoke-CippWebhookProcessing { $ExecutingUser ) - + $Tenant = Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $TenantFilter } Write-Host "Received data. Our Action List is $($data.CIPPAction)" $ActionList = ($data.CIPPAction | ConvertFrom-Json -ErrorAction SilentlyContinue).value @@ -51,36 +51,64 @@ function Invoke-CippWebhookProcessing { } } } + + # Save audit log entry to table + $LocationInfo = $Data.CIPPLocationInfo | ConvertFrom-Json -ErrorAction SilentlyContinue + $GenerateJSON = New-CIPPAlertTemplate -format 'json' -data $Data -ActionResults $ActionResults -CIPPURL $CIPPURL + $JsonContent = @{ + Title = $GenerateJSON.Title + ActionUrl = $GenerateJSON.ButtonUrl + ActionText = $GenerateJSON.ButtonText + RawData = $Data + IP = $data.ClientIP + PotentialLocationInfo = $LocationInfo + ActionsTaken = $ActionResults + } | ConvertTo-Json -Depth 15 -Compress + + $CIPPAlert = @{ + Type = 'table' + Title = $GenerateJSON.Title + JSONContent = $JsonContent + TenantFilter = $TenantFilter + TableName = 'AuditLogs' + } + $LogId = Send-CIPPAlert @CIPPAlert + + $AuditLogLink = '{0}/tenant/administration/audit-logs?customerId={1}&logId={2}' -f $CIPPURL, $Tenant.customerId, $LogId + $GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -ActionResults $ActionResults -CIPPURL $CIPPURL + Write-Host 'Going to create the content' foreach ($action in $ActionList ) { switch ($action) { 'generatemail' { - Write-Host 'Going to create the email' - $GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -ActionResults $ActionResults -CIPPURL $CIPPURL + $CIPPAlert = @{ + Type = 'email' + Title = $GenerateEmail.title + HTMLContent = $GenerateEmail.htmlcontent + TenantFilter = $TenantFilter + } Write-Host 'Going to send the mail' - Send-CIPPAlert -Type 'email' -Title $GenerateEmail.title -HTMLContent $GenerateEmail.htmlcontent -TenantFilter $TenantFilter + Send-CIPPAlert @CIPPAlert Write-Host 'email should be sent' } 'generatePSA' { - $GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -ActionResults $ActionResults -CIPPURL $CIPPURL - Send-CIPPAlert -Type 'psa' -Title $GenerateEmail.title -HTMLContent $GenerateEmail.htmlcontent -TenantFilter $TenantFilter + $CIPPAlert = @{ + Type = 'psa' + Title = $GenerateEmail.title + HTMLContent = $GenerateEmail.htmlcontent + TenantFilter = $TenantFilter + } + Send-CIPPAlert @CIPPAlert } 'generateWebhook' { - Write-Host 'Generating the webhook content' - $LocationInfo = $Data.CIPPLocationInfo | ConvertFrom-Json -ErrorAction SilentlyContinue - $GenerateJSON = New-CIPPAlertTemplate -format 'json' -data $Data -ActionResults $ActionResults -CIPPURL $CIPPURL - $JsonContent = @{ - Title = $GenerateJSON.Title - ActionUrl = $GenerateJSON.ButtonUrl - ActionText = $GenerateJSON.ButtonText - RawData = $Data - IP = $data.ClientIP - PotentialLocationInfo = $LocationInfo - ActionsTaken = [string]($ActionResults | ConvertTo-Json -Depth 15 -Compress) - } | ConvertTo-Json -Depth 15 -Compress + $CippAlert = @{ + Type = 'webhook' + Title = $GenerateJSON.Title + JSONContent = $JsonContent + TenantFilter = $TenantFilter + } Write-Host 'Sending Webhook Content' - #Write-Host $JsonContent - Send-CIPPAlert -Type 'webhook' -Title $GenerateJSON.Title -JSONContent $JsonContent -TenantFilter $TenantFilter + Send-CIPPAlert @CippAlert } } } diff --git a/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 b/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 index 39b8957832c6..9e9d3d40cf48 100644 --- a/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 @@ -22,6 +22,7 @@ function Test-CIPPAuditLogRules { 'OAuth2:Token' 'SAS:EndAuth' 'SAS:ProcessAuth' + 'deviceAuth:ReprocessTls' ) $TrustedIPTable = Get-CIPPTable -TableName 'trustedIps' diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index 53f3589025cb..4b946d678c24 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -93,7 +93,7 @@ function Invoke-HuduExtensionSync { } @{ Title = 'Exchange Admin Portal' - URL = 'https://outlook.office365.com/ecp/?rfr=Admin_o365&exsvurl=1&delegatedOrg={0}' -f $Tenant.initialDomainName + URL = 'https://admin.exchange.microsoft.com/?landingpage=homepage&form=mac_sidebar&delegatedOrg={0}' -f $Tenant.initialDomainName Icon = 'fas fa-mail-bulk' } @{ diff --git a/UpdatePermissions/run.ps1 b/UpdatePermissions/run.ps1 index 00ea3b7e9ad2..e99383ecc69b 100644 --- a/UpdatePermissions/run.ps1 +++ b/UpdatePermissions/run.ps1 @@ -2,16 +2,29 @@ param($Timer) try { - $Tenants = Get-Tenants -IncludeAll -TriggerRefresh | Where-Object { $_.customerId -ne $env:TenantId -and $_.Excluded -eq $false } - $Queue = New-CippQueueEntry -Name 'Update Permissions' -TotalTasks ($Tenants | Measure-Object).Count - $TenantBatch = $Tenants | Select-Object defaultDomainName, customerId, displayName, @{n = 'FunctionName'; exp = { 'UpdatePermissionsQueue' } }, @{n = 'QueueId'; exp = { $Queue.RowKey } } - - if (($Tenants | Measure-Object).Count -gt 0) { + $Tenants = Get-Tenants -IncludeAll | Where-Object { $_.customerId -ne $env:TenantId -and $_.Excluded -eq $false } + $CPVTable = Get-CIPPTable -TableName cpvtenants + $CPVRows = Get-CIPPAzDataTableEntity @CPVTable + $ModuleRoot = (Get-Module CIPPCore).ModuleBase + $SAMManifest = Get-Item -Path "$ModuleRoot\Public\SAMManifest.json" + $AdditionalPermissions = Get-Item -Path "$ModuleRoot\Public\AdditionalPermissions.json" + $Tenants = $Tenants | ForEach-Object { + $CPVRow = $CPVRows | Where-Object -Property Tenant -EQ $_.customerId + if (!$CPVRow -or $env:ApplicationID -notin $CPVRow.applicationId -or $SAMManifest.LastWriteTime.ToUniversalTime() -gt $CPVRow.Timestamp.DateTime -or $AdditionalPermissions.LastWriteTime.ToUniversalTime() -ge $CPVRow.Timestamp.DateTime -or $CPVRow.Timestamp.DateTime -le (Get-Date).AddDays(-7).ToUniversalTime() -or !$_.defaultDomainName) { + $_ + } + } + $TenantCount = ($Tenants | Measure-Object).Count + if ($TenantCount -gt 0) { + $Queue = New-CippQueueEntry -Name 'Update Permissions' -TotalTasks $TenantCount + $TenantBatch = $Tenants | Select-Object defaultDomainName, customerId, displayName, @{n = 'FunctionName'; exp = { 'UpdatePermissionsQueue' } }, @{n = 'QueueId'; exp = { $Queue.RowKey } } $InputObject = [PSCustomObject]@{ OrchestratorName = 'UpdatePermissionsOrchestrator' Batch = @($TenantBatch) } $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress) Write-Host "Started permissions orchestration with ID = '$InstanceId'" + } else { + Write-Host 'No tenants require permissions update' } -} catch {} \ No newline at end of file +} catch {} diff --git a/version_latest.txt b/version_latest.txt index f3b5af39e430..6abaeb2f9072 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -6.1.1 +6.2.0