diff --git a/Activity_AddOrUpdateTableRows/run.ps1 b/Activity_AddOrUpdateTableRows/run.ps1 index 6bb7e218b987..adc07df73e23 100644 --- a/Activity_AddOrUpdateTableRows/run.ps1 +++ b/Activity_AddOrUpdateTableRows/run.ps1 @@ -3,11 +3,10 @@ $TableName = ($TableParams.Context['TableName']) $Table = Get-CippTable -tablename $TableName foreach ($param in $TableParams.Entity) { - try { - #Sending each item indivually, if it fails, log an error. - Add-CIPPAzDataTableEntity @Table -Entity $param -Force - } - catch { - Write-LogMessage -API 'Activity_AddOrUpdateTableRows' -message "Unable to write to '$($TableParams.TableName)' Using RowKey $($param.RowKey) table: $($_.Exception.Message)" -sev error - } + try { + #Sending each item indivually, if it fails, log an error. + Add-CIPPAzDataTableEntity @Table -Entity $param -Force + } catch { + Write-LogMessage -API 'Activity_AddOrUpdateTableRows' -message "Unable to write to '$($TableParams.TableName)' Using RowKey $($param.RowKey)" -LogData (Get-CippException -Exception $_) -sev error + } } diff --git a/Applications_Orchestrator/run.ps1 b/Applications_Orchestrator/run.ps1 index 9c8457ff91fc..ebf60eb55628 100644 --- a/Applications_Orchestrator/run.ps1 +++ b/Applications_Orchestrator/run.ps1 @@ -17,7 +17,7 @@ try { $Outputs = Wait-ActivityFunction -Task $ParallelTasks Write-Host $Outputs } -catch { +catch { Write-Host "Applications_Orchestrator exception: $($_.Exception.Message)" } finally { diff --git a/Applications_Upload/run.ps1 b/Applications_Upload/run.ps1 index a92637a42882..b1aacdc41318 100644 --- a/Applications_Upload/run.ps1 +++ b/Applications_Upload/run.ps1 @@ -1,14 +1,14 @@ param($name) $Table = Get-CippTable -tablename 'apps' -$Filter = "PartitionKey eq 'apps' and RowKey eq '$name'" +$Filter = "PartitionKey eq 'apps' and RowKey eq '$name'" Set-Location (Get-Item $PSScriptRoot).Parent.FullName $ChocoApp = (Get-CIPPAzDataTableEntity @Table -filter $Filter).JSON | ConvertFrom-Json $intuneBody = $ChocoApp.IntuneBody -$tenants = if ($chocoapp.Tenant -eq 'AllTenants') { +$tenants = if ($chocoapp.Tenant -eq 'AllTenants') { (Get-tenants).defaultDomainName } else { $chocoapp.Tenant -} +} if ($chocoApp.type -eq 'MSPApp') { [xml]$Intunexml = Get-Content "AddMSPApp\$($ChocoApp.MSPAppName).app.xml" $intunewinFilesize = (Get-Item "AddMSPApp\$($ChocoApp.MSPAppName).intunewin") @@ -25,7 +25,7 @@ $ContentBody = ConvertTo-Json @{ name = $intunexml.ApplicationInfo.FileName size = [int64]$intunexml.ApplicationInfo.UnencryptedContentSize sizeEncrypted = [int64]($intunewinFilesize).length -} +} $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter $RemoveCacheFile = if ($chocoapp.Tenant -ne 'AllTenants') { Remove-AzDataTableEntity @Table -Entity $clearRow @@ -54,11 +54,11 @@ foreach ($tenant in $tenants) { Try { $ApplicationList = (New-graphGetRequest -Uri $baseuri -tenantid $Tenant) | Where-Object { $_.DisplayName -eq $ChocoApp.ApplicationName } - if ($ApplicationList.displayname.count -ge 1) { + if ($ApplicationList.displayname.count -ge 1) { Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message "$($ChocoApp.ApplicationName) exists. Skipping this application" -Sev 'Info' continue } - if ($chocoApp.type -eq 'WinGet') { + if ($chocoApp.type -eq 'WinGet') { Write-Host 'Winget!' Write-Host ($intuneBody | ConvertTo-Json -Compress) $NewApp = New-GraphPostRequest -Uri $baseuri -Body ($intuneBody | ConvertTo-Json -Compress) -Type POST -tenantid $tenant @@ -79,8 +79,8 @@ foreach ($tenant in $tenants) { $AzFileUri = New-graphGetRequest -Uri "$($BaseURI)/$($NewApp.id)/microsoft.graph.win32lobapp/contentVersions/1/files/$($ContentReq.id)" -tenantid $tenant if ($AZfileuri.uploadState -like '*fail*') { break } Start-Sleep -Milliseconds 300 - } while ($AzFileUri.AzureStorageUri -eq $null) - + } while ($AzFileUri.AzureStorageUri -eq $null) + $chunkSizeInBytes = 4mb [byte[]]$bytes = [System.IO.File]::ReadAllBytes($($intunewinFilesize.fullname)) $chunks = [Math]::Ceiling($bytes.Length / $chunkSizeInBytes) @@ -89,15 +89,15 @@ foreach ($tenant in $tenants) { $Upload = Invoke-RestMethod -Uri "$($AzFileUri.azureStorageUri)&comp=block&blockid=$id" -Method Put -Headers @{'x-ms-blob-type' = 'BlockBlob' } -InFile $inFile -ContentType 'application/octet-stream' $ConfirmUpload = Invoke-RestMethod -Uri "$($AzFileUri.azureStorageUri)&comp=blocklist" -Method Put -Body "$id" $CommitReq = New-graphPostRequest -Uri "$($BaseURI)/$($NewApp.id)/microsoft.graph.win32lobapp/contentVersions/1/files/$($ContentReq.id)/commit" -Body $EncBody -Type POST -tenantid $tenant - + do { $CommitStateReq = New-graphGetRequest -Uri "$($BaseURI)/$($NewApp.id)/microsoft.graph.win32lobapp/contentVersions/1/files/$($ContentReq.id)" -tenantid $tenant if ($CommitStateReq.uploadState -like '*fail*') { Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message "$($ChocoApp.ApplicationName) Commit failed. Please check if app uploaded succesful" -Sev 'Warning' - break + break } Start-Sleep -Milliseconds 300 - } while ($CommitStateReq.uploadState -eq 'commitFilePending') + } while ($CommitStateReq.uploadState -eq 'commitFilePending') $CommitFinalizeReq = New-graphPostRequest -Uri "$($BaseURI)/$($NewApp.id)" -tenantid $tenant -Body '{"@odata.type":"#microsoft.graph.win32lobapp","committedContentVersion":"1"}' -type PATCH Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message "Added Application $($chocoApp.ApplicationName)" -Sev 'Info' if ($AssignTo -ne 'On') { @@ -108,7 +108,7 @@ foreach ($tenant in $tenants) { Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message 'Successfully added Application' -Sev 'Info' } catch { "Failed to add Application for $($Tenant): $($_.Exception.Message)" - Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message "Failed adding Application $($ChocoApp.ApplicationName). Error: $($_.Exception.Message)" -Sev 'Error' + Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message "Failed adding Application $($ChocoApp.ApplicationName). Error: $($_.Exception.Message)" -LogData (Get-CippException -Exception $_) -Sev 'Error' continue } diff --git a/BestPracticeAnalyser_All/run.ps1 b/BestPracticeAnalyser_All/run.ps1 index ddd92560ccda..6e90102a161a 100644 --- a/BestPracticeAnalyser_All/run.ps1 +++ b/BestPracticeAnalyser_All/run.ps1 @@ -107,7 +107,7 @@ $AddRow = foreach ($Template in $templates) { try { Add-CIPPAzDataTableEntity @Table -Entity $Result -Force } catch { - Write-LogMessage -API 'BPA' -tenant $tenant -message "Error getting saving data for $($template.Name) - $($TenantName.customerId). Error: $($_.Exception.Message)" -sev Error + Write-LogMessage -API 'BPA' -tenant $tenant -message "Error getting saving data for $($template.Name) - $($TenantName.customerId). Error: $($_.Exception.Message)" -LogData (Get-CippException -Exception $_) -sev Error } } diff --git a/DomainAnalyser_All/function.json b/DomainAnalyser_All/function.json deleted file mode 100644 index 50f47ad53465..000000000000 --- a/DomainAnalyser_All/function.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "bindings": [ - { - "name": "DomainObject", - "direction": "in", - "type": "activityTrigger" - } - ] -} diff --git a/DomainAnalyser_All/run.ps1 b/DomainAnalyser_All/run.ps1 deleted file mode 100644 index c9f0b989c04e..000000000000 --- a/DomainAnalyser_All/run.ps1 +++ /dev/null @@ -1,244 +0,0 @@ -param($DomainObject) - -Import-Module DNSHealth - -try { - $ConfigTable = Get-CippTable -tablename Config - $Filter = "PartitionKey eq 'Domains' and RowKey eq 'Domains'" - $Config = Get-CIPPAzDataTableEntity @ConfigTable -Filter $Filter - - $ValidResolvers = @('Google', 'CloudFlare', 'Quad9') - if ($ValidResolvers -contains $Config.Resolver) { - $Resolver = $Config.Resolver - } else { - $Resolver = 'Google' - $Config = @{ - PartitionKey = 'Domains' - RowKey = 'Domains' - Resolver = $Resolver - } - Add-CIPPAzDataTableEntity @ConfigTable -Entity $Config -Force - } -} catch { - $Resolver = 'Google' -} -Set-DnsResolver -Resolver $Resolver - -$Domain = $DomainObject.rowKey - -try { - $Tenant = $DomainObject.TenantDetails | ConvertFrom-Json -ErrorAction Stop -} catch { - $Tenant = @{Tenant = 'None' } -} - -#Write-Host "$($DomainObject.TenantDetails)" - -$Result = [PSCustomObject]@{ - Tenant = $Tenant.Tenant - GUID = $($Domain.Replace('.', '')) - LastRefresh = $(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') - Domain = $Domain - NSRecords = (Read-NSRecord -Domain $Domain).Records - ExpectedSPFRecord = '' - ActualSPFRecord = '' - SPFPassAll = '' - ActualMXRecords = '' - MXPassTest = '' - DMARCPresent = '' - DMARCFullPolicy = '' - DMARCActionPolicy = '' - DMARCReportingActive = '' - DMARCPercentagePass = '' - DNSSECPresent = '' - MailProvider = '' - DKIMEnabled = '' - DKIMRecords = '' - Score = '' - MaximumScore = 160 - ScorePercentage = '' - ScoreExplanation = '' -} - -$Scores = [PSCustomObject]@{ - SPFPresent = 10 - SPFCorrectAll = 20 - MXRecommended = 10 - DMARCPresent = 10 - DMARCSetQuarantine = 20 - DMARCSetReject = 30 - DMARCReportingActive = 20 - DMARCPercentageGood = 20 - DNSSECPresent = 20 - DKIMActiveAndWorking = 20 -} - -$ScoreDomain = 0 -# Setup Score Explanation -$ScoreExplanation = [System.Collections.Generic.List[string]]::new() - -# Check MX Record -$MXRecord = Read-MXRecord -Domain $Domain -ErrorAction Stop - -$Result.ExpectedSPFRecord = $MXRecord.ExpectedInclude -$Result.MXPassTest = $false -$Result.ActualMXRecords = $MXRecord.Records - -# Check fail counts to ensure all tests pass -#$MXWarnCount = $MXRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count -$MXFailCount = $MXRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count - -if ($MXFailCount -eq 0) { - $Result.MXPassTest = $true - $ScoreDomain += $Scores.MXRecommended -} else { - $ScoreExplanation.Add('MX record did not pass validation') | Out-Null -} - -if ([string]::IsNullOrEmpty($MXRecord.MailProvider)) { - $Result.MailProvider = 'Unknown' -} else { - $Result.MailProvider = $MXRecord.MailProvider.Name -} - -# Get SPF Record -try { - $SPFRecord = Read-SpfRecord -Domain $Domain -ErrorAction Stop - if ($SPFRecord.RecordCount -gt 0) { - $Result.ActualSPFRecord = $SPFRecord.Record - if ($SPFRecord.RecordCount -eq 1) { - $ScoreDomain += $Scores.SPFPresent - } else { - $ScoreExplanation.Add('Multiple SPF records detected') | Out-Null - } - } else { - $Result.ActualSPFRecord = 'No SPF Record' - $ScoreExplanation.Add('No SPF Record Found') | Out-Null - } -} catch { - $Message = 'SPF Exception: {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -sev Error - throw $Message -} - -# Check SPF Record -$Result.SPFPassAll = $false - -# Check warning + fail counts to ensure all tests pass -#$SPFWarnCount = $SPFRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count -$SPFFailCount = $SPFRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count - -if ($SPFFailCount -eq 0) { - $ScoreDomain += $Scores.SPFCorrectAll - $Result.SPFPassAll = $true -} else { - $ScoreExplanation.Add('SPF record did not pass validation') | Out-Null -} - -# Get DMARC Record -try { - $DMARCPolicy = Read-DmarcPolicy -Domain $Domain -ErrorAction Stop - - If ([string]::IsNullOrEmpty($DMARCPolicy.Record)) { - $Result.DMARCPresent = $false - $ScoreExplanation.Add('No DMARC Records Found') | Out-Null - } else { - $Result.DMARCPresent = $true - $ScoreDomain += $Scores.DMARCPresent - - $Result.DMARCFullPolicy = $DMARCPolicy.Record - if ($DMARCPolicy.Policy -eq 'reject' -and $DMARCPolicy.SubdomainPolicy -eq 'reject') { - $Result.DMARCActionPolicy = 'Reject' - $ScoreDomain += $Scores.DMARCSetReject - } - if ($DMARCPolicy.Policy -eq 'none') { - $Result.DMARCActionPolicy = 'None' - $ScoreExplanation.Add('DMARC is not being enforced') | Out-Null - } - if ($DMARCPolicy.Policy -eq 'quarantine') { - $Result.DMARCActionPolicy = 'Quarantine' - $ScoreDomain += $Scores.DMARCSetQuarantine - $ScoreExplanation.Add('DMARC Partially Enforced with quarantine') | Out-Null - } - - $ReportEmailCount = $DMARCPolicy.ReportingEmails | Measure-Object | Select-Object -ExpandProperty Count - if ($ReportEmailCount -gt 0) { - $Result.DMARCReportingActive = $true - $ScoreDomain += $Scores.DMARCReportingActive - } else { - $Result.DMARCReportingActive = $False - $ScoreExplanation.Add('DMARC Reporting not Configured') | Out-Null - } - - if ($DMARCPolicy.Percent -eq 100) { - $Result.DMARCPercentagePass = $true - $ScoreDomain += $Scores.DMARCPercentageGood - } else { - $Result.DMARCPercentagePass = $false - $ScoreExplanation.Add('DMARC Not Checking All Messages') | Out-Null - } - } -} catch { - $Message = 'DMARC Exception: {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -sev Error - throw $Message -} - -# DNS Sec Check -try { - $DNSSECResult = Test-DNSSEC -Domain $Domain -ErrorAction Stop - $DNSSECFailCount = $DNSSECResult.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count - $DNSSECWarnCount = $DNSSECResult.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count - if (($DNSSECFailCount + $DNSSECWarnCount) -eq 0) { - $Result.DNSSECPresent = $true - $ScoreDomain += $Scores.DNSSECPresent - } else { - $Result.DNSSECPresent = $false - $ScoreExplanation.Add('DNSSEC Not Configured or Enabled') | Out-Null - } -} catch { - $Message = 'DNSSEC Exception: {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -sev Error - throw $Message -} - -# DKIM Check -try { - $DkimParams = @{ - Domain = $Domain - FallbackToMicrosoftSelectors = $true - } - if (![string]::IsNullOrEmpty($DomainObject.DkimSelectors)) { - $DkimParams.Selectors = $DomainObject.DkimSelectors | ConvertFrom-Json - } - - $DkimRecord = Read-DkimRecord @DkimParams -ErrorAction Stop - - $DkimRecordCount = $DkimRecord.Records | Measure-Object | Select-Object -ExpandProperty Count - $DkimFailCount = $DkimRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count - #$DkimWarnCount = $DkimRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count - if ($DkimRecordCount -gt 0 -and $DkimFailCount -eq 0) { - $Result.DKIMEnabled = $true - $ScoreDomain += $Scores.DKIMActiveAndWorking - $Result.DKIMRecords = $DkimRecord.Records | Select-Object Selector, Record - } else { - $Result.DKIMEnabled = $false - $ScoreExplanation.Add('DKIM Not Configured') | Out-Null - } -} catch { - $Message = 'DKIM Exception: {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -sev Error - throw $Message -} -# Final Score -$Result.Score = $ScoreDomain -$Result.ScorePercentage = [int](($Result.Score / $Result.MaximumScore) * 100) -$Result.ScoreExplanation = ($ScoreExplanation) -join ', ' - - -$DomainObject.DomainAnalyser = ($Result | ConvertTo-Json -Compress).ToString() - -# Final Write to Output -Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message "DNS Analyser Finished For $Domain" -sev Info - -Write-Output $DomainObject \ No newline at end of file diff --git a/DomainAnalyser_GetTenantDomains/function.json b/DomainAnalyser_GetTenantDomains/function.json deleted file mode 100644 index ce320a44c1a3..000000000000 --- a/DomainAnalyser_GetTenantDomains/function.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "bindings": [ - { - "name": "name", - "type": "activityTrigger", - "direction": "in" - } - ] -} diff --git a/DomainAnalyser_GetTenantDomains/run.ps1 b/DomainAnalyser_GetTenantDomains/run.ps1 deleted file mode 100644 index 4e0fd71f2be8..000000000000 --- a/DomainAnalyser_GetTenantDomains/run.ps1 +++ /dev/null @@ -1,92 +0,0 @@ -param($name) - -$Tenants = Get-Tenants -$ExcludedTenants = Get-Tenants -SkipList -$DomainTable = Get-CippTable -tablename 'Domains' - -$TenantDomains = $Tenants | ForEach-Object -Parallel { - Import-Module CippCore - $Tenant = $_ - # Get Domains to Lookup - try { - $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/domains' -tenantid $Tenant.defaultDomainName | Where-Object { ($_.id -notlike '*.microsoftonline.com' -and $_.id -NotLike '*.exclaimer.cloud' -and $_.id -NotLike '*.codetwo.online' -and $_.id -NotLike '*.call2teams.com' -and $_.isVerified) } - foreach ($d in $domains) { - [PSCustomObject]@{ - Tenant = $Tenant.defaultDomainName - Domain = $d.id - AuthenticationType = $d.authenticationType - IsAdminManaged = $d.isAdminManaged - IsDefault = $d.isDefault - IsInitial = $d.isInitial - IsRoot = $d.isRoot - IsVerified = $d.isVerified - SupportedServices = $d.supportedServices - } - } - } catch { - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.defaultDomainName -message "DNS Analyser GraphGetRequest Exception: $($_.Exception.Message)" -sev Error - } -} | Sort-Object -Unique -Property Domain - -# Cleanup domains from tenants with errors, skip domains with manually set selectors or mail providers -foreach ($Exclude in $ExcludedTenants) { - $Filter = "PartitionKey eq 'TenantDomains' and TenantId eq '{0}'" -f $Exclude.defaultDomainName - $CleanupRows = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter - $CleanupCount = ($CleanupRows | Measure-Object).Count - if ($CleanupCount -gt 0) { - Write-LogMessage -API 'DomainAnalyser' -tenant $Exclude.defaultDomainName -message "Cleaning up $CleanupCount domain(s) for excluded tenant" -sev Info - Remove-AzDataTableEntity @DomainTable -Entity $CleanupRows - } -} - -$TenantCount = ($TenantDomains | Measure-Object).Count -if ($TenantCount -gt 0) { - Write-Host "$TenantCount tenant Domains" - - # Process tenant domain results - try { - $TenantDomainObjects = foreach ($Tenant in $TenantDomains) { - $TenantDetails = ($Tenant | ConvertTo-Json -Compress).ToString() - $Filter = "PartitionKey eq '{0}' and RowKey eq '{1}'" -f $Tenant.Tenant, $Tenant.Domain - $OldDomain = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter - - if ($OldDomain) { - Remove-AzDataTableEntity @DomainTable -Entity $OldDomain | Out-Null - } - - $Filter = "PartitionKey eq 'TenantDomains' and RowKey eq '{0}'" -f $Tenant.Domain - $Domain = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter - - if (!$Domain) { - $DomainObject = [pscustomobject]@{ - DomainAnalyser = '' - TenantDetails = $TenantDetails - TenantId = $Tenant.Tenant - DkimSelectors = '' - MailProviders = '' - RowKey = $Tenant.Domain - PartitionKey = 'TenantDomains' - } - - if ($OldDomain) { - $DomainObject.DkimSelectors = $OldDomain.DkimSelectors - $DomainObject.MailProviders = $OldDomain.MailProviders - } - $Domain = $DomainObject - } else { - $Domain.TenantDetails = $TenantDetails - if ($OldDomain) { - $Domain.DkimSelectors = $OldDomain.DkimSelectors - $Domain.MailProviders = $OldDomain.MailProviders - } - } - # Return domain object to list - $Domain - } - - # Batch insert all tenant domains - try { - Add-CIPPAzDataTableEntity @DomainTable -Entity $TenantDomainObjects -Force - } catch { Write-LogMessage -API 'DomainAnalyser' -message "Domain Analyser GetTenantDomains Error $($_.Exception.Message)" -sev info } - } catch { Write-LogMessage -API 'DomainAnalyser' -message "GetTenantDomains loop exception: $($_.Exception.Message) line $($_.InvocationInfo.ScriptLineNumber)" -sev 'Error' } -} diff --git a/DomainAnalyser_Orchestration/function.json b/DomainAnalyser_Orchestration/function.json deleted file mode 100644 index 7326b39c184d..000000000000 --- a/DomainAnalyser_Orchestration/function.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "bindings": [ - { - "name": "Context", - "type": "orchestrationTrigger", - "direction": "in" - } - ] -} \ No newline at end of file diff --git a/DomainAnalyser_Orchestration/run.ps1 b/DomainAnalyser_Orchestration/run.ps1 deleted file mode 100644 index 34848e03284d..000000000000 --- a/DomainAnalyser_Orchestration/run.ps1 +++ /dev/null @@ -1,40 +0,0 @@ -param($Context) - -try { - - $DurableRetryOptions = @{ - FirstRetryInterval = (New-TimeSpan -Seconds 5) - MaxNumberOfAttempts = 1 - BackoffCoefficient = 2 - } - $RetryOptions = New-DurableRetryOptions @DurableRetryOptions - - # Sync tenants - try { - Invoke-ActivityFunction -FunctionName 'DomainAnalyser_GetTenantDomains' -Input 'Tenants' - } catch { Write-Host "EXCEPTION: TenantDomains $($_.Exception.Message)" } - - # Get list of all domains to process - $Batch = Invoke-ActivityFunction -FunctionName 'Activity_GetAllTableRows' -Input 'Domains' - - $ParallelTasks = foreach ($Item in $Batch) { - Invoke-DurableActivity -FunctionName 'DomainAnalyser_All' -Input $item -NoWait -RetryOptions $RetryOptions - } - - # Collect activity function results and send to database - $TableParams = Get-CippTable -tablename 'Domains' - $TableParams.Entity = Wait-ActivityFunction -Task $ParallelTasks - $TableParams.Force = $true - $TableParams = $TableParams | ConvertTo-Json -Compress - - try { - Invoke-ActivityFunction -FunctionName 'Activity_AddOrUpdateTableRows' -Input $TableParams - } catch { - Write-Host "Orchestrator exception UpdateDomains $($_.Exception.Message)" - } -} catch { - Write-LogMessage -API 'DomainAnalyser' -message "Domain Analyser Orchestrator Error $($_.Exception.Message)" -sev info - #Write-Host $_.Exception | ConvertTo-Json -} finally { - Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser has Finished' -sev Info -} \ No newline at end of file diff --git a/DomainAnalyser_OrchestrationStarter/run.ps1 b/DomainAnalyser_OrchestrationStarter/run.ps1 index 7c85e87640cd..b2fd0769428c 100644 --- a/DomainAnalyser_OrchestrationStarter/run.ps1 +++ b/DomainAnalyser_OrchestrationStarter/run.ps1 @@ -1,19 +1,20 @@ using namespace System.Net param($Request, $TriggerMetadata) -if ($CurrentlyRunning) { - $Results = [pscustomobject]@{'Results' = 'Already running. Please wait for the current instance to finish' } - Write-LogMessage -API 'DomainAnalyser' -message 'Attempted to start domain analysis but an instance was already running.' -sev Info -} -else { - $InstanceId = Start-NewOrchestration -FunctionName 'DomainAnalyser_Orchestration' - Write-Host "Started orchestration with ID = '$InstanceId'" - $Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId - Write-LogMessage -API 'DomainAnalyser' -message 'Started retrieving domain information' -sev Info - $Results = [pscustomobject]@{'Results' = 'Started running analysis' } -} -Write-Host ($Orchestrator | ConvertTo-Json) +$Results = [pscustomobject]@{'Results' = 'Domain Analyser started' } +$InputObject = [PSCustomObject]@{ + QueueFunction = [PSCustomObject]@{ + FunctionName = 'GetTenants' + DurableName = 'DomainAnalyserTenant' + TenantParams = @{ + IncludeAll = $true + } + } + OrchestratorName = 'DomainAnalyser_Tenants' + SkipLog = $true +} +Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Compress -Depth 5) Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK diff --git a/Domain_OrchestrationStarterTimer/run.ps1 b/Domain_OrchestrationStarterTimer/run.ps1 index 67e945736bb1..63d7cf8985bb 100644 --- a/Domain_OrchestrationStarterTimer/run.ps1 +++ b/Domain_OrchestrationStarterTimer/run.ps1 @@ -1,22 +1,33 @@ param($Timer) -if ($env:DEV_SKIP_DOMAIN_TIMER) { +if ($env:DEV_SKIP_DOMAIN_TIMER) { Write-Host 'Skipping DomainAnalyser timer' - exit 0 + exit 0 } try { if ($CurrentlyRunning) { $Results = [pscustomobject]@{'Results' = 'Already running. Please wait for the current instance to finish' } Write-LogMessage -API 'DomainAnalyser' -message 'Attempted to start analysis but an instance was already running.' -sev Info - } - else { - $InstanceId = Start-NewOrchestration -FunctionName 'DomainAnalyser_Orchestration' + } else { + #$InstanceId = Start-NewOrchestration -FunctionName 'DomainAnalyser_Orchestration' Write-Host "Started orchestration with ID = '$InstanceId'" - $Orchestrator = New-OrchestrationCheckStatusResponse -Request $Timer -InstanceId $InstanceId + #Orchestrator = New-OrchestrationCheckStatusResponse -Request $Timer -InstanceId $InstanceId Write-LogMessage -API 'DomainAnalyser' -message 'Starting Domain Analyser' -sev Info $Results = [pscustomobject]@{'Results' = 'Starting Domain Analyser' } + + $InputObject = [PSCustomObject]@{ + QueueFunction = [PSCustomObject]@{ + FunctionName = 'GetTenants' + DurableName = 'DomainAnalyserTenant' + TenantParams = @{ + IncludeAll = $true + } + } + OrchestratorName = 'DomainAnalyser_Tenants' + SkipLog = $true + } + Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Compress -Depth 5) } Write-Host ($Orchestrator | ConvertTo-Json) -} -catch { Write-Host "Domain_OrchestratorStarterTimer Exception $($_.Exception.Message)" } \ No newline at end of file +} catch { Write-Host "Domain_OrchestratorStarterTimer Exception $($_.Exception.Message)" } \ No newline at end of file diff --git a/ExecSchedulerBillingRun/run.ps1 b/ExecSchedulerBillingRun/run.ps1 index ff93986817b2..3ea7e6621fac 100644 --- a/ExecSchedulerBillingRun/run.ps1 +++ b/ExecSchedulerBillingRun/run.ps1 @@ -3,20 +3,19 @@ param($QueueItem) # Get the current universal time in the default string format. try { - Write-LogMessage -API "Scheduler_Billing" -tenant "none" -message "Starting billing processing." -sev Info + Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message 'Starting billing processing.' -sev Info $Table = Get-CIPPTable -TableName Extensionsconfig $Configuration = (Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -Depth 10 foreach ($ConfigItem in $Configuration.psobject.properties.name) { switch ($ConfigItem) { - "Gradient" { + 'Gradient' { If ($Configuration.Gradient.enabled -and $Configuration.Gradient.BillingEnabled) { New-GradientServiceSyncRun } } } } -} -catch { - Write-LogMessage -API "Scheduler_Billing" -tenant "none" -message "Could not start billing processing $($_.Exception.Message)" -sev Error +} catch { + Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message 'Could not start billing processing' -sev Error -LogData (Get-CippException -Exception $_) } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index 6f03e28b64e2..befa8155df6c 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -9,7 +9,7 @@ function Add-CIPPAzDataTableEntity { foreach ($SingleEnt in $Entity) { try { - Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt + Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt -ErrorAction Stop } catch [System.Exception] { if ($_.Exception.ErrorCode -eq 'PropertyValueTooLarge' -or $_.Exception.ErrorCode -eq 'EntityTooLarge') { try { @@ -52,6 +52,8 @@ function Add-CIPPAzDataTableEntity { throw "Error processing entity: $($_.Exception.Message)." } } else { + Write-Host "THE ERROR IS $($_.Exception.ErrorCode)" + throw $_ } } diff --git a/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 b/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 index a4c66a07cb7b..b29972bcce3a 100644 --- a/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 @@ -1,18 +1,18 @@ function Add-CIPPGroupMember( [string]$ExecutingUser, - [string]$GroupType, + [string]$GroupType, [string]$GroupId, - [string]$Member, + [string]$Member, [string]$TenantFilter, [string]$APIName = 'Add Group Member' ) { try { if ($member -like '*#EXT#*') { $member = [System.Web.HttpUtility]::UrlEncode($member) } - $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($member)" -tenantid $TenantFilter).id + $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($member)" -tenantid $TenantFilter).id $addmemberbody = "{ `"members@odata.bind`": $(ConvertTo-Json @($MemberIDs)) }" if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') { $Params = @{ Identity = $GroupId; Member = $member; BypassSecurityGroupManagerCheck = $true } - New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true } else { New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)" -tenantid $TenantFilter -type patch -body $addmemberbody -Verbose } @@ -21,9 +21,9 @@ function Add-CIPPGroupMember( return $message return } catch { - $message = "Failed to add user $($Member) to $($GroupId): $($_.Exception.Message)" - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message $message -Sev 'error' - return $message + $message = "Failed to add user $($Member) to $($GroupId)" + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message $message -Sev 'error' -LogData (Get-CippException -Exception $_) + return $message } } diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index 04c5d358d599..8cd5b159d458 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -18,8 +18,7 @@ function Add-CIPPScheduledTask { $ht[$p.Key] = $p.Value } $Parameters[$Key] = [PSCustomObject]$ht - } - else { + } else { $Parameters[$Key] = $Param } } @@ -30,10 +29,15 @@ function Add-CIPPScheduledTask { } $AdditionalProperties = ([PSCustomObject]$AdditionalProperties | ConvertTo-Json -Compress) if ($Parameters -eq 'null') { $Parameters = '' } + if (!$Task.RowKey) { + $RowKey = (New-Guid).Guid + } else { + $RowKey = $Task.RowKey + } $entity = @{ PartitionKey = [string]'ScheduledTask' TaskState = [string]'Planned' - RowKey = [string]"$(New-Guid)" + RowKey = [string]$RowKey Tenant = [string]$task.TenantFilter Name = [string]$task.Name Command = [string]$task.Command.value @@ -46,10 +50,9 @@ function Add-CIPPScheduledTask { Results = 'Planned' } try { - Add-CIPPAzDataTableEntity @Table -Entity $entity - } - catch { + Add-CIPPAzDataTableEntity @Table -Entity $entity -Force + } catch { return "Could not add task: $($_.Exception.Message)" } - return "Successfully added task" + return 'Successfully added task' } \ No newline at end of file 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 new file mode 100644 index 000000000000..c5aa1f420919 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 @@ -0,0 +1,252 @@ +function Push-DomainAnalyserDomain { + param($Item) + $DomainTable = Get-CippTable -tablename 'Domains' + $Filter = "PartitionKey eq 'TenantDomains' and RowKey eq '{0}'" -f $Item.RowKey + $DomainObject = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter + + try { + $ConfigTable = Get-CippTable -tablename Config + $Filter = "PartitionKey eq 'Domains' and RowKey eq 'Domains'" + $Config = Get-CIPPAzDataTableEntity @ConfigTable -Filter $Filter + + $ValidResolvers = @('Google', 'CloudFlare', 'Quad9') + if ($ValidResolvers -contains $Config.Resolver) { + $Resolver = $Config.Resolver + } else { + $Resolver = 'Google' + $Config = @{ + PartitionKey = 'Domains' + RowKey = 'Domains' + Resolver = $Resolver + } + Add-CIPPAzDataTableEntity @ConfigTable -Entity $Config -Force + } + } catch { + $Resolver = 'Google' + } + Set-DnsResolver -Resolver $Resolver + + $Domain = $DomainObject.rowKey + + try { + $Tenant = $DomainObject.TenantDetails | ConvertFrom-Json -ErrorAction Stop + } catch { + $Tenant = @{Tenant = 'None' } + } + + $Result = [PSCustomObject]@{ + Tenant = $Tenant.Tenant + TenantID = $Tenant.TenantGUID + GUID = $($Domain.Replace('.', '')) + LastRefresh = $(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') + Domain = $Domain + NSRecords = (Read-NSRecord -Domain $Domain).Records + ExpectedSPFRecord = '' + ActualSPFRecord = '' + SPFPassAll = '' + ActualMXRecords = '' + MXPassTest = '' + DMARCPresent = '' + DMARCFullPolicy = '' + DMARCActionPolicy = '' + DMARCReportingActive = '' + DMARCPercentagePass = '' + DNSSECPresent = '' + MailProvider = '' + DKIMEnabled = '' + DKIMRecords = '' + Score = '' + MaximumScore = 160 + ScorePercentage = '' + ScoreExplanation = '' + } + + $Scores = [PSCustomObject]@{ + SPFPresent = 10 + SPFCorrectAll = 20 + MXRecommended = 10 + DMARCPresent = 10 + DMARCSetQuarantine = 20 + DMARCSetReject = 30 + DMARCReportingActive = 20 + DMARCPercentageGood = 20 + DNSSECPresent = 20 + DKIMActiveAndWorking = 20 + } + + $ScoreDomain = 0 + # Setup Score Explanation + $ScoreExplanation = [System.Collections.Generic.List[string]]::new() + + # Check MX Record + $MXRecord = Read-MXRecord -Domain $Domain -ErrorAction Stop + + $Result.ExpectedSPFRecord = $MXRecord.ExpectedInclude + $Result.MXPassTest = $false + $Result.ActualMXRecords = $MXRecord.Records + + # Check fail counts to ensure all tests pass + #$MXWarnCount = $MXRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count + $MXFailCount = $MXRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count + + if ($MXFailCount -eq 0) { + $Result.MXPassTest = $true + $ScoreDomain += $Scores.MXRecommended + } else { + $ScoreExplanation.Add('MX record did not pass validation') | Out-Null + } + + if ([string]::IsNullOrEmpty($MXRecord.MailProvider)) { + $Result.MailProvider = 'Unknown' + } else { + $Result.MailProvider = $MXRecord.MailProvider.Name + } + + # Get SPF Record + try { + $SPFRecord = Read-SpfRecord -Domain $Domain -ErrorAction Stop + if ($SPFRecord.RecordCount -gt 0) { + $Result.ActualSPFRecord = $SPFRecord.Record + if ($SPFRecord.RecordCount -eq 1) { + $ScoreDomain += $Scores.SPFPresent + } else { + $ScoreExplanation.Add('Multiple SPF records detected') | Out-Null + } + } else { + $Result.ActualSPFRecord = 'No SPF Record' + $ScoreExplanation.Add('No SPF Record Found') | Out-Null + } + } catch { + $Message = 'SPF Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error + throw $Message + } + + # Check SPF Record + $Result.SPFPassAll = $false + + # Check warning + fail counts to ensure all tests pass + #$SPFWarnCount = $SPFRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count + $SPFFailCount = $SPFRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count + + if ($SPFFailCount -eq 0) { + $ScoreDomain += $Scores.SPFCorrectAll + $Result.SPFPassAll = $true + } else { + $ScoreExplanation.Add('SPF record did not pass validation') | Out-Null + } + + # Get DMARC Record + try { + $DMARCPolicy = Read-DmarcPolicy -Domain $Domain -ErrorAction Stop + + If ([string]::IsNullOrEmpty($DMARCPolicy.Record)) { + $Result.DMARCPresent = $false + $ScoreExplanation.Add('No DMARC Records Found') | Out-Null + } else { + $Result.DMARCPresent = $true + $ScoreDomain += $Scores.DMARCPresent + + $Result.DMARCFullPolicy = $DMARCPolicy.Record + if ($DMARCPolicy.Policy -eq 'reject' -and $DMARCPolicy.SubdomainPolicy -eq 'reject') { + $Result.DMARCActionPolicy = 'Reject' + $ScoreDomain += $Scores.DMARCSetReject + } + if ($DMARCPolicy.Policy -eq 'none') { + $Result.DMARCActionPolicy = 'None' + $ScoreExplanation.Add('DMARC is not being enforced') | Out-Null + } + if ($DMARCPolicy.Policy -eq 'quarantine') { + $Result.DMARCActionPolicy = 'Quarantine' + $ScoreDomain += $Scores.DMARCSetQuarantine + $ScoreExplanation.Add('DMARC Partially Enforced with quarantine') | Out-Null + } + + $ReportEmailCount = $DMARCPolicy.ReportingEmails | Measure-Object | Select-Object -ExpandProperty Count + if ($ReportEmailCount -gt 0) { + $Result.DMARCReportingActive = $true + $ScoreDomain += $Scores.DMARCReportingActive + } else { + $Result.DMARCReportingActive = $False + $ScoreExplanation.Add('DMARC Reporting not Configured') | Out-Null + } + + if ($DMARCPolicy.Percent -eq 100) { + $Result.DMARCPercentagePass = $true + $ScoreDomain += $Scores.DMARCPercentageGood + } else { + $Result.DMARCPercentagePass = $false + $ScoreExplanation.Add('DMARC Not Checking All Messages') | Out-Null + } + } + } catch { + $Message = 'DMARC Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error + throw $Message + } + + # DNS Sec Check + try { + $DNSSECResult = Test-DNSSEC -Domain $Domain -ErrorAction Stop + $DNSSECFailCount = $DNSSECResult.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count + $DNSSECWarnCount = $DNSSECResult.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count + if (($DNSSECFailCount + $DNSSECWarnCount) -eq 0) { + $Result.DNSSECPresent = $true + $ScoreDomain += $Scores.DNSSECPresent + } else { + $Result.DNSSECPresent = $false + $ScoreExplanation.Add('DNSSEC Not Configured or Enabled') | Out-Null + } + } catch { + $Message = 'DNSSEC Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error + throw $Message + } + + # DKIM Check + try { + $DkimParams = @{ + Domain = $Domain + FallbackToMicrosoftSelectors = $true + } + if (![string]::IsNullOrEmpty($DomainObject.DkimSelectors)) { + $DkimParams.Selectors = $DomainObject.DkimSelectors | ConvertFrom-Json + } + + $DkimRecord = Read-DkimRecord @DkimParams -ErrorAction Stop + + $DkimRecordCount = $DkimRecord.Records | Measure-Object | Select-Object -ExpandProperty Count + $DkimFailCount = $DkimRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count + #$DkimWarnCount = $DkimRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count + if ($DkimRecordCount -gt 0 -and $DkimFailCount -eq 0) { + $Result.DKIMEnabled = $true + $ScoreDomain += $Scores.DKIMActiveAndWorking + $Result.DKIMRecords = $DkimRecord.Records | Select-Object Selector, Record + } else { + $Result.DKIMEnabled = $false + $ScoreExplanation.Add('DKIM Not Configured') | Out-Null + } + } catch { + $Message = 'DKIM Exception' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error + throw $Message + } + # Final Score + $Result.Score = $ScoreDomain + $Result.ScorePercentage = [int](($Result.Score / $Result.MaximumScore) * 100) + $Result.ScoreExplanation = ($ScoreExplanation) -join ', ' + + $DomainObject.DomainAnalyser = ($Result | ConvertTo-Json -Compress).ToString() + + try { + $DomainTable.Entity = $DomainObject + $DomainTable.Force = $true + Add-CIPPAzDataTableEntity @DomainTable -Entity $DomainObject -Force + + # Final Write to Output + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message "DNS Analyser Finished For $Domain" -sev Info + } catch { + Write-LogMessage -API -API 'DomainAnalyser' -tenant $tenant.tenant -message "Error saving domain $Domain to table " -sev Error -LogData (Get-CippException -Exception $_) + } + return $null +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 new file mode 100644 index 000000000000..b376215dca70 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 @@ -0,0 +1,107 @@ +function Push-DomainAnalyserTenant { + <# + .FUNCTIONALITY + Entrypoint + #> + param($Item) + + $Tenant = Get-Tenants | Where-Object { $_.customerId -eq $Item.customerId } + $DomainTable = Get-CippTable -tablename 'Domains' + + if ($Tenant.Excluded -eq $true) { + $Filter = "PartitionKey eq 'TenantDomains' and TenantId eq '{0}'" -f $Exclude.defaultDomainName + $CleanupRows = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter + $CleanupCount = ($CleanupRows | Measure-Object).Count + if ($CleanupCount -gt 0) { + Write-LogMessage -API 'DomainAnalyser' -tenant $Exclude.defaultDomainName -message "Cleaning up $CleanupCount domain(s) for excluded tenant" -sev Info + Remove-AzDataTableEntity @DomainTable -Entity $CleanupRows + } + } elseif ($Tenant.GraphErrorCount -gt 50) { + return + } else { + try { + $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/domains' -tenantid $Tenant.defaultDomainName | Where-Object { ($_.id -notlike '*.microsoftonline.com' -and $_.id -NotLike '*.exclaimer.cloud' -and $_.id -Notlike '*.excl.cloud' -and $_.id -NotLike '*.codetwo.online' -and $_.id -NotLike '*.call2teams.com' -and $_.isVerified) } + + $TenantDomains = foreach ($d in $domains) { + [PSCustomObject]@{ + Tenant = $Tenant.defaultDomainName + TenantGUID = $Tenant.customerId + InitialDomainName = $Tenant.initialDomainName + Domain = $d.id + AuthenticationType = $d.authenticationType + IsAdminManaged = $d.isAdminManaged + IsDefault = $d.isDefault + IsInitial = $d.isInitial + IsRoot = $d.isRoot + IsVerified = $d.isVerified + SupportedServices = $d.supportedServices + } + } + + $DomainCount = ($TenantDomains | Measure-Object).Count + if ($DomainCount -gt 0) { + Write-Host "$TenantCount tenant Domains" + try { + $TenantDomainObjects = foreach ($Tenant in $TenantDomains) { + $TenantDetails = ($Tenant | ConvertTo-Json -Compress).ToString() + $Filter = "PartitionKey eq '{0}' and RowKey eq '{1}'" -f $Tenant.Tenant, $Tenant.Domain + $OldDomain = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter + + if ($OldDomain) { + Remove-AzDataTableEntity @DomainTable -Entity $OldDomain | Out-Null + } + + $Filter = "PartitionKey eq 'TenantDomains' and RowKey eq '{0}'" -f $Tenant.Domain + $Domain = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter + + if (!$Domain -or $null -eq $Domain.TenantGUID) { + $DomainObject = [pscustomobject]@{ + DomainAnalyser = '' + TenantDetails = $TenantDetails + TenantId = $Tenant.Tenant + TenantGUID = $Tenant.TenantGUID + DkimSelectors = '' + MailProviders = '' + RowKey = $Tenant.Domain + PartitionKey = 'TenantDomains' + } + + if ($OldDomain) { + $DomainObject.DkimSelectors = $OldDomain.DkimSelectors + $DomainObject.MailProviders = $OldDomain.MailProviders + } + $Domain = $DomainObject + } else { + $Domain.TenantDetails = $TenantDetails + if ($OldDomain) { + $Domain.DkimSelectors = $OldDomain.DkimSelectors + $Domain.MailProviders = $OldDomain.MailProviders + } + } + # Return domain object to list + $Domain + } + + # Batch insert tenant domains + try { + Add-CIPPAzDataTableEntity @DomainTable -Entity $TenantDomainObjects -Force + $InputObject = [PSCustomObject]@{ + Batch = $TenantDomainObjects | Select-Object RowKey, @{n = 'FunctionName'; exp = { 'DomainAnalyserDomain' } } + OrchestratorName = "DomainAnalyser_$($Tenant.defaultDomainName)" + SkipLog = $true + } + Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Compress -Depth 5) + } catch { + Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser GetTenantDomains error' -sev info -LogData (Get-CippException -Exception $_) + } + } catch { + Write-LogMessage -API 'DomainAnalyser' -message 'GetTenantDomains loop error' -sev 'Error' -LogData (Get-CippException -Exception $_) + } + } + } catch { + Write-Host (Get-CippException -Exception $_) + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.defaultDomainName -message 'DNS Analyser GraphGetRequest' -LogData (Get-CippException -Exception $_) -sev Error + } + } + return $null +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertMFAAlertUsers.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertMFAAlertUsers.ps1 index e6401a7b117f..6af3ca798606 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertMFAAlertUsers.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertMFAAlertUsers.ps1 @@ -6,7 +6,7 @@ function Push-CIPPAlertMFAAlertUsers { ) try { - $users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails?$filter=isMfaRegistered eq false and userType eq ''member''&$select=userPrincipalName,lastUpdatedDateTime,isMfaRegistered' -tenantid $($Item.tenant) + $users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails?$top=999&filter=isMfaRegistered eq false and userType eq ''member''&$select=userPrincipalName,lastUpdatedDateTime,isMfaRegistered' -tenantid $($Item.tenant) if ($users.UserPrincipalName) { Write-AlertMessage -tenant $Item.tenant -message "The following $($users.Count) users do not have MFA registered: $($users.UserPrincipalName -join ', ')" } diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertNewAppApproval.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertNewAppApproval.ps1 index 438fd62739d6..f5317b9769a5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertNewAppApproval.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertNewAppApproval.ps1 @@ -6,7 +6,7 @@ function Push-CIPPAlertNewAppApproval { [pscustomobject]$Item ) try { - $Approvals = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/identityGovernance/appConsent/appConsentRequests' -tenantid $item.tenant + $Approvals = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/identityGovernance/appConsent/appConsentRequests' -tenantid $item.tenant | Where-Object -Property requestStatus -EQ 'inProgress' if ($Approvals.count -gt 1) { Write-AlertMessage -tenant $($Item.tenant) -message "There is are $($Approvals.count) App Approvals waiting." } diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 index b6938cc1389c..221068716478 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 @@ -215,39 +215,40 @@ Function Push-ExecOnboardTenantQueue { $OnboardingSteps.Step3.Status = 'failed' $OnboardingSteps.Step3.Message = 'Failed to map security groups, no pending invite available' } + } - do { - $AccessAssignments = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$Id/accessAssignments" - Start-Sleep -Seconds 15 - } while ($AccessAssignments.status -contains 'pending' -and (Get-Date) -lt $Start.AddMinutes(8)) + do { + $AccessAssignments = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$Id/accessAssignments" + Start-Sleep -Seconds 15 + } while ($AccessAssignments.status -contains 'pending' -and (Get-Date) -lt $Start.AddMinutes(8)) - if ($AccessAssignments.status -notcontains 'pending') { - $OnboardingSteps.Step3.Message = 'Group check: Access assignments are mapped and active' - $OnboardingSteps.Step3.Status = 'succeeded' - if ($Item.AddMissingGroups -eq $true) { - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Checking for missing groups for SAM user' }) - $SamUserId = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me?`$select=id").id - $CurrentMemberships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me/transitiveMemberOf?`$select=id,displayName" - foreach ($Role in $Item.Roles) { - if ($CurrentMemberships.id -notcontains $Role.GroupId) { - $PostBody = @{ - '@odata.id' = 'https://graph.microsoft.com/v1.0/directoryObjects/{0}' -f $SamUserId - } | ConvertTo-Json -Compress - try { - New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($Role.GroupId)/members/`$ref" -body $PostBody -AsApp $true -NoAuthCheck $true - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Added SAM user to $($Role.GroupName)" }) - } catch { - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Failed to add SAM user to $($Role.GroupName) - $($_.Exception.Message)" }) - } + if ($AccessAssignments.status -notcontains 'pending') { + $OnboardingSteps.Step3.Message = 'Group check: Access assignments are mapped and active' + $OnboardingSteps.Step3.Status = 'succeeded' + if ($Item.AddMissingGroups -eq $true) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Checking for missing groups for SAM user' }) + $SamUserId = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me?`$select=id").id + $CurrentMemberships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me/transitiveMemberOf?`$select=id,displayName" + foreach ($Role in $Item.Roles) { + if ($CurrentMemberships.id -notcontains $Role.GroupId) { + $PostBody = @{ + '@odata.id' = 'https://graph.microsoft.com/v1.0/directoryObjects/{0}' -f $SamUserId + } | ConvertTo-Json -Compress + try { + New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($Role.GroupId)/members/`$ref" -body $PostBody -AsApp $true -NoAuthCheck $true + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Added SAM user to $($Role.GroupName)" }) + } catch { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Failed to add SAM user to $($Role.GroupName) - $($_.Exception.Message)" }) } } - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'SAM user group check completed' }) } - } else { - $OnboardingSteps.Step3.Message = 'Group check: Access assignments are still pending, try again later' - $OnboardingSteps.Step3.Status = 'failed' - $TenantOnboarding.Status = 'failed' + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'SAM user group check completed' }) } + } else { + $OnboardingSteps.Step3.Message = 'Group check: Access assignments are still pending, try again later' + $OnboardingSteps.Step3.Status = 'failed' + $TenantOnboarding.Status = 'failed' + Write-LogMessage -API 'Onboarding' -message "Tenant onboarding failed at group mapping step for $($Relationship.customer.displayName)" -Sev 'Error' } $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) @@ -274,11 +275,7 @@ Function Push-ExecOnboardTenantQueue { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Clearing tenant cache' }) $y = 0 do { - try { - Remove-CIPPCache -tenantsOnly $true - } catch {} - - $Tenant = Get-Tenants -IncludeAll | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 + $Tenant = Get-Tenants -TriggerRefresh -IncludeAll | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 $y++ Start-Sleep -Seconds 20 } while (!$Tenant -and $y -le 4) @@ -302,6 +299,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 + Write-LogMessage -API 'Onboarding' -message "Tenant onboarding failed at CPV step for $($Relationship.customer.displayName)" -Sev 'Error' return } $Refreshing = $true @@ -327,10 +325,7 @@ Function Push-ExecOnboardTenantQueue { $OnboardingSteps.Step4.Status = 'succeeded' $OnboardingSteps.Step4.Message = 'CPV permissions refreshed' if ($Tenant.defaultDomainName -match 'Domain Error') { - try { - Remove-CIPPCache -tenantsOnly $true - } catch {} - $Tenant = Get-Tenants -IncludeAll | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 + $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' }) @@ -364,6 +359,7 @@ Function Push-ExecOnboardTenantQueue { } catch { $UserCount = 0 $ApiError = $_.Exception.Message + $ApiException = $_ } if ($UserCount -gt 0) { @@ -375,6 +371,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 + Write-LogMessage -API 'Onboarding' -message "Tenant onboarding succeeded for $($Relationship.customer.displayName)" -Sev 'Info' } else { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'API Test failed: {0}' -f $ApiError }) $OnboardingSteps.Step5.Status = 'failed' @@ -383,6 +380,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 + Write-LogMessage -API 'Onboarding' -message "Tenant onboarding API test failed for $($Relationship.customer.displayName)" -Sev 'Error' -LogData (Get-CippException -Exception $ApiException) } } } catch { @@ -392,5 +390,6 @@ 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 + Write-LogMessage -API 'Onboarding' -message "Tenant onboarding failed for $Id" -Sev 'Error' -LogData (Get-CippException -Exception $_) } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-GetTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-GetTenants.ps1 new file mode 100644 index 000000000000..4d9e274b7aaa --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-GetTenants.ps1 @@ -0,0 +1,10 @@ +function Push-GetTenants { + Param($Item) + + $Params = $Item.TenantParams | ConvertTo-Json | ConvertFrom-Json -AsHashtable + try { + Get-Tenants @Params | Select-Object customerId, @{n = 'FunctionName'; e = { $Item.DurableName } } + } catch { + Write-Host "GetTenants Exception $($_.Exception.Message)" + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 index 4d321a825f4e..fa4872195b0e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 @@ -6,12 +6,14 @@ function Push-PublicWebhookProcess { Invoke-CippGraphWebhookProcessing -Data ($Item.Data | ConvertFrom-Json) -CIPPID $Item.CIPPID -WebhookInfo ($Item.Webhookinfo | ConvertFrom-Json) } elseif ($Item.Type -eq 'AuditLog') { Invoke-CippWebhookProcessing -TenantFilter $Item.TenantFilter -Data ($Item.Data | ConvertFrom-Json) -CIPPPURL $Item.CIPPURL + } elseif ($Item.Type -eq 'PartnerCenter') { + Invoke-CippPartnerWebhookProcessing -Data ($Item.Data | ConvertFrom-Json) } } catch { Write-Host "Webhook Exception: $($_.Exception.Message)" } finally { $WebhookIncoming = Get-CIPPTable -TableName WebhookIncoming $Entity = $Item | Select-Object -Property RowKey, PartitionKey - Remove-AzDataTableEntity @WebhookIncoming -Entity $Entity + Remove-AzDataTableEntity @WebhookIncoming -Entity $Entity } } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 new file mode 100644 index 000000000000..1644f9a961dc --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 @@ -0,0 +1,55 @@ +function Invoke-ExecPartnerWebhook { + Param($Request, $TriggerMetadata) + + switch ($Request.Query.Action) { + 'ListEventTypes' { + $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration/events' + $Results = New-GraphGetRequest -uri $Uri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + } + 'ListSubscription' { + try { + $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' + $Results = New-GraphGetRequest -uri $Uri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + } catch {} + if (!$Results) { + $Results = [PSCustomObject]@{ + webhoookUrl = 'None' + lastModifiedTimestamp = 'Never' + webhookEvents = @() + } + } + } + 'CreateSubscription' { + $BaseURL = ([System.Uri]$request.headers.'x-ms-original-url').Host + $Webhook = @{ + TenantFilter = $env:TenantId + PartnerCenter = $true + BaseURL = $BaseURL + EventType = $Request.body.EventType + ExecutingUser = $Request.headers.'x-ms-client-principal' + } + $Results = New-CIPPGraphSubscription @Webhook + } + 'SendTest' { + $Results = New-GraphPOSTRequest -uri 'https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents' -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + } + 'ValidateTest' { + $Results = New-GraphGetRequest -uri "https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents/$($Request.Query.CorrelationId)" -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + } + default { + $Results = 'Invalid Action' + } + } + + $Body = [PSCustomObject]@{ + Results = $Results + Metadata = [PSCustomObject]@{ + Action = $Request.Query.Action + } + } + + Push-OutputBinding -Name Response -Value @{ + StatusCode = [System.Net.HttpStatusCode]::OK + Body = $Body + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ExecScheduledCommand.ps1 deleted file mode 100644 index ce73476b5c97..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ExecScheduledCommand.ps1 +++ /dev/null @@ -1,93 +0,0 @@ - using namespace System.Net - - Function Invoke-ExecScheduledCommand { - <# - .FUNCTIONALITY - Entrypoint - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - $commandParameters = $QueueItem.Parameters - -$tenant = $QueueItem.Parameters['TenantFilter'] -Write-Host 'started task' -try { - try { - $results = & $QueueItem.command @commandParameters - } - catch { - $results = "Task Failed: $($_.Exception.Message)" - - } - - Write-Host 'ran the command' - if ($results -is [String]) { - $results = @{ Results = $results } - } - if ($results -is [array]) { - $results = $results | Where-Object { $_ -is [string] } - $results = $results | ForEach-Object { @{ Results = $_ } } - } - - $results = $results | Select-Object * -ExcludeProperty RowKey, PartitionKey - - $StoredResults = $results | ConvertTo-Json -Compress -Depth 20 | Out-String - if ($StoredResults.Length -gt 64000 -or $task.Tenant -eq 'AllTenants') { - $StoredResults = @{ Results = 'The results for this query are too long to store in this table, or the query was meant for All Tenants. Please use the options to send the results to another target to be able to view the results. ' } | ConvertTo-Json -Compress - } -} -catch { - $errorMessage = $_.Exception.Message - if ($task.Recurrence -gt 0) { $State = 'Failed - Planned' } else { $State = 'Failed' } - Update-AzDataTableEntity @Table -Entity @{ - PartitionKey = $task.PartitionKey - RowKey = $task.RowKey - Results = "$errorMessage" - TaskState = $State - } - Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Failed to execute task $($task.Name): $errorMessage" -sev Error -} - - -$TableDesign = '' -$HTML = ($results | Select-Object * -ExcludeProperty RowKey, PartitionKey | ConvertTo-Html -Fragment) -replace '', "$TableDesign
" | Out-String -$title = "Scheduled Task $($task.Name) - $($task.ExpectedRunTime)" -Write-Host $title -switch -wildcard ($task.PostExecution) { - '*psa*' { Send-CIPPAlert -Type 'psa' -Title $title -HTMLContent $HTML } - '*email*' { Send-CIPPAlert -Type 'email' -Title $title -HTMLContent $HTML } - '*webhook*' { - $Webhook = [PSCustomObject]@{ - 'Tenant' = $tenant - 'TaskInfo' = $QueueItem.TaskInfo - 'Results' = $Results - } - Send-CIPPAlert -Type 'webhook' -Title $title -JSONContent $($Webhook | ConvertTo-Json -Depth 20) - } -} - -Write-Host 'ran the command' - -if ($task.Recurrence -le '0' -or $task.Recurrence -eq $null) { - Update-AzDataTableEntity @Table -Entity @{ - PartitionKey = $task.PartitionKey - RowKey = $task.RowKey - Results = "$StoredResults" - TaskState = 'Completed' - } -} -else { - $nextRun = (Get-Date).AddDays($task.Recurrence) - $nextRunUnixTime = [int64]($nextRun - (Get-Date '1/1/1970')).TotalSeconds - Update-AzDataTableEntity @Table -Entity @{ - PartitionKey = $task.PartitionKey - RowKey = $task.RowKey - Results = "$StoredResults" - TaskState = 'Planned' - ScheduledTime = "$nextRunUnixTime" - } -} -Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Successfully executed task: $($task.name)" -sev Info - - } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 index 067da939c2ca..41768a704af5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 @@ -88,7 +88,7 @@ Function Invoke-ExecDnsConfig { } 'GetConfig' { $body = [pscustomobject]$Config - Write-LogMessage -API $APINAME -tenant 'Global' -user $request.headers.'x-ms-client-principal' -message 'Retrieved DNS configuration' -Sev 'Info' + Write-LogMessage -API $APINAME -tenant 'Global' -user $request.headers.'x-ms-client-principal' -message 'Retrieved DNS configuration' -Sev 'Debug' } 'RemoveDomain' { $Filter = "RowKey eq '{0}'" -f $Request.Query.Domain diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeTenant.ps1 index ca63f9f20afa..ca3e85feed9a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeTenant.ps1 @@ -17,13 +17,13 @@ Function Invoke-ExecExcludeTenant { $TenantsTable = Get-CippTable -tablename Tenants if ($Request.Query.List) { - $ExcludedFilter = "PartitionKey eq 'Tenants' and Excluded eq true" - $ExcludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter $ExcludedFilter - Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message 'got excluded tenants list' -Sev 'Info' + $ExcludedFilter = "PartitionKey eq 'Tenants' and Excluded eq true" + $ExcludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter $ExcludedFilter + Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message 'got excluded tenants list' -Sev 'Debug' $body = @($ExcludedTenants) } elseif ($Request.query.ListAll) { - $ExcludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -filter "PartitionKey eq 'Tenants'" - Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message 'got excluded tenants list' -Sev 'Info' + $ExcludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -filter "PartitionKey eq 'Tenants'" + Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message 'got excluded tenants list' -Sev 'Debug' $body = @($ExcludedTenants) } try { @@ -31,7 +31,7 @@ Function Invoke-ExecExcludeTenant { $name = $Request.Query.TenantFilter if ($Request.Query.AddExclusion) { $Tenants = Get-Tenants -IncludeAll | Where-Object { $Request.body.value -contains $_.customerId } - + $Excluded = foreach ($Tenant in $Tenants) { $Tenant.Excluded = $true $Tenant.ExcludeUser = $username @@ -41,17 +41,17 @@ Function Invoke-ExecExcludeTenant { Write-Host ($Excluded | ConvertTo-Json) Update-AzDataTableEntity @TenantsTable -Entity ([pscustomobject]$Excluded) #Remove-CIPPCache - Write-LogMessage -API $APINAME -tenant $($name) -user $request.headers.'x-ms-client-principal' -message "Added exclusion for customer(s): $($Excluded.defaultDomainName -join ',')" -Sev 'Info' + Write-LogMessage -API $APINAME -tenant $($name) -user $request.headers.'x-ms-client-principal' -message "Added exclusion for customer(s): $($Excluded.defaultDomainName -join ',')" -Sev 'Info' $body = [pscustomobject]@{'Results' = "Success. Added exclusions for customer(s): $($Excluded.defaultDomainName -join ',')" } } if ($Request.Query.RemoveExclusion) { $Filter = "PartitionKey eq 'Tenants' and defaultDomainName eq '{0}'" -f $name - $Tenant = Get-CIPPAzDataTableEntity @TenantsTable -Filter $Filter + $Tenant = Get-CIPPAzDataTableEntity @TenantsTable -Filter $Filter $Tenant.Excluded = $false $Tenant.ExcludeUser = '' $Tenant.ExcludeDate = '' - Update-AzDataTableEntity @TenantsTable -Entity $Tenant + Update-AzDataTableEntity @TenantsTable -Entity $Tenant #Remove-CIPPCache Write-LogMessage -API $APINAME -tenant $($name) -user $request.headers.'x-ms-client-principal' -message "Removed exclusion for customer $($name)" -Sev 'Info' $body = [pscustomobject]@{'Results' = "Success. We've removed $name from the excluded tenants." } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecSetOoO.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecSetOoO.ps1 index 128df28ca6a9..c0a004791254 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecSetOoO.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecSetOoO.ps1 @@ -21,13 +21,22 @@ Function Invoke-ExecSetOoO { } $StartTime = $Request.body.StartTime $EndTime = $Request.body.EndTime - + + $OutOfOffice = @{ + userid = $Request.body.user + InternalMessage = $InternalMessage + ExternalMessage = $ExternalMessage + TenantFilter = $TenantFilter + State = $Request.Body.AutoReplyState + APIName = $APINAME + ExecutingUser = $request.headers.'x-ms-client-principal' + StartTime = $StartTime + EndTime = $EndTime + } + Write-Host ($OutOfOffice | ConvertTo-Json -Depth 10) + $Results = try { - if ($Request.Body.AutoReplyState -ne 'Scheduled') { - Set-CIPPOutOfOffice -userid $Request.body.user -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' -InternalMessage $InternalMessage -ExternalMessage $ExternalMessage -State $Request.Body.AutoReplyState - } else { - Set-CIPPOutOfOffice -userid $Request.body.user -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' -InternalMessage $InternalMessage -ExternalMessage $ExternalMessage -StartTime $StartTime -EndTime $EndTime -State $Request.Body.AutoReplyState - } + Set-CIPPOutOfOffice @OutOfOffice } catch { "Could not add out of office message for $($username). Error: $($_.Exception.Message)" } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 index 51f31ef28b70..48f3f8badb80 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 @@ -59,7 +59,7 @@ Function Invoke-ListSites { # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = @($GraphRequest) + Body = @($GraphRequest | Sort-Object -Property UPN) }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 index 5c83df245e5f..c5969ad1d8ae 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 @@ -18,7 +18,7 @@ Function Invoke-ListTeams { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter if ($request.query.type -eq 'List') { - $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&`$select=id,displayname,description,visibility,mailNickname" -tenantid $TenantFilter + $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&`$select=id,displayname,description,visibility,mailNickname" -tenantid $TenantFilter | Sort-Object -Property displayName } $TeamID = $request.query.ID Write-Host $TeamID @@ -37,7 +37,7 @@ Function Invoke-ListTeams { Members = @($Members) Owners = @($owners) InstalledApps = @($AppsList) - } + } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 index 38d6f00157bc..9f843768ab9b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 @@ -54,6 +54,18 @@ function Invoke-PublicWebhooks { ## Push webhook data to queue #Invoke-CippGraphWebhookProcessing -Data $ReceivedItem -CIPPID $request.Query.CIPPID -WebhookInfo $Webhookinfo + } elseif ($Request.Query.Type -eq 'PartnerCenter') { + [pscustomobject]$ReceivedItem = $Request.Body + $Entity = [PSCustomObject]@{ + PartitionKey = 'Webhook' + RowKey = [string](New-Guid).Guid + Type = $Request.Query.Type + Data = [string]($ReceivedItem | ConvertTo-Json -Depth 10) + CIPPID = $Request.Query.CIPPID + WebhookInfo = [string]($WebhookInfo | ConvertTo-Json -Depth 10) + FunctionName = 'PublicWebhookProcess' + } + Add-CIPPAzDataTableEntity @WebhookIncoming -Entity $Entity } else { # Auditlog Subscriptions try { @@ -90,6 +102,7 @@ function Invoke-PublicWebhooks { Write-Host "Our operations: $Operations" Write-Host "Logs to download: $LogsToDownload" if ($ReceivedItem.ContentType -in $LogsToDownload -or 'AnyLog' -in $LogsToDownload) { + if ($ReceivedItem.ContentType -eq 'Audit.SharePoint') { continue } $Data = New-GraphPostRequest -type GET -uri "https://manage.office.com/api/v1.0/$($ReceivedItem.tenantId)/activity/feed/audit/$($ReceivedItem.contentid)" -tenantid $TenantFilter -scope 'https://manage.office.com/.default' } else { Write-Host "No data to download for $($ReceivedItem.ContentType)" diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 index 85e3330926c0..9aa191e2e44f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 @@ -14,13 +14,14 @@ Function Invoke-ListTenantDetails { try { $tenantfilter = $Request.Query.TenantFilter - $org = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $tenantfilter | Select-Object displayName, city, country, countryLetterCode, street, state, postalCode, + $org = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $tenantfilter | Select-Object displayName, id, city, country, countryLetterCode, street, state, postalCode, @{ Name = 'businessPhones'; Expression = { $_.businessPhones -join ', ' } }, @{ Name = 'technicalNotificationMails'; Expression = { $_.technicalNotificationMails -join ', ' } }, tenantType, createdDateTime, onPremisesLastPasswordSyncDateTime, onPremisesLastSyncDateTime, onPremisesSyncEnabled, assignedPlans } catch { $org = [PSCustomObject]@{ displayName = 'Error loading tenant' + id = '' city = '' country = '' countryLetterCode = '' diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 index 3d2967edb591..3c183a98af86 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 @@ -21,15 +21,12 @@ Function Invoke-ListTenants { StatusCode = [HttpStatusCode]::OK Body = $GraphRequest }) - $InputObject = [PSCustomObject]@{ - OrchestratorName = 'UpdateTenantsOrchestrator' - Batch = @(@{'FunctionName' = 'UpdateTenants' }) - } - #Write-Host ($InputObject | ConvertTo-Json) - $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) - exit - } + Get-Tenants -IncludeAll -TriggerRefresh + } + if ($Request.query.TriggerRefresh) { + Get-Tenants -IncludeAll -TriggerRefresh + } try { $tenantfilter = $Request.Query.TenantFilter $Tenants = Get-Tenants -IncludeErrors -SkipDomains diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCAPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCAPolicy.ps1 index ab6635459e4b..547828a337f4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCAPolicy.ps1 @@ -16,11 +16,10 @@ Function Invoke-AddCAPolicy { $results = foreach ($Tenant in $tenants) { try { - $CAPolicy = New-CIPPCAPolicy -TenantFilter $tenant -state $request.body.NewState -RawJSON $Request.body.RawJSON -APIName $APIName -ExecutingUser $request.headers.'x-ms-client-principal' + $CAPolicy = New-CIPPCAPolicy -replacePattern $Request.body.replacename -Overwrite $request.body.overwrite -TenantFilter $tenant -state $request.body.NewState -RawJSON $Request.body.RawJSON -APIName $APIName -ExecutingUser $request.headers.'x-ms-client-principal' Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added Conditional Access Policy $($Displayname)" -Sev 'Info' "Successfully added Conditional Access Policy for $($Tenant)" - } - catch { + } catch { "Failed to add policy for $($Tenant): $($_.Exception.Message)" Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Failed to add Conditional Access Policy $($Displayname). Error: $($_.Exception.Message)" -Sev 'Error' continue diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 index 7fc95e0d7af1..3f4b8d651650 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 @@ -40,6 +40,32 @@ Function Invoke-AddCATemplate { } if ($excludelocations) { $JSON.conditions.locations.excludeLocations = $excludelocations } + if ($JSON.conditions.users.includeUsers) { + $JSON.conditions.users.includeUsers = @($JSON.conditions.users.includeUsers | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $TenantFilter).displayName + }) + } + + if ($JSON.conditions.users.excludeUsers) { + $JSON.conditions.users.excludeUsers = @($JSON.conditions.users.excludeUsers | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $TenantFilter).displayName + }) + } + + if ($JSON.conditions.users.includeGroups) { + $JSON.conditions.users.includeGroups = @($JSON.conditions.users.includeGroups | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($_)" -tenantid $TenantFilter).displayName + }) + } + if ($JSON.conditions.users.excludeGroups) { + $JSON.conditions.users.excludeGroups = @($JSON.conditions.users.excludeGroups | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($_)" -tenantid $TenantFilter).displayName + }) + } $JSON | Add-Member -NotePropertyName 'LocationInfo' -NotePropertyValue @($IncludeJSON, $ExcludeJSON) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCACheck.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCACheck.ps1 new file mode 100644 index 000000000000..137c55a9c28b --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCACheck.ps1 @@ -0,0 +1,58 @@ +using namespace System.Net + +Function Invoke-ExecCaCheck { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + $Tenant = $request.body.tenantFilter + $UserID = $request.body.userId.value + if ($Request.body.IncludeApplications.value) { + $IncludeApplications = $Request.body.IncludeApplications.value + } else { + $IncludeApplications = '67ad5377-2d78-4ac2-a867-6300cda00e85' + } + $results = try { + $CAContext = @{ + '@odata.type' = '#microsoft.graph.whatIfApplicationContext' + 'includeApplications' = @($IncludeApplications) + } + $ConditionalAccessWhatIfDefinition = @{ + 'conditionalAccessWhatIfSubject' = @{ + '@odata.type' = '#microsoft.graph.userSubject' + 'userId' = "$userId" + } + 'conditionalAccessContext' = $CAContext + 'conditionalAccessWhatIfConditions' = @{} + } + $whatIfConditions = $ConditionalAccessWhatIfDefinition.conditionalAccessWhatIfConditions + if ($Request.body.UserRiskLevel) { $whatIfConditions.userRiskLevel = $Request.body.UserRiskLevel.value } + if ($Request.body.SignInRiskLevel) { $whatIfConditions.signInRiskLevel = $Request.body.SignInRiskLevel.value } + if ($Request.body.ClientAppType) { $whatIfConditions.clientAppType = $Request.body.ClientAppType.value } + if ($Request.body.DevicePlatform) { $whatIfConditions.devicePlatform = $Request.body.DevicePlatform.value } + if ($Request.body.Country) { $whatIfConditions.country = $Request.body.Country.value } + if ($Request.body.IpAddress) { $whatIfConditions.ipAddress = $Request.body.IpAddress.value } + + $JSONBody = $ConditionalAccessWhatIfDefinition | ConvertTo-Json -Depth 10 + Write-Host $JSONBody + $Request = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/evaluate' -tenantid $tenant -type POST -body $JsonBody -AsApp $true + $Request + } catch { + "Failed to execute check: $($_.Exception.Message)" + } + + $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 d6a5965f2055..4739df9c2df1 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 @@ -64,6 +64,8 @@ Function Invoke-ExecGDAPInvite { } else { $Message = 'Error creating GDAP relationship request' } + + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created GDAP Invite - $InviteUrl" -Sev 'Info' } } catch { $Message = 'Error creating GDAP relationship' @@ -71,8 +73,6 @@ Function Invoke-ExecGDAPInvite { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $env:TenantID -message "$($Message): $($_.Exception.Message)" -Sev 'Error' } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created GDAP Invite - $InviteUrl" -Sev 'Info' - $body = @{ Message = $Message Invite = $InviteEntity diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsDeploy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsDeploy.ps1 index 567452b932c7..a97bb06cf6de 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsDeploy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsDeploy.ps1 @@ -15,7 +15,7 @@ Function Invoke-AddStandardsDeploy { $username = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($user)) | ConvertFrom-Json).userDetails try { - $Tenants = ($Request.body | Select-Object Select_*).psobject.properties.value + $Tenants = $Request.body.Tenant $Settings = ($request.body | Select-Object -Property *, v2* -ExcludeProperty Select_*, None ) $Settings | Add-Member -NotePropertyName 'v2.1' -NotePropertyValue $true -Force if ($Settings.phishProtection.remediate) { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsTemplate.ps1 new file mode 100644 index 000000000000..27c8774bae3c --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsTemplate.ps1 @@ -0,0 +1,32 @@ +using namespace System.Net + +Function Invoke-AddStandardsTemplate { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $GUID = (New-Guid).GUID + $JSON = (ConvertTo-Json -Depth 100 -InputObject ($Request.body | Select-Object standards, name)) + $Table = Get-CippTable -tablename 'templates' + $Table.Force = $true + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$JSON" + RowKey = "$GUID" + PartitionKey = 'StandardsTemplate' + GUID = "$GUID" + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created CA Template $($Request.body.name) with GUID $GUID" -Sev 'Debug' + $body = [pscustomobject]@{'Results' = 'Successfully added template' } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $body + }) + +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 index 7ff48e0ca1c9..0e632d4fba35 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 @@ -77,7 +77,7 @@ Function Invoke-ListBPA { # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = ($Results | ConvertTo-Json -Depth 15) + Body = (ConvertTo-Json -Depth 15 -InputObject $Results) }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 new file mode 100644 index 000000000000..acc984d6e0ab --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 @@ -0,0 +1,27 @@ +using namespace System.Net + +Function Invoke-listStandardTemplates { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + + $Table = Get-CippTable -tablename 'templates' + $Filter = "PartitionKey eq 'StandardsTemplate'" + $Templates = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) | ForEach-Object { + $data = $_.JSON | ConvertFrom-Json -Depth 100 + $data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.GUID -Force + $data + } | Sort-Object -Property displayName + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @($Templates) + }) + +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 index 6587c2c41822..f04c365c5ccf 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 @@ -33,7 +33,10 @@ Function Invoke-ExecGraphExplorerPreset { } $params = $Request.Body.preset | Select-Object endpoint, '$filter', '$select', '$count', '$expand', '$search', NoPagination, '$top', IsShared - if ($params.'$select') { $params.'$select' = ($params.'$select').value -join ',' } + + if ($params.'$select'.value) { + $params.'$select' = ($params.'$select').value -join ',' + } $Preset = [PSCustomObject]@{ PartitionKey = 'Preset' diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearch.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearch.ps1 index 838f23ee3cd6..eda323666fa1 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearch.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearch.ps1 @@ -19,8 +19,29 @@ Function Invoke-ExecUniversalSearch { try { $tenantfilter = Get-Tenants - $payload = '{ "returnsPartialResults":true, "displayName":"getUsers", "target": { "allTenants":true }, "operationDefinition": { "values":["@sys.normalize([ConsistencyLevel: eventual GET /v1.0/users?$top=5&$search=\"userPrincipalName:' + $request.query.name + '\" OR \"displayName:' + $request.query.name + '\"])"] }, "aggregationDefinition": { "values":["@sys.append([/result],50)"] } }' - $GraphRequest = (New-GraphPOSTRequest -noauthcheck $true -type 'POST' -uri 'https://graph.microsoft.com/beta/tenantRelationships/managedTenants/managedTenantOperations' -tenantid $env:TenantID -body $payload).result.Results | ConvertFrom-Json | Where-Object { $_.'_TenantId' -in $tenantfilter.customerId } + $payload = [PSCustomObject]@{ + returnsPartialResults = $false + displayName = 'getUsers' + target = [PSCustomObject]@{ + allTenants = $true + } + operationDefinition = [PSCustomObject]@{ + values = @( + "@sys.normalize([ConsistencyLevel: eventual GET /v1.0/users?`$top=5&`$search=`"userPrincipalName:$($Request.query.name)`" OR `"displayName:$($Request.query.name)`"])" + ) + } + aggregationDefinition = [PSCustomObject]@{ + values = @( + '@sys.append([/result],50)' + ) + } + } | ConvertTo-Json -Depth 10 + $GraphRequest = (New-GraphPOSTRequest -noauthcheck $true -type 'POST' -uri 'https://graph.microsoft.com/beta/tenantRelationships/managedTenants/managedTenantOperations' -tenantid $env:TenantID -body $payload -IgnoreErrors $true) + if (!$GraphRequest.result.results) { + $GraphRequest = ($GraphRequest.error.message | ConvertFrom-Json).result.results | ConvertFrom-Json | Where-Object { $_.'_TenantId' -in $tenantfilter.customerId } + } else { + $GraphRequest.result.Results | ConvertFrom-Json -ErrorAction SilentlyContinue | Where-Object { $_.'_TenantId' -in $tenantfilter.customerId } + } $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 index 2cd1d9cd9bac..95866c41c395 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 @@ -133,8 +133,13 @@ function Invoke-ListGraphRequest { else { $StatusCode = [HttpStatusCode]::BadRequest } } + if ($request.Query.Sort) { + $GraphRequestData.Results = $GraphRequestData.Results | Sort-Object -Property $request.Query.Sort + } + $Outputdata = $GraphRequestData | ConvertTo-Json -Depth 20 -Compress + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $GraphRequestData | ConvertTo-Json -Depth 20 -Compress + Body = $Outputdata }) } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 index 319b43a00995..a3963bd0bc94 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 @@ -12,7 +12,7 @@ Function Invoke-ListLogs { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' if ($request.Query.Filter -eq 'True') { - $LogLevel = if ($Request.query.Severity) { ($Request.query.Severity).split(',') } else { 'Info', 'Warn', 'Error', 'Critical', 'Alert' } + $LogLevel = if ($Request.query.Severity) { ($Request.query.Severity).split(',') } else { 'Info', 'Warn', 'Error', 'Critical', 'Alert' } $PartitionKey = $Request.query.DateFilter $username = $Request.Query.User } else { @@ -25,7 +25,7 @@ Function Invoke-ListLogs { $ReturnedLog = if ($Request.Query.ListLogs) { Get-CIPPAzDataTableEntity @Table -Property PartitionKey | Sort-Object -Unique PartitionKey | Select-Object PartitionKey | ForEach-Object { - @{ + @{ value = $_.PartitionKey label = $_.PartitionKey } @@ -34,13 +34,17 @@ Function Invoke-ListLogs { $Filter = "PartitionKey eq '{0}'" -f $PartitionKey $Rows = Get-CIPPAzDataTableEntity @Table -Filter $Filter | Where-Object { $_.Severity -In $LogLevel -and $_.user -like $username } foreach ($Row in $Rows) { - @{ + $LogData = if ($Row.LogData -and (Test-Json -Json $Row.LogData)) { + $Row.LogData | ConvertFrom-Json + } else { $Row.LogData } + [PSCustomObject]@{ DateTime = $Row.Timestamp Tenant = $Row.Tenant API = $Row.API Message = $Row.Message User = $Row.Username Severity = $Row.Severity + LogData = $LogData TenantID = if ($Row.TenantID -ne $null) { $Row.TenantID } else { diff --git a/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1 b/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1 index 0d3092fc54c3..9b1885d5c465 100644 --- a/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1 @@ -30,7 +30,7 @@ function Get-CIPPAuthentication { return $true } catch { - Write-LogMessage -message "Could not retrieve keys from Keyvault: $($_.Exception.Message)" -Sev 'CRITICAL' -API 'CIPP Authentication' + Write-LogMessage -message 'Could not retrieve keys from Keyvault' -Sev 'CRITICAL' -API 'CIPP Authentication' -LogData (Get-CippException -Exception $_) return $false } } diff --git a/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 b/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 index 7d886ea2912f..a80a5d3b002e 100644 --- a/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 @@ -4,18 +4,17 @@ function Get-CIPPBitlockerKey { param ( $device, $TenantFilter, - $APIName = "Get Bitlocker key", + $APIName = 'Get Bitlocker key', $ExecutingUser ) try { - $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/informationProtection/bitlocker/recoveryKeys?`$filter=deviceId eq '$($device)'" -tenantid $TenantFilter | ForEach-Object { + $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/informationProtection/bitlocker/recoveryKeys?`$filter=deviceId eq '$($device)'" -tenantid $TenantFilter | ForEach-Object { (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/informationProtection/bitlocker/recoveryKeys/$($_.id)?`$select=key" -tenantid $TenantFilter).key } return $GraphRequest - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev "Error" -tenant $TenantFilter + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) return "Could not add out of office message for $($userid). Error: $($_.Exception.Message)" } } diff --git a/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 b/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 index e39e6d5953ba..fc4ad53915a1 100644 --- a/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 @@ -6,7 +6,7 @@ function Get-CIPPDomainAnalyser { # Get all the things if ($TenantFilter -ne 'AllTenants') { - $DomainTable.Filter = "TenantId eq '{0}'" -f $TenantFilter + $DomainTable.Filter = "TenantGUID eq '{0}'" -f $TenantFilter } try { diff --git a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 index 73a2295b3be1..40eb80366161 100644 --- a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 @@ -3,7 +3,7 @@ function Get-CIPPMFAState { [CmdletBinding()] param ( $TenantFilter, - $APIName = "Get MFA Status", + $APIName = 'Get MFA Status', $ExecutingUser ) @@ -23,8 +23,7 @@ function Get-CIPPMFAState { Try { $MFARegistration = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails' -tenantid $TenantFilter) - } - catch { + } catch { $CAState.Add('Not Licensed for Conditional Access') | Out-Null $MFARegistration = $null } @@ -51,8 +50,7 @@ function Get-CIPPMFAState { } } } - } - catch { + } catch { } } @@ -68,12 +66,10 @@ function Get-CIPPMFAState { if ($CA -like '*All Users*') { if ($ExcludeAllUsers -contains $_.ObjectId) { $UserCAState.Add("Excluded from $($policy.displayName) - All Users") | Out-Null } else { $UserCAState.Add($CA) | Out-Null } - } - elseif ($CA -like '*Specific Applications*') { + } elseif ($CA -like '*Specific Applications*') { if ($ExcludeSpecific -contains $_.ObjectId) { $UserCAState.Add("Excluded from $($policy.displayName) - Specific Applications") | Out-Null } else { $UserCAState.Add($CA) | Out-Null } - } - else { + } else { Write-Host 'Adding to CA' $UserCAState.Add($CA) | Out-Null } @@ -81,7 +77,8 @@ function Get-CIPPMFAState { $PerUser = if ($_.StrongAuthenticationRequirements.StrongAuthenticationRequirement.state -ne $null) { $_.StrongAuthenticationRequirements.StrongAuthenticationRequirement.state } else { 'Disabled' } - $MFARegUser = if (($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName).IsMFARegistered -eq $null) { $false } else { ($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName).IsMFARegistered } + $MFARegUser = if (($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName).IsMFARegistered -eq $null) { $false } else { ($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName) } + [PSCustomObject]@{ Tenant = $TenantFilter ID = $_.ObjectId @@ -90,7 +87,8 @@ function Get-CIPPMFAState { AccountEnabled = $_.accountEnabled PerUser = $PerUser isLicensed = $_.isLicensed - MFARegistration = $MFARegUser + MFARegistration = $MFARegUser.IsMFARegistered + MFAMethods = $($MFARegUser.authMethods -join ', ') CoveredByCA = ($UserCAState -join ', ') CoveredBySD = $SecureDefaultsState RowKey = [string]($_.UserPrincipalName).replace('#', '') diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-CippException.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-CippException.ps1 new file mode 100644 index 000000000000..92c4d936dd22 --- /dev/null +++ b/Modules/CIPPCore/Public/GraphHelper/Get-CippException.ps1 @@ -0,0 +1,14 @@ +function Get-CippException { + Param( + $Exception + ) + + [PSCustomObject]@{ + Message = $Exception.Exception.Message + NormalizedError = Get-NormalizedError -message $Exception.Exception.Message + Position = $Exception.InvocationInfo.PositionMessage + ScriptName = $Exception.InvocationInfo.ScriptName + LineNumber = $Exception.InvocationInfo.ScriptLineNumber + Category = $Exception.CategoryInfo.ToString() + } +} diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 index bcb9b94ef9cc..02196d6f58e4 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 @@ -10,7 +10,8 @@ function Get-Tenants { [switch]$IncludeAll, [switch]$IncludeErrors, [switch]$SkipDomains, - [switch]$TriggerRefresh + [switch]$TriggerRefresh, + [switch]$CleanOld ) $TenantsTable = Get-CippTable -tablename 'Tenants' @@ -32,11 +33,27 @@ function Get-Tenants { if (($IncludedTenantsCache | Measure-Object).Count -eq 0) { $BuildRequired = $true - } + } + + if ($CleanOld) { + $GDAPRelationships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active' and not startsWith(displayName,'MLT_')&`$select=customer,autoExtendDuration,endDateTime" -NoAuthCheck:$true + $GDAPList = foreach ($Relationship in $GDAPRelationships) { + [PSCustomObject]@{ + customerId = $Relationship.customer.tenantId + displayName = $Relationship.customer.displayName + autoExtend = ($Relationship.autoExtendDuration -ne 'PT0S') + relationshipEnd = $Relationship.endDateTime + } + } + $CurrentTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and Excluded eq false" + $CurrentTenants | Where-Object { $_.customerId -notin $GDAPList.customerId } | ForEach-Object { + Remove-AzDataTableEntity @TenantsTable -Entity $_ + } + } if ($BuildRequired -or $TriggerRefresh.IsPresent) { #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" -NoAuthCheck:$true + $GDAPRelationships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active' and not startsWith(displayName,'MLT_')&`$select=customer,autoExtendDuration,endDateTime" -NoAuthCheck:$true $GDAPList = foreach ($Relationship in $GDAPRelationships) { [PSCustomObject]@{ customerId = $Relationship.customer.tenantId @@ -45,16 +62,15 @@ function Get-Tenants { relationshipEnd = $Relationship.endDateTime } } + $ActiveRelationships = $GDAPList | Where-Object { $_.customerId -notin $SkipListCache.customerId } - $TenantList = $ActiveRelationships | Group-Object -Property customerId | ForEach-Object -Parallel { + $TenantList = $ActiveRelationships | Group-Object -Property customerId | ForEach-Object { Write-Host "Processing $($_.Name) to add to tenant list." - Import-Module CIPPCore - Import-Module AzBobbyTables - $ExistingTenantInfo = Get-CIPPAzDataTableEntity @using:TenantsTable -Filter "PartitionKey eq 'Tenants' and RowKey eq '$($_.Name)'" - if ($ExistingTenantInfo -and $ExistingInfo.RequiresRefresh -eq $false) { + $ExistingTenantInfo = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and RowKey eq '$($_.Name)'" + if ($ExistingTenantInfo -and $ExistingTenantInfo.RequiresRefresh -eq $false) { Write-Host 'Existing tenant found. We already have it cached, skipping.' $ExistingTenantInfo - continue + return } $LatestRelationship = $_.Group | Sort-Object -Property relationshipEnd | Select-Object -Last 1 $AutoExtend = ($_.Group | Where-Object { $_.autoExtend -eq $true } | Measure-Object).Count -gt 0 @@ -72,13 +88,12 @@ function Get-Tenants { $defaultDomainName = $Domain $initialDomainName = $Domain $RequiresRefresh = $true - + } catch { Write-LogMessage -API 'Get-Tenants' -message "Tried adding $($LatestRelationship.customerId) to tenant list but failed to get domains - $($_.Exception.Message)" -level 'Critical' - } } - + [PSCustomObject]@{ PartitionKey = 'Tenants' RowKey = $_.Name @@ -120,17 +135,17 @@ function Get-Tenants { }) | Out-Null } foreach ($Tenant in $TenantList) { - if ($Tenant.defaultDomainName -eq 'Invalid' -or !$Tenant.defaultDomainName) { continue } + 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 + } $IncludedTenantsCache.Add($Tenant) | Out-Null } - } - - if ($IncludedTenantsCache) { - Add-CIPPAzDataTableEntity @TenantsTable -Entity $IncludedTenantsCache -Force - $CurrentTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and Excluded eq false" - $CurrentTenants | Where-Object { $_.customerId -notin $IncludedTenantsCache.customerId } | ForEach-Object { - Remove-AzDataTableEntity -Context $TenantsTable -Entity $_ -Force + if ($IncludedTenantsCache) { + Add-CIPPAzDataTableEntity @TenantsTable -Entity $IncludedTenantsCache -Force | Out-Null } } + + return ($IncludedTenantsCache | Where-Object { $null -ne $_.defaultDomainName -and ($_.defaultDomainName -notmatch 'Domain Error' -or $IncludeAll.IsPresent) } | Sort-Object -Property displayName) } diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 index 9236b7559fcf..315881e1048b 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 @@ -1,5 +1,5 @@ -function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp, $NoAuthCheck, $skipTokenCache, $AddedHeaders, $contentType) { +function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp, $NoAuthCheck, $skipTokenCache, $AddedHeaders, $contentType, $IgnoreErrors) { <# .FUNCTIONALITY Internal @@ -20,7 +20,7 @@ function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp, $N $contentType = 'application/json; charset=utf-8' } try { - $ReturnedData = (Invoke-RestMethod -Uri $($uri) -Method $TYPE -Body $body -Headers $headers -ContentType $contentType) + $ReturnedData = (Invoke-RestMethod -Uri $($uri) -Method $TYPE -Body $body -Headers $headers -ContentType $contentType -SkipHttpErrorCheck:$IgnoreErrors) } catch { $Message = if ($_.ErrorDetails.Message) { Get-NormalizedError -Message $_.ErrorDetails.Message diff --git a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 index fbef7fae41bf..8dec84538272 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 @@ -1,14 +1,25 @@ -function Write-LogMessage ($message, $tenant = 'None', $API = 'None', $tenantId = $null, $user, $sev) { +function Write-LogMessage { <# .FUNCTIONALITY Internal #> + Param( + $message, + $tenant = 'None', + $API = 'None', + $tenantId = $null, + $user, + $sev, + $LogData = '' + ) try { $username = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($user)) | ConvertFrom-Json).userDetails } catch { $username = $user } + if ($LogData) { $LogData = ConvertTo-Json -InputObject $LogData -Depth 10 -Compress } + $Table = Get-CIPPTable -tablename CippLogs if (!$tenant) { $tenant = 'None' } @@ -27,13 +38,14 @@ function Write-LogMessage ($message, $tenant = 'None', $API = 'None', $tenantId 'SentAsAlert' = $false 'PartitionKey' = $PartitionKey 'RowKey' = ([guid]::NewGuid()).ToString() + 'LogData' = [string]$LogData } if ($tenantId) { $TableRow.Add('TenantID', [string]$tenantId) } - + $Table.Entity = $TableRow Add-CIPPAzDataTableEntity @Table | Out-Null } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 new file mode 100644 index 000000000000..5a16c69d530d --- /dev/null +++ b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 @@ -0,0 +1,75 @@ +function Invoke-CippPartnerWebhookProcessing { + [CmdletBinding()] + param ( + $Data + ) + + try { + if ($Data.AuditUri) { + $AuditLog = New-GraphGetRequest -uri $Data.AuditUri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + } + + Switch ($Data.EventName) { + 'test-created' { + Write-LogMessage -API 'Webhooks' -message 'Partner Center webhook test received' -Sev 'Info' + } + default { + if ($Data.EventName -eq 'granular-admin-relationship-approved') { + if ($AuditLog.resourceNewValue) { + $AuditObj = $AuditLog.resourceNewValue | ConvertFrom-Json + Write-LogMessage -API 'Webhooks' -message "Partner Webhook: GDAP Relationship for $($AuditObj.customer.organizationDisplayName) was approved, starting onboarding" -LogData $AuditObj -Sev 'Alert' + $Id = $AuditObj.Id + $OnboardingSteps = [PSCustomObject]@{ + 'Step1' = @{ + 'Status' = 'pending' + 'Title' = 'Step 1: GDAP Invite' + 'Message' = 'Waiting for onboarding job to start' + } + 'Step2' = @{ + 'Status' = 'pending' + 'Title' = 'Step 2: GDAP Role Test' + 'Message' = 'Waiting for Step 1' + } + 'Step3' = @{ + 'Status' = 'pending' + 'Title' = 'Step 3: GDAP Group Mapping' + 'Message' = 'Waiting for Step 2' + } + 'Step4' = @{ + 'Status' = 'pending' + 'Title' = 'Step 4: CPV Refresh' + 'Message' = 'Waiting for Step 3' + } + 'Step5' = @{ + 'Status' = 'pending' + 'Title' = 'Step 5: Graph API Test' + 'Message' = 'Waiting for Step 4' + } + } + $TenantOnboarding = [PSCustomObject]@{ + PartitionKey = 'Onboarding' + RowKey = [string]$Id + CustomerId = '' + Status = 'queued' + OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + Relationship = '' + Logs = '' + Exception = '' + } + $OnboardTable = Get-CIPPTable -TableName 'TenantOnboarding' + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + Push-ExecOnboardTenantQueue -Item @{ Id = $Id } + } else { + if ($AuditLog) { + Write-LogMessage -API 'Webhooks' -message "Partner Center $($Data.EventName) audit log webhook received" -LogData $AuditObj -Sev 'Alert' + } else { + Write-LogMessage -API 'Webhooks' -message "Partner Center $($Data.EventName) webhook received" -LogData $Data -Sev 'Alert' + } + } + } + } + } + } catch { + Write-LogMessage -API 'Webhooks' -message 'Error processing Partner Center webhook' -LogData (Get-CippException -Exception $_) -Sev 'Error' + } +} diff --git a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 index db62dae8b160..86e7e8a41c3f 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 @@ -82,7 +82,7 @@ function Invoke-CippWebhookProcessing { { 'UserLoggedIn' -eq $data.operation -and $hosting -eq $true -and !$TrustedIps } { $data.operation = 'HostedIP' } { 'UserLoggedIn' -eq $data.operation -and $Country -notin $AllowedLocations -and $data.ResultStatus -eq 'Success' -and $TableObj.ResultStatusDetail -eq 'Success' } { Write-Host "$($country) is not in $($AllowedLocations)" - $data.operation = 'UserLoggedInFromUnknownLocation' + $data.operation = 'UserLoggedInFromUnknownLocation' } { 'UserloggedIn' -eq $data.operation -and $data.UserType -eq 2 -and $data.ResultStatus -eq 'Success' -and $TableObj.ResultStatusDetail -eq 'Success' } { $data.operation = 'AdminLoggedIn' } default { break } @@ -130,7 +130,7 @@ function Invoke-CippWebhookProcessing { $key = $parts[0] $operator = $parts[1] $value = $parts[2] - if (!$value) { + if (!$value) { Write-Host 'blank value, skip' continue } @@ -165,9 +165,9 @@ function Invoke-CippWebhookProcessing { $RuleDisabled = 0 New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'get-inboxrule' -cmdParams @{Mailbox = $username } | ForEach-Object { $null = New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Disable-InboxRule' -cmdParams @{Confirm = $false; Identity = $_.Identity } - "Disabled Inbox Rule $($_.Identity) for $username" + "Disabled Inbox Rule $($_.Identity) for $username" $RuleDisabled ++ - } + } if ($RuleDisabled) { "Disabled $RuleDisabled Inbox Rules for $username" } else { @@ -211,7 +211,7 @@ function Invoke-CippWebhookProcessing { } } Write-Host 'Going to create the content' - foreach ($action in $dos) { + foreach ($action in $dos) { switch ($action.execute) { 'generatemail' { Write-Host 'Going to create the email' @@ -220,9 +220,9 @@ function Invoke-CippWebhookProcessing { Send-CIPPAlert -Type 'email' -Title $GenerateEmail.title -HTMLContent $GenerateEmail.htmlcontent -TenantFilter $TenantFilter Write-Host 'email should be sent' - } + } 'generatePSA' { - $GenerateEmail = New-CIPPAlertTemplate -format 'html'-data $Data -LocationInfo $Location -ActionResults $ActionResults + $GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -LocationInfo $Location -ActionResults $ActionResults Send-CIPPAlert -Type 'psa' -Title $GenerateEmail.title -HTMLContent $GenerateEmail.htmlcontent -TenantFilter $TenantFilter } 'generateWebhook' { diff --git a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 index 1d749a69bf76..78f3173c2aee 100644 --- a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 @@ -6,6 +6,7 @@ function New-CIPPCAPolicy { $TenantFilter, $State, $Overwrite, + $ReplacePattern = 'none', $APIName = 'Create CA Policy', $ExecutingUser ) @@ -101,19 +102,42 @@ function New-CIPPCAPolicy { $index = [array]::IndexOf($JSONObj.conditions.locations.excludeLocations, $location) $JSONObj.conditions.locations.excludeLocations[$index] = $lookup.id } - + switch ($ReplacePattern) { + 'none' { + Write-Host 'Replacement pattern for inclusions and exclusions is none' + break + } + 'AllUsers' { + Write-Host 'Replacement pattern for inclusions and exclusions is All users. This policy will now apply to everyone.' + if ($JSONObj.conditions.users.includeUsers -ne 'All') { $JSONObj.conditions.users.includeUsers = @('All') } + if ($JSONObj.conditions.users.excludeUsers) { $JSONObj.conditions.users.excludeUsers = @() } + if ($JSONObj.conditions.users.includeGroups) { $JSONObj.conditions.users.includeGroups = @() } + if ($JSONObj.conditions.users.excludeGroups) { $JSONObj.conditions.users.excludeGroups = @() } + } + 'displayName' { + Write-Host 'Replacement pattern for inclusions and exclusions is displayName.' + $users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/users?$select=id,displayName' -tenantid $TenantFilter + $Groups = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$select=id,displayName' -tenantid $TenantFilter + + if ($JSONObj.conditions.users.includeUsers -notin 'All', 'None', 'GuestOrExternalUsers') { $JSONObj.conditions.users.includeUsers = @(($users | Where-Object -Property displayName -In $JSONObj.conditions.users.includeUsers).id) } + if ($JSONObj.conditions.users.excludeUsers) { $JSONObj.conditions.users.excludeUsers = @(($users | Where-Object -Property displayName -In $JSONObj.conditions.users.excludeUsers).id) } + if ($JSONObj.conditions.users.includeGroups) { $JSONObj.conditions.users.includeGroups = @(($groups | Where-Object -Property displayName -In $JSONObj.conditions.users.includeGroups).id) } + if ($JSONObj.conditions.users.excludeGroups) { $JSONObj.conditions.users.excludeGroups = @(($groups | Where-Object -Property displayName -In $JSONObj.conditions.users.excludeGroups).id) } + } + + } $JsonObj.PSObject.Properties.Remove('LocationInfo') $RawJSON = $JSONObj | ConvertTo-Json -Depth 10 Write-Host $RawJSON try { Write-Host 'Checking' - $CheckExististing = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $TenantFilter - if ($displayname -in $CheckExististing.displayName) { + $CheckExististing = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $TenantFilter | Where-Object -Property displayName -EQ $displayname + if ($CheckExististing) { if ($Overwrite -ne $true) { Throw "Conditional Access Policy with Display Name $($Displayname) Already exists" return $false } else { - Write-Host 'overwriting' + Write-Host "overwriting $($CheckExististing.id)" $PatchRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Updated Conditional Access Policy $($JSONObj.Displayname) to the template standard." -Sev 'Info' return "Updated policy $displayname for $tenantfilter" diff --git a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 index 6eac507448ec..378df5a1cb48 100644 --- a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 @@ -11,7 +11,8 @@ function New-CIPPGraphSubscription { $EventType, $APIName = 'Create Webhook', $ExecutingUser, - [Switch]$Recreate + [Switch]$Recreate, + [switch]$PartnerCenter ) $CIPPID = (New-Guid).GUID $WebhookTable = Get-CIPPTable -TableName webhookTable @@ -76,13 +77,77 @@ function New-CIPPGraphSubscription { } } } + } elseif ($PartnerCenter.IsPresent) { + $WebhookFilter = "PartitionKey eq '$($env:TenantId)'" + $ExistingWebhooks = Get-CIPPAzDataTableEntity @WebhookTable -Filter $WebhookFilter + $CIPPID = $env:TenantId + $MatchedWebhook = $ExistingWebhooks | Where-Object { $_.Resource -eq 'PartnerCenter' -and $_.RowKey -eq $CIPPID } + + # Required event types + $EventList = [System.Collections.Generic.List[string]]@('test-created', 'granular-admin-relationship-approved') + if (($EventType | Measure-Object).count -gt 0) { + foreach ($Event in $EventType) { + if ($EventList -notcontains $Event) { + $EventList.Add($Event) + } + } + } + + $Body = [PSCustomObject]@{ + WebhookUrl = "https://$BaseURL/API/PublicWebhooks?CIPPID=$($CIPPID)&Type=PartnerCenter" + WebhookEvents = @($EventList) + } + try { + $EventCompare = Compare-Object $EventList ($MatchedWebhook.EventType | ConvertFrom-Json) + } catch { + $EventCompare = $false + } + try { + $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' + try { + $Existing = New-GraphGetRequest -NoAuthCheck $true -uri $Uri -tenantid $env:TenantId -scope 'https://api.partnercenter.microsoft.com/.default' + } catch { $Existing = $false } + if (!$Existing -or $Existing.webhookUrl -ne $MatchedWebhook.WebhookNotificationUrl -or $EventCompare) { + if ($Existing.WebhookUrl) { + $Action = 'Updated' + $Method = 'PUT' + Write-Host 'updating webhook' + } else { + $Action = 'Created' + $Method = 'POST' + Write-Host 'creating webhook' + } + + $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' + $GraphRequest = New-GraphPOSTRequest -uri $Uri -type $Method -tenantid $env:TenantId -scope 'https://api.partnercenter.microsoft.com/.default' -body ($Body | ConvertTo-Json) -NoAuthCheck $true + + $WebhookRow = @{ + PartitionKey = [string]$CIPPID + RowKey = [string]$CIPPID + EventType = [string](ConvertTo-Json -InputObject $EventList) + Resource = [string]'PartnerCenter' + SubscriptionID = [string]$GraphRequest.SubscriberId + Expiration = 'Does Not Expire' + WebhookNotificationUrl = [string]$Body.WebhookUrl + } + $null = Add-CIPPAzDataTableEntity @WebhookTable -Entity $WebhookRow -Force + Write-LogMessage -user $ExecutingUser -API $APIName -message "$Action Partner Center Webhook subscription" -Sev 'Info' -tenant 'PartnerTenant' + return "$Action Partner Center Webhook subscription" + } else { + Write-LogMessage -user $ExecutingUser -API $APIName -message 'Existing Partner Center Webhook subscription found' -Sev 'Info' -tenant 'PartnerTenant' + return 'Existing Partner Center Webhook subscription found' + } + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to create Partner Center Webhook Subscription: $($_.Exception.Message)" -Sev 'Error' -tenant 'PartnerTenant' + return "Failed to create Partner Webhook Subscription: $($_.Exception.Message)" + } + } else { # First check if there is an exsiting Webhook in place $WebhookFilter = "PartitionKey eq '$($TenantFilter)'" $ExistingWebhooks = Get-CIPPAzDataTableEntity @WebhookTable -Filter $WebhookFilter $MatchedWebhook = $ExistingWebhooks | Where-Object { $_.Resource -eq $Resource } - if (($MatchedWebhook | Measure-Object).count -eq 0 -or $Recreate) { - + if (($MatchedWebhook | Measure-Object).count -eq 0 -or $Recreate.IsPresent) { $expiredate = (Get-Date).AddDays(1).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') $params = @{ changeType = $TypeofSubscription @@ -90,10 +155,10 @@ function New-CIPPGraphSubscription { resource = $Resource expirationDateTime = $expiredate } | ConvertTo-Json - + $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/subscriptions' -tenantid $TenantFilter -type POST -body $params -verbose - #If creation is succesfull, we store the GUID in the storage table webhookTable to make sure we can check against this later on. + #If creation is succesfull, we store the GUID in the storage table webhookTable to make sure we can check against this later on. #We store the GUID as rowkey, the event type, the resource, and the expiration date as properties, we also add the Tenant name so we can easily find this later on. #We don't store the return, because Ms decided that a renewal or re-authenticate does not change the url, but does change the id... $WebhookRow = @{ @@ -108,7 +173,7 @@ function New-CIPPGraphSubscription { } $null = Add-CIPPAzDataTableEntity @WebhookTable -Entity $WebhookRow #todo: add remove webhook function, add check webhook function, add list webhooks function - #add refresh webhook function based on table. + #add refresh webhook function based on table. Write-LogMessage -user $ExecutingUser -API $APIName -message "Created Graph Webhook subscription for $($TenantFilter)" -Sev 'Info' -tenant $TenantFilter } else { Write-LogMessage -user $ExecutingUser -API $APIName -message "Existing Graph Webhook subscription for $($TenantFilter) found" -Sev 'Info' -tenant $TenantFilter @@ -117,8 +182,6 @@ function New-CIPPGraphSubscription { return "Created Webhook subscription for $($TenantFilter)" } catch { Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to create Webhook Subscription: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter - Return "Failed to create Webhook Subscription for $($TenantFilter): $($_.Exception.Message)" + Return "Failed to create Webhook Subscription for $($TenantFilter): $($_.Exception.Message)" } - } - diff --git a/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 b/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 index 79b6222f5c37..ee1266ca949d 100644 --- a/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 @@ -6,7 +6,7 @@ function Set-CIPPOutOfOffice { $ExternalMessage, $TenantFilter, $State, - $APIName = "Set Out of Office", + $APIName = 'Set Out of Office', $ExecutingUser, $StartTime, $EndTime @@ -19,19 +19,17 @@ function Set-CIPPOutOfOffice { if (-not $EndTime) { $EndTime = (Get-Date $StartTime).AddDays(7) } - if ($State -ne "Scheduled") { - $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-MailboxAutoReplyConfiguration" -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage } -Anchor $userid - Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Out-of-office for $($userid) to $state" -Sev "Info" -tenant $TenantFilter + if ($State -ne 'Scheduled') { + $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxAutoReplyConfiguration' -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage } -Anchor $userid + Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Out-of-office for $($userid) to $state" -Sev 'Info' -tenant $TenantFilter return "Set Out-of-office for $($userid) to $state." - } - else { - $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-MailboxAutoReplyConfiguration" -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage; StartTime = $StartTime; EndTime = $EndTime } -Anchor $userid - Write-LogMessage -user $ExecutingUser -API $APIName -message "Scheduled Out-of-office for $($userid) between $StartTime and $EndTime" -Sev "Info" -tenant $TenantFilter + } else { + $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxAutoReplyConfiguration' -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage; StartTime = $StartTime; EndTime = $EndTime } -Anchor $userid + Write-LogMessage -user $ExecutingUser -API $APIName -message "Scheduled Out-of-office for $($userid) between $StartTime and $EndTime" -Sev 'Info' -tenant $TenantFilter return "Scheduled Out-of-office for $($userid) between $($StartTime.toString()) and $($EndTime.toString())" } - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev "Error" -tenant $TenantFilter + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) return "Could not add out of office message for $($userid). Error: $($_.Exception.Message)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 b/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 new file mode 100644 index 000000000000..e3ad3c8dd83e --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 @@ -0,0 +1,27 @@ +function Set-CIPPSignature { + [CmdletBinding()] + param ( + $userid, + $InternalMessage, + $ExternalMessage, + $TenantFilter, + $State, + $APIName = 'Set Outlook Roaming Signature', + $ExecutingUser, + $StartTime, + $EndTime + ) + + try { + $SignatureProfile = @' +[{"name":"Roaming_New_Signature","itemClass":"","id":"","scope":"AdeleV@M365x42953883.OnMicrosoft.com","parentSetting":"","secondaryKey":"","type":"String","timestamp":638296273181532792,"metadata":"","value":"Kelvin","isFirstSync":"true","source":"UserOverride"}] +'@ + $GraphRequest = New-GraphPostRequest -uri 'https://substrate.office.com/ows/beta/outlookcloudsettings/settings/global' -tenantid $TenantFilter -type PATCH -contentType 'application/json' -verbose -scope 'https://outlook.office.com/.default' + Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Out-of-office for $($userid) to $state" -Sev 'Info' -tenant $TenantFilter + return "Set Out-of-office for $($userid) to $state." + + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev 'Error' -tenant $TenantFilter + return "Could not add out of office message for $($userid). Error: $($_.Exception.Message)" + } +} diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 index f82b937d9e57..110f2fde20e4 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 @@ -2,7 +2,7 @@ function Test-CIPPAccessPermissions { [CmdletBinding()] param ( $TenantFilter, - $APIName = "Access Check", + $APIName = 'Access Check', $ExecutingUser ) Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Started permissions check' -Sev 'Debug' @@ -20,12 +20,12 @@ function Test-CIPPAccessPermissions { TenantId = '' UserPrincipalName = '' } - Write-Host "Setting success to true by default." + Write-Host 'Setting success to true by default.' $Success = $true try { Set-Location (Get-Item $PSScriptRoot).FullName $ExpectedPermissions = Get-Content '.\SAMManifest.json' | ConvertFrom-Json - + $null = Get-CIPPAuthentication $GraphToken = Get-GraphToken -returnRefresh $true -SkipCache $true if ($GraphToken) { $GraphPermissions = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/myorganization/applications?`$filter=appId eq '$env:ApplicationID'" -NoAuthCheck $true @@ -38,7 +38,7 @@ function Test-CIPPAccessPermissions { $KV = $ENV:WEBSITE_DEPLOYMENT_ID $KeyVaultRefresh = Get-AzKeyVaultSecret -VaultName $kv -Name 'RefreshToken' -AsPlainText if ($ENV:RefreshToken -ne $KeyVaultRefresh) { - Write-Host "Setting success to false due to nonmaching token." + Write-Host 'Setting success to false due to nonmaching token.' $Success = $false $Messages.Add('Your refresh token does not match key vault, clear your cache or wait 30 minutes.') | Out-Null @@ -47,43 +47,38 @@ function Test-CIPPAccessPermissions { Href = 'https://docs.cipp.app/setup/installation/cleartokencache' } ) | Out-Null - } - else { + } else { $Messages.Add('Your refresh token matches key vault.') | Out-Null } - } - catch { + } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Key vault exception: $($_) " -Sev 'Error' } } try { $AccessTokenDetails = Read-JwtAccessDetails -Token $GraphToken.access_token -erroraction SilentlyContinue - } - catch { + } catch { $AccessTokenDetails = [PSCustomObject]@{ Name = '' AuthMethods = @() } Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Token exception: $($_) " -Sev 'Error' $Success = $false - Write-Host "Setting success to false due to not able to decode token." + Write-Host 'Setting success to false due to not able to decode token.' } if ($AccessTokenDetails.Name -eq '') { $Messages.Add('Your refresh token is invalid, check for line breaks or missing characters.') | Out-Null - Write-Host "Setting success to false invalid token." + Write-Host 'Setting success to false invalid token.' $Success = $false - } - else { + } else { if ($AccessTokenDetails.AuthMethods -contains 'mfa') { $Messages.Add('Your access token contains the MFA claim.') | Out-Null - } - else { + } else { $Messages.Add('Your access token does not contain the MFA claim, Refresh your SAM tokens.') | Out-Null - Write-Host "Setting success to False due to invalid list of claims." + Write-Host 'Setting success to False due to invalid list of claims.' $Success = $false $Links.Add([PSCustomObject]@{ @@ -107,16 +102,14 @@ function Test-CIPPAccessPermissions { Href = 'https://docs.cipp.app/setup/installation/permissions' } ) | Out-Null - } - else { + } else { $Messages.Add('Your Secure Application Model has all required permissions') | Out-Null } - } - catch { + } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Permissions check failed: $($_) " -Sev 'Error' $Messages.Add("We could not connect to the API to retrieve the permissions. There might be a problem with the secure application model configuration. The returned error is: $(Get-NormalizedError -message $_)") | Out-Null - Write-Host "Setting success to False due to not being able to connect." + Write-Host 'Setting success to False due to not being able to connect.' $Success = $false } diff --git a/Modules/CippEntrypoints/CippEntrypoints.psm1 b/Modules/CippEntrypoints/CippEntrypoints.psm1 index f0bf7fcf2d1b..7c1b9b56833d 100644 --- a/Modules/CippEntrypoints/CippEntrypoints.psm1 +++ b/Modules/CippEntrypoints/CippEntrypoints.psm1 @@ -57,6 +57,7 @@ function Receive-CippOrchestrationTrigger { } else { $OrchestratorInput = $Context.Input } + Write-Host "Orchestrator started $($OrchestratorInput.OrchestratorName)" $DurableRetryOptions = @{ FirstRetryInterval = (New-TimeSpan -Seconds 5) @@ -77,16 +78,17 @@ function Receive-CippOrchestrationTrigger { } if (($Batch | Measure-Object).Count -gt 0) { - foreach ($Item in $Batch) { - $null = Invoke-DurableActivity -FunctionName 'CIPPActivityFunction' -Input $Item -NoWait -RetryOptions $RetryOptions -ErrorAction Stop + $Tasks = foreach ($Item in $Batch) { + Invoke-DurableActivity -FunctionName 'CIPPActivityFunction' -Input $Item -NoWait -RetryOptions $RetryOptions -ErrorAction Stop } + $null = Wait-ActivityFunction -Task $Tasks } if ($Context.IsReplaying -ne $true -and $OrchestratorInput.SkipLog -ne $true) { Write-LogMessage -API $OrchestratorInput.OrchestratorName -tenant $tenant -message "Finished $($OrchestratorInput.OrchestratorName)" -sev Info } } catch { - Write-Host "Orchestrator error $($_.Exception.Message)" + Write-Host "Orchestrator error $($_.Exception.Message) line $($_.InvocationInfo.ScriptLineNumber)" } } diff --git a/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 b/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 index 5e14f3e1e80d..16b4cef697f8 100644 --- a/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 +++ b/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 @@ -18,6 +18,7 @@ function New-HaloPSATicket { lookupdisplay = 'Enter Details Manually' } client_id = ($client | Select-Object -Last 1) + _forcereassign = $true site_id = $null user_name = $null reportedby = $null diff --git a/Scheduler_GetWebhooks/run.ps1 b/Scheduler_GetWebhooks/run.ps1 index b262f320738b..b55b57d1f05c 100644 --- a/Scheduler_GetWebhooks/run.ps1 +++ b/Scheduler_GetWebhooks/run.ps1 @@ -3,11 +3,12 @@ param($Timer) try { $webhookTable = Get-CIPPTable -tablename webhookTable - $Webhooks = Get-CIPPAzDataTableEntity @webhookTable + $Webhooks = Get-CIPPAzDataTableEntity @webhookTable -Property RowKey if (($Webhooks | Measure-Object).Count -eq 0) { Write-Host 'No webhook subscriptions found. Exiting.' return } + Write-Host 'Processing webhooks' $InputObject = [PSCustomObject]@{ OrchestratorName = 'WebhookOrchestrator' @@ -16,8 +17,10 @@ try { } SkipLog = $true } + Write-Host ($InputObject | ConvertTo-Json -Depth 5) $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) Write-Host "Started orchestration with ID = '$InstanceId'" } catch { - Write-LogMessage -API 'Webhooks' -message "Error processing webhooks - $($_.Exception.Message)" -sev Error + Write-LogMessage -API 'Webhooks' -message 'Error processing webhooks' -sev Error -LogData (Get-CippException -Exception $_) + Write-Host ( 'Webhook error {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message) } diff --git a/profile.ps1 b/profile.ps1 index f30159db12cb..ec6aa4e19b86 100644 --- a/profile.ps1 +++ b/profile.ps1 @@ -15,9 +15,10 @@ # Import modules @('CippCore', 'CippExtensions', 'Az.KeyVault', 'Az.Accounts') | ForEach-Object { try { + $Module = $_ Import-Module -Name $_ -ErrorAction Stop } catch { - Write-LogMessage -message "Failed to import module $($_): $_.Exception.Message" -Sev 'debug' + Write-LogMessage -message "Failed to import module - $Module" -LogData (Get-CippException -Exception $_) -Sev 'debug' $_.Exception.Message } } @@ -32,7 +33,7 @@ try { $Auth = Get-CIPPAuthentication } } catch { - Write-LogMessage -message "Could not retrieve keys from Keyvault: $($_.Exception.Message)" -Sev 'debug' + Write-LogMessage -message 'Could not retrieve keys from Keyvault' -LogData (Get-CippException -Exception $_) -Sev 'debug' } # Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell. diff --git a/version_latest.txt b/version_latest.txt index 6ffbe8ba8ebd..d2ff458a0121 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.4.3 +5.5.3 \ No newline at end of file