From cb53d870c0d22d8c1279e161c7ef7e046b629052 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 2 Jun 2024 12:54:35 -0400 Subject: [PATCH 01/54] Change Role permission --- .../Users/Invoke-ExecJITAdmin.ps1 | 52 +++++++++++++++++++ .../Public/Entrypoints/Invoke-ListRoles.ps1 | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 new file mode 100644 index 000000000000..ab2cc1ecd5b2 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -0,0 +1,52 @@ +using namespace System.Net + +Function Invoke-ExecJITAdmin { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Identity.Role.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + #If UserId is a guid, get the user's UPN + if ($Request.body.UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.body.UserId)" -tenantid $Request.body.TenantFilter).userPrincipalName + } + if ($Request.body.vacation -eq 'true') { + $StartDate = $Request.body.StartDate + $TaskBody = @{ + TenantFilter = $Request.body.TenantFilter + Name = "Add CA Exclusion Vacation Mode: $Username - $($Request.body.TenantFilter)" + Command = @{ + value = 'Set-CIPPCAExclusion' + label = 'Set-CIPPCAExclusion' + } + Parameters = @{ + ExclusionType = 'Add' + UserID = $Request.body.UserID + PolicyId = $Request.body.PolicyId + UserName = $Username + } + ScheduledTime = $StartDate + } + Add-CIPPScheduledTask -Task $TaskBody -hidden $false + #Removal of the exclusion + $TaskBody.Parameters.ExclusionType = 'Remove' + $TaskBody.Name = "Remove CA Exclusion Vacation Mode: $username - $($Request.body.TenantFilter)" + $TaskBody.ScheduledTime = $Request.body.EndDate + Add-CIPPScheduledTask -Task $TaskBody -hidden $false + $body = @{ Results = "Successfully added vacation mode schedule for $Username." } + } else { + Set-CIPPCAExclusion -TenantFilter $Request.body.TenantFilter -ExclusionType $Request.body.ExclusionType -UserID $Request.body.UserID -PolicyId $Request.body.PolicyId -executingUser $request.headers.'x-ms-client-principal' -UserName $Username + } + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + }) + +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListRoles.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListRoles.ps1 index 9f17fde986f1..b56c40828a63 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListRoles.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListRoles.ps1 @@ -5,7 +5,7 @@ Function Invoke-ListRoles { .FUNCTIONALITY Entrypoint .ROLE - Tenant.Directory.Read + Identity.Role.Read #> [CmdletBinding()] param($Request, $TriggerMetadata) From f4e4b10e5eb84c9c00b63356d5f937c5de5e5964 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 2 Jun 2024 12:58:31 -0400 Subject: [PATCH 02/54] Update Invoke-ExecJITAdmin.ps1 --- .../Administration/Users/Invoke-ExecJITAdmin.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index ab2cc1ecd5b2..15c879d16589 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -16,17 +16,17 @@ Function Invoke-ExecJITAdmin { if ($Request.body.UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.body.UserId)" -tenantid $Request.body.TenantFilter).userPrincipalName } - if ($Request.body.vacation -eq 'true') { + <#if ($Request.body.vacation -eq 'true') { $StartDate = $Request.body.StartDate $TaskBody = @{ TenantFilter = $Request.body.TenantFilter - Name = "Add CA Exclusion Vacation Mode: $Username - $($Request.body.TenantFilter)" + Name = "Set JIT Admin: $Username - $($Request.body.TenantFilter)" Command = @{ - value = 'Set-CIPPCAExclusion' - label = 'Set-CIPPCAExclusion' + value = 'Set-CIPPJITAdmin' + label = 'Set-CIPPJITAdmin' } Parameters = @{ - ExclusionType = 'Add' + UserType = 'Add' UserID = $Request.body.UserID PolicyId = $Request.body.PolicyId UserName = $Username @@ -42,7 +42,7 @@ Function Invoke-ExecJITAdmin { $body = @{ Results = "Successfully added vacation mode schedule for $Username." } } else { Set-CIPPCAExclusion -TenantFilter $Request.body.TenantFilter -ExclusionType $Request.body.ExclusionType -UserID $Request.body.UserID -PolicyId $Request.body.PolicyId -executingUser $request.headers.'x-ms-client-principal' -UserName $Username - } + }#> Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK From 8c4005a5bcebdb6dafe7bafb5c68c53396664d05 Mon Sep 17 00:00:00 2001 From: Esco Date: Mon, 3 Jun 2024 14:38:30 +0200 Subject: [PATCH 03/54] Added Malware Filter Rule --- ...Invoke-CIPPStandardMalwareFilterPolicy.ps1 | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 index 178c5b048861..b2856a8031f7 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 @@ -21,6 +21,17 @@ function Invoke-CIPPStandardMalwareFilterPolicy { ($CurrentState.EnableExternalSenderAdminNotifications -eq $Settings.EnableExternalSenderAdminNotifications) -and (($null -eq $Settings.ExternalSenderAdminAddress) -or ($CurrentState.ExternalSenderAdminAddress -eq $Settings.ExternalSenderAdminAddress)) + $AcceptedDomains = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AcceptedDomain' + + $RuleState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MalwareFilterRule' | + Where-Object -Property Name -EQ "CIPP $PolicyName" | + Select-Object Name, MalwareFilterPolicy, Priority, RecipientDomainIs + + $RuleStateIsCorrect = ($RuleState.Name -eq "CIPP $PolicyName") -and + ($RuleState.MalwareFilterPolicy -eq $PolicyName) -and + ($RuleState.Priority -eq 0) -and + (!(Compare-Object -ReferenceObject $RuleState.RecipientDomainIs -DifferenceObject $AcceptedDomains.Name)) + if ($Settings.remediate -eq $true) { if ($StateIsCorrect -eq $true) { @@ -52,6 +63,29 @@ function Invoke-CIPPStandardMalwareFilterPolicy { Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create Malware Filter Policy. Error: $ErrorMessage" -sev Error } } + + if ($RuleStateIsCorrect -eq $false) { + $cmdparams = @{ + MalwareFilterPolicy = $PolicyName + Priority = 0 + RecipientDomainIs = $AcceptedDomains.Name + } + + try { + if ($RuleState.Name -eq "CIPP $PolicyName") { + $cmdparams.Add('Identity', "CIPP $PolicyName") + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-MalwareFilterRule' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Malware Filter Rule' -sev Info + } else { + $cmdparams.Add('Name', "CIPP $PolicyName") + New-ExoRequest -tenantid $Tenant -cmdlet 'New-MalwareFilterRule' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Created Malware Filter Rule' -sev Info + } + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create Malware Filter Rule. Error: $ErrorMessage" -sev Error + } + } } if ($Settings.alert -eq $true) { @@ -67,4 +101,4 @@ function Invoke-CIPPStandardMalwareFilterPolicy { Add-CIPPBPAField -FieldName 'MalwareFilterPolicy' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant } -} \ No newline at end of file +} From 8e6048ea3416bc7cd34f88ab428bfa317cf832c2 Mon Sep 17 00:00:00 2001 From: Esco Date: Mon, 3 Jun 2024 14:58:44 +0200 Subject: [PATCH 04/54] Added FilesTypes to Policy --- .../Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 index b2856a8031f7..d9ae65ac222f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 @@ -9,11 +9,12 @@ function Invoke-CIPPStandardMalwareFilterPolicy { $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MalwareFilterPolicy' | Where-Object -Property Name -EQ $PolicyName | - Select-Object Name, EnableFileFilter, FileTypeAction, ZapEnabled, QuarantineTag, EnableInternalSenderAdminNotifications, InternalSenderAdminAddress, EnableExternalSenderAdminNotifications, ExternalSenderAdminAddress + Select-Object Name, EnableFileFilter, FileTypeAction, FileTypes, ZapEnabled, QuarantineTag, EnableInternalSenderAdminNotifications, InternalSenderAdminAddress, EnableExternalSenderAdminNotifications, ExternalSenderAdminAddress $StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and ($CurrentState.EnableFileFilter -eq $true) -and ($CurrentState.FileTypeAction -eq $Settings.FileTypeAction) -and + ($null -ne $CurrentState.FileTypes) -and ($CurrentState.ZapEnabled -eq $true) -and ($CurrentState.QuarantineTag -eq $Settings.QuarantineTag) -and ($CurrentState.EnableInternalSenderAdminNotifications -eq $Settings.EnableInternalSenderAdminNotifications) -and @@ -48,6 +49,12 @@ function Invoke-CIPPStandardMalwareFilterPolicy { ExternalSenderAdminAddress = $Settings.ExternalSenderAdminAddress } + if ($null -eq $CurrentState.FileTypes) { + $cmdparams.Add('FileTypes', @('ace', 'ani', 'apk', 'app', 'appx', 'arj', 'bat', 'cab', 'cmd', 'com', 'deb', 'dex', 'dll', 'docm', 'elf', 'exe', 'hta', 'img', 'iso', 'jar', 'jnlp', 'kext', 'lha', 'lib', 'library', 'lnk', 'lzh', 'macho', 'msc', 'msi', 'msix', 'msp', 'mst', 'pif', 'ppa', 'ppam', 'reg', 'rev', 'scf', 'scr', 'sct', 'sys', 'uif', 'vb', 'vbe', 'vbs', 'vxd', 'wsc', 'wsf', 'wsh', 'xll', 'xz', 'z')) + } else { + $cmdparams.Add('FileTypes', $CurrentState.FileTypes) + } + try { if ($CurrentState.Name -eq $PolicyName) { $cmdparams.Add('Identity', $PolicyName) From ce4b4320bdc3decfc1ec68a5350738bfc011fe21 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 3 Jun 2024 10:12:14 -0400 Subject: [PATCH 05/54] Fix cache clear errors --- .../Activity Triggers/Push-UpdateTenants.ps1 | 15 +++++++++-- .../Tenant/Invoke-ListTenants.ps1 | 26 ++++++++++++++----- .../Public/GraphHelper/Remove-CIPPCache.ps1 | 19 +++++++++----- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-UpdateTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-UpdateTenants.ps1 index f60a27a57b26..811d54b229be 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-UpdateTenants.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-UpdateTenants.ps1 @@ -10,13 +10,24 @@ function Push-UpdateTenants { Write-Host 'Update Tenants already running' return } - $Queue = New-CippQueueEntry -Name 'Update Tenants' -Reference $QueueReference + $Queue = New-CippQueueEntry -Name 'Update Tenants' -Reference $QueueReference -TotalTasks 1 try { + $QueueTask = @{ + QueueId = $Queue.RowKey + Name = 'Get tenant list' + Status = 'Running' + } + $TaskStatus = Set-CippQueueTask @QueueTask + $QueueTask.TaskId = $TaskStatus.RowKey Update-CippQueueEntry -RowKey $Queue.RowKey -Status 'Running' - Get-Tenants | Out-Null + Get-Tenants -IncludeAll -TriggerRefresh | Out-Null Update-CippQueueEntry -RowKey $Queue.RowKey -Status 'Completed' + $QueueTask.Status = 'Completed' + Set-CippQueueTask @QueueTask } catch { Write-Host "Queue Error: $($_.Exception.Message)" Update-CippQueueEntry -RowKey $Queue.RowKey -Status 'Failed' + $QueueTask.Status = 'Failed' + Set-CippQueueTask @QueueTask } } \ No newline at end of file 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 67b0d6562ec7..7e3d3da4e22c 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 @@ -12,7 +12,7 @@ Function Invoke-ListTenants { $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' $TenantAccess = Test-CIPPAccess -Request $Request -TenantList if ($TenantAccess -notcontains 'AllTenants') { @@ -22,17 +22,29 @@ Function Invoke-ListTenants { } # Clear Cache - if ($request.Query.ClearCache -eq 'true') { - Remove-CIPPCache -tenantsOnly $request.query.TenantsOnly - $GraphRequest = [pscustomobject]@{'Results' = 'Successfully completed request.' } + if ($Request.Query.ClearCache -eq $true) { + Remove-CIPPCache -tenantsOnly $Request.Query.TenantsOnly + + $InputObject = [PSCustomObject]@{ + Batch = @( + @{ + FunctionName = 'UpdateTenants' + } + ) + OrchestratorName = 'UpdateTenants' + SkipLog = $true + } + Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Compress -Depth 5) + + $GraphRequest = [pscustomobject]@{'Results' = 'Cache has been cleared and a tenant refresh is queued.' } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $GraphRequest }) - Get-Tenants -IncludeAll -TriggerRefresh - + #Get-Tenants -IncludeAll -TriggerRefresh + return } - if ($Request.query.TriggerRefresh) { + if ($Request.Query.TriggerRefresh) { Get-Tenants -IncludeAll -TriggerRefresh } try { diff --git a/Modules/CIPPCore/Public/GraphHelper/Remove-CIPPCache.ps1 b/Modules/CIPPCore/Public/GraphHelper/Remove-CIPPCache.ps1 index 1bbab4a3f025..99057374f0de 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Remove-CIPPCache.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Remove-CIPPCache.ps1 @@ -9,9 +9,12 @@ function Remove-CIPPCache { # Remove all tenants except excluded $TenantsTable = Get-CippTable -tablename 'Tenants' $Filter = "PartitionKey eq 'Tenants' and Excluded eq false" - $ClearIncludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter $Filter - Remove-AzDataTableEntity @TenantsTable -Entity $ClearIncludedTenants - if ($tenantsonly -eq 'false') { + $ClearIncludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter $Filter -Property PartitionKey, RowKey + if ($ClearIncludedTenants) { + Remove-AzDataTableEntity @TenantsTable -Entity $ClearIncludedTenants + } + + if ($TenantsOnly -eq 'false') { Write-Host 'Clearing all' # Remove Domain Analyser cached results $DomainsTable = Get-CippTable -tablename 'Domains' @@ -20,11 +23,15 @@ function Remove-CIPPCache { $_.DomainAnalyser = '' $_ } - Update-AzDataTableEntity @DomainsTable -Entity $ClearDomainAnalyserRows + if ($ClearDomainAnalyserRows) { + Update-AzDataTableEntity @DomainsTable -Entity $ClearDomainAnalyserRows + } #Clear BPA - $BPATable = Get-CippTable -tablename 'cachebpa' + $BPATable = Get-CippTable -tablename 'cachebpav2' $ClearBPARows = Get-CIPPAzDataTableEntity @BPATable - Remove-AzDataTableEntity @BPATable -Entity $ClearBPARows + if ($ClearBPARows) { + Remove-AzDataTableEntity @BPATable -Entity $ClearBPARows + } $ENV:SetFromProfile = $null $Script:SkipListCache = $Null $Script:SkipListCacheEmpty = $Null From fcd5f6e4637f77625dfa0cb477602f8825f5614c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 3 Jun 2024 11:12:50 -0400 Subject: [PATCH 06/54] Durable clear on version change --- .../CIPPCore/Public/Clear-CippDurables.ps1 | 62 +++++++++++++++++++ .../CIPP/Core/Invoke-ExecDurableFunctions.ps1 | 8 ++- profile.ps1 | 17 +++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 Modules/CIPPCore/Public/Clear-CippDurables.ps1 diff --git a/Modules/CIPPCore/Public/Clear-CippDurables.ps1 b/Modules/CIPPCore/Public/Clear-CippDurables.ps1 new file mode 100644 index 000000000000..eb1949a39078 --- /dev/null +++ b/Modules/CIPPCore/Public/Clear-CippDurables.ps1 @@ -0,0 +1,62 @@ +function Clear-CippDurables { + [CmdletBinding(SupportsShouldProcess = $true)] + Param() + # Collect info + $StorageContext = New-AzStorageContext -ConnectionString $env:AzureWebJobsStorage + $FunctionName = $env:WEBSITE_SITE_NAME + + # Get orchestrators + $InstancesTable = Get-CippTable -TableName ('{0}Instances' -f $FunctionName) + $HistoryTable = Get-CippTable -TableName ('{0}History' -f $FunctionName) + $Yesterday = (Get-Date).AddDays(-1).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + $Filter = "CreatedTime ge datetime'$Yesterday' or RuntimeStatus eq 'Pending' or RuntimeStatus eq 'Running'" + $Instances = Get-CippAzDataTableEntity @InstancesTable -Filter $Filter + + $Queues = Get-AzStorageQueue -Context $StorageContext -Name ('{0}*' -f $FunctionName) | Select-Object -Property Name, ApproximateMessageCount, QueueClient + + $RunningQueues = $Queues | Where-Object { $_.ApproximateMessageCount -gt 0 } + foreach ($Queue in $RunningQueues) { + Write-Information "- Removing queue: $($Queue.Name), message count: $($Queue.ApproximateMessageCount)" + if ($PSCmdlet.ShouldProcess($Queue.Name, 'Clear Queue')) { + $Queue.QueueClient.ClearMessagesAsync() + } + } + + $QueueTable = Get-CippTable -TableName 'CippQueue' + $CippQueue = Invoke-ListCippQueue + $QueueEntities = foreach ($Queue in $CippQueue) { + if ($Queue.Status -eq 'Running') { + $Queue.TotalTasks = $Queue.CompletedTasks + $Queue | Select-Object -Property PartitionKey, RowKey, TotalTasks + } + } + if (($QueueEntities | Measure-Object).Count -gt 0) { + if ($PSCmdlet.ShouldProcess('Queues', 'Mark Failed')) { + Update-AzDataTableEntity @QueueTable -Entity $QueueEntities + } + } + + $CippQueueTasks = Get-CippTable -TableName 'CippQueueTasks' + $RunningTasks = Get-CIPPAzDataTableEntity @CippQueueTasks -Filter "Status eq 'Running'" -Property RowKey, PartitionKey, Status + if (($RunningTasks | Measure-Object).Count -gt 0) { + if ($PSCmdlet.ShouldProcess('Tasks', 'Mark Failed')) { + $UpdatedTasks = foreach ($Task in $RunningTasks) { + $Task.Status = 'Failed' + $Task + } + Update-AzDataTableEntity @CippQueueTasks -Entity $UpdatedTasks + } + } + + Remove-AzDataTable @InstancesTable + Remove-AzDataTable @HistoryTable + $BlobContainer = '{0}-largemessages' -f $Function.Name + if (Get-AzStorageContainer -Name $BlobContainer -Context $StorageContext -ErrorAction SilentlyContinue) { + Write-Information "- Removing blob container: $BlobContainer" + if ($PSCmdlet.ShouldProcess($BlobContainer, 'Remove Blob Container')) { + Remove-AzStorageContainer -Name $BlobContainer -Context $StorageContext -Confirm:$false -Force + } + } + $null = Get-CippTable -TableName ('{0}History' -f $FunctionName) + Write-Information 'Durable Orchestrators and Queues have been cleared' +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 index c2c194438e96..09da6bd2c990 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 @@ -141,9 +141,13 @@ function Invoke-ExecDurableFunctions { $HistoryTable = Get-CippTable -TableName ('{0}History' -f $FunctionName) if ($Request.Query.PartitionKey) { $HistoryEntities = Get-CIPPAzDataTableEntity @HistoryTable -Filter "PartitionKey eq '$($Request.Query.PartitionKey)'" -Property RowKey, PartitionKey - Remove-AzDataTableEntity @HistoryTable -Entity $HistoryEntities + if ($HistoryEntities) { + Remove-AzDataTableEntity @HistoryTable -Entity $HistoryEntities + } $Instance = Get-CIPPAzDataTableEntity @InstancesTable -Filter "PartitionKey eq '$($Request.Query.PartitionKey)'" -Property RowKey, PartitionKey - Remove-AzDataTableEntity @InstancesTable -Entity $Instance + if ($Instance) { + Remove-AzDataTableEntity @InstancesTable -Entity $Instance + } $Body = [PSCustomObject]@{ Results = 'Orchestrator {0} purged successfully' -f $Request.Query.PartitionKey } diff --git a/profile.ps1 b/profile.ps1 index 1aa6a00d98c6..134944f4d7e7 100644 --- a/profile.ps1 +++ b/profile.ps1 @@ -46,6 +46,23 @@ try { Write-LogMessage -message 'Could not retrieve keys from Keyvault' -LogData (Get-CippException -Exception $_) -Sev 'debug' } +$CurrentVersion = (Get-Content .\version_latest.txt).trim() +$Table = Get-CippTable -tablename 'Version' +$LastStartup = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'Version' and RowKey eq 'Version'" +if ($CurrentVersion -ne $LastStartup.Version) { + Write-Host "Version has changed from $($LastStartup.Version) to $CurrentVersion" + Clear-CippDurables + if ($LastStartup) { + $LastStartup.Version = $CurrentVersion + } else { + $LastStartup = [PSCustomObject]@{ + PartitionKey = 'Version' + RowKey = 'Version' + Version = $CurrentVersion + } + } + Update-AzDataTableEntity @Table -Entity $LastStartup +} # Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell. # Enable-AzureRmAlias From b71dfb3dffec98072adc14feed2b1ec1410f4c76 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 3 Jun 2024 11:22:29 -0400 Subject: [PATCH 07/54] Fix tenant access check --- .../CIPP/Settings/Invoke-ExecAccessChecks.ps1 | 10 +++++----- Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 | 7 ++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 index 019e3af0997e..f655b21c2a91 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 @@ -16,14 +16,14 @@ Function Invoke-ExecAccessChecks { # Write to the Azure Functions log stream. Write-Host 'PowerShell HTTP trigger function processed a request.' - if ($Request.query.Permissions -eq 'true') { - $Results = Test-CIPPAccessPermissions -tenantfilter $ENV:tenantid -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' + if ($Request.Query.Permissions -eq 'true') { + $Results = Test-CIPPAccessPermissions -tenantfilter $ENV:tenantid -APIName $APINAME -ExecutingUser $Request.Headers.'x-ms-client-principal' } - if ($Request.query.Tenants -eq 'true') { - $Results = Test-CIPPAccessTenant -Tenantcsv $Request.body.TenantId + if ($Request.Query.Tenants -eq 'true') { + $Results = Test-CIPPAccessTenant -TenantCSV $Request.Body.tenantid } - if ($Request.query.GDAP -eq 'true') { + if ($Request.Query.GDAP -eq 'true') { $Results = Test-CIPPGDAPRelationships } diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 index 014218b3cc16..842b4f4b66aa 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 @@ -22,9 +22,6 @@ function Test-CIPPAccessTenant { $Tenants = ($TenantCSV).split(',') if (!$Tenants) { $results = 'Could not load the tenants list from cache. Please run permissions check first, or visit the tenants page.' } $TenantList = Get-Tenants - $TenantIds = foreach ($Tenant in $Tenants) { - ($TenantList | Where-Object { $_.defaultDomainName -eq $Tenant }).customerId - } $results = foreach ($tenant in $Tenants) { $AddedText = '' @@ -32,9 +29,9 @@ function Test-CIPPAccessTenant { $TenantId = ($TenantList | Where-Object { $_.defaultDomainName -eq $tenant }).customerId $BulkRequests = $ExpectedRoles | ForEach-Object { @( @{ - id = "roleManagement_$($_.id)" + id = "roleManagement_$($_.Id)" method = 'GET' - url = "roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '$($_.id)'&`$expand=principal" + url = "roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '$($_.Id)'&`$expand=principal" } ) } From 16bd1f120d47df6050d4125e5079c00b498b4742 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 3 Jun 2024 13:06:30 -0400 Subject: [PATCH 08/54] JIT Admin --- .../Users/Invoke-ExecJITAdmin.ps1 | 45 ++++++++++- .../Public/Get-CIPPSchemaExtensions.ps1 | 79 +++++++++++++++++++ .../Public/Set-CIPPSchemaExtension.ps1 | 52 ------------ .../CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 | 24 ++++-- 4 files changed, 137 insertions(+), 63 deletions(-) create mode 100644 Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 delete mode 100644 Modules/CIPPCore/Public/Set-CIPPSchemaExtension.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index 15c879d16589..5ca4705ad81f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -11,11 +11,48 @@ Function Invoke-ExecJITAdmin { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - #If UserId is a guid, get the user's UPN - if ($Request.body.UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { - $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.body.UserId)" -tenantid $Request.body.TenantFilter).userPrincipalName + Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + Write-Information ($Request.Body | ConvertTo-Json -Depth 10) + if ($Request.Query.Action -eq 'List') { + $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } + Write-Information "Schema: $($Schema)" + $Query = @{ + TenantFilter = $Request.Query.TenantFilter + Endpoint = 'users' + Parameters = @{ + '$count' = 'true' + '$select' = "id,displayName,userPrincipalName,$($Schema.id)" + '$filter' = "$($Schema.id)/jitAdminEnabled eq true or $($Schema.id)/jitAdminEnabled eq false" + } + } + $Users = Get-GraphRequestList @Query | Where-Object { $_.id } + $Results = $Users | ForEach-Object { + $MemberOf = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_.id)/memberOf/microsoft.graph.directoryRole/?`$select=id,displayName" -tenantid $Request.Query.TenantFilter -ComplexFilter + [PSCustomObject]@{ + id = $_.id + displayName = $_.displayName + userPrincipalName = $_.userPrincipalName + jitAdminEnabled = $_.($Schema.id).jitAdminEnabled + jitAdminExpiration = $_.($Schema.id).jitAdminExpiration + memberOf = $MemberOf + } + } + + + Write-Information ($Results | ConvertTo-Json -Depth 10) + $Body = @{ + Results = @($Results) + Metadata = @{ + Parameters = $Query.Parameters + } + } + } else { + #If UserId is a guid, get the user's UPN + if ($Request.body.UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.body.UserId)" -tenantid $Request.body.TenantFilter).userPrincipalName + } } + <#if ($Request.body.vacation -eq 'true') { $StartDate = $Request.body.StartDate $TaskBody = @{ diff --git a/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 b/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 new file mode 100644 index 000000000000..26794daffea7 --- /dev/null +++ b/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 @@ -0,0 +1,79 @@ +function Get-CIPPSchemaExtensions { + [CmdletBinding()] + Param() + + $Schemas = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/schemaExtensions?`$filter=owner eq '$($env:applicationid)' and status eq 'Available'" -NoAuthCheck $true -AsApp $true + + $SchemaDefinitions = [PSCustomObject]@( + @{ + id = 'cippUser' + description = 'CIPP User Schema' + targetTypes = @('User') + properties = @( + @{ + name = 'jitAdminEnabled' + type = 'Boolean' + } + @{ + name = 'jitAdminExpiration' + type = 'DateTime' + } + @{ + name = 'mailboxType' + type = 'String' + } + @{ + name = 'archiveEnabled' + type = 'Boolean' + } + @{ + name = 'autoExpandingArchiveEnabled' + type = 'Boolean' + } + ) + } + ) + foreach ($SchemaDefinition in $SchemaDefinitions) { + $SchemaFound = $false + foreach ($Schema in $Schemas) { + if ($Schema.id -match $SchemaDefinition.id) { + $SchemaFound = $true + $Schema = $Schemas | Where-Object { $_.id -match $SchemaDefinition.id } + $Patch = @{} + if (Compare-Object -ReferenceObject ($SchemaDefinition.properties | Select-Object name, type) -DifferenceObject $Schema.properties) { + $Patch.properties = $Properties + } + if ($Schema.status -ne 'Available') { + $Patch.status = 'Available' + } + if ($Schema.targetTypes -ne $SchemaDefinition.targetTypes) { + $Patch.targetTypes = $SchemaDefinition.targetTypes + } + if ($Patch.Keys.Count -gt 0) { + Write-Information "Updating $($Schema.id)" + $Json = ConvertTo-Json -Depth 5 -InputObject $Patch + New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/v1.0/schemaExtensions/$($Schema.id)" -Body $Json -AsApp $true -NoAuthCheck $true + } else { + $Schema + } + } + } + if (!$SchemaFound) { + Write-Information "Creating Schema Extension for $($SchemaDefinition.id)" + $Body = [PSCustomObject]@{ + id = 'cippUser' + description = 'CIPP User' + targetTypes = $SchemaDefinition.TargetTypes + properties = $SchemaDefinition.Properties + } + + $Json = ConvertTo-Json -Depth 5 -InputObject $Body + $Schema = New-GraphPOSTRequest -type POST -Uri 'https://graph.microsoft.com/v1.0/schemaExtensions' -Body $Json -AsApp $true -NoAuthCheck $true + $Patch = [PSCustomObject]@{ + status = 'Available' + } + $PatchJson = ConvertTo-Json -Depth 5 -InputObject $Patch + New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/v1.0/schemaExtensions/$($Schema.id)" -Body $PatchJson -AsApp $true -NoAuthCheck $true + } + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Set-CIPPSchemaExtension.ps1 b/Modules/CIPPCore/Public/Set-CIPPSchemaExtension.ps1 deleted file mode 100644 index 111e02daa7d1..000000000000 --- a/Modules/CIPPCore/Public/Set-CIPPSchemaExtension.ps1 +++ /dev/null @@ -1,52 +0,0 @@ -function Set-CIPPSchemaExtension { - [CmdletBinding()] - Param() - - $Schema = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/schemaExtensions?`$filter=owner eq '$($env:applicationid)'" -NoAuthCheck $true -AsApp $true - - $Properties = [PSCustomObject]@( - @{ - name = 'jitAdminEnabled' - type = 'Boolean' - } - @{ - name = 'jitAdminExpiration' - type = 'DateTime' - } - ) - $TargetTypes = @('User') - - if (!$Schema.id) { - $Body = [PSCustomObject]@{ - id = 'cippSchema' - description = 'CIPP Schema Extension' - targetTypes = $TargetTypes - properties = $Properties - } - - $Json = ConvertTo-Json -Depth 5 -InputObject $Body - Write-Host $Json - $Schema = New-GraphPOSTRequest -type POST -Uri 'https://graph.microsoft.com/v1.0/schemaExtensions' -Body $Json -AsApp $true -NoAuthCheck $true - $Schema.status = 'Available' - New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/v1.0/schemaExtensions/$($Schema.id)" -Body $Json -AsApp $true -NoAuthCheck $true - } else { - $Schema = $Schema | Where-Object { $_.id -match 'cippSchema' } - $Patch = @{} - if (Compare-Object -ReferenceObject ($Properties | Select-Object name, type) -DifferenceObject $Schema.properties) { - $Patch.properties = $Properties - } - if ($Schema.status -ne 'Available') { - $Patch.status = 'Available' - } - if ($Schema.targetTypes -ne $TargetTypes) { - $Patch.targetTypes = $TargetTypes - } - - if ($Patch.Keys.Count -gt 0) { - $Json = ConvertTo-Json -Depth 5 -InputObject $Patch - New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/v1.0/schemaExtensions/$($Schema.id)" -Body $Json -AsApp $true -NoAuthCheck $true - } else { - $Schema - } - } -} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 index 143d12ae02b5..1eeef4afbecd 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 @@ -4,17 +4,27 @@ function Set-CIPPUserJITAdmin { [string]$TenantFilter, [string]$UserId, [switch]$Enabled, - $Expiration + $Expiration, + [switch]$Clear ) - $Schema = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/schemaExtensions?`$filter=owner eq '$($env:applicationid)'" -NoAuthCheck $true -AsApp $true | Where-Object { $_.owner -eq $env:applicationid } - $Body = [PSCustomObject]@{ - "$($Schema.id)" = @{ - jitAdminEnabled = $Enabled.IsPresent - jitAdminExpiration = $Expiration + $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } + if ($Clear.IsPresent) { + $Body = [PSCustomObject]@{ + "$($Schema.id)" = @{ + jitAdminEnabled = $null + jitAdminExpiration = $null + } + } + } else { + $Body = [PSCustomObject]@{ + "$($Schema.id)" = @{ + jitAdminEnabled = $Enabled.IsPresent + jitAdminExpiration = $Expiration.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } } } + $Json = ConvertTo-Json -Depth 5 -InputObject $Body - Write-Host $Json New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/beta/users/$UserId" -Body $Json -tenantid $TenantFilter } \ No newline at end of file From 2481a8315dcc978b2704f8e9b508f12b44ef9032 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 3 Jun 2024 13:25:02 -0400 Subject: [PATCH 09/54] Add Application.ReadWrite.All - Application --- Modules/CIPPCore/Public/SAMManifest.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json index 4fce3279465c..3ee73e6b09d1 100644 --- a/Modules/CIPPCore/Public/SAMManifest.json +++ b/Modules/CIPPCore/Public/SAMManifest.json @@ -155,9 +155,10 @@ { "id": "84bccea3-f856-4a8a-967b-dbe0a3d53a64", "type": "Scope" }, { "id": "280b3b69-0437-44b1-bc20-3b2fca1ee3e9", "type": "Scope" }, { "id": "885f682f-a990-4bad-a642-36736a74b0c7", "type": "Scope" }, - { "id": "913b9306-0ce1-42b8-9137-6a7df690a760", "type": "Role"}, - { "id": "cb8f45a0-5c2e-4ea1-b803-84b870a7d7ec", "type": "Scope"}, - { "id": "4c06a06a-098a-4063-868e-5dfee3827264", "type": "Scope"} + { "id": "913b9306-0ce1-42b8-9137-6a7df690a760", "type": "Role" }, + { "id": "cb8f45a0-5c2e-4ea1-b803-84b870a7d7ec", "type": "Scope" }, + { "id": "4c06a06a-098a-4063-868e-5dfee3827264", "type": "Scope" }, + { "id": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9", "type": "Role" } ] }, { From 82da597161decb45c790e6b55a4071dffe4cab50 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 3 Jun 2024 14:19:40 -0400 Subject: [PATCH 10/54] Fix license report --- .../Public/Entrypoints/Invoke-ListLicenses.ps1 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 index 1fc0f4ca57e9..82bb1e4aab1f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 @@ -49,9 +49,13 @@ Function Invoke-ListLicenses { Write-Host "Started permissions orchestration with ID = '$InstanceId'" } } else { - $GraphRequest = $Rows | ForEach-Object { - $TermInfo = $_.TermInfo | ConvertFrom-Json -ErrorAction SilentlyContinue - $_.TermInfo = $TermInfo + $GraphRequest = $Rows | Where-Object { $_.License } | ForEach-Object { + if ($_.TermInfo) { + $TermInfo = $_.TermInfo | ConvertFrom-Json -ErrorAction SilentlyContinue + $_.TermInfo = $TermInfo + } else { + $_ | Add-Member -NotePropertyName TermInfo -NotePropertyValue $null + } $_ } } From b4a58fd86dbd9146fb30b4b2a42fae9658cb7cad Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 3 Jun 2024 15:17:17 -0400 Subject: [PATCH 11/54] JIT Admin functions --- .../Users/Invoke-ExecJITAdmin.ps1 | 94 ++++++++++++++----- .../CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 | 87 +++++++++++++---- .../Public/Set-CIPPUserJITAdminProperties.ps1 | 30 ++++++ 3 files changed, 165 insertions(+), 46 deletions(-) create mode 100644 Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index 5ca4705ad81f..9d89e0a494d1 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -12,7 +12,7 @@ Function Invoke-ExecJITAdmin { $APIName = $TriggerMetadata.FunctionName Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - Write-Information ($Request.Body | ConvertTo-Json -Depth 10) + if ($Request.Query.Action -eq 'List') { $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } Write-Information "Schema: $($Schema)" @@ -47,43 +47,85 @@ Function Invoke-ExecJITAdmin { } } } else { - #If UserId is a guid, get the user's UPN + Write-Information ($Request.Body | ConvertTo-Json -Depth 10) if ($Request.body.UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.body.UserId)" -tenantid $Request.body.TenantFilter).userPrincipalName } - } - <#if ($Request.body.vacation -eq 'true') { - $StartDate = $Request.body.StartDate - $TaskBody = @{ - TenantFilter = $Request.body.TenantFilter - Name = "Set JIT Admin: $Username - $($Request.body.TenantFilter)" + $Start = ([System.DateTimeOffset]::FromUnixTimeSeconds($Request.Body.StartDate)).DateTime.ToLocalTime() + $Expiration = ([System.DateTimeOffset]::FromUnixTimeSeconds($Request.Body.EndDate)).DateTime.ToLocalTime() + $Results = [System.Collections.Generic.List[string]]::new() + + if ($Request.Body.useraction -eq 'create') { + Write-Information "Creating JIT Admin user $($Request.Body.UserPrincipalName)" + $JITAdmin = @{ + User = @{ + 'FirstName' = $Request.Body.FirstName + 'LastName' = $Request.Body.LastName + 'UserPrincipalName' = $Request.Body.UserPrincipalName + } + Expiration = $Expiration + Action = 'Create' + } + $CreateResult = Set-CIPPUserJITAdmin @JITAdmin + $Results.Add("Created User: $($CreateResult.userPrincipalName)") + $Results.Add("Password: $($CreateResult.password)") + } + $Parameters = @{ + TenantFilter = $Request.Body.TenantFilter + User = @{ + 'UserPrincipalName' = $Username + } + Roles = $Request.Body.AdminRoles + Action = 'AddRoles' + Expiration = $Expiration + } + if ($Start -gt (Get-Date)) { + $Results.Add("Scheduling JIT Admin enable task for $Username") + $TaskBody = @{ + TenantFilter = $Request.Body.TenantFilter + Name = "JIT Admin (enable): $Username" + Command = @{ + value = 'Set-CIPPUserJITAdmin' + label = 'Set-CIPPUserJITAdmin' + } + Parameters = $Parameters + ScheduledTime = $Request.Body.StartDate + } + Add-CIPPScheduledTask -Task $TaskBody -hidden $false + Set-CIPPUserJITAdminProperties -TenantFilter $Request.Body.TenantFilter -UserId $UserObj.id -Expiration $Expiration + $Results.Add("Scheduled JIT Admin enable task for $Username") + } else { + $Results.Add("Executing JIT Admin enable task for $Username") + Set-CIPPUserJITAdmin @Parameters + } + + $DisableTaskBody = @{ + TenantFilter = $Request.Body.TenantFilter + Name = "JIT Admin (disable): $($Request.Body.UserPrincipalName)" Command = @{ - value = 'Set-CIPPJITAdmin' - label = 'Set-CIPPJITAdmin' + value = 'Set-CIPPUserJITAdmin' + label = 'Set-CIPPUserJITAdmin' } Parameters = @{ - UserType = 'Add' - UserID = $Request.body.UserID - PolicyId = $Request.body.PolicyId - UserName = $Username + TenantFilter = $Request.Body.TenantFilter + User = @{ + 'UserPrincipalName' = $Request.Body.UserPrincipalName + } + Roles = $Request.Body.AdminRoles + Action = $Request.Body.ExpireAction } - ScheduledTime = $StartDate + ScheduledTime = $Request.Body.EndDate } - Add-CIPPScheduledTask -Task $TaskBody -hidden $false - #Removal of the exclusion - $TaskBody.Parameters.ExclusionType = 'Remove' - $TaskBody.Name = "Remove CA Exclusion Vacation Mode: $username - $($Request.body.TenantFilter)" - $TaskBody.ScheduledTime = $Request.body.EndDate - Add-CIPPScheduledTask -Task $TaskBody -hidden $false - $body = @{ Results = "Successfully added vacation mode schedule for $Username." } - } else { - Set-CIPPCAExclusion -TenantFilter $Request.body.TenantFilter -ExclusionType $Request.body.ExclusionType -UserID $Request.body.UserID -PolicyId $Request.body.PolicyId -executingUser $request.headers.'x-ms-client-principal' -UserName $Username - }#> + Add-CIPPScheduledTask -Task $DisableTaskBody -hidden $false + $Results.Add("Scheduled JIT Admin disable task for $Username") + $Body = @{ + Results = @($Results) + } + } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $Body }) - } diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 index 1eeef4afbecd..0848966155b9 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 @@ -1,30 +1,77 @@ function Set-CIPPUserJITAdmin { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess = $true)] Param( [string]$TenantFilter, - [string]$UserId, - [switch]$Enabled, - $Expiration, - [switch]$Clear + $User, + [string[]]$Roles, + [string]$Action, + $Expiration ) - $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } - if ($Clear.IsPresent) { - $Body = [PSCustomObject]@{ - "$($Schema.id)" = @{ - jitAdminEnabled = $null - jitAdminExpiration = $null - } + if ($PSCmdlet.ShouldProcess("User: $($User.UserPrincipalName)", "Action: $Action")) { + if ($Action -ne 'Create') { + $UserObj = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/users/$($User.UserPrincipalName)" -tenantid $TenantFilter } - } else { - $Body = [PSCustomObject]@{ - "$($Schema.id)" = @{ - jitAdminEnabled = $Enabled.IsPresent - jitAdminExpiration = $Expiration.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + + switch ($Action) { + 'Create' { + $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } + $Body = @{ + accountEnabled = $true + displayName = $User.FirstName + ' ' + $User.LastName + userPrincipalName = $User.UserPrincipalName + passwordProfile = @{ + forceChangePasswordNextSignIn = $true + password = New-passwordString + } + "$($Schema.id)" = @{ + jitAdminEnabled = $false + jitAdminExpiration = $Expiration.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } + } + $Json = ConvertTo-Json -Depth 5 -InputObject $Body + $NewUser = New-GraphPOSTRequest -Uri 'https://graph.microsoft.com/beta/users' -Body $Json -tenantid $TenantFilter + [PSCustomObject]@{ + id = $NewUser.id + userPrincipalName = $NewUser.userPrincipalName + password = $Body.passwordProfile.password + } + } + 'AddRoles' { + $Roles = $Roles | ForEach-Object { + try { + $Body = @{ + '@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$($UserObj.id)" + } + $Json = ConvertTo-Json -Depth 5 -InputObject $Body + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/directoryRoles(roleTemplateId='$($_)')/members/`$ref" -tenantid $TenantFilter -body $Json + } finally {} + Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled -Expiration $Expiration + return "Added admin roles to user $($UserObj.displayName) ($($UserObj.userPrincipalName))" + } + } + 'RemoveRoles' { + $Roles = $Roles | ForEach-Object { + try { + $null = New-GraphPOSTRequest -type DELETE -uri "https://graph.microsoft.com/beta/directoryRoles(roleTemplateId='$($_)')/members/$($UserObj.id)/`$ref" -tenantid $TenantFilter + } finally {} + } + Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Clear + return "Removed admin roles from user $($UserObj.displayName)" + } + 'DeleteUser' { + $null = New-GraphPOSTRequest -type DELETE -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $TenantFilter + return "Deleted user $($UserObj.displayName) ($($UserObj.userPrincipalName)) with id $($UserObj.id)" + } + 'DisableUser' { + $Body = @{ + accountEnabled = $false + } + $Json = ConvertTo-Json -Depth 5 -InputObject $Body + New-GraphPOSTRequest -type PATCH -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $TenantFilter -body $Json + Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled:$false + return "Disabled user $($UserObj.displayName) ($($UserObj.userPrincipalName))" } } } - - $Json = ConvertTo-Json -Depth 5 -InputObject $Body - New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/beta/users/$UserId" -Body $Json -tenantid $TenantFilter } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 new file mode 100644 index 000000000000..21e2a7d19bfb --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 @@ -0,0 +1,30 @@ +function Set-CIPPUserJITAdminProperties { + [CmdletBinding()] + Param( + [string]$TenantFilter, + [string]$UserId, + [switch]$Enabled, + $Expiration, + [switch]$Clear + ) + + $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } + if ($Clear.IsPresent) { + $Body = [PSCustomObject]@{ + "$($Schema.id)" = @{ + jitAdminEnabled = $null + jitAdminExpiration = $null + } + } + } else { + $Body = [PSCustomObject]@{ + "$($Schema.id)" = @{ + jitAdminEnabled = $Enabled.IsPresent + jitAdminExpiration = $Expiration.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } + } + } + + $Json = ConvertTo-Json -Depth 5 -InputObject $Body + New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/beta/users/$UserId" -Body $Json -tenantid $TenantFilter +} \ No newline at end of file From 08bf03446324f0414172c8689bfe71d26ad94b9f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 3 Jun 2024 16:09:51 -0400 Subject: [PATCH 12/54] Fix JIT bug --- .../Administration/Users/Invoke-ExecJITAdmin.ps1 | 16 ++++++++-------- Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 | 4 ++-- .../Public/Set-CIPPUserJITAdminProperties.ps1 | 1 + 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index 9d89e0a494d1..ec5dbb935a9a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -48,8 +48,8 @@ Function Invoke-ExecJITAdmin { } } else { Write-Information ($Request.Body | ConvertTo-Json -Depth 10) - if ($Request.body.UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { - $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.body.UserId)" -tenantid $Request.body.TenantFilter).userPrincipalName + if ($Request.Body.UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.Body.UserId)" -tenantid $Request.Body.TenantFilter).userPrincipalName } $Start = ([System.DateTimeOffset]::FromUnixTimeSeconds($Request.Body.StartDate)).DateTime.ToLocalTime() @@ -68,6 +68,7 @@ Function Invoke-ExecJITAdmin { Action = 'Create' } $CreateResult = Set-CIPPUserJITAdmin @JITAdmin + $Username = $CreateResult.userPrincipalName $Results.Add("Created User: $($CreateResult.userPrincipalName)") $Results.Add("Password: $($CreateResult.password)") } @@ -81,7 +82,6 @@ Function Invoke-ExecJITAdmin { Expiration = $Expiration } if ($Start -gt (Get-Date)) { - $Results.Add("Scheduling JIT Admin enable task for $Username") $TaskBody = @{ TenantFilter = $Request.Body.TenantFilter Name = "JIT Admin (enable): $Username" @@ -93,8 +93,8 @@ Function Invoke-ExecJITAdmin { ScheduledTime = $Request.Body.StartDate } Add-CIPPScheduledTask -Task $TaskBody -hidden $false - Set-CIPPUserJITAdminProperties -TenantFilter $Request.Body.TenantFilter -UserId $UserObj.id -Expiration $Expiration - $Results.Add("Scheduled JIT Admin enable task for $Username") + Set-CIPPUserJITAdminProperties -TenantFilter $Request.Body.TenantFilter -UserId $Request.Body.UserId -Expiration $Expiration + $Results.Add("Scheduling JIT Admin enable task for $Username") } else { $Results.Add("Executing JIT Admin enable task for $Username") Set-CIPPUserJITAdmin @Parameters @@ -102,7 +102,7 @@ Function Invoke-ExecJITAdmin { $DisableTaskBody = @{ TenantFilter = $Request.Body.TenantFilter - Name = "JIT Admin (disable): $($Request.Body.UserPrincipalName)" + Name = "JIT Admin (disable): $Username" Command = @{ value = 'Set-CIPPUserJITAdmin' label = 'Set-CIPPUserJITAdmin' @@ -110,7 +110,7 @@ Function Invoke-ExecJITAdmin { Parameters = @{ TenantFilter = $Request.Body.TenantFilter User = @{ - 'UserPrincipalName' = $Request.Body.UserPrincipalName + 'UserPrincipalName' = $Username } Roles = $Request.Body.AdminRoles Action = $Request.Body.ExpireAction @@ -118,7 +118,7 @@ Function Invoke-ExecJITAdmin { ScheduledTime = $Request.Body.EndDate } Add-CIPPScheduledTask -Task $DisableTaskBody -hidden $false - $Results.Add("Scheduled JIT Admin disable task for $Username") + $Results.Add("Scheduling JIT Admin disable task for $Username") $Body = @{ Results = @($Results) } diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 index 0848966155b9..1b7a32c8cfe7 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 @@ -46,9 +46,9 @@ function Set-CIPPUserJITAdmin { $Json = ConvertTo-Json -Depth 5 -InputObject $Body $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/directoryRoles(roleTemplateId='$($_)')/members/`$ref" -tenantid $TenantFilter -body $Json } finally {} - Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled -Expiration $Expiration - return "Added admin roles to user $($UserObj.displayName) ($($UserObj.userPrincipalName))" } + Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled -Expiration $Expiration + return "Added admin roles to user $($UserObj.displayName) ($($UserObj.userPrincipalName))" } 'RemoveRoles' { $Roles = $Roles | ForEach-Object { diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 index 21e2a7d19bfb..cc9e19f082ba 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 @@ -26,5 +26,6 @@ function Set-CIPPUserJITAdminProperties { } $Json = ConvertTo-Json -Depth 5 -InputObject $Body + Write-Information $Json New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/beta/users/$UserId" -Body $Json -tenantid $TenantFilter } \ No newline at end of file From 4da414cc8dfb99710ca49540432581a49d04abde Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 3 Jun 2024 20:39:57 -0400 Subject: [PATCH 13/54] JIT/Scheduler --- .../Scheduler/Invoke-ListScheduledItems.ps1 | 8 ++- .../Users/Invoke-ExecJITAdmin.ps1 | 32 ++++++++--- .../CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 | 53 ++++++++++++------- 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 index 98e51126ed22..81231ca9df96 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 @@ -13,7 +13,7 @@ Function Invoke-ListScheduledItems { # Write to the Azure Functions log stream. Write-Host 'PowerShell HTTP trigger function processed a request.' $Table = Get-CIPPTable -TableName 'ScheduledTasks' - if ($Request.query.Showhidden -eq 'true') { + if ($Request.Query.Showhidden -eq $true) { $HiddenTasks = $false } else { $HiddenTasks = $true @@ -24,7 +24,11 @@ Function Invoke-ListScheduledItems { $Tasks = $Tasks | Where-Object -Property TenantId -In $AllowedTenants } $ScheduledTasks = foreach ($Task in $tasks) { - $Task.Parameters = $Task.Parameters | ConvertFrom-Json + if ($Task.Parameters) { + $Task.Parameters = $Task.Parameters | ConvertFrom-Json -ErrorAction SilentlyContinue + } else { + $Task | Add-Member -NotePropertyName Parameters -NotePropertyValue @{} + } $Task } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index ec5dbb935a9a..010d7d85fd10 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -15,7 +15,7 @@ Function Invoke-ExecJITAdmin { if ($Request.Query.Action -eq 'List') { $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } - Write-Information "Schema: $($Schema)" + #Write-Information "Schema: $($Schema)" $Query = @{ TenantFilter = $Request.Query.TenantFilter Endpoint = 'users' @@ -26,8 +26,18 @@ Function Invoke-ExecJITAdmin { } } $Users = Get-GraphRequestList @Query | Where-Object { $_.id } + $BulkRequests = $Users | ForEach-Object { @( + @{ + id = $_.id + method = 'GET' + url = "users/$($_.id)/memberOf/microsoft.graph.directoryRole/?`$select=id,displayName" + } + ) + } + $RoleResults = New-GraphBulkRequest -tenantid $Request.Query.TenantFilter -Requests $BulkRequests + #Write-Information ($RoleResults | ConvertTo-Json -Depth 10 ) $Results = $Users | ForEach-Object { - $MemberOf = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_.id)/memberOf/microsoft.graph.directoryRole/?`$select=id,displayName" -tenantid $Request.Query.TenantFilter -ComplexFilter + $MemberOf = ($RoleResults | Where-Object -Property id -EQ $_.id).body.value | Select-Object displayName, id [PSCustomObject]@{ id = $_.id displayName = $_.displayName @@ -38,8 +48,7 @@ Function Invoke-ExecJITAdmin { } } - - Write-Information ($Results | ConvertTo-Json -Depth 10) + #Write-Information ($Results | ConvertTo-Json -Depth 10) $Body = @{ Results = @($Results) Metadata = @{ @@ -47,7 +56,7 @@ Function Invoke-ExecJITAdmin { } } } else { - Write-Information ($Request.Body | ConvertTo-Json -Depth 10) + #Write-Information ($Request.Body | ConvertTo-Json -Depth 10) if ($Request.Body.UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.Body.UserId)" -tenantid $Request.Body.TenantFilter).userPrincipalName } @@ -59,18 +68,20 @@ Function Invoke-ExecJITAdmin { if ($Request.Body.useraction -eq 'create') { Write-Information "Creating JIT Admin user $($Request.Body.UserPrincipalName)" $JITAdmin = @{ - User = @{ + User = [PSCustomObject]@{ 'FirstName' = $Request.Body.FirstName 'LastName' = $Request.Body.LastName 'UserPrincipalName' = $Request.Body.UserPrincipalName } - Expiration = $Expiration - Action = 'Create' + Expiration = $Expiration + Action = 'Create' + TenantFilter = $Request.Body.TenantFilter } $CreateResult = Set-CIPPUserJITAdmin @JITAdmin $Username = $CreateResult.userPrincipalName $Results.Add("Created User: $($CreateResult.userPrincipalName)") $Results.Add("Password: $($CreateResult.password)") + Start-Sleep -Seconds 1 } $Parameters = @{ TenantFilter = $Request.Body.TenantFilter @@ -115,6 +126,11 @@ Function Invoke-ExecJITAdmin { Roles = $Request.Body.AdminRoles Action = $Request.Body.ExpireAction } + PostExecution = @{ + Webhook = [bool]$Request.Body.PostExecution.Webhook + Email = [bool]$Request.Body.PostExecution.Email + PSA = [bool]$Request.Body.PostExecution.PSA + } ScheduledTime = $Request.Body.EndDate } Add-CIPPScheduledTask -Task $DisableTaskBody -hidden $false diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 index 1b7a32c8cfe7..62923cc006e6 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 @@ -17,24 +17,31 @@ function Set-CIPPUserJITAdmin { 'Create' { $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } $Body = @{ + givenName = $User.FirstName + surname = $User.LastName accountEnabled = $true displayName = $User.FirstName + ' ' + $User.LastName userPrincipalName = $User.UserPrincipalName + mailNickname = $User.UserPrincipalName.Split('@')[0] passwordProfile = @{ - forceChangePasswordNextSignIn = $true - password = New-passwordString - } - "$($Schema.id)" = @{ - jitAdminEnabled = $false - jitAdminExpiration = $Expiration.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + forceChangePasswordNextSignIn = $true + forceChangePasswordNextSignInWithMfa = $false + password = New-passwordString } } $Json = ConvertTo-Json -Depth 5 -InputObject $Body - $NewUser = New-GraphPOSTRequest -Uri 'https://graph.microsoft.com/beta/users' -Body $Json -tenantid $TenantFilter - [PSCustomObject]@{ - id = $NewUser.id - userPrincipalName = $NewUser.userPrincipalName - password = $Body.passwordProfile.password + #Write-Information $Json + #Write-Information $TenantFilter + try { + $NewUser = New-GraphPOSTRequest -type POST -Uri 'https://graph.microsoft.com/beta/users' -Body $Json -tenantid $TenantFilter + [PSCustomObject]@{ + id = $NewUser.id + userPrincipalName = $NewUser.userPrincipalName + password = $Body.passwordProfile.password + } + } catch { + Write-Information "Error creating user: $($_.Exception.Message)" + throw $_.Exception.Message } } 'AddRoles' { @@ -44,8 +51,8 @@ function Set-CIPPUserJITAdmin { '@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$($UserObj.id)" } $Json = ConvertTo-Json -Depth 5 -InputObject $Body - $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/directoryRoles(roleTemplateId='$($_)')/members/`$ref" -tenantid $TenantFilter -body $Json - } finally {} + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/directoryRoles(roleTemplateId='$($_)')/members/`$ref" -tenantid $TenantFilter -body $Json -ErrorAction SilentlyContinue + } catch {} } Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled -Expiration $Expiration return "Added admin roles to user $($UserObj.displayName) ($($UserObj.userPrincipalName))" @@ -54,23 +61,31 @@ function Set-CIPPUserJITAdmin { $Roles = $Roles | ForEach-Object { try { $null = New-GraphPOSTRequest -type DELETE -uri "https://graph.microsoft.com/beta/directoryRoles(roleTemplateId='$($_)')/members/$($UserObj.id)/`$ref" -tenantid $TenantFilter - } finally {} + } catch {} } Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Clear return "Removed admin roles from user $($UserObj.displayName)" } 'DeleteUser' { - $null = New-GraphPOSTRequest -type DELETE -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $TenantFilter - return "Deleted user $($UserObj.displayName) ($($UserObj.userPrincipalName)) with id $($UserObj.id)" + try { + $null = New-GraphPOSTRequest -type DELETE -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $TenantFilter + return "Deleted user $($UserObj.displayName) ($($UserObj.userPrincipalName)) with id $($UserObj.id)" + } catch { + return "Error deleting user $($UserObj.displayName) ($($UserObj.userPrincipalName)): $($_.Exception.Message)" + } } 'DisableUser' { $Body = @{ accountEnabled = $false } $Json = ConvertTo-Json -Depth 5 -InputObject $Body - New-GraphPOSTRequest -type PATCH -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $TenantFilter -body $Json - Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled:$false - return "Disabled user $($UserObj.displayName) ($($UserObj.userPrincipalName))" + try { + New-GraphPOSTRequest -type PATCH -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $TenantFilter -body $Json + Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled:$false + return "Disabled user $($UserObj.displayName) ($($UserObj.userPrincipalName))" + } catch { + return "Error disabling user $($UserObj.displayName) ($($UserObj.userPrincipalName)): $($_.Exception.Message)" + } } } } From bbc85048c14a4229c53126fe2875085d9a22e475 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 4 Jun 2024 09:27:23 -0400 Subject: [PATCH 14/54] Fix bulk request --- .../Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index 010d7d85fd10..b83438b46bcd 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -34,7 +34,7 @@ Function Invoke-ExecJITAdmin { } ) } - $RoleResults = New-GraphBulkRequest -tenantid $Request.Query.TenantFilter -Requests $BulkRequests + $RoleResults = New-GraphBulkRequest -tenantid $Request.Query.TenantFilter -Requests @($BulkRequests) #Write-Information ($RoleResults | ConvertTo-Json -Depth 10 ) $Results = $Users | ForEach-Object { $MemberOf = ($RoleResults | Where-Object -Property id -EQ $_.id).body.value | Select-Object displayName, id From b5faa2e4d457b92bb8687c98ae003465199e3b31 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 4 Jun 2024 13:16:15 -0400 Subject: [PATCH 15/54] Webhook alert bugfix --- .../Activity Triggers/Push-Schedulerwebhookcreation.ps1 | 7 ++++--- Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 | 4 ++-- Scheduler_GetQueue/run.ps1 | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-Schedulerwebhookcreation.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-Schedulerwebhookcreation.ps1 index d3ca1dc6b455..8c56ccb98971 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-Schedulerwebhookcreation.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-Schedulerwebhookcreation.ps1 @@ -7,8 +7,9 @@ function Push-Schedulerwebhookcreation { $item ) $Table = Get-CIPPTable -TableName 'SchedulerConfig' - $WebhookTable = Get-CIPPTable -TableName 'WebhookTable' + $WebhookTable = Get-CIPPTable -TableName 'webhookTable' + #Write-Information ($item | ConvertTo-Json -Depth 10) $Row = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$($item.SchedulerRow)'" if (!$Row) { Write-Host "No row found for $($item.SchedulerRow). Full received item was $($item | ConvertTo-Json)" @@ -34,7 +35,7 @@ function Push-Schedulerwebhookcreation { $NewSub = New-CIPPGraphSubscription -TenantFilter $Tenant -EventType $Row.webhookType -BaseURL $Row.CIPPURL -auditLogAPI $true if ($NewSub.Success -and $Row.tenantid -ne 'AllTenants') { Remove-AzDataTableEntity @Table -Entity $Row - } else { + } else { Write-Host "Failed to create webhook for $Tenant - $($Row.webhookType) - $($_.Exception.Message)" Write-LogMessage -message "Failed to create webhook for $Tenant - $($Row.webhookType)" -Sev 'Error' -LogData $_.Exception } @@ -47,5 +48,5 @@ function Push-Schedulerwebhookcreation { } } } - + } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 index f7d0eb366389..535156115d0b 100644 --- a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 @@ -55,12 +55,12 @@ function New-CIPPGraphSubscription { return @{ success = $true; message = "Webhook exists for $($TenantFilter) for the log $($EventType)" } Write-LogMessage -user $ExecutingUser -API $APIName -message "Webhook subscription for $($TenantFilter) already exists" -Sev 'Info' -tenant $TenantFilter } else { - Remove-AzDataTableEntity @WebhookTable -Entity @{ PartitionKey = $TenantFilter; RowKey = $CIPPID } | Out-Null + Remove-AzDataTableEntity @WebhookTable -Entity @{ PartitionKey = $TenantFilter; RowKey = [string]$CIPPID } | Out-Null Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to create Webhook Subscription for $($TenantFilter): $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter return @{ success = $false; message = "Failed to create Webhook Subscription for $($TenantFilter): $($_.Exception.Message)" } } } - + } elseif ($PartnerCenter.IsPresent) { $WebhookFilter = "PartitionKey eq '$($env:TenantId)'" $ExistingWebhooks = Get-CIPPAzDataTableEntity @WebhookTable -Filter $WebhookFilter diff --git a/Scheduler_GetQueue/run.ps1 b/Scheduler_GetQueue/run.ps1 index f58f9d667ea6..87d355d1476c 100644 --- a/Scheduler_GetQueue/run.ps1 +++ b/Scheduler_GetQueue/run.ps1 @@ -21,7 +21,7 @@ $Tasks = foreach ($Tenant in $Tenants) { Tag = 'AllTenants' TenantID = $t.customerId Type = $Tenant.type - RowKey = $t.RowKey + RowKey = $Tenant.RowKey } } } From 23fd7e0865027c7eb9903b20f61ca767a425f69e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 4 Jun 2024 14:05:30 -0400 Subject: [PATCH 16/54] Break graph request loop on invalid url --- Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 index bc0ef417a3c3..28e88e204d79 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 @@ -61,7 +61,7 @@ function New-GraphGetRequest { } throw $Message } - } until ($null -eq $NextURL) + } until ($null -eq $NextURL -or ' ' -eq $NextURL) $Tenant.LastGraphError = '' Update-AzDataTableEntity @TenantsTable -Entity $Tenant return $ReturnedData From bd54d80b4c5b61778512bdda5d69d6921c3c07f8 Mon Sep 17 00:00:00 2001 From: Esco Date: Wed, 5 Jun 2024 11:40:12 +0200 Subject: [PATCH 17/54] Added more Anti-Phishing actions --- .../Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 index d561c3cc51d3..5fd7e8eeb346 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -24,7 +24,10 @@ function Invoke-CIPPStandardAntiPhishPolicy { ($CurrentState.EnableUnauthenticatedSender -eq $true) -and ($CurrentState.EnableViaTag -eq $true) -and ($CurrentState.MailboxIntelligenceProtectionAction -eq $Settings.MailboxIntelligenceProtectionAction) -and - ($CurrentState.MailboxIntelligenceQuarantineTag -eq $Settings.MailboxIntelligenceQuarantineTag) + ($CurrentState.MailboxIntelligenceQuarantineTag -eq $Settings.MailboxIntelligenceQuarantineTag) -and + ($CurrentState.TargetedUserProtectionAction -eq $Settings.TargetedUserProtectionAction) -and + ($CurrentState.TargetedDomainProtectionAction -eq $Settings.TargetedDomainProtectionAction) -and + ($CurrentState.EnableOrganizationDomainsProtection -eq $true) $AcceptedDomains = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AcceptedDomain' @@ -55,6 +58,9 @@ function Invoke-CIPPStandardAntiPhishPolicy { EnableViaTag = $true MailboxIntelligenceProtectionAction = $Settings.MailboxIntelligenceProtectionAction MailboxIntelligenceQuarantineTag = $Settings.MailboxIntelligenceQuarantineTag + TargetedUserProtectionAction = $Settings.TargetedUserProtectionAction + TargetedDomainProtectionAction = $Settings.TargetedDomainProtectionAction + EnableOrganizationDomainsProtection = $true } try { @@ -110,4 +116,4 @@ function Invoke-CIPPStandardAntiPhishPolicy { Add-CIPPBPAField -FieldName 'AntiPhishPolicy' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant } -} \ No newline at end of file +} From c965ba3630f1c7fe8f5b9cd5a22a88d0516f88c3 Mon Sep 17 00:00:00 2001 From: Esco Date: Wed, 5 Jun 2024 11:41:24 +0200 Subject: [PATCH 18/54] Convert to CRLF --- .../Invoke-CIPPStandardAntiPhishPolicy.ps1 | 238 +++++++++--------- 1 file changed, 119 insertions(+), 119 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 index 5fd7e8eeb346..1eeb304a0768 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -1,119 +1,119 @@ -function Invoke-CIPPStandardAntiPhishPolicy { - <# - .FUNCTIONALITY - Internal - #> - - param($Tenant, $Settings) - $PolicyName = 'Default Anti-Phishing Policy' - - $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishPolicy' | - Where-Object -Property Name -EQ $PolicyName | - Select-Object Name, Enabled, PhishThresholdLevel, EnableMailboxIntelligence, EnableMailboxIntelligenceProtection, EnableSpoofIntelligence, EnableFirstContactSafetyTips, EnableSimilarUsersSafetyTips, EnableSimilarDomainsSafetyTips, EnableUnusualCharactersSafetyTips, EnableUnauthenticatedSender, EnableViaTag, MailboxIntelligenceProtectionAction, MailboxIntelligenceQuarantineTag - - $StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and - ($CurrentState.Enabled -eq $true) -and - ($CurrentState.PhishThresholdLevel -eq $Settings.PhishThresholdLevel) -and - ($CurrentState.EnableMailboxIntelligence -eq $true) -and - ($CurrentState.EnableMailboxIntelligenceProtection -eq $true) -and - ($CurrentState.EnableSpoofIntelligence -eq $true) -and - ($CurrentState.EnableFirstContactSafetyTips -eq $Settings.EnableFirstContactSafetyTips) -and - ($CurrentState.EnableSimilarUsersSafetyTips -eq $Settings.EnableSimilarUsersSafetyTips) -and - ($CurrentState.EnableSimilarDomainsSafetyTips -eq $Settings.EnableSimilarDomainsSafetyTips) -and - ($CurrentState.EnableUnusualCharactersSafetyTips -eq $Settings.EnableUnusualCharactersSafetyTips) -and - ($CurrentState.EnableUnauthenticatedSender -eq $true) -and - ($CurrentState.EnableViaTag -eq $true) -and - ($CurrentState.MailboxIntelligenceProtectionAction -eq $Settings.MailboxIntelligenceProtectionAction) -and - ($CurrentState.MailboxIntelligenceQuarantineTag -eq $Settings.MailboxIntelligenceQuarantineTag) -and - ($CurrentState.TargetedUserProtectionAction -eq $Settings.TargetedUserProtectionAction) -and - ($CurrentState.TargetedDomainProtectionAction -eq $Settings.TargetedDomainProtectionAction) -and - ($CurrentState.EnableOrganizationDomainsProtection -eq $true) - - $AcceptedDomains = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AcceptedDomain' - - $RuleState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishRule' | - Where-Object -Property Name -EQ "CIPP $PolicyName" | - Select-Object Name, AntiPhishPolicy, Priority, RecipientDomainIs - - $RuleStateIsCorrect = ($RuleState.Name -eq "CIPP $PolicyName") -and - ($RuleState.AntiPhishPolicy -eq $PolicyName) -and - ($RuleState.Priority -eq 0) -and - (!(Compare-Object -ReferenceObject $RuleState.RecipientDomainIs -DifferenceObject $AcceptedDomains.Name)) - - if ($Settings.remediate -eq $true) { - if ($StateIsCorrect -eq $true) { - Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Anti-phishing Policy already correctly configured' -sev Info - } else { - $cmdparams = @{ - Enabled = $true - PhishThresholdLevel = $Settings.PhishThresholdLevel - EnableMailboxIntelligence = $true - EnableMailboxIntelligenceProtection = $true - EnableSpoofIntelligence = $true - EnableFirstContactSafetyTips = $Settings.EnableFirstContactSafetyTips - EnableSimilarUsersSafetyTips = $Settings.EnableSimilarUsersSafetyTips - EnableSimilarDomainsSafetyTips = $Settings.EnableSimilarDomainsSafetyTips - EnableUnusualCharactersSafetyTips = $Settings.EnableUnusualCharactersSafetyTips - EnableUnauthenticatedSender = $true - EnableViaTag = $true - MailboxIntelligenceProtectionAction = $Settings.MailboxIntelligenceProtectionAction - MailboxIntelligenceQuarantineTag = $Settings.MailboxIntelligenceQuarantineTag - TargetedUserProtectionAction = $Settings.TargetedUserProtectionAction - TargetedDomainProtectionAction = $Settings.TargetedDomainProtectionAction - EnableOrganizationDomainsProtection = $true - } - - try { - if ($CurrentState.Name -eq $PolicyName) { - $cmdparams.Add('Identity', $PolicyName) - New-ExoRequest -tenantid $Tenant -cmdlet 'Set-AntiPhishPolicy' -cmdparams $cmdparams - Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Anti-phishing Policy' -sev Info - } else { - $cmdparams.Add('Name', $PolicyName) - New-ExoRequest -tenantid $Tenant -cmdlet 'New-AntiPhishPolicy' -cmdparams $cmdparams - Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Created Anti-phishing Policy' -sev Info - } - } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create Anti-phishing Policy. Error: $ErrorMessage" -sev Error - } - } - - if ($RuleStateIsCorrect -eq $false) { - $cmdparams = @{ - AntiPhishPolicy = $PolicyName - Priority = 0 - RecipientDomainIs = $AcceptedDomains.Name - } - - try { - if ($RuleState.Name -eq "CIPP $PolicyName") { - $cmdparams.Add('Identity', "CIPP $PolicyName") - New-ExoRequest -tenantid $Tenant -cmdlet 'Set-AntiPhishRule' -cmdparams $cmdparams - Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated AntiPhish Rule' -sev Info - } else { - $cmdparams.Add('Name', "CIPP $PolicyName") - New-ExoRequest -tenantid $Tenant -cmdlet 'New-AntiPhishRule' -cmdparams $cmdparams - Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Created AntiPhish Rule' -sev Info - } - } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create AntiPhish Rule. Error: $ErrorMessage" -sev Error - } - } - } - - if ($Settings.alert -eq $true) { - - if ($StateIsCorrect -eq $true) { - Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Anti-phishing Policy is enabled' -sev Info - } else { - Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Anti-phishing Policy is not enabled' -sev Alert - } - } - - if ($Settings.report -eq $true) { - Add-CIPPBPAField -FieldName 'AntiPhishPolicy' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant - } - -} +function Invoke-CIPPStandardAntiPhishPolicy { + <# + .FUNCTIONALITY + Internal + #> + + param($Tenant, $Settings) + $PolicyName = 'Default Anti-Phishing Policy' + + $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishPolicy' | + Where-Object -Property Name -EQ $PolicyName | + Select-Object Name, Enabled, PhishThresholdLevel, EnableMailboxIntelligence, EnableMailboxIntelligenceProtection, EnableSpoofIntelligence, EnableFirstContactSafetyTips, EnableSimilarUsersSafetyTips, EnableSimilarDomainsSafetyTips, EnableUnusualCharactersSafetyTips, EnableUnauthenticatedSender, EnableViaTag, MailboxIntelligenceProtectionAction, MailboxIntelligenceQuarantineTag + + $StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and + ($CurrentState.Enabled -eq $true) -and + ($CurrentState.PhishThresholdLevel -eq $Settings.PhishThresholdLevel) -and + ($CurrentState.EnableMailboxIntelligence -eq $true) -and + ($CurrentState.EnableMailboxIntelligenceProtection -eq $true) -and + ($CurrentState.EnableSpoofIntelligence -eq $true) -and + ($CurrentState.EnableFirstContactSafetyTips -eq $Settings.EnableFirstContactSafetyTips) -and + ($CurrentState.EnableSimilarUsersSafetyTips -eq $Settings.EnableSimilarUsersSafetyTips) -and + ($CurrentState.EnableSimilarDomainsSafetyTips -eq $Settings.EnableSimilarDomainsSafetyTips) -and + ($CurrentState.EnableUnusualCharactersSafetyTips -eq $Settings.EnableUnusualCharactersSafetyTips) -and + ($CurrentState.EnableUnauthenticatedSender -eq $true) -and + ($CurrentState.EnableViaTag -eq $true) -and + ($CurrentState.MailboxIntelligenceProtectionAction -eq $Settings.MailboxIntelligenceProtectionAction) -and + ($CurrentState.MailboxIntelligenceQuarantineTag -eq $Settings.MailboxIntelligenceQuarantineTag) -and + ($CurrentState.TargetedUserProtectionAction -eq $Settings.TargetedUserProtectionAction) -and + ($CurrentState.TargetedDomainProtectionAction -eq $Settings.TargetedDomainProtectionAction) -and + ($CurrentState.EnableOrganizationDomainsProtection -eq $true) + + $AcceptedDomains = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AcceptedDomain' + + $RuleState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishRule' | + Where-Object -Property Name -EQ "CIPP $PolicyName" | + Select-Object Name, AntiPhishPolicy, Priority, RecipientDomainIs + + $RuleStateIsCorrect = ($RuleState.Name -eq "CIPP $PolicyName") -and + ($RuleState.AntiPhishPolicy -eq $PolicyName) -and + ($RuleState.Priority -eq 0) -and + (!(Compare-Object -ReferenceObject $RuleState.RecipientDomainIs -DifferenceObject $AcceptedDomains.Name)) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Anti-phishing Policy already correctly configured' -sev Info + } else { + $cmdparams = @{ + Enabled = $true + PhishThresholdLevel = $Settings.PhishThresholdLevel + EnableMailboxIntelligence = $true + EnableMailboxIntelligenceProtection = $true + EnableSpoofIntelligence = $true + EnableFirstContactSafetyTips = $Settings.EnableFirstContactSafetyTips + EnableSimilarUsersSafetyTips = $Settings.EnableSimilarUsersSafetyTips + EnableSimilarDomainsSafetyTips = $Settings.EnableSimilarDomainsSafetyTips + EnableUnusualCharactersSafetyTips = $Settings.EnableUnusualCharactersSafetyTips + EnableUnauthenticatedSender = $true + EnableViaTag = $true + MailboxIntelligenceProtectionAction = $Settings.MailboxIntelligenceProtectionAction + MailboxIntelligenceQuarantineTag = $Settings.MailboxIntelligenceQuarantineTag + TargetedUserProtectionAction = $Settings.TargetedUserProtectionAction + TargetedDomainProtectionAction = $Settings.TargetedDomainProtectionAction + EnableOrganizationDomainsProtection = $true + } + + try { + if ($CurrentState.Name -eq $PolicyName) { + $cmdparams.Add('Identity', $PolicyName) + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-AntiPhishPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Anti-phishing Policy' -sev Info + } else { + $cmdparams.Add('Name', $PolicyName) + New-ExoRequest -tenantid $Tenant -cmdlet 'New-AntiPhishPolicy' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Created Anti-phishing Policy' -sev Info + } + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create Anti-phishing Policy. Error: $ErrorMessage" -sev Error + } + } + + if ($RuleStateIsCorrect -eq $false) { + $cmdparams = @{ + AntiPhishPolicy = $PolicyName + Priority = 0 + RecipientDomainIs = $AcceptedDomains.Name + } + + try { + if ($RuleState.Name -eq "CIPP $PolicyName") { + $cmdparams.Add('Identity', "CIPP $PolicyName") + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-AntiPhishRule' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated AntiPhish Rule' -sev Info + } else { + $cmdparams.Add('Name', "CIPP $PolicyName") + New-ExoRequest -tenantid $Tenant -cmdlet 'New-AntiPhishRule' -cmdparams $cmdparams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Created AntiPhish Rule' -sev Info + } + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create AntiPhish Rule. Error: $ErrorMessage" -sev Error + } + } + } + + if ($Settings.alert -eq $true) { + + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Anti-phishing Policy is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Anti-phishing Policy is not enabled' -sev Alert + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'AntiPhishPolicy' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant + } + +} From 8dbf34b0ad62bd8364926b1691d4cddebbc6ecb9 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 5 Jun 2024 13:46:27 +0200 Subject: [PATCH 19/54] fix naming issue. --- ...oke-UpdateSecureScore.ps1 => Invoke-ExecUpdateSecureScore.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/{Invoke-UpdateSecureScore.ps1 => Invoke-ExecUpdateSecureScore.ps1} (100%) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-UpdateSecureScore.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecUpdateSecureScore.ps1 similarity index 100% rename from Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-UpdateSecureScore.ps1 rename to Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecUpdateSecureScore.ps1 From 7cdb878b10d19f7caccf4ee210e594c888b4f749 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 5 Jun 2024 09:00:08 -0400 Subject: [PATCH 20/54] Custom Role block list --- .../Get-CIPPRolePermissions.ps1 | 3 +- .../Public/Authentication/Test-CIPPAccess.ps1 | 112 +++++++++++------- .../CIPP/Settings/Invoke-ExecCustomRole.ps1 | 12 +- profile.ps1 | 1 + 4 files changed, 81 insertions(+), 47 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Get-CIPPRolePermissions.ps1 b/Modules/CIPPCore/Public/Authentication/Get-CIPPRolePermissions.ps1 index 0f932ee7f842..f3e1f525ac57 100644 --- a/Modules/CIPPCore/Public/Authentication/Get-CIPPRolePermissions.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Get-CIPPRolePermissions.ps1 @@ -21,7 +21,8 @@ function Get-CIPPRolePermissions { [PSCustomObject]@{ Role = $Role.RowKey Permissions = $Permissions.PSObject.Properties.Value - AllowedTenants = $Role.AllowedTenants | ConvertFrom-Json + AllowedTenants = if ($Role.AllowedTenants) { $Role.AllowedTenants | ConvertFrom-Json } else { @() } + BlockedTenants = if ($Role.BlockedTenants) { $Role.BlockedTenants | ConvertFrom-Json } else { @() } } } else { throw "Role $RoleName not found." diff --git a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 index 5fcd0395a783..1336247272bb 100644 --- a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 @@ -23,69 +23,91 @@ function Test-CIPPAccess { } } } - if (($CustomRoles | Measure-Object).Count -gt 0 ) { + if (($CustomRoles | Measure-Object).Count -gt 0) { $Tenants = Get-Tenants -IncludeErrors + $PermissionsFound = $false $PermissionSet = foreach ($CustomRole in $CustomRoles) { try { Get-CIPPRolePermissions -Role $CustomRole + $PermissionsFound = $true } catch { Write-Information $_.Exception.Message + continue } } - if ($TenantList.IsPresent) { - $AllowedTenants = foreach ($Permission in $PermissionSet) { - foreach ($Tenant in $Permission.AllowedTenants) { - $Tenant - } - } - return $AllowedTenants - } - - if (($PermissionSet | Measure-Object).Count -eq 0) { - return $true - } else { - $FunctionName = 'Invoke-{0}' -f $Request.Params.CIPPEndpoint - $Help = Get-Help $FunctionName - # Check API for required role - $APIRole = $Help.Role - foreach ($Role in $PermissionSet) { - # Loop through each custom role permission and check API / Tenant access - $TenantAllowed = $false - $APIAllowed = $false - foreach ($Perm in $Role.Permissions) { - if ($Perm -match $APIRole) { - $APIAllowed = $true - break + if ($PermissionsFound) { + if ($TenantList.IsPresent) { + $LimitedTenantList = foreach ($Permission in $PermissionSet) { + if (($Permission.AllowedTenants | Measure-Object).Count -eq 0 -and ($Permission.BlockedTenants | Measure-Object).Count -eq 0) { + return @('AllTenants') + } else { + if ($Permission.AllowedTenants -contains 'AllTenants') { + $Permission.AllowedTenants = $Tenants.customerId + } + $Permission.AllowedTenants | Where-Object { $Permission.BlockedTenants -notcontains $_ } } } - if ($APIAllowed) { - # Check tenant level access - if ($Role.AllowedTenants -contains 'AllTenants') { - $TenantAllowed = $true - } elseif ($Request.Query.TenantFilter -eq 'AllTenants' -or $Request.Body.TenantFilter -eq 'AllTenants') { - $TenantAllowed = $false - } else { - $Tenant = ($Tenants | Where-Object { $Request.Query.TenantFilter -eq $_.customerId -or $Request.Body.TenantFilter -eq $_.customerId -or $Request.Query.TenantFilter -eq $_.defaultDomainName -or $Request.Body.TenantFilter -eq $_.defaultDomainName }).customerId + Write-Information ($LimitedTenantList | ConvertTo-Json) + return $LimitedTenantList + } - if ($Tenant) { - $TenantAllowed = $Role.AllowedTenants -contains $Tenant - if (!$TenantAllowed) { continue } + if (($PermissionSet | Measure-Object).Count -eq 0) { + return $true + } else { + $FunctionName = 'Invoke-{0}' -f $Request.Params.CIPPEndpoint + $Help = Get-Help $FunctionName + # Check API for required role + $APIRole = $Help.Role + foreach ($Role in $PermissionSet) { + # Loop through each custom role permission and check API / Tenant access + $TenantAllowed = $false + $APIAllowed = $false + foreach ($Perm in $Role.Permissions) { + if ($Perm -match $APIRole) { + $APIAllowed = $true break - } else { + } + } + if ($APIAllowed) { + # Check tenant level access + if (($Role.BlockedTenants | Measure-Object).Count -eq 0 -and $Role.AllowedTenants -contains 'AllTenants') { $TenantAllowed = $true - break + } elseif ($Request.Query.TenantFilter -eq 'AllTenants' -or $Request.Body.TenantFilter -eq 'AllTenants') { + $TenantAllowed = $false + } else { + $Tenant = ($Tenants | Where-Object { $Request.Query.TenantFilter -eq $_.customerId -or $Request.Body.TenantFilter -eq $_.customerId -or $Request.Query.TenantFilter -eq $_.defaultDomainName -or $Request.Body.TenantFilter -eq $_.defaultDomainName }).customerId + if ($Role.AllowedTenants -contains 'AllTenants') { + $AllowedTenants = $Tenants + } else { + $AllowedTenants = $Role.AllowedTenants + } + + if ($Tenant) { + $TenantAllowed = $AllowedTenants -contains $Tenant -and $Role.BlockedTenants -notcontains $Tenant + if (!$TenantAllowed) { continue } + break + } else { + $TenantAllowed = $true + break + } } } } + if (!$APIAllowed) { + throw "Access to this CIPP API endpoint is not allowed, the '$($Role.Role)' custom role does not have the required permission: $APIRole" + } + if (!$TenantAllowed) { + throw 'Access to this tenant is not allowed' + } else { + return $true + } } - if (!$APIAllowed) { - throw "Access to this CIPP API endpoint is not allowed, the '$($Role.Role)' custom role does not have the required permission: $APIRole" - } - if (!$TenantAllowed) { - throw 'Access to this tenant is not allowed' - } else { - return $true + } else { + # No permissions found for any roles + if ($TenantList.IsPresent) { + return @('AllTenants') } + return $true } } else { return $true diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 index 45b0e6931d89..0b6f0055c15a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 @@ -16,6 +16,7 @@ function Invoke-ExecCustomRole { 'RowKey' = "$($Request.Body.RoleName)" 'Permissions' = "$($Request.Body.Permissions | ConvertTo-Json -Compress)" 'AllowedTenants' = "$($Request.Body.AllowedTenants | ConvertTo-Json -Compress)" + 'BlockedTenants' = "$($Request.Body.BlockedTenants | ConvertTo-Json -Compress)" } Add-CIPPAzDataTableEntity @Table -Entity $Role -Force | Out-Null $Body = @{Results = 'Custom role saved' } @@ -37,7 +38,16 @@ function Invoke-ExecCustomRole { } else { $Body = foreach ($Role in $Body) { $Role.Permissions = $Role.Permissions | ConvertFrom-Json - $Role.AllowedTenants = @($Role.AllowedTenants | ConvertFrom-Json) + if ($Role.AllowedTenants) { + $Role.AllowedTenants = @($Role.AllowedTenants | ConvertFrom-Json) + } else { + $Role | Add-Member -NotePropertyName AllowedTenants -NotePropertyValue @() + } + if ($Role.BlockedTenants) { + $Role.BlockedTenants = @($Role.BlockedTenants | ConvertFrom-Json) + } else { + $Role | Add-Member -NotePropertyName BlockedTenants -NotePropertyValue @() + } $Role } $Body = @($Body) diff --git a/profile.ps1 b/profile.ps1 index 134944f4d7e7..f72dcd0c4c7d 100644 --- a/profile.ps1 +++ b/profile.ps1 @@ -46,6 +46,7 @@ try { Write-LogMessage -message 'Could not retrieve keys from Keyvault' -LogData (Get-CippException -Exception $_) -Sev 'debug' } +Set-Location -Path $PSScriptRoot $CurrentVersion = (Get-Content .\version_latest.txt).trim() $Table = Get-CippTable -tablename 'Version' $LastStartup = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'Version' and RowKey eq 'Version'" From b890d0f49cc7767a823719b9b1d58f3f4fedc81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 5 Jun 2024 15:23:56 +0200 Subject: [PATCH 21/54] Add Graph role and Pronouns standard --- Modules/CIPPCore/Public/SAMManifest.json | 3 +- .../Invoke-CIPPStandardEnablePronouns.ps1 | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json index 3ee73e6b09d1..b6b291da57b4 100644 --- a/Modules/CIPPCore/Public/SAMManifest.json +++ b/Modules/CIPPCore/Public/SAMManifest.json @@ -158,7 +158,8 @@ { "id": "913b9306-0ce1-42b8-9137-6a7df690a760", "type": "Role" }, { "id": "cb8f45a0-5c2e-4ea1-b803-84b870a7d7ec", "type": "Scope" }, { "id": "4c06a06a-098a-4063-868e-5dfee3827264", "type": "Scope" }, - { "id": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9", "type": "Role" } + { "id": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9", "type": "Role" }, + { "id": "e67e6727-c080-415e-b521-e3f35d5248e9", "type": "Scope" } ] }, { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 new file mode 100644 index 000000000000..56d5c8a1815e --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 @@ -0,0 +1,52 @@ +function Invoke-CIPPStandardEnablePronouns { + <# + .FUNCTIONALITY + Internal + #> + param ($Tenant, $Settings) + + $Uri = 'https://graph.microsoft.com/v1.0/admin/people/pronouns' + try { + $CurrentState = New-GraphGetRequest -Uri $Uri -tenantid $Tenant + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not get CurrentState for Pronouns. Error: $ErrorMessage" -sev Error + Exit + } + Write-Host $CurrentState + + if ($Settings.remediate -eq $true) { + Write-Host 'Time to remediate' + + if ($CurrentState.isEnabledInOrganization -eq $true) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Pronouns are already enabled.' -sev Info + } else { + $CurrentState.isEnabledInOrganization = $true + try { + $Body = ConvertTo-Json -InputObject $CurrentState -Depth 10 -Compress + New-GraphPostRequest -Uri $Uri -tenantid $Tenant -Body $Body -type PATCH + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Enabled pronouns.' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to enable pronouns. Error: $ErrorMessage" -sev Error + } + } + } + + if ($Settings.alert -eq $true) { + + if ($CurrentState.isEnabledInOrganization -eq $true) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Pronouns are enabled.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Pronouns are not enabled.' -sev Alert + } + + } + + if ($Settings.report -eq $true) { + + Add-CIPPBPAField -FieldName 'PronounsEnabled' -FieldValue $CurrentState.isEnabledInOrganization -StoreAs bool -Tenant $tenant + + } + +} \ No newline at end of file From 97bcfead71d75bbabaa959ff97d81c65bfd08b1c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 5 Jun 2024 09:55:52 -0400 Subject: [PATCH 22/54] Suppress output --- Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 | 6 +++--- Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 index 62923cc006e6..6825deb4e62e 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 @@ -15,7 +15,7 @@ function Set-CIPPUserJITAdmin { switch ($Action) { 'Create' { - $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } + #$Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } $Body = @{ givenName = $User.FirstName surname = $User.LastName @@ -54,7 +54,7 @@ function Set-CIPPUserJITAdmin { $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/directoryRoles(roleTemplateId='$($_)')/members/`$ref" -tenantid $TenantFilter -body $Json -ErrorAction SilentlyContinue } catch {} } - Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled -Expiration $Expiration + Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled -Expiration $Expiration | Out-Null return "Added admin roles to user $($UserObj.displayName) ($($UserObj.userPrincipalName))" } 'RemoveRoles' { @@ -63,7 +63,7 @@ function Set-CIPPUserJITAdmin { $null = New-GraphPOSTRequest -type DELETE -uri "https://graph.microsoft.com/beta/directoryRoles(roleTemplateId='$($_)')/members/$($UserObj.id)/`$ref" -tenantid $TenantFilter } catch {} } - Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Clear + Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Clear | Out-Null return "Removed admin roles from user $($UserObj.displayName)" } 'DeleteUser' { diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 index cc9e19f082ba..c6203128f2f9 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 @@ -27,5 +27,5 @@ function Set-CIPPUserJITAdminProperties { $Json = ConvertTo-Json -Depth 5 -InputObject $Body Write-Information $Json - New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/beta/users/$UserId" -Body $Json -tenantid $TenantFilter + New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/beta/users/$UserId" -Body $Json -tenantid $TenantFilter | Out-Null } \ No newline at end of file From 2bfcbbfe8b7790ae472ec9225ebc2900c7150474 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 5 Jun 2024 10:43:01 -0400 Subject: [PATCH 23/54] Add JIT comments --- .../Users/Invoke-ExecJITAdmin.ps1 | 2 +- .../Invoke-ListFunctionParameters.ps1 | 2 +- .../CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 | 43 ++++++++++++++++--- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index b83438b46bcd..6b0ceb571cf1 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -68,7 +68,7 @@ Function Invoke-ExecJITAdmin { if ($Request.Body.useraction -eq 'create') { Write-Information "Creating JIT Admin user $($Request.Body.UserPrincipalName)" $JITAdmin = @{ - User = [PSCustomObject]@{ + User = @{ 'FirstName' = $Request.Body.FirstName 'LastName' = $Request.Body.LastName 'UserPrincipalName' = $Request.Body.UserPrincipalName diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListFunctionParameters.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListFunctionParameters.ps1 index 64c465e799e9..e3060be55daa 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListFunctionParameters.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListFunctionParameters.ps1 @@ -27,7 +27,7 @@ function Invoke-ListFunctionParameters { $CommandQuery.Name = $Function } $IgnoreList = 'entryPoint', 'internal' - $CommonParameters = @('Verbose', 'Debug', 'ErrorAction', 'WarningAction', 'InformationAction', 'ErrorVariable', 'WarningVariable', 'InformationVariable', 'OutVariable', 'OutBuffer', 'PipelineVariable', 'TenantFilter', 'APIName', 'ExecutingUser', 'ProgressAction') + $CommonParameters = @('Verbose', 'Debug', 'ErrorAction', 'WarningAction', 'InformationAction', 'ErrorVariable', 'WarningVariable', 'InformationVariable', 'OutVariable', 'OutBuffer', 'PipelineVariable', 'TenantFilter', 'APIName', 'ExecutingUser', 'ProgressAction', 'WhatIf', 'Confirm') $TemporaryBlacklist = 'Get-CIPPAuthentication', 'Invoke-CippWebhookProcessing', 'Invoke-ListFunctionParameters', 'New-CIPPAPIConfig', 'New-CIPPGraphSubscription' try { $Functions = Get-Command @CommandQuery | Where-Object { $_.Visibility -eq 'Public' } diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 index 6825deb4e62e..c41b893c5a05 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 @@ -1,11 +1,45 @@ function Set-CIPPUserJITAdmin { + <# + .SYNOPSIS + Just-in-time admin management + + .DESCRIPTION + Just-in-time admin management for CIPP. This function can create users, add roles, remove roles, delete, or disable a user. + + .PARAMETER TenantFilter + Tenant to manage for JIT admin + + .PARAMETER User + User object to manage JIT admin roles, required property UserPrincipalName - If user is being created we also require FirstName and LastName + + .PARAMETER Roles + List of Role GUIDs to add or remove + + .PARAMETER Action + Action to perform: Create, AddRoles, RemoveRoles, DeleteUser, DisableUser + + .PARAMETER Expiration + DateTime for expiration + + .EXAMPLE + Set-CIPPUserJITAdmin -TenantFilter 'contoso.onmicrosoft.com' -User @{UserPrincipalName = 'jit@contoso.onmicrosoft.com'} -Roles @('62e90394-69f5-4237-9190-012177145e10') -Action 'AddRoles' -Expiration (Get-Date).AddDays(1) + + #> [CmdletBinding(SupportsShouldProcess = $true)] Param( + [Parameter(Mandatory = $true)] [string]$TenantFilter, - $User, + + [Parameter(Mandatory = $true)] + [hashtable]$User, + [string[]]$Roles, + + [Parameter(Mandatory = $true)] + [ValidateSet('Create', 'AddRoles', 'RemoveRoles', 'DeleteUser', 'DisableUser')] [string]$Action, - $Expiration + + [datetime]$Expiration ) if ($PSCmdlet.ShouldProcess("User: $($User.UserPrincipalName)", "Action: $Action")) { @@ -15,7 +49,6 @@ function Set-CIPPUserJITAdmin { switch ($Action) { 'Create' { - #$Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } $Body = @{ givenName = $User.FirstName surname = $User.LastName @@ -30,8 +63,6 @@ function Set-CIPPUserJITAdmin { } } $Json = ConvertTo-Json -Depth 5 -InputObject $Body - #Write-Information $Json - #Write-Information $TenantFilter try { $NewUser = New-GraphPOSTRequest -type POST -Uri 'https://graph.microsoft.com/beta/users' -Body $Json -tenantid $TenantFilter [PSCustomObject]@{ @@ -81,7 +112,7 @@ function Set-CIPPUserJITAdmin { $Json = ConvertTo-Json -Depth 5 -InputObject $Body try { New-GraphPOSTRequest -type PATCH -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $TenantFilter -body $Json - Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled:$false + Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $UserObj.id -Enabled:$false | Out-Null return "Disabled user $($UserObj.displayName) ($($UserObj.userPrincipalName))" } catch { return "Error disabling user $($UserObj.displayName) ($($UserObj.userPrincipalName)): $($_.Exception.Message)" From 7f53eeb519143ed198ee1bea3275f34bbb3fb859 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 5 Jun 2024 12:39:37 -0400 Subject: [PATCH 24/54] Add logging --- .../HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 | 2 ++ .../Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 index 0b6f0055c15a..dafe35d226f8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 @@ -11,6 +11,7 @@ function Invoke-ExecCustomRole { $Table = Get-CippTable -tablename 'CustomRoles' switch ($Request.Query.Action) { 'AddUpdate' { + Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API 'ExecCustomRole' -message "Saved custom role $($Request.Body.RoleName)" -Sev 'Info' $Role = @{ 'PartitionKey' = 'CustomRoles' 'RowKey' = "$($Request.Body.RoleName)" @@ -22,6 +23,7 @@ function Invoke-ExecCustomRole { $Body = @{Results = 'Custom role saved' } } 'Delete' { + Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API 'ExecCustomRole' -message "Deleted custom role $($Request.Body.RoleName)" -Sev 'Info' $Role = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$($Request.Body.RoleName)'" -Property RowKey, PartitionKey Remove-AzDataTableEntity @Table -Entity $Role $Body = @{Results = 'Custom role deleted' } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index 6b0ceb571cf1..ad887810061a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -10,7 +10,7 @@ Function Invoke-ExecJITAdmin { [CmdletBinding()] param($Request, $TriggerMetadata) - $APIName = $TriggerMetadata.FunctionName + $APIName = 'ExecJITAdmin' Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' if ($Request.Query.Action -eq 'List') { @@ -56,7 +56,7 @@ Function Invoke-ExecJITAdmin { } } } else { - #Write-Information ($Request.Body | ConvertTo-Json -Depth 10) + Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APINAME -message "Executing JIT Admin for $($Request.Body.UserPrincipalName)" -Sev 'Info' if ($Request.Body.UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.Body.UserId)" -tenantid $Request.Body.TenantFilter).userPrincipalName } @@ -66,6 +66,7 @@ Function Invoke-ExecJITAdmin { $Results = [System.Collections.Generic.List[string]]::new() if ($Request.Body.useraction -eq 'create') { + Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APINAME -message "Creating JIT Admin user $($Request.Body.UserPrincipalName)" -Sev 'Info' Write-Information "Creating JIT Admin user $($Request.Body.UserPrincipalName)" $JITAdmin = @{ User = @{ From 68e0d95c7ce21299d1b96415e8567e2a8a73e50f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 5 Jun 2024 17:28:12 -0400 Subject: [PATCH 25/54] PasswordPusher integration --- .../Settings/Invoke-ExecExtensionTest.ps1 | 9 + .../Administration/Users/Invoke-AddUser.ps1 | 6 + .../Users/Invoke-AddUserBulk.ps1 | 28 +- .../CIPPCore/Public/Set-CIPPResetPassword.ps1 | 25 +- .../CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 | 10 +- .../Private/Set-PwPushConfig.ps1 | 18 + .../CippExtensions/Public/New-PwPushLink.ps1 | 24 + .../PassPushPosh/0.2.3/PSGetModuleInfo.xml | 142 +++ Modules/PassPushPosh/0.2.3/PassPushPosh.psd1 | 140 +++ Modules/PassPushPosh/0.2.3/PassPushPosh.psm1 | 1012 +++++++++++++++++ 10 files changed, 1390 insertions(+), 24 deletions(-) create mode 100644 Modules/CippExtensions/Private/Set-PwPushConfig.ps1 create mode 100644 Modules/CippExtensions/Public/New-PwPushLink.ps1 create mode 100644 Modules/PassPushPosh/0.2.3/PSGetModuleInfo.xml create mode 100644 Modules/PassPushPosh/0.2.3/PassPushPosh.psd1 create mode 100644 Modules/PassPushPosh/0.2.3/PassPushPosh.psm1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 index 7c2f271c13a5..78e82e78121a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 @@ -37,6 +37,15 @@ Function Invoke-ExecExtensionTest { $token = Get-NinjaOneToken -configuration $Configuration.NinjaOne $Results = [pscustomobject]@{'Results' = 'Succesfully Connected to NinjaOne' } } + 'PWPush' { + $Payload = 'This is a test from CIPP' + $PasswordLink = New-PwPushLink -Payload $Payload + if ($PasswordLink) { + $Results = [pscustomobject]@{'Results' = 'Succesfully generated PWPush'; 'Link' = $PasswordLink } + } else { + $Results = [pscustomobject]@{'Results' = 'PWPush is not enabled' } + } + } } } catch { $Results = [pscustomobject]@{'Results' = "Failed to connect: $($_.Exception.Message) $($_.InvocationInfo.ScriptLineNumber)" } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 index 5a4b4f95e08c..d554111c480c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUser.ps1 @@ -55,6 +55,12 @@ Function Invoke-AddUser { $bodyToShip = ConvertTo-Json -Depth 10 -InputObject $BodyToship -Compress $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $UserObj.tenantID -type POST -body $BodyToship -verbose Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($UserObj.tenantID) -message "Created user $($UserObj.displayname) with id $($GraphRequest.id) " -Sev 'Info' + + #PWPush + $PasswordLink = New-PwPushLink -Payload $password + if ($PasswordLink) { + $password = $PasswordLink + } $results.add('Created user.') $results.add("Username: $($UserprincipalName)") $results.add("Password: $password") diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUserBulk.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUserBulk.ps1 index 8169512aaeee..2d33113d1ffb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUserBulk.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddUserBulk.ps1 @@ -13,8 +13,7 @@ Function Invoke-AddUserBulk { $APIName = 'AddUserBulk' Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' $TenantFilter = $Request.body.TenantFilter - $Results = [System.Collections.ArrayList]@() - foreach ($userobj in $request.body.BulkUser) { + $Body = foreach ($userobj in $request.body.BulkUser) { Write-Host 'PowerShell HTTP trigger function processed a request.' try { $password = if ($userobj.password) { $userobj.password } else { New-passwordString } @@ -32,24 +31,29 @@ Function Invoke-AddUserBulk { $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $TenantFilter -type POST -body $BodyToship Write-Host "Graph request is $GraphRequest" Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message "Created user $($userobj.displayname) with id $($GraphRequest.id) " -Sev 'Info' - $results.add("Created user $($UserprincipalName). Password is $password") | Out-Null + + #PWPush + $PasswordLink = New-PwPushLink -Payload $password + if ($PasswordLink) { + $password = $PasswordLink + } + $results = "Created user $($UserprincipalName). Password is $password" + } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message "Failed to create user. Error:$($_.Exception.Message)" -Sev 'Error' - $body = $results.add("Failed to create user. $($_.Exception.Message)" ) + $results = "Failed to create user. $($_.Exception.Message)" + } + [PSCustomObject]@{ + 'Results' = $results + 'Username' = $UserprincipalName + 'Password' = $password } } - $body = [pscustomobject] @{ - 'Results' = @($results) - 'Username' = $UserprincipalName - 'Password' = $password - 'CopyFrom' = $copyFromResults - } - # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = $Body + Body = @($Body) }) } diff --git a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 index 6128fd2af888..aff8463210be 100644 --- a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 @@ -3,26 +3,31 @@ function Set-CIPPResetPassword { param( $userid, $tenantFilter, - $APIName = "Reset Password", + $APIName = 'Reset Password', $ExecutingUser, [bool]$forceChangePasswordNextSignIn = $true ) - try { + try { $password = New-passwordString $passwordProfile = @{ - "passwordProfile" = @{ - "forceChangePasswordNextSignIn" = $forceChangePasswordNextSignIn - "password" = $password + 'passwordProfile' = @{ + 'forceChangePasswordNextSignIn' = $forceChangePasswordNextSignIn + 'password' = $password } } | ConvertTo-Json -Compress - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/users/$($userid)" -tenantid $TenantFilter -type PATCH -body $passwordProfile -verbose - Write-LogMessage -user $ExecutingUser -API $APIName -message "Reset the password for $($userid). User must change password is set to $forceChangePasswordNextSignIn" -Sev "Info" -tenant $TenantFilter + $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/users/$($userid)" -tenantid $TenantFilter -type PATCH -body $passwordProfile -verbose + + #PWPush + $PasswordLink = New-PwPushLink -Payload $password + if ($PasswordLink) { + $password = $PasswordLink + } + Write-LogMessage -user $ExecutingUser -API $APIName -message "Reset the password for $($userid). User must change password is set to $forceChangePasswordNextSignIn" -Sev 'Info' -tenant $TenantFilter return "Reset the password for $($userid). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password" - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not reset password for $($userid)" -Sev "Error" -tenant $TenantFilter + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not reset password for $($userid)" -Sev 'Error' -tenant $TenantFilter return "Could not reset password for $($userid). Error: $($_.Exception.Message)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 index c41b893c5a05..692b6c4828ef 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 @@ -49,6 +49,7 @@ function Set-CIPPUserJITAdmin { switch ($Action) { 'Create' { + $Password = New-passwordString $Body = @{ givenName = $User.FirstName surname = $User.LastName @@ -59,16 +60,21 @@ function Set-CIPPUserJITAdmin { passwordProfile = @{ forceChangePasswordNextSignIn = $true forceChangePasswordNextSignInWithMfa = $false - password = New-passwordString + password = $Password } } $Json = ConvertTo-Json -Depth 5 -InputObject $Body try { $NewUser = New-GraphPOSTRequest -type POST -Uri 'https://graph.microsoft.com/beta/users' -Body $Json -tenantid $TenantFilter + #PWPush + $PasswordLink = New-PwPushLink -Payload $Password + if ($PasswordLink) { + $Password = $PasswordLink + } [PSCustomObject]@{ id = $NewUser.id userPrincipalName = $NewUser.userPrincipalName - password = $Body.passwordProfile.password + password = $Password } } catch { Write-Information "Error creating user: $($_.Exception.Message)" diff --git a/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 b/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 new file mode 100644 index 000000000000..72f1a0e5c9f8 --- /dev/null +++ b/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 @@ -0,0 +1,18 @@ +function Set-PwPushConfig { + param( + $Configuration + ) + $InitParams = @{} + if ($Configuration.BaseUrl) { + $InitParams.BaseUrl = $Configuration.BaseUrl + } + if ($Configuration.EmailAddress) { + $null = Connect-AzAccount -Identity + $ApiKey = (Get-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name 'PWPush' -AsPlainText) + if ($ApiKey) { + $InitParams.ApiKey = $ApiKey + $InitParams.EmailAddress = $Configuration.EmailAddress + } + } + Initialize-PassPushPosh @InitParams +} \ No newline at end of file diff --git a/Modules/CippExtensions/Public/New-PwPushLink.ps1 b/Modules/CippExtensions/Public/New-PwPushLink.ps1 new file mode 100644 index 000000000000..a8739f39c9b6 --- /dev/null +++ b/Modules/CippExtensions/Public/New-PwPushLink.ps1 @@ -0,0 +1,24 @@ +function New-PwPushLink { + [CmdletBinding()] + Param( + $Payload + ) + $Table = Get-CIPPTable -TableName Extensionsconfig + $Configuration = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json).PWPush + if ($Configuration.Enabled) { + Set-PwPushConfig -Configuration $Configuration + $PushParams = @{ + Payload = $Payload + } + if ($Configuration.ExpireAfterDays) { $PushParams.ExpireAfterDays = $Configuration.ExpireAfterDays } + if ($Configuration.ExpireAfterViews) { $PushParams.ExpireAfterViews = $Configuration.ExpireAfterViews } + if ($Configuration.DeletableByViewer) { $PushParams.DeletableByViewer = $Configuration.DeletableByViewer } + $Link = New-Push @PushParams | Select-Object Link, LinkRetrievalStep + if ($Configuration.RetrievalStep) { + $Link.Link = $Link.LinkRetrievalStep + } + $Link | Select-Object -ExpandProperty Link + } else { + return $false + } +} \ No newline at end of file diff --git a/Modules/PassPushPosh/0.2.3/PSGetModuleInfo.xml b/Modules/PassPushPosh/0.2.3/PSGetModuleInfo.xml new file mode 100644 index 000000000000..00cc59d45a7d --- /dev/null +++ b/Modules/PassPushPosh/0.2.3/PSGetModuleInfo.xml @@ -0,0 +1,142 @@ + + + + Microsoft.PowerShell.Commands.PSRepositoryItemInfo + System.Management.Automation.PSCustomObject + System.Object + + + PassPushPosh + 0.2.3 + Module + *PassPushPosh* is a PowerShell Module for interfacing with the Password Pusher secure password / string sharing application, primarily through pwpush.com. It supports creating, retrieving, and deleting anonymous and authenticated pushes, links in any supported language, and getting Push and Dashboard data for authenticated users._x000D__x000A__x000D__x000A_Cmdlets provide clear responses to errors, support additional messaging via -Debug and -Verbose, transaction testing via -Whatif and -Confirm, and in general try to be as "Powershell-y" as possible. + Adam Burley + AdamBurley + Adam Burley, 2022 +
2023-03-05T00:10:36-05:00
+ + + https://www.gnu.org/licenses/gpl-3.0.en.html + https://github.com/adamburley/PassPushPosh + + + + System.Object[] + System.Array + System.Object + + + PSEdition_Desktop + PSEdition_Core + Windows + Linux + MacOS + Password + PSModule + + + + + System.Collections.Hashtable + System.Object + + + + DscResource + + + + + + + Cmdlet + + + + Function + + + + ConvertTo-PasswordPush + Get-Dashboard + Get-Push + Get-PushAuditLog + Get-SecretLink + Initialize-PassPushPosh + New-PasswordPush + New-Push + Remove-Push + + + + + Workflow + + + + RoleCapability + + + + Command + + + + ConvertTo-PasswordPush + Get-Dashboard + Get-Push + Get-PushAuditLog + Get-SecretLink + Initialize-PassPushPosh + New-PasswordPush + New-Push + Remove-Push + + + + + + + 0.2.3 - Bug fixing in New-Push. See PR #1_x000D__x000A_ 0.2.2 - Fixed issue with Get-Dashboard returning error referencing -JsonIsArray parameter_x000D__x000A_ General - Module is generally functional but has not been extensively bug-tested. Reccomend not implementing into a production environment at this time. + + + + + https://www.powershellgallery.com/api/v2 + PSGallery + NuGet + + + System.Management.Automation.PSCustomObject + System.Object + + + Adam Burley, 2022 + *PassPushPosh* is a PowerShell Module for interfacing with the Password Pusher secure password / string sharing application, primarily through pwpush.com. It supports creating, retrieving, and deleting anonymous and authenticated pushes, links in any supported language, and getting Push and Dashboard data for authenticated users._x000D__x000A__x000D__x000A_Cmdlets provide clear responses to errors, support additional messaging via -Debug and -Verbose, transaction testing via -Whatif and -Confirm, and in general try to be as "Powershell-y" as possible. + False + 0.2.3 - Bug fixing in New-Push. See PR #1_x000D__x000A_ 0.2.2 - Fixed issue with Get-Dashboard returning error referencing -JsonIsArray parameter_x000D__x000A_ General - Module is generally functional but has not been extensively bug-tested. Reccomend not implementing into a production environment at this time. + True + True + 44 + 76 + 15168 + 3/5/2023 12:10:36 AM -05:00 + 3/5/2023 12:10:36 AM -05:00 + 5/23/2024 10:57:25 AM -04:00 + PSEdition_Desktop PSEdition_Core Windows Linux MacOS Password PSModule PSFunction_ConvertTo-PasswordPush PSCommand_ConvertTo-PasswordPush PSFunction_Get-Dashboard PSCommand_Get-Dashboard PSFunction_Get-Push PSCommand_Get-Push PSFunction_Get-PushAuditLog PSCommand_Get-PushAuditLog PSFunction_Get-SecretLink PSCommand_Get-SecretLink PSFunction_Initialize-PassPushPosh PSCommand_Initialize-PassPushPosh PSFunction_New-PasswordPush PSCommand_New-PasswordPush PSFunction_New-Push PSCommand_New-Push PSFunction_Remove-Push PSCommand_Remove-Push PSIncludes_Function + False + 2024-05-23T10:57:25Z + 0.2.3 + Adam Burley + false + Module + PassPushPosh.nuspec|PassPushPosh.psd1|PassPushPosh.psm1 + 5d8a1afd-a912-440f-a9b9-e79f42a05f21 + 5.1 + Burley.dev + + + C:\GitHub\CIPP Workspace\CIPP-API\Modules\PassPushPosh\0.2.3 +
+
+
diff --git a/Modules/PassPushPosh/0.2.3/PassPushPosh.psd1 b/Modules/PassPushPosh/0.2.3/PassPushPosh.psd1 new file mode 100644 index 000000000000..1c911df9d9de --- /dev/null +++ b/Modules/PassPushPosh/0.2.3/PassPushPosh.psd1 @@ -0,0 +1,140 @@ +# +# Module manifest for module 'PassPushPosh' +# +# Generated by: Adam Burley +# +# Generated on: 3/4/2023 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'PassPushPosh.psm1' + +# Version number of this module. +ModuleVersion = '0.2.3' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = '5d8a1afd-a912-440f-a9b9-e79f42a05f21' + +# Author of this module +Author = 'Adam Burley' + +# Company or vendor of this module +CompanyName = 'Burley.dev' + +# Copyright statement for this module +Copyright = 'Adam Burley, 2022' + +# Description of the functionality provided by this module +Description = '*PassPushPosh* is a PowerShell Module for interfacing with the Password Pusher secure password / string sharing application, primarily through pwpush.com. It supports creating, retrieving, and deleting anonymous and authenticated pushes, links in any supported language, and getting Push and Dashboard data for authenticated users. + +Cmdlets provide clear responses to errors, support additional messaging via -Debug and -Verbose, transaction testing via -Whatif and -Confirm, and in general try to be as "Powershell-y" as possible.' + +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '5.1' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = 'ConvertTo-PasswordPush', 'Get-Dashboard', 'Get-Push', + 'Get-PushAuditLog', 'Get-SecretLink', 'Initialize-PassPushPosh', + 'New-PasswordPush', 'New-Push', 'Remove-Push' + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +VariablesToExport = '*' + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = 'Get-PushPreview' + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = 'PSEdition_Desktop','PSEdition_Core','Windows','Linux','MacOS','Password' + + # A URL to the license for this module. + LicenseUri = 'https://www.gnu.org/licenses/gpl-3.0.en.html' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/adamburley/PassPushPosh' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + ReleaseNotes = @" + 0.2.3 - Bug fixing in New-Push. See PR #1 + 0.2.2 - Fixed issue with Get-Dashboard returning error referencing -JsonIsArray parameter + General - Module is generally functional but has not been extensively bug-tested. Reccomend not implementing into a production environment at this time. +"@ + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable + +# HelpInfo URI of this module +HelpInfoURI = 'https://github.com/adamburley/PassPushPosh/blob/main/Docs' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} + diff --git a/Modules/PassPushPosh/0.2.3/PassPushPosh.psm1 b/Modules/PassPushPosh/0.2.3/PassPushPosh.psm1 new file mode 100644 index 000000000000..e159d6a80d3d --- /dev/null +++ b/Modules/PassPushPosh/0.2.3/PassPushPosh.psm1 @@ -0,0 +1,1012 @@ +class PasswordPush { + [string]$Payload + [string] hidden $__UrlToken + [string] hidden $__LinkBase + [string]$Language + [bool]$RetrievalStep + [bool]$IsExpired + [bool]$IsDeleted + [bool]$IsDeletableByViewer + [int]$ExpireAfterDays + [int]$DaysRemaining + [int]$ExpireAfterViews + [int]$ViewsRemaining + [DateTime]$DateCreated + [DateTime]$DateUpdated + [DateTime]$DateExpired + # Added by constructors: + #[string]$URLToken + #[string]$Link + #[string]$LinkDirect + #[string]$LinkRetrievalStep + + PasswordPush() { + # Blank constructor + } + + # Constructor to allow casting or explicit import from a PSObject Representing the result of an API call + PasswordPush([PSCustomObject]$APIresponseObject) { + throw NotImplementedException + } + + # Allow casting or explicit import from the raw Content of an API call + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Scope = 'Function', Justification = 'Global variables are used for module session helpers.')] + PasswordPush([string]$JsonResponse) { + Write-Debug 'New PasswordPush object instantiated from JsonResponse string' + Initialize-PassPushPosh # Initialize the module if not yet done. + + $_j = $JsonResponse | ConvertFrom-Json + $this.Payload = $_j.payload + $this.IsExpired = $_j.expired + $this.IsDeleted = $_j.deleted + $this.IsDeletableByViewer = $_j.deletable_by_viewer + $this.ExpireAfterDays = $_j.expire_after_days + $this.DaysRemaining = $_j.days_remaining + $this.ExpireAfterViews = $_j.expire_after_views + $this.ViewsRemaining = $_j.views_remaining + $this.DateCreated = $_j.created_at + $this.DateUpdated = $_j.updated_at + $this.DateExpired = if ($_j.expired_on) { $_j.expired_on } else { [DateTime]0 } + + $this.Language = $Global:PPPLanguage + + $this | Add-Member -Name 'UrlToken' -MemberType ScriptProperty -Value { + return $this.__UrlToken + } -SecondValue { + $this.__UrlToken = $_ + $this.__LinkBase = "$Global:PPPBaseUrl/$($this.Language)/p/$($this.__UrlToken)" + } + $this.__UrlToken = $_j.url_token + $this.__LinkBase = "$Global:PPPBaseUrl/$($this.Language)/p/$($this.__UrlToken)" + $this | Add-Member -Name 'LinkDirect' -MemberType ScriptProperty -Value { return $this.__LinkBase } -SecondValue { + Write-Warning 'LinkDirect is a read-only calculated member.' + Write-Debug 'Link* members are calculated based on the Global BaseUrl and Language and Push Retrieval Step values' + } + $this | Add-Member -Name 'LinkRetrievalStep' -MemberType ScriptProperty -Value { return "$($this.__LinkBase)/r" } -SecondValue { + Write-Warning 'LinkRetrievalStep is a read-only calculated member.' + Write-Debug 'Link* members are calculated based on the Global BaseUrl and Language and Push Retrieval Step values' + } + $this | Add-Member -Name 'Link' -MemberType ScriptProperty -Value { + $_Link = if ($this.RetrievalStep) { $this.LinkRetrievalStep } else { $this.LinkDirect } + Write-Debug "Presented Link: $_link" + return $_Link + } -SecondValue { + Write-Warning 'Link is a read-only calculated member.' + Write-Debug 'Link* members are calculated based on the Global BaseUrl and Language and Push Retrieval Step values' + } + } +} + +function ConvertTo-PasswordPush { + <# + .SYNOPSIS + Convert API call response to a PasswordPush object + + .DESCRIPTION + Accepts a JSON string returned from the Password Pusher API and converts it to a [PasswordPush] object. + This allows calculated push retrieval URLs, language enumeration, and a more "PowerShell" experience. + Generally you won't need to use this directly, it's automatically invoked within Register-Push and Request-Push. + + .INPUTS + [string] + + .OUTPUTS + [PasswordPush] for single object + [PasswordPush[]] for Json array data + + .EXAMPLE + # Common usage - from within the Register-Push cmdlet + PS> $myPush = Register-Push -Payload "This is my secret!" + PS> $myPush.Link # The link parameter always presents the URL as it would appear with the same settings selected on pwpush.com + + https://pwpush.com/en/p/rz6nryvl-d4 + + .EXAMPLE + # Manually invoking the API + PS> $rawJson = Invoke-WebRequest ` + -Uri https://pwpush.com/en/p.json ` + -Method Post ` + -Body '{"password": { "payload": "This is my secret!"}}' ` + -ContentType 'application/json' | + Select-Object -ExpandProperty Content + PS> $rawJson + {"expire_after_days":7,"expire_after_views":5,"expired":false,"url_token":"rz6nryvl-d4","created_at":"2022-11-18T14:16:29.821Z","updated_at":"2022-11-18T14:16:29.821Z","deleted":false,"deletable_by_viewer":true,"retrieval_step":false,"expired_on":null,"days_remaining":7,"views_remaining":5} + PS> $rawJson | ConvertTo-PasswordPush + UrlToken : rz6nryvl-d4 + LinkDirect : https://pwpush.com/en/p/rz6nryvl-d4 + LinkRetrievalStep : https://pwpush.com/en/p/rz6nryvl-d4/r + Link : https://pwpush.com/en/p/rz6nryvl-d4 + Payload : + Language : en + RetrievalStep : False + IsExpired : False + IsDeleted : False + IsDeletableByViewer : True + ExpireAfterDays : 7 + DaysRemaining : 7 + ExpireAfterViews : 5 + ViewsRemaining : 5 + DateCreated : 11/18/2022 2:16:29 PM + DateUpdated : 11/18/2022 2:16:29 PM + DateExpired : 1/1/0001 12:00:00 AM + + .LINK + https://github.com/adamburley/PassPushPosh/blob/main/Docs/ConvertTo-PasswordPush.md + + .NOTES + Needs a rewrite / cleanup + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', Justification = 'Creates a new object, no risk of overwriting data.')] + [CmdletBinding()] + [OutputType([PasswordPush])] + param( + # The string result of an API call from the Password Pusher application + [parameter(Mandatory, ValueFromPipeline)] + [ValidateNotNullOrEmpty()] + [string]$JsonResponse + ) + process { + try { + $jsonObject = $JsonResponse | ConvertFrom-Json + foreach ($o in $jsonObject) { + [PasswordPush]($o | ConvertTo-Json) # TODO fix this mess + } + } + catch { + Write-Debug 'Error in ConvertTo-PasswordPush coercing JSON object to PasswordPush object' + Write-Debug "JsonResponse parameter value: [[$JsonResponse]]" + Write-Error $_ + } + } +} +function Get-Dashboard { + <# + .SYNOPSIS + Get a list of active or expired Pushes for an authenticated user + + .DESCRIPTION + Retrieves a list of Pushes - active or expired - for an authenticated user. + Active and Expired are different endpoints, so to get both you'll need to make + two calls. + + .INPUTS + [string] 'Active' or 'Expired' + + .OUTPUTS + [PasswordPush[]] Array of pushes with data + [string] raw response body from API call + + .EXAMPLE + Get-Dashboard + + .EXAMPLE + Get-Dashboard Active + + .EXAMPLE + Get-Dashboard -Dashboard Expired + + .EXAMPLE + Get-Dashboard -Raw + [{"expire_after_days":1,"expire_after_views":5,"expired":false,"url_token":"xm3q7czvtdpmyg","created_at":"2022-11-19T18:10:42.055Z","updated_at":"2022-11-19T18:10:42.055Z","deleted":false,"deletable_by_viewer":true,"retrieval_step":false,"expired_on":null,"note":null,"days_remaining":1,"views_remaining":3}] + + .LINK + https://github.com/adamburley/PassPushPosh/blob/main/Docs/Get-Dashboard.md + + .LINK + https://pwpush.com/api/1.0/dashboard.en.html + + .LINK + Get-PushAuditLog + + .NOTES + TODO update Invoke-Webrequest flow and error-handling to match other functions + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Scope = 'Function', Justification = 'Global variables are used for module session helpers.')] + [CmdletBinding()] + [OutputType([PasswordPush[]],[string])] + param( + # URL Token from a secret + [parameter(Position=0)] + [ValidateSet('Active','Expired')] + [ValidateNotNullOrEmpty()] + [string] + $Dashboard = 'Active', + + # Return content of API call directly + [Parameter()] + [switch] + $Raw + ) + if (-not $Global:PPPHeaders) { Write-Error 'Dashboard access requires authentication. Run Initialize-PassPushPosh and pass your email address and API key before retrying.' -ErrorAction Stop -Category AuthenticationError } + try { + $uri = "$Global:PPPBaseUrl/d/" + if ($Dashboard -eq 'Active') { $uri += 'active.json' } + elseif ($Dashboard -eq 'Expired') { $uri += 'expired.json' } + Write-Debug "Requesting $uri" + $response = Invoke-WebRequest -Uri $uri -Method Get -Headers $Global:PPPHeaders -ErrorAction Stop + if ($Raw) { return $response.Content } + else { + return $response.Content | ConvertTo-PasswordPush + } + } catch { + Write-Verbose "An exception was caught: $($_.Exception.Message)" + if ($DebugPreference -eq [System.Management.Automation.ActionPreference]::Continue) { + Set-Variable -Scope Global -Name 'PPPLastError' -Value $_ + Write-Debug -Message 'Response object set to global variable $PPPLastError' + } + throw # Re-throw the error + } +} +function Get-Push { + <# + .SYNOPSIS + Retrieve the secret contents of a Push + + .DESCRIPTION + Accepts a URL Token string, returns the contents of a Push along with + metadata regarding that Push. Note, Get-Push will return data on an expired + Push (datestamps, etc) even if it does not return the Push contents. + + .INPUTS + [string] + + .OUTPUTS + [PasswordPush] or [string] + + .EXAMPLE + Get-Push -URLToken gzv65wiiuciy + + .EXAMPLE + Get-Push -URLToken gzv65wiiuciy -Raw + {"payload":"I am your payload!","expired":false,"deleted":false,"expired_on":"","expire_after_days":1,"expire_after_views":4,"url_token":"bwzehzem_xu-","created_at":"2022-11-21T13:20:08.635Z","updated_at":"2022-11-21T13:23:45.342Z","deletable_by_viewer":true,"retrieval_step":false,"days_remaining":1,"views_remaining":4} + + .LINK + https://github.com/adamburley/PassPushPosh/blob/main/Docs/Get-Push.md + + .LINK + https://pwpush.com/api/1.0/passwords/show.en.html + + .LINK + New-Push + + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars','',Scope='Function',Justification='Global variables are used for module session helpers.')] + [CmdletBinding()] + [OutputType([PasswordPush])] + param( + # URL Token for the secret + [parameter(Mandatory,ValueFromPipeline,Position=0)] + [ValidateNotNullOrEmpty()] + [Alias('Token')] + $URLToken, + + # Return the raw response body from the API call + [Parameter()] + [switch] + $Raw + ) + begin { Initialize-PassPushPosh -Verbose:$VerbosePreference -Debug:$DebugPreference } + + process { + try { + $iwrSplat = @{ + 'Method' = 'Get' + 'ContentType' = 'application/json' + 'Uri' = "$Global:PPPBaseUrl/p/$URLToken.json" + 'UserAgent' = $Global:PPPUserAgent + } + if ($Global:PPPHeaders) { $iwrSplat['Headers'] = $Global:PPPHeaders } + Write-Verbose "Sending HTTP request: $($iwrSplat | Out-String)" + $response = Invoke-WebRequest @iwrSplat -ErrorAction Stop + if ($DebugPreference -eq [System.Management.Automation.ActionPreference]::Continue) { + Set-Variable -Scope Global -Name PPPLastCall -Value $response + Write-Debug 'Response to Invoke-WebRequest set to PPPLastCall Global variable' + } + if ($Raw) { + Write-Debug "Returning raw object:`n$($response.Content)" + return $response.Content + } + return $response.Content | ConvertTo-PasswordPush + } catch { + Write-Verbose "An exception was caught: $($_.Exception.Message)" + if ($DebugPreference -eq [System.Management.Automation.ActionPreference]::Continue) { + Set-Variable -Scope Global -Name PPPLastError -Value $_ + Write-Debug -Message 'Response object set to global variable $PPPLastError' + } + } + } +} +function Get-PushAuditLog { + <# + .SYNOPSIS + Get the view log of an authenticated Push + + .DESCRIPTION + Retrieves the view log of a Push created under an authenticated session. + Returns an array of custom objects with view data. If the query is + successful but there are no results, it returns an empty array. + If there's an error, a single object is returned with information. + See "handling errors" under NOTES + + .INPUTS + [string] + + .OUTPUTS + [PsCustomObject[]] Array of entries. + [PsCustomObject] If there's an error in the call, it will be returned an object with a property + named 'error'. The value of that member will contain more information + + .EXAMPLE + Get-PushAuditLog -URLToken 'mytokenfromapush' + ip : 75.202.43.56,102.70.135.200 + user_agent : Mozilla/5.0 (Macintosh; Darwin 21.6.0 Darwin Kernel Version 21.6.0: Mon Aug 22 20:20:05 PDT 2022; root:xnu-8020.140.49~2/RELEASE_ARM64_T8101; + en-US) PowerShell/7.2.7 + referrer : + successful : True + created_at : 11/19/2022 6:32:42 PM + updated_at : 11/19/2022 6:32:42 PM + kind : 0 + + .EXAMPLE + # If there are no views, an empty array is returned + Get-PushAuditLog -URLToken 'mytokenthatsneverbeenseen' + + .LINK + https://github.com/adamburley/PassPushPosh/blob/main/Docs/Get-PushAuditLog.md + + .LINK + https://pwpush.com/api/1.0/passwords/audit.en.html + + .LINK + Get-Dashboard + + .NOTES + Handling Errors: + The API returns different HTTP status codes and results depending where the + call fails. + + | HTTP RESPONSE | Error Reason | Response Body | Sample Object Returned | Note | + |------------------|---------------------------------|----------------------------------------------|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| + | 401 UNAUTHORIZED | Invalid API key or email | None | @{ 'Error'= 'Authentication error. Verify email address and API key.'; 'ErrorCode'= 401 } | | + | 200 OK | Push created by another account | {"error":"That push doesn't belong to you."} | @{ 'Error'= "That Push doesn't belong to you"; 'ErrorCode'= 403 } | Function transforms error code to 403 to allow easier response management | + | 404 NOT FOUND | Invalid URL token | None | @{ 'Error'= 'Invalid token. Verify your Push URL token is correct.'; 'ErrorCode'= 404 } | This is different than the response to a delete Push query - in this case it will only return 404 if the token is invalid. | + + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Scope = 'Function', Justification = 'Global variables are used for module session helpers.')] + [CmdletBinding()] + [OutputType([PSCustomObject[]],[string])] + param( + # URL Token from a secret + [parameter(ValueFromPipeline)] + [ValidateNotNullOrEmpty()] + [string] + $URLToken, + + # Return content of API call directly + [Parameter()] + [switch] + $Raw + ) + begin { + if (-not $Global:PPPHeaders) { Write-Error 'Retrieving audit logs requires authentication. Run Initialize-PassPushPosh and pass your email address and API key before retrying.' -ErrorAction Stop -Category AuthenticationError } + } + process { + try { + $uri = "$Global:PPPBaseUrl/p/$URLToken/audit.json" + Write-Debug 'Requesting $uri' + $response = Invoke-WebRequest -Uri $uri -Method Get -Headers $Global:PPPHeaders -ErrorAction Stop + if ([int]$response.StatusCode -eq 200 -and $response.Content -ieq "{`"error`":`"That push doesn't belong to you.`"}") { + $result = [PSCustomObject]@{ 'Error' = "That Push doesn't belong to you"; 'ErrorCode' = 403 } + Write-Warning $result.Error + return $result + } + if ($Raw) { return $response.Content } else { return $response.Content | ConvertFrom-Json } + } + catch { + Write-Verbose "An exception was caught: $($_.Exception.Message)" + if ([int]$_.Exception.Response.StatusCode -eq 401) { # Could be optimized + $result = [PSCustomObject]@{ 'Error' = 'Authentication error. Verify email address and API key.'; 'ErrorCode' = 401 } + Write-Warning $result.Error + return $result + } elseif ([int]$_.Exception.Response.StatusCode -eq 404) { + $result = [PSCustomObject]@{ 'Error' = 'Invalid token. Verify your Push URL token is correct.'; 'ErrorCode' = 404 } + Write-Warning $result.Error + return $result + } + elseif ($DebugPreference -eq [System.Management.Automation.ActionPreference]::Continue) { + Set-Variable -Scope Global -Name 'PPPLastError' -Value $_ + Write-Debug -Message 'Response object set to global variable $PPPLastError' + return [PSCustomObject]@{ + 'Error' = $_.Exception.Message + 'ErrorCode' = [int]$_.Exception.Response.StatusCode + 'ErrorMessage' = $_.Exception.Response.ReasonPhrase + } + } + } + } +} + +# Invalid API key / email - 401 +# Invalid URL Token - 404 +# Valid token but not mine - 200, content = {"error":"That push doesn't belong to you."} +# Success but no views - 200, content = : {"views":[]} +# Success with view history {"views":[{"ip":"75.118.137.58,172.70.135.200","user_agent":"Mozilla/5.0 (Macintosh; Darwin 21.6.0 Darwin Kernel Version 21.6.0: Mon Aug 22 20:20:05 PDT 2022; root:xnu-8020.140.49~2/RELEASE_ARM64_T8101; en-US) PowerShell/7.2.7","referrer":"","successful":true,"created_at":"2022-11-19T18:32:42.277Z","updated_at":"2022-11-19T18:32:42.277Z","kind":0}]} +# Content.Views +<# +ip : 75.118.137.58,172.70.135.200 +user_agent : Mozilla/5.0 (Macintosh; Darwin 21.6.0 Darwin Kernel Version 21.6.0: Mon Aug 22 20:20:05 PDT 2022; root:xnu-8020.140.49~2/RELEASE_ARM64_T8101; +en-US) PowerShell/7.2.7 +referrer : +successful : True +created_at : 11/19/2022 6:32:42 PM +updated_at : 11/19/2022 6:32:42 PM +kind : 0 +#> +function Get-SecretLink { + <# + .SYNOPSIS + Returns a fully qualified secret link to a push of given URL Token + + .DESCRIPTION + Accepts a string value for a URL Token and retrieves a full URL link to the secret. + Returned value is a 1-step retrieval link depending on option selected during Push creation. + Returns false if URL Token is invalid, however it will return a URL if the token is valid + but the Push is expired or deleted. + + .INPUTS + [string] URL Token value + + .OUTPUTS + [string] Fully qualified URL + [bool] $False if Push URL Token is invalid. Note: Expired or deleted Pushes will still return a link. + + .EXAMPLE + Get-SecretLink -URLToken gzv65wiiuciy + https://pwpush.com/en/p/gzv65wiiuciy/r + + .EXAMPLE + # En France + PS > Get-SecretLink -URLToken gzv65wiiuciy -Language fr + https://pwpush.com/fr/p/gzv65wiiuciy/r + + .EXAMPLE + Get-SecretLink -URLToken gzv65wiiuciy -Raw + { "url": "https://pwpush.com/es/p/0fkapnbo_pwp4gi8uy0/r" } + + .LINK + https://github.com/adamburley/PassPushPosh/blob/main/Docs/Get-SecretLink.md + + .LINK + https://pwpush.com/api/1.0/passwords/preview.en.html + + .NOTES + Including this endpoint for completeness - however it is generally unnecessary. + The only thing this endpoint does is return a different value depending if "Use 1-click retrieval step" + was selected when the Push was created. Since both the 1-click and the direct links are available + regardless if that option is selected, the links are calculable and both are included by default in a + [PasswordPush] object. + + As it returns false if a Push URL token is not valid you can use it to test if a Push exists without + burning a view. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars','',Scope='Function',Justification='Global variables are used for module session helpers.')] + [CmdletBinding()] + [Alias('Get-PushPreview')] + [OutputType('[string]')] + param( + # URL Token for the secret + [parameter(Mandatory, ValueFromPipeline)] + [ValidateLength(5, 256)] + [string]$URLToken, + + # Language for returned links. Defaults to system language, can be overridden here. + [Parameter()] + [string] + $Language = $Global:PPPLanguage, + + # Return the raw response body from the API call + [Parameter()] + [switch] + $Raw + ) + begin { Initialize-PassPushPosh -Verbose:$VerbosePreference -Debug:$DebugPreference } + process { + try { + if ($Language -ine 'en') { $uri += "?push_locale=$Language" } + $iwrSplat = @{ + 'Method' = 'Get' + 'ContentType' = 'application/json' + 'Uri' = "$Global:PPPBaseUrl/p/$URLToken/preview.json" + 'UserAgent' = $Global:PPPUserAgent + } + if ($Global:PPPHeaders) { $iwrSplat['Headers'] = $Global:PPPHeaders } + Write-Verbose "Sending HTTP request: $($iwrSplat | Out-String)" + $responseContent = Invoke-WebRequest @iwrSplat | Select-Object -ExpandProperty Content + if ($Raw) { return $responseContent } + else { return $responseContent | ConvertFrom-Json | Select-Object -ExpandProperty url } + } + catch { + Write-Verbose "An exception was caught: $($_.Exception.Message)" + if ($DebugPreference -eq [System.Management.Automation.ActionPreference]::Continue) { + Set-Variable -Scope Global -Name 'PPPLastError' -Value $_ + Write-Debug -Message 'Response object set to global variable $PPPLastError' + } + } + } +} +function Initialize-PassPushPosh { + <# + .SYNOPSIS + Initialize the PassPushPosh module + + .DESCRIPTION + Sets global variables to handle the server URL, headers (authentication), and language. + Called automatically by module Functions if it is not called explicitly prior, so you don't actually need + to call it unless you're going to use the authenticated API or alternate server, etc + Default parameters use the pwpush.com domain, anonymous authentication, and whatever language your computer + is set to. + + .EXAMPLE + # Initialize with default settings + PS > Initialize-PassPushPosh + + .EXAMPLE + # Initialize with authentication + PS > Initialize-PassPushPosh -EmailAddress 'youremail@example.com' -ApiKey '239jf0jsdflskdjf' -Verbose + + VERBOSE: Initializing PassPushPosh. ApiKey: [x-kdjf], BaseUrl: https://pwpush.com + + .EXAMPLE + # Initialize with another server with authentication + PS > Initialize-PassPushPosh -BaseUrl https://myprivatepwpushinstance.com -EmailAddress 'youremail@example.com' -ApiKey '239jf0jsdflskdjf' -Verbose + + VERBOSE: Initializing PassPushPosh. ApiKey: [x-kdjf], BaseUrl: https://myprivatepwpushinstance.com + + .EXAMPLE + # Set a custom User Agent + PS > InitializePassPushPosh -UserAgent "I'm a cool dude with a cool script." + + .LINK + https://github.com/adamburley/PassPushPosh/blob/main/Docs/Initialize-PassPushPosh.md + + .NOTES + All variables set by this function start with PPP. + - PPPHeaders + - PPPLanguage + - PPPUserAgent + - PPPBaseUrl + + -WhatIf setting for Set-Variable -Global is disabled, otherwise -WhatIf + calls for other functions would return incorrect data in the case this + function has not yet run. + + TODO: Review API key pattern for parameter validation + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars','',Scope='Function',Justification='Global variables are used for module session helpers.')] + [CmdletBinding(DefaultParameterSetName='Anonymous')] + param ( + # Email address to use for authenticated calls. + [Parameter(Mandatory,Position=0,ParameterSetName='Authenticated')] + [ValidatePattern('.+\@.+\..+')] + [string]$EmailAddress, + + # API Key for authenticated calls. + [Parameter(Mandatory,Position=1,ParameterSetName='Authenticated')] + [ValidateLength(5,256)] + [string]$ApiKey, + + # Base URL for API calls. Allows use of module with private instances of Password Pusher + # Default: https://pwpush.com + [Parameter(Position=0,ParameterSetName='Anonymous')] + [Parameter(Position=2,ParameterSetName='Authenticated')] + [ValidatePattern('^https?:\/\/[a-zA-Z0-9-_]+.[a-zA-Z0-9]+')] + [string]$BaseUrl, + + # Language to render resulting links in. Defaults to host OS language, or English if + # host OS language is not available + [Parameter()] + [string] + $Language, + + # Set a specific user agent. Default user agent is a combination of the + # module info, what your OS reports itself as, and a hash based on + # your username + workstation or domain name. This way the UA can be + # semi-consistent across sessions but not identifying. + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $UserAgent, + + # Force setting new information. If module is already initialized you can use this to + # Re-initialize with default settings. Implied if either ApiKey or BaseUrl is provided. + [Parameter()][switch]$Force + ) + if ($Global:PPPBaseURL -and $true -inotin $Force, [bool]$ApiKey, [bool]$BaseUrl, [bool]$UserAgent) { Write-Debug -Message 'PassPushPosh is already initialized.' } + else { + $defaultBaseUrl = 'https://pwpush.com' + $apiKeyOutput = if ($ApiKey) { 'x-' + $ApiKey.Substring($ApiKey.Length-4) } else { 'None' } + + if (-not $Global:PPPBaseURL) { # Not initialized + if (-not $BaseUrl) { $BaseUrl = $defaultBaseUrl } + Write-Verbose "Initializing PassPushPosh. ApiKey: [$apiKeyOutput], BaseUrl: $BaseUrl" + } elseif ($Force -or $ApiKey -or $BaseURL) { + if (-not $BaseUrl) { $BaseUrl = $defaultBaseUrl } + $oldApiKeyOutput = if ($Global:PPPApiKey) { 'x-' + $Global:PPPApiKey.Substring($Global:PPPApiKey.Length-4) } else { 'None' } + Write-Verbose "Re-initializing PassPushPosh. Old ApiKey: [$oldApiKeyOutput] New ApiKey: [$apiKeyOutput], Old BaseUrl: $Global:PPPBaseUrl New BaseUrl: $BaseUrl" + } + if ($PSCmdlet.ParameterSetName -eq 'Authenticated') { + Set-Variable -Scope Global -Name PPPHeaders -WhatIf:$false -Value @{ + 'X-User-Email' = $EmailAddress + 'X-User-Token' = $ApiKey + } + } elseif ($Global:PPPHeaders) { # Remove if present - covers case where module is reinitialized from an authenticated to an anonymous session + Remove-Variable -Scope Global -Name PPPHeaders -WhatIf:$false + } + $availableLanguages = ('en','ca','cs','da','de','es','fi','fr','hu','it','nl','no','pl','pt-BR','sr','sv') + if (-not $Language) { + $Culture = Get-Culture + Write-Debug "Detected Culture: $($Culture.DisplayName)" + $matchedLanguage = $Culture.TwoLetterISOLanguageName, $Culture.IetfLanguageTag | + Foreach-Object { if ($_ -iin $availableLanguages) { $_ } } | + Select-Object -First 1 + if ($matchedLanguage) { + Write-Debug "Language is supported in Password Pusher." + $Language = $matchedLanguage + } else { Write-Warning "Detected language $($Culture.DisplayName) is not supported in PasswordPusher. Defaulting to English." } + } else { + if ($Language -iin $availableLanguages) { + Write-Debug "Language [$Language] is available in PasswordPusher." + } else + { + Write-Warning "Language [$Language] is not available in PasswordPusher. Defaulting to english." + $Language = 'en' + } + } + + if (-not $UserAgent) { + $osVersion = [System.Environment]::OSVersion + $userAtDomain = "{0}@{1}" -f [System.Environment]::UserName, [System.Environment]::UserDomainName + $uAD64 = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($userAtDomain)) + Write-Debug "$userAtDomain transformed to $uAD64. First 20 characters $($uAD64.Substring(0,20))" + $UserAgent = "PassPushPosh/$((Get-Module -Name PassPushPosh).Version.ToString()) $Language $osVersion/$($uAD64.Substring(0,20))" + Write-Verbose "Generated user agent: $UserAgent" + } else { + Write-Verbose "Using specified user agent: $UserAgent" + } + + Set-Variable -WhatIf:$false -Scope Global -Name PPPBaseURL -Value $BaseUrl.TrimEnd('/') + Set-Variable -WhatIf:$false -Scope Global -Name PPPLanguage -Value $Language + Set-Variable -WhatIf:$false -Scope Global -Name PPPUserAgent -Value $UserAgent + } +} +function New-PasswordPush { + <# + .SYNOPSIS + Create a new blank Password Push object. + + .DESCRIPTION + Creates a blank [PasswordPush]. + Generally not needed, use ConvertTo-PasswordPush + See New-Push if you're trying to create a new secret to send + + .INPUTS + None + + .OUTPUTS + [PasswordPush] + + .EXAMPLE + New-PasswordPush + + .LINK + https://github.com/adamburley/PassPushPosh/blob/main/Docs/New-PasswordPush.md + + .NOTES + TODO Rewrite - make this work including read-only properties + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', Justification = 'Creates a new object, no risk of overwriting data.')] + [CmdletBinding()] + param () + return [PasswordPush]::new() +} +function New-Push { + <# + .SYNOPSIS + Create a new Password Push + + .DESCRIPTION + Create a new Push on the specified Password Pusher instance. The + programmatic equivalent of going to pwpush.com and entering info. + Returns [PasswordPush] object. Link member is a link created based on + 1-step setting and language specified, however both 1-step and direct links + are always provided at LinkRetrievalStep and LinkDirect. + + .EXAMPLE + $myPush = New-Push "Here's my secret!" + PS > $myPush | Select-Object Link, LinkRetrievalStep, LinkDirect + + Link : https://pwpush.com/en/p/gzv65wiiuciy # Requested style + LinkRetrievalStep : https://pwpush.com/en/p/gzv65wiiuciy/r # 1-step + LinkDirect : https://pwpush.com/en/p/gzv65wiiuciy # Direct + + .EXAMPLE + "Super secret secret" | New-Push -RetrievalStep | Select-Object -ExpandProperty Link + + https://pwpush.com/en/p/gzv65wiiuciy/r + + + .EXAMPLE + # "Burn after reading" style Push + PS > New-Push -Payload "Still secret text!" -ExpireAfterViews 1 -RetrievalStep + + .INPUTS + [string] + + .OUTPUTS + [PasswordPush] Push object + [string] Raw result of API call + + .LINK + https://github.com/adamburley/PassPushPosh/blob/main/Docs/New-Push.md + + .LINK + https://pwpush.com/api/1.0/passwords/create.en.html + + .LINK + Get-Push + + .NOTES + Maximum for -ExpireAfterDays and -ExpireAfterViews is based on the default + values for Password Pusher and what's used on the public instance + (pwpush.com). If you're using this with a private instance and want to + override that value you'll need to fork this module. + + TODO: Support [PasswordPush] input objects, testing + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars','',Scope='Function',Justification='Global variables are used for module session helpers.')] + [CmdletBinding(SupportsShouldProcess,ConfirmImpact='Low',DefaultParameterSetName='Anonymous')] + [OutputType([PasswordPush],[string],[bool])] # Returntype should be [PasswordPush] but I've yet to find a way to add class access to a function on a module... + param( + # The password or secret text to share. + [Parameter(Mandatory=$true,ValueFromPipeline,Position=0)] + [Alias('Password')] + [ValidateNotNullOrEmpty()] + [string]$Payload, + + # Label for this Push (requires Authenticated session) + [Parameter(ParameterSetName='RequiresAuthentication')] + [ValidateNotNullOrEmpty()] + [string]$Note, + + # Expire secret link and delete after this many days. + [Parameter()] + [ValidateRange(1,90)] + [int] + $ExpireAfterDays, + + # Expire secret link after this many views. + [Parameter()] + [ValidateRange(1,100)] + [int] + $ExpireAfterViews, + + # Allow the recipient of a Push to delete it. + [Parameter()] + [switch] + $DeletableByViewer, + + # Require recipient click an extra link to view Push payload. + # Helps to avoid chat systems and URL scanners from eating up views. + # Note that the retrieval step URL is always available for a push. This + # parameter changes if the 1-click link is used in the Link parameter + # and returned from the secret link helper (Get-SecretLink) + [Parameter()] + [switch] + $RetrievalStep, + + # Override Language. Useful if sending to someone who speaks a + # different language. You can change this after the fact by changing + # the URL by hand or by requesting a link for the given token from the + # preview helper endpoint ( See Request-SecretLink ) + [Parameter()] + [string] + $Language, + + # Return the raw response body from the API call + [Parameter()] + [switch] + $Raw + ) + + begin { + Initialize-PassPushPosh -Verbose:$VerbosePreference -Debug:$DebugPreference + } + + process { + if ($PSCmdlet.ParameterSetName -eq 'RequiresAuthentication' -and -not $Global:PPPHeaders.'X-User-Token') { Write-Error -Message 'Setting a note requires an authenticated call.'; return $false } + + $body = @{ + 'password' = @{ + 'payload' = $Payload + } + } + $shouldString = 'Submit {0} push with Payload of length {1}' -f $PSCmdlet.ParameterSetName, $Payload.Length + if ($Note) { + $body.password.note = $note + $shouldString += " with note $note" + } + if ($ExpireAfterDays) { + $body.password.expire_after_days = $ExpireAfterDays + $shouldString += ', expire after {0} days' -f $ExpireAfterDays + } + if ($ExpireAfterViews) { + $body.password.expire_after_views = $ExpireAfterViews + $shouldString += ', expire after {0} views' -f $ExpireAfterViews + } + $body.password.deletable_by_viewer = if ($DeletableByViewer) { + $shouldString += ', deletable by viewer' + $true + } else { + $shouldString += ', NOT deletable by viewer' + $false + } + $body.password.retrieval_step = if ($RetrievalStep) { + $shouldString += ', with a 1-click retrieval step' + $true + } else { + $shouldString += ', with a direct link' + $false + } + if (-not $Language) { $Language = $Global:PPPLanguage } + $shouldString += ' in language "{0}"' -f $Language + if ($VerbosePreference -eq [System.Management.Automation.ActionPreference]::Continue) { + # Sanitize input so we're not logging or outputting the payload + $vBody = $body.Clone() + $vBody.password.payload = "A payload of length $($body.password.payload.Length.ToString())" + $vBs = $vBody | ConvertTo-Json | Out-String + Write-Verbose "Call Body (sanitized): $vBs" + } + + $iwrSplat = @{ + 'Method' = 'Post' + 'ContentType' = 'application/json' + 'Body' = ($body | ConvertTo-Json) + 'Uri' = "$Global:PPPBaseUrl/$Language/p.json" + 'UserAgent' = $Global:PPPUserAgent + } + if ($Global:PPPHeaders.'X-User-Token') { $iwrSplat['Headers'] = $Global:PPPHeaders } + Write-Verbose "Sending HTTP request (minus body): $($iwrSplat | Select-Object Method,ContentType,Uri,UserAgent,Headers | Out-String)" + if ($PSCmdlet.ShouldProcess($shouldString, $iwrSplat.Uri, 'Submit new Push')) { + try { + $response = Invoke-WebRequest @iwrSplat + if ($DebugPreference -eq [System.Management.Automation.ActionPreference]::Continue) { + Set-Variable -Scope Global -Name PPPLastCall -Value $response + Write-Debug 'Response to Invoke-WebRequest set to PPPLastCall Global variable' + } + if ($Raw) { + Write-Debug "Returning raw object: $($response.Content)" + return $response.Content + } + return $response.Content | ConvertTo-PasswordPush + } catch { + Write-Verbose "An exception was caught: $($_.Exception.Message)" + if ($DebugPreference -eq [System.Management.Automation.ActionPreference]::Continue) { + Set-Variable -Scope Global -Name PPPLastError -Value $_ + Write-Debug -Message 'Response object set to global variable $PPPLastError' + } + } + } + } +} +function Remove-Push { + <# + .SYNOPSIS + Remove a Push + + .DESCRIPTION + Remove (invalidate) an active push. Requires the Push be either set as + deletable by viewer, or that you are authenticated as the creator of the + Push. + + If you have authorization to delete a push (deletable by viewer TRUE or + you are the Push owner) the endpoint will always return 200 OK with a Push + object, regardless if the Push was previously deleted or expired. + + If the Push URL Token is invalid OR you are not authorized to delete the + Push, the endpoint returns 404 and this function returns $false + + .INPUTS + [string] URL Token + [PasswordPush] representing the Push to remove + + .OUTPUTS + [bool] True on success, otherwise False + + .EXAMPLE + Remove-Push -URLToken bwzehzem_xu- + + .EXAMPLE + Remove-Push -URLToken -Raw + {"expired":true,"deleted":true,"expired_on":"2022-11-21T13:23:45.341Z","expire_after_days":1,"expire_after_views":4,"url_token":"bwzehzem_xu-","created_at":"2022-11-21T13:20:08.635Z","updated_at":"2022-11-21T13:23:45.342Z","deletable_by_viewer":true,"retrieval_step":false,"days_remaining":1,"views_remaining":4} + + .LINK + https://github.com/adamburley/PassPushPosh/blob/main/Docs/Remove-Push.md + + .LINK + https://pwpush.com/api/1.0/passwords/destroy.en.html + + .NOTES + TODO testing and debugging + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars','',Scope='Function',Justification='Global variables are used for module session helpers.')] + [CmdletBinding(SupportsShouldProcess,DefaultParameterSetName='Token')] + [OutputType([PasswordPush],[string],[bool])] + param( + # URL Token for the secret + [parameter(ValueFromPipeline,ParameterSetName='Token')] + [ValidateNotNullOrEmpty()] + [Alias('Token')] + [string] + $URLToken, + + # PasswordPush object + [Parameter(ValueFromPipeline,ParameterSetName='Object')] + [PasswordPush] + $PushObject, + + # Return the raw response body from the API call + [parameter()] + [switch] + $Raw + ) + process { + try { + if ($PSCmdlet.ParameterSetName -eq 'Object') { + Write-Debug -Message "Remove-Push was passed a PasswordPush object with URLToken: [$($PushObject.URLToken)]" + if (-not $PushObject.IsDeletableByViewer -and -not $Global:PPPHeaders) { #Pre-qualify if this will succeed + Write-Warning -Message 'Unable to remove Push. Push is not marked as deletable by viewer and you are not authenticated.' + return $false + } + if ($PushObject.IsDeletableByViewer) { + Write-Verbose "Push is flagged as deletable by viewer, should be deletable." + } else { Write-Verbose "In an authenticated API session. Push will be deletable if it was created by authenticated user." } + $URLToken = $PushObject.URLToken + } else { + Write-Debug -Message "Remove-Push was passed a URLToken: [$URLToken]" + } + Write-Verbose -Message "Push with URL Token [$URLToken] will be deleted if 'Deletable by viewer' was enabled or you are the creator of the push and are authenticated." + $iwrSplat = @{ + 'Method' = 'Delete' + 'ContentType' = 'application/json' + 'Uri' = "$Global:PPPBaseUrl/p/$URLToken.json" + 'UserAgent' = $Global:PPPUserAgent + } + if ($Global:PPPHeaders) { $iwrSplat['Headers'] = $Global:PPPHeaders } + Write-Verbose "Sending HTTP request: $($iwrSplat | Out-String)" + if ($PSCmdlet.ShouldProcess('Delete',"Push with token [$URLToken]")) { + $response = Invoke-WebRequest @iwrSplat + if ($DebugPreference -eq [System.Management.Automation.ActionPreference]::Continue) { + Set-Variable -Scope Global -Name PPPLastCall -Value $response + Write-Debug 'Response to Invoke-WebRequest set to PPPLastCall Global variable' + } + if ($Raw) { + Write-Debug "Returning raw object: $($response.Content)" + return $response.Content + } + return $response.Content | ConvertTo-PasswordPush + } + } catch { + if ($_.Exception.Response.StatusCode -eq 404) { + Write-Warning "Failed to delete Push. This can indicate an invalid URL Token, that the password was not marked deletable, or that you are not the owner." + return $false + } else { + Write-Verbose "An exception was caught: $($_.Exception.Message)" + if ($DebugPreference -eq [System.Management.Automation.ActionPreference]::Continue) { + Set-Variable -Scope Global -Name PPPLastError -Value $_ + Write-Debug -Message 'Response object set to global variable $PPPLastError' + } + $_ + } + } + } +} From 90a4f9c4e206c603153dd89ec6f0aab6d43bd789 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 5 Jun 2024 17:46:55 -0400 Subject: [PATCH 26/54] Update Set-PwPushConfig.ps1 --- Modules/CippExtensions/Private/Set-PwPushConfig.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 b/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 index 72f1a0e5c9f8..0698cc53fe86 100644 --- a/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 +++ b/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 @@ -10,7 +10,7 @@ function Set-PwPushConfig { $null = Connect-AzAccount -Identity $ApiKey = (Get-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name 'PWPush' -AsPlainText) if ($ApiKey) { - $InitParams.ApiKey = $ApiKey + $InitParams.APIKey = $ApiKey $InitParams.EmailAddress = $Configuration.EmailAddress } } From b51d0dc53bcfa1084af3ca590e9cf2643af20b66 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 5 Jun 2024 17:51:30 -0400 Subject: [PATCH 27/54] Update Set-PwPushConfig.ps1 --- Modules/CippExtensions/Private/Set-PwPushConfig.ps1 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 b/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 index 0698cc53fe86..d43ffa888575 100644 --- a/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 +++ b/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 @@ -7,8 +7,13 @@ function Set-PwPushConfig { $InitParams.BaseUrl = $Configuration.BaseUrl } if ($Configuration.EmailAddress) { - $null = Connect-AzAccount -Identity - $ApiKey = (Get-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name 'PWPush' -AsPlainText) + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $ApiKey = (Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'PWPush' and RowKey eq 'PWPush'").APIKey + } else { + $null = Connect-AzAccount -Identity + $ApiKey = Get-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name 'PWPush' -AsPlainText + } if ($ApiKey) { $InitParams.APIKey = $ApiKey $InitParams.EmailAddress = $Configuration.EmailAddress From d856a6235dad91de2ec1eccce28026cb5487880d Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 6 Jun 2024 12:39:34 +0200 Subject: [PATCH 28/54] exclude legacy --- .../Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 index 83372682cbac..c0c6e0d17db3 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 @@ -18,11 +18,11 @@ function Invoke-CIPPStandardTransportRuleTemplate { if ($Existing) { Write-Host 'Found existing' $RequestParams | Add-Member -NotePropertyValue $RequestParams.name -NotePropertyName Identity - $GraphRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-TransportRule' -cmdParams ($RequestParams | Select-Object -Property * -ExcludeProperty GUID, Comments, HasSenderOverride, ExceptIfHasSenderOverride, ExceptIfMessageContainsDataClassifications, MessageContainsDataClassifications) -useSystemMailbox $true + $GraphRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-TransportRule' -cmdParams ($RequestParams | Select-Object -Property * -ExcludeProperty GUID, Comments, HasSenderOverride, ExceptIfHasSenderOverride, ExceptIfMessageContainsDataClassifications, MessageContainsDataClassifications, UseLegacyRegex) -useSystemMailbox $true Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully set transport rule for $tenant" -sev 'Info' } else { Write-Host 'Creating new' - $GraphRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'New-TransportRule' -cmdParams ($RequestParams | Select-Object -Property * -ExcludeProperty GUID, Comments, HasSenderOverride, ExceptIfHasSenderOverride, ExceptIfMessageContainsDataClassifications, MessageContainsDataClassifications) -useSystemMailbox $true + $GraphRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'New-TransportRule' -cmdParams ($RequestParams | Select-Object -Property * -ExcludeProperty GUID, Comments, HasSenderOverride, ExceptIfHasSenderOverride, ExceptIfMessageContainsDataClassifications, MessageContainsDataClassifications, UseLegacyRegex) -useSystemMailbox $true Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully created transport rule for $tenant" -sev 'Info' } From 0d20db2a06635368dd67873bb4ed6e9632c04625 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 6 Jun 2024 12:56:27 +0200 Subject: [PATCH 29/54] Create new backup functionality --- .../CIPP/Settings/Invoke-ExecRunBackup.ps1 | 33 ++--------------- Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 Modules/CIPPCore/Public/New-CIPPBackup.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRunBackup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRunBackup.ps1 index 5dd1070a2c49..ef8d564acdca 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRunBackup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRunBackup.ps1 @@ -12,36 +12,11 @@ Function Invoke-ExecRunBackup { $APIName = $TriggerMetadata.FunctionName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - try { - if ($request.query.Selected) { - $BackupTables = $request.query.Selected -split ',' - } else { - $BackupTables = @( - 'bpa' - 'Config' - 'Domains' - 'ExcludedLicenses' - 'templates' - 'standards' - 'SchedulerConfig' - ) - } - $CSVfile = foreach ($CSVTable in $BackupTables) { - $Table = Get-CippTable -tablename $CSVTable - Get-CIPPAzDataTableEntity @Table | Select-Object *, @{l = 'table'; e = { $CSVTable } } - } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created backup' -Sev 'Debug' - - $body = [pscustomobject]@{ - 'Results' = 'Created backup' - backup = $CSVfile - } - } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup: $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" } + $CSVfile = New-CIPPBackup -BackupType 'CIPP' + $body = [pscustomobject]@{ + 'Results' = 'Created backup' + backup = $CSVfile } - - # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 new file mode 100644 index 000000000000..3ec96fd0317e --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -0,0 +1,37 @@ +function New-CIPPBackup { + [CmdletBinding()] + param ( + $backupType, + $TenantFilter, + $APIName = 'CIPP Backup', + $ExecutingUser + ) + + $BackupData = switch ($backupType) { + #If backup type is CIPP, create CIPP backup. + 'CIPP' { + try { + $BackupTables = @( + 'bpa' + 'Config' + 'Domains' + 'ExcludedLicenses' + 'templates' + 'standards' + 'SchedulerConfig' + ) + $CSVfile = foreach ($CSVTable in $BackupTables) { + $Table = Get-CippTable -tablename $CSVTable + Get-CIPPAzDataTableEntity @Table | Select-Object *, @{l = 'table'; e = { $CSVTable } } + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created backup' -Sev 'Debug' + $CSVfile + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup: $($_.Exception.Message)" -Sev 'Error' + $body = [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" } + } + } + } + return $BackupData +} + From 8a9d51e6dc79d8cd7cdd788cba67f188880f65db Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 6 Jun 2024 14:16:14 +0200 Subject: [PATCH 30/54] add list backups --- .../CIPP/Core/Invoke-ExecListBackup.ps1 | 21 ++++++++++ .../Invoke-ListConditionalAccessPolicies.ps1 | 4 +- Modules/CIPPCore/Public/Get-CIPPBackup.ps1 | 14 +++++++ Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 38 ++++++++++++++++++- 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1 create mode 100644 Modules/CIPPCore/Public/Get-CIPPBackup.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1 new file mode 100644 index 000000000000..a0b21d1f8de9 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1 @@ -0,0 +1,21 @@ +using namespace System.Net + +Function Invoke-ExecAddAlert { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.Backup.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $Result = Get-CIPPBackup -type $Request.body.Type -TenantFilter $Request.body.TenantFilter + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API 'Alerts' -message $request.body.text -Sev $request.body.Severity + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @($Result) + }) + +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 index 337fc9c46ace..9c46ceae1ec4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 @@ -404,8 +404,8 @@ Function Invoke-ListConditionalAccessPolicies { $AllNamedLocations = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations' -tenantid $tenantfilter $AllApplications = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/applications' -tenantid $tenantfilter $AllRoleDefinitions = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions' -tenantid $tenantfilter - $GroupListOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups' -tenantid $tenantfilter - $UserListOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $tenantfilter | Select-Object * -ExcludeProperty *extensionAttribute* + $GroupListOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $tenantfilter + $UserListOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $tenantfilter | Select-Object * -ExcludeProperty *extensionAttribute* $GraphRequest = foreach ($cap in $ConditionalAccessPolicyOutput) { $temp = [PSCustomObject]@{ diff --git a/Modules/CIPPCore/Public/Get-CIPPBackup.ps1 b/Modules/CIPPCore/Public/Get-CIPPBackup.ps1 new file mode 100644 index 000000000000..e463202983a1 --- /dev/null +++ b/Modules/CIPPCore/Public/Get-CIPPBackup.ps1 @@ -0,0 +1,14 @@ +function Get-CIPPBackup { + [CmdletBinding()] + param ( + [string]$Type, + [string]$TenantFilter + ) + $Table = Get-CippTable -tablename "$($Type)Backup" + if ($TenantFilter) { + $Filter = "PartitionKey eq '$($Type)Backup' and TenantFilter eq '$($TenantFilter)'" + $Table.Filter = $Filter + } + $Info = Get-CIPPAzDataTableEntity @Table + return $info +} diff --git a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index 3ec96fd0317e..b0e960829858 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -2,6 +2,7 @@ function New-CIPPBackup { [CmdletBinding()] param ( $backupType, + $StorageOutput = 'default', $TenantFilter, $APIName = 'CIPP Backup', $ExecutingUser @@ -28,9 +29,44 @@ function New-CIPPBackup { $CSVfile } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup: $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" } + [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" } } } + + #If Backup type is ConditionalAccess, create Conditional Access backup. + 'ConditionalAccess' { + $ConditionalAccessPolicyOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $tenantfilter + $AllNamedLocations = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations' -tenantid $tenantfilter + switch ($StorageOutput) { + 'default' { + [PSCustomObject]@{ + ConditionalAccessPolicies = $ConditionalAccessPolicyOutput + NamedLocations = $AllNamedLocations + } + } + 'table' { + #Store output in tablestorage for Recovery + $RowKey = $TenantFilter + '_' + (Get-Date).ToString('yyyy-MM-dd-HHmm') + $entity = [PSCustomObject]@{ + PartitionKey = 'ConditionalAccessBackup' + RowKey = $RowKey + TenantFilter = $TenantFilter + Policies = [string]($ConditionalAccessPolicyOutput | ConvertTo-Json -Compress -Depth 10) + NamedLocations = [string]($AllNamedLocations | ConvertTo-Json -Compress -Depth 10) + } + $Table = Get-CippTable -tablename 'ConditionalAccessBackup' + try { + $Result = Add-CIPPAzDataTableEntity @Table -entity $entity -Force + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created backup for Conditional Access Policies' -Sev 'Debug' + $Result + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup for Conditional Access Policies: $($_.Exception.Message)" -Sev 'Error' + [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" } + } + } + } + } + } return $BackupData } From ea0dc89f329043d1c368345ff8c14daceb715e73 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 6 Jun 2024 17:26:44 +0200 Subject: [PATCH 31/54] Fixed add policy issue https://github.com/KelvinTegelaar/CIPP/issues/2497 --- .../HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 | 3 ++- Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 index a00bc8c60e64..2df6b1541877 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 @@ -27,6 +27,7 @@ Function Invoke-AddPolicy { try { switch ($Request.Body.TemplateType) { 'AppProtection' { + $PlatformType = 'deviceAppManagement' $TemplateType = ($RawJSON | ConvertFrom-Json).'@odata.type' -replace '#microsoft.graph.', '' $TemplateTypeURL = "$($TemplateType)s" $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenant @@ -85,7 +86,7 @@ Function Invoke-AddPolicy { } Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added policy $($Displayname)" -Sev 'Info' if ($AssignTo) { - Set-CIPPAssignedPolicy -GroupName $AssignTo -PolicyId $CreateRequest.id -Type $TemplateTypeURL -TenantFilter $tenant + Set-CIPPAssignedPolicy -GroupName $AssignTo -PolicyId $CreateRequest.id -Type $TemplateTypeURL -TenantFilter $tenant -PlatformType $PlatformType } "Successfully added policy for $($Tenant)" } catch { diff --git a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 index 25cc772e5802..15ecab0ea749 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 @@ -5,6 +5,7 @@ function Set-CIPPAssignedPolicy { $PolicyId, $Type, $TenantFilter, + $PlatformType = 'deviceManagement', $APIName = 'Assign Policy', $ExecutingUser ) @@ -69,7 +70,7 @@ function Set-CIPPAssignedPolicy { assignments = @($assignmentsObject) } if ($PSCmdlet.ShouldProcess($GroupName, "Assigning policy $PolicyId")) { - $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$Type('$($PolicyId)')/assign" -tenantid $tenantFilter -type POST -body ($assignmentsObject | ConvertTo-Json -Depth 10) + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/$($PlatformType)/$Type('$($PolicyId)')/assign" -tenantid $tenantFilter -type POST -body ($assignmentsObject | ConvertTo-Json -Depth 10) Write-LogMessage -user $ExecutingUser -API $APIName -message "Assigned Policy to $($GroupName)" -Sev 'Info' -tenant $TenantFilter } return "Assigned policy to $($GroupName) Policy ID is $($PolicyId)." From 4457008bb75fd0a2ce978e717b48e08a5ad50995 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 6 Jun 2024 18:43:59 +0200 Subject: [PATCH 32/54] add language option to autopilot profile --- .../Autopilot/Invoke-AddAutopilotConfig.ps1 | 23 +++++++++++++++---- .../Set-CIPPDefaultAPDeploymentProfile.ps1 | 3 ++- .../Standards/Invoke-CIPPStandardAPConfig.ps1 | 2 +- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAutopilotConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAutopilotConfig.ps1 index 38c9161bbabd..a87a2bcb7824 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAutopilotConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAutopilotConfig.ps1 @@ -19,16 +19,29 @@ Function Invoke-AddAutopilotConfig { # Input bindings are passed in via param block. $Tenants = ($Request.body | Select-Object Select_*).psobject.properties.value - $displayname = $request.body.Displayname - $description = $request.body.Description $AssignTo = if ($request.body.Assignto -ne 'on') { $request.body.Assignto } - $Profbod = $Request.body + $Profbod = [pscustomobject]$Request.body $usertype = if ($Profbod.NotLocalAdmin -eq 'true') { 'standard' } else { 'administrator' } $DeploymentMode = if ($profbod.DeploymentMode -eq 'true') { 'shared' } else { 'singleUser' } + $profileParams = @{ + displayname = $request.body.Displayname + description = $request.body.Description + usertype = $usertype + DeploymentMode = $DeploymentMode + assignto = $AssignTo + devicenameTemplate = $Profbod.deviceNameTemplate + allowWhiteGlove = $Profbod.allowWhiteGlove + CollectHash = $Profbod.collectHash + hideChangeAccount = $Profbod.hideChangeAccount + hidePrivacy = $Profbod.hidePrivacy + hideTerms = $Profbod.hideTerms + Autokeyboard = $Profbod.Autokeyboard + Language = $ProfBod.languages.value + } $results = foreach ($Tenant in $tenants) { - Set-CIPPDefaultAPDeploymentProfile -tenantFilter $tenant -displayname $displayname -description $description -usertype $usertype -DeploymentMode $DeploymentMode -assignto $AssignTo -devicenameTemplate $Profbod.deviceNameTemplate -allowWhiteGlove $Profbod.allowWhiteGlove -CollectHash $Profbod.collectHash -hideChangeAccount $Profbod.hideChangeAccount -hidePrivacy $Profbod.hidePrivacy -hideTerms $Profbod.hideTerms -Autokeyboard $Profbod.Autokeyboard + $profileParams['tenantFilter'] = $Tenant + Set-CIPPDefaultAPDeploymentProfile @profileParams } - $body = [pscustomobject]@{'Results' = $results } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 index d953ee938285..07f2646a7243 100644 --- a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 @@ -15,6 +15,7 @@ function Set-CIPPDefaultAPDeploymentProfile { $hideTerms, $Autokeyboard, $ExecutingUser, + $Language = 'os-default', $APIName = 'Add Default Enrollment Status Page' ) try { @@ -23,7 +24,7 @@ function Set-CIPPDefaultAPDeploymentProfile { 'displayName' = "$($displayname)" 'description' = "$($description)" 'deviceNameTemplate' = "$($DeviceNameTemplate)" - 'language' = 'os-default' + 'language' = "$($Language)" 'enableWhiteGlove' = $([bool]($allowWhiteGlove)) 'deviceType' = 'windowsPc' 'extractHardwareHash' = $([bool]($CollectHash)) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAPConfig.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAPConfig.ps1 index 6311c6680421..b6ffea3946d5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAPConfig.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAPConfig.ps1 @@ -12,7 +12,7 @@ function Invoke-CIPPStandardAPConfig { Write-Host $($settings | ConvertTo-Json -Depth 100) if ($settings.NotLocalAdmin -eq $true) { $usertype = 'Standard' } else { $usertype = 'Administrator' } $DeploymentMode = if ($settings.DeploymentMode -eq 'true') { 'shared' } else { 'singleUser' } - Set-CIPPDefaultAPDeploymentProfile -tenantFilter $tenant -displayname $settings.DisplayName -description $settings.Description -usertype $usertype -DeploymentMode $DeploymentMode -assignto $settings.Assignto -devicenameTemplate $Settings.DeviceNameTemplate -allowWhiteGlove $Settings.allowWhiteGlove -CollectHash $Settings.CollectHash -hideChangeAccount $Settings.HideChangeAccount -hidePrivacy $Settings.HidePrivacy -hideTerms $Settings.HideTerms -Autokeyboard $Settings.Autokeyboard + Set-CIPPDefaultAPDeploymentProfile -tenantFilter $tenant -displayname $settings.DisplayName -description $settings.Description -usertype $usertype -DeploymentMode $DeploymentMode -assignto $settings.Assignto -devicenameTemplate $Settings.DeviceNameTemplate -allowWhiteGlove $Settings.allowWhiteGlove -CollectHash $Settings.CollectHash -hideChangeAccount $Settings.HideChangeAccount -hidePrivacy $Settings.HidePrivacy -hideTerms $Settings.HideTerms -Autokeyboard $Settings.Autokeyboard -Language $Settings.languages.value } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message #Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create Default Autopilot config: $ErrorMessage" -sev 'Error' From d97a92f33ed863f09b06b7a252e0fc3f36b9cf9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Thu, 6 Jun 2024 20:40:45 +0200 Subject: [PATCH 33/54] Fex spamfilter thingys not working --- .../Email-Exchange/Invoke-EditSpamFilter.ps1 | 9 ++++----- Modules/CIPPCore/Public/Invoke-RemoveSpamfilter.ps1 | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-EditSpamFilter.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-EditSpamFilter.ps1 index 1c638ab96ea1..931299cb6102 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-EditSpamFilter.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-EditSpamFilter.ps1 @@ -20,13 +20,12 @@ Function Invoke-EditSpamFilter { try { $cmdlet = if ($request.query.state -eq 'enable') { 'Enable-HostedContentFilterRule' } else { 'Disable-HostedContentFilterRule' } - $GraphRequest = New-ExoRequest -tenantid $Tenantfilter -cmdlet $cmdlet -cmdParams $params + $GraphRequest = New-ExoRequest -tenantid $Tenantfilter -cmdlet $cmdlet -cmdParams $params -useSystemmailbox $true $Result = "Set Spamfilter rule to $($request.query.State)" Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenantfilter -message "Set Spamfilter rule $($Request.query.name) to $($request.query.State)" -sev Info - } - catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenantfilter -message "Failed setting Spamfilter rule $($Request.query.guid) to $($request.query.State). Error:$($_.Exception.Message)" -Sev 'Error' - $ErrorMessage = Get-NormalizedError -Message $_.Exception + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenantfilter -message "Failed setting Spamfilter rule $($Request.query.guid) to $($request.query.State). Error:$ErrorMessage" -Sev 'Error' $Result = $ErrorMessage } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/Invoke-RemoveSpamfilter.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveSpamfilter.ps1 index a1b05e12c312..55f525aa36f2 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveSpamfilter.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveSpamfilter.ps1 @@ -14,20 +14,20 @@ Function Invoke-RemoveSpamfilter { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' $Tenantfilter = $request.Query.tenantfilter - $Params = @{ Identity = $request.query.name } try { $cmdlet = 'Remove-HostedContentFilterRule' - $GraphRequest = New-ExoRequest -tenantid $Tenantfilter -cmdlet $cmdlet -cmdParams $params + $GraphRequest = New-ExoRequest -tenantid $Tenantfilter -cmdlet $cmdlet -cmdParams $params -useSystemmailbox $true $cmdlet = 'Remove-HostedContentFilterPolicy' - $GraphRequest = New-ExoRequest -tenantid $Tenantfilter -cmdlet $cmdlet -cmdParams $params + $GraphRequest = New-ExoRequest -tenantid $Tenantfilter -cmdlet $cmdlet -cmdParams $params -useSystemmailbox $true $Result = "Deleted $($Request.query.name)" Write-LogMessage -API 'TransportRules' -tenant $tenantfilter -message "Deleted transport rule $($Request.query.name)" -sev Debug } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception + Write-LogMessage -API 'TransportRules' -tenant $tenantfilter -message "Failed deleting transport rule $($Request.query.name). Error:$ErrorMessage" -Sev Error $Result = $ErrorMessage } # Associate values to output bindings by calling 'Push-OutputBinding'. From 95715e340df2caa043767f1014f98162023285c4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 6 Jun 2024 22:14:38 +0200 Subject: [PATCH 34/54] fixes https://github.com/KelvinTegelaar/CIPP/issues/2521 --- ...voke-ListUserConditionalAccessPolicies.ps1 | 28 +++++++++++-------- .../Invoke-CIPPStandardEnableMailTips.ps1 | 2 +- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 index dd0ced4e4e68..c56760788f20 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 @@ -22,20 +22,24 @@ Function Invoke-ListUserConditionalAccessPolicies { $UserID = $Request.Query.UserID try { - $json = '{"conditions":{"users":{"allUsers":2,"included":{"userIds":["' + $UserID + '"],"groupIds":[]},"excluded":{"userIds":[],"groupIds":[]}},"servicePrincipals":{"allServicePrincipals":1,"includeAllMicrosoftApps":false,"excludeAllMicrosoftApps":false,"userActions":[],"stepUpTags":[]},"conditions":{"minUserRisk":{"noRisk":false,"lowRisk":false,"mediumRisk":false,"highRisk":false,"applyCondition":false},"minSigninRisk":{"noRisk":false,"lowRisk":false,"mediumRisk":false,"highRisk":false,"applyCondition":false},"servicePrincipalRiskLevels":{"noRisk":false,"lowRisk":false,"mediumRisk":false,"highRisk":false,"applyCondition":false},"devicePlatforms":{"all":2,"included":{"android":false,"ios":false,"windowsPhone":false,"windows":false,"macOs":false,"linux":false},"excluded":null,"applyCondition":false},"locations":{"applyCondition":true,"includeLocationType":2,"excludeAllTrusted":false},"clientApps":{"applyCondition":false,"specificClientApps":false,"webBrowsers":false,"exchangeActiveSync":false,"onlyAllowSupportedPlatforms":false,"mobileDesktop":false},"clientAppsV2":{"applyCondition":false,"webBrowsers":false,"mobileDesktop":false,"modernAuth":false,"exchangeActiveSync":false,"onlyAllowSupportedPlatforms":false,"otherClients":false},"deviceState":{"includeDeviceStateType":1,"excludeDomainJoionedDevice":false,"excludeCompliantDevice":false,"applyCondition":true}}},"country":"","device":{}}' - $ConditionalAccessPolicyOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $tenantfilter - } catch { - $ConditionalAccessPolicyOutput = @{} - } - - $GraphRequest = foreach ($cap in $ConditionalAccessPolicyOutput) { - if ($cap.id -in $UserPolicies.policyId) { - $temp = [PSCustomObject]@{ - id = $cap.id - displayName = $cap.displayName + $IncludeApplications = 'All' + $CAContext = @{ + '@odata.type' = '#microsoft.graph.whatIfApplicationContext' + 'includeApplications' = @($IncludeApplications) + } + $ConditionalAccessWhatIfDefinition = @{ + 'conditionalAccessWhatIfSubject' = @{ + '@odata.type' = '#microsoft.graph.userSubject' + 'userId' = "$userId" } - $temp + 'conditionalAccessContext' = $CAContext + 'conditionalAccessWhatIfConditions' = @{} } + $JSONBody = $ConditionalAccessWhatIfDefinition | ConvertTo-Json -Depth 10 + + $GraphRequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $tenantfilter + } catch { + $GraphRequest = @{} } Write-Host $GraphRequest diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 index 516af7e005ed..05ab9cfbd117 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 @@ -14,7 +14,7 @@ function Invoke-CIPPStandardEnableMailTips { Write-LogMessage -API 'Standards' -tenant $Tenant -message 'All MailTips are already enabled.' -sev Info } else { try { - New-ExoRequest -tenantid $Tenant -cmdlet 'Set-OrganizationConfig' -cmdparams @{ MailTipsAllTipsEnabled = $true; MailTipsExternalRecipientsTipsEnabled = $true; MailTipsGroupMetricsEnabled = $true; MailTipsLargeAudienceThreshold = $Settings.MailTipsLargeAudienceThreshold } + New-ExoRequest -useSystemMailbox $true -tenantid $Tenant -cmdlet 'Set-OrganizationConfig' -cmdparams @{ MailTipsAllTipsEnabled = $true; MailTipsExternalRecipientsTipsEnabled = $true; MailTipsGroupMetricsEnabled = $true; MailTipsLargeAudienceThreshold = $Settings.MailTipsLargeAudienceThreshold } Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Enabled all MailTips' -sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message From 6ca472c12f6d71caf304632f60c00f43da306bf8 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 6 Jun 2024 22:21:02 +0200 Subject: [PATCH 35/54] applied ca policies --- .../Users/Invoke-ListUserConditionalAccessPolicies.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 index c56760788f20..c717f77589f1 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserConditionalAccessPolicies.ps1 @@ -22,7 +22,7 @@ Function Invoke-ListUserConditionalAccessPolicies { $UserID = $Request.Query.UserID try { - $IncludeApplications = 'All' + $IncludeApplications = '67ad5377-2d78-4ac2-a867-6300cda00e85' $CAContext = @{ '@odata.type' = '#microsoft.graph.whatIfApplicationContext' 'includeApplications' = @($IncludeApplications) @@ -37,7 +37,7 @@ Function Invoke-ListUserConditionalAccessPolicies { } $JSONBody = $ConditionalAccessWhatIfDefinition | ConvertTo-Json -Depth 10 - $GraphRequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $tenantfilter + $GraphRequest = (New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/evaluate' -tenantid $tenantFilter -type POST -body $JsonBody -AsApp $true).value } catch { $GraphRequest = @{} } From 5c91b0e53546dc9b16be3e33496c9be1efafd866 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 6 Jun 2024 23:23:56 +0200 Subject: [PATCH 36/54] allows sam to retry after error --- .../HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 index e61a272a80ac..5da4b48da857 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 @@ -13,13 +13,22 @@ Function Invoke-ExecSAMSetup { $UserCreds = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($request.headers.'x-ms-client-principal')) | ConvertFrom-Json) if ($Request.query.error) { Add-Type -AssemblyName System.Web + $ErrorCode = Get-normalizedError -Message [System.Web.HttpUtility]::UrlDecode($Request.Query.error_description) + if ($Request.Query.ErrorCount -lt 2) { + $NewUrl = "$($Request.headers.'x-ms-original-url')&Errors=$($Request.Query.ErrorCount + 1)" + $body = 'An error occurred. We will try again in 3 seconds. The received error was {0}. Reloading... ' -f $ErrorCode, $NewUrl + } else { + $body = 'An error occurred, and we have reached the maximum amount of retries. The received error was {0}. ' -f $ErrorCode + } + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ ContentType = 'text/html' StatusCode = [HttpStatusCode]::Forbidden - Body = Get-normalizedError -Message [System.Web.HttpUtility]::UrlDecode($Request.Query.error_description) + Body = $body }) exit } + if ('admin' -notin $UserCreds.userRoles) { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ ContentType = 'text/html' From e7dde84076b8cc4efdd1bd8786f65a18b8a64813 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Fri, 7 Jun 2024 00:06:35 +0200 Subject: [PATCH 37/54] Add or update the Azure App Service build and deployment workflow config --- .github/workflows/dev_cippdb5ta.yml | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/dev_cippdb5ta.yml diff --git a/.github/workflows/dev_cippdb5ta.yml b/.github/workflows/dev_cippdb5ta.yml new file mode 100644 index 000000000000..05a8b1d3e70a --- /dev/null +++ b/.github/workflows/dev_cippdb5ta.yml @@ -0,0 +1,30 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Powershell project to Azure Function App - cippdb5ta + +on: + push: + branches: + - dev + workflow_dispatch: + +env: + AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root + +jobs: + deploy: + runs-on: windows-latest + + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v4 + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: 'cippdb5ta' + slot-name: 'Production' + package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_B9812E9D29B34E42AA8D226BAE4E9147 }} \ No newline at end of file From 2b4d6afd5a168654c69bb04f9aea116e33aaf51e Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Fri, 7 Jun 2024 00:07:52 +0200 Subject: [PATCH 38/54] Delete .github/workflows/dev_cippdb5ta.yml --- .github/workflows/dev_cippdb5ta.yml | 30 ----------------------------- 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/dev_cippdb5ta.yml diff --git a/.github/workflows/dev_cippdb5ta.yml b/.github/workflows/dev_cippdb5ta.yml deleted file mode 100644 index 05a8b1d3e70a..000000000000 --- a/.github/workflows/dev_cippdb5ta.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action -# More GitHub Actions for Azure: https://github.com/Azure/actions - -name: Build and deploy Powershell project to Azure Function App - cippdb5ta - -on: - push: - branches: - - dev - workflow_dispatch: - -env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root - -jobs: - deploy: - runs-on: windows-latest - - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v4 - - - name: 'Run Azure Functions Action' - uses: Azure/functions-action@v1 - id: fa - with: - app-name: 'cippdb5ta' - slot-name: 'Production' - package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} - publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_B9812E9D29B34E42AA8D226BAE4E9147 }} \ No newline at end of file From 2a8067ca3dfdd1f114045ef3fb57aabd6e47f9ba Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 7 Jun 2024 00:21:34 +0200 Subject: [PATCH 39/54] SAM Wizard update --- .../HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 index 5da4b48da857..0fecfa4027e1 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 @@ -14,7 +14,7 @@ Function Invoke-ExecSAMSetup { if ($Request.query.error) { Add-Type -AssemblyName System.Web $ErrorCode = Get-normalizedError -Message [System.Web.HttpUtility]::UrlDecode($Request.Query.error_description) - if ($Request.Query.ErrorCount -lt 2) { + if ($Request.Query.ErrorCount -lt 2 -or $Request.Query.ErrorCount -eq $null) { $NewUrl = "$($Request.headers.'x-ms-original-url')&Errors=$($Request.Query.ErrorCount + 1)" $body = 'An error occurred. We will try again in 3 seconds. The received error was {0}. Reloading... ' -f $ErrorCode, $NewUrl } else { From de1cbe691e257e73bb91a77c10eaf27783f083d0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 7 Jun 2024 00:37:38 +0200 Subject: [PATCH 40/54] added retry logic --- .../CIPP/Setup/Invoke-ExecSAMSetup.ps1 | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 index 0fecfa4027e1..d423e863ff0c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 @@ -13,22 +13,13 @@ Function Invoke-ExecSAMSetup { $UserCreds = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($request.headers.'x-ms-client-principal')) | ConvertFrom-Json) if ($Request.query.error) { Add-Type -AssemblyName System.Web - $ErrorCode = Get-normalizedError -Message [System.Web.HttpUtility]::UrlDecode($Request.Query.error_description) - if ($Request.Query.ErrorCount -lt 2 -or $Request.Query.ErrorCount -eq $null) { - $NewUrl = "$($Request.headers.'x-ms-original-url')&Errors=$($Request.Query.ErrorCount + 1)" - $body = 'An error occurred. We will try again in 3 seconds. The received error was {0}. Reloading... ' -f $ErrorCode, $NewUrl - } else { - $body = 'An error occurred, and we have reached the maximum amount of retries. The received error was {0}. ' -f $ErrorCode - } - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ ContentType = 'text/html' StatusCode = [HttpStatusCode]::Forbidden - Body = $body + Body = Get-normalizedError -Message [System.Web.HttpUtility]::UrlDecode($Request.Query.error_description) }) exit } - if ('admin' -notin $UserCreds.userRoles) { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ ContentType = 'text/html' @@ -90,18 +81,27 @@ Function Invoke-ExecSAMSetup { if ($Request.query.error -eq 'invalid_client') { $Results = 'Client ID was not found in Azure. Try waiting 10 seconds to try again, if you have gotten this error after 5 minutes, please restart the process.' } if ($request.query.code) { try { - $TenantId = $Rows.tenantid - if (!$TenantId) { $TenantId = $ENV:TenantId } - $AppID = $Rows.appid - if (!$AppID) { $appid = $env:ApplicationId } - $URL = ($Request.headers.'x-ms-original-url').split('?') | Select-Object -First 1 - if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { - $clientsecret = $Secret.ApplicationSecret - } else { - $clientsecret = Get-AzKeyVaultSecret -VaultName $kv -Name 'applicationsecret' -AsPlainText - } - if (!$clientsecret) { $clientsecret = $ENV:ApplicationSecret } - $RefreshToken = Invoke-RestMethod -Method POST -Body "client_id=$appid&scope=https://graph.microsoft.com/.default+offline_access+openid+profile&code=$($request.query.code)&grant_type=authorization_code&redirect_uri=$($url)&client_secret=$clientsecret" -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" + $tries = 0 + do { + $tries ++ + $TenantId = $Rows.tenantid + if (!$TenantId) { $TenantId = $ENV:TenantId } + $AppID = $Rows.appid + if (!$AppID) { $appid = $env:ApplicationId } + $URL = ($Request.headers.'x-ms-original-url').split('?') | Select-Object -First 1 + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { + $clientsecret = $Secret.ApplicationSecret + } else { + $clientsecret = Get-AzKeyVaultSecret -VaultName $kv -Name 'applicationsecret' -AsPlainText + } + if (!$clientsecret) { $clientsecret = $ENV:ApplicationSecret } + try { + $RefreshToken = Invoke-RestMethod -Method POST -Body "client_id=$appid&scope=https://graph.microsoft.com/.default+offline_access+openid+profile&code=$($request.query.code)&grant_type=authorization_code&redirect_uri=$($url)&client_secret=$clientsecret" -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" + } catch { + $RefreshToken = $null + } + if ($tries -ge 1) { Start-Sleep -Seconds 1 } + } until ($RefreshToken.access_token -or $tries -gt 3) if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { $Secret.RefreshToken = $RefreshToken.refresh_token @@ -109,7 +109,7 @@ Function Invoke-ExecSAMSetup { } else { Set-AzKeyVaultSecret -VaultName $kv -Name 'RefreshToken' -SecretValue (ConvertTo-SecureString -String $RefreshToken.refresh_token -AsPlainText -Force) } - + $Results = 'Authentication is now complete. You may now close this window.' try { $SetupPhase = $rows.validated = $true From 0a94610c6bf5fbb1cb59a7410eddd25d782860e1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 7 Jun 2024 00:47:28 +0200 Subject: [PATCH 41/54] solving auth issues --- .../HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 index d423e863ff0c..1f7a4fe3ccf8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 @@ -84,6 +84,7 @@ Function Invoke-ExecSAMSetup { $tries = 0 do { $tries ++ + $Auth = Get-CIPPAuthentication $TenantId = $Rows.tenantid if (!$TenantId) { $TenantId = $ENV:TenantId } $AppID = $Rows.appid @@ -101,7 +102,7 @@ Function Invoke-ExecSAMSetup { $RefreshToken = $null } if ($tries -ge 1) { Start-Sleep -Seconds 1 } - } until ($RefreshToken.access_token -or $tries -gt 3) + } until ($RefreshToken.refresh_token -or $tries -gt 3) if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { $Secret.RefreshToken = $RefreshToken.refresh_token From 2a8c3bc074cc2af5aecf9eeedc5591c162b90ff0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 7 Jun 2024 00:56:49 +0200 Subject: [PATCH 42/54] tmp --- .../CIPP/Setup/Invoke-ExecSAMSetup.ps1 | 37 +++++++------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 index 1f7a4fe3ccf8..3818794ddbb0 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 @@ -81,28 +81,19 @@ Function Invoke-ExecSAMSetup { if ($Request.query.error -eq 'invalid_client') { $Results = 'Client ID was not found in Azure. Try waiting 10 seconds to try again, if you have gotten this error after 5 minutes, please restart the process.' } if ($request.query.code) { try { - $tries = 0 - do { - $tries ++ - $Auth = Get-CIPPAuthentication - $TenantId = $Rows.tenantid - if (!$TenantId) { $TenantId = $ENV:TenantId } - $AppID = $Rows.appid - if (!$AppID) { $appid = $env:ApplicationId } - $URL = ($Request.headers.'x-ms-original-url').split('?') | Select-Object -First 1 - if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { - $clientsecret = $Secret.ApplicationSecret - } else { - $clientsecret = Get-AzKeyVaultSecret -VaultName $kv -Name 'applicationsecret' -AsPlainText - } - if (!$clientsecret) { $clientsecret = $ENV:ApplicationSecret } - try { - $RefreshToken = Invoke-RestMethod -Method POST -Body "client_id=$appid&scope=https://graph.microsoft.com/.default+offline_access+openid+profile&code=$($request.query.code)&grant_type=authorization_code&redirect_uri=$($url)&client_secret=$clientsecret" -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" - } catch { - $RefreshToken = $null - } - if ($tries -ge 1) { Start-Sleep -Seconds 1 } - } until ($RefreshToken.refresh_token -or $tries -gt 3) + $TenantId = $Rows.tenantid + if (!$TenantId) { $TenantId = $ENV:TenantId } + $AppID = $Rows.appid + if (!$AppID) { $appid = $env:ApplicationId } + $URL = ($Request.headers.'x-ms-original-url').split('?') | Select-Object -First 1 + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { + $clientsecret = $Secret.ApplicationSecret + } else { + $clientsecret = Get-AzKeyVaultSecret -VaultName $kv -Name 'applicationsecret' -AsPlainText + } + if (!$clientsecret) { $clientsecret = $ENV:ApplicationSecret } + Write-Host "client_id=$appid&scope=https://graph.microsoft.com/.default+offline_access+openid+profile&code=$($request.query.code)&grant_type=authorization_code&redirect_uri=$($url)&client_secret=$clientsecret" -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" + $RefreshToken = Invoke-RestMethod -Method POST -Body "client_id=$appid&scope=https://graph.microsoft.com/.default+offline_access+openid+profile&code=$($request.query.code)&grant_type=authorization_code&redirect_uri=$($url)&client_secret=$clientsecret" -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { $Secret.RefreshToken = $RefreshToken.refresh_token @@ -110,7 +101,7 @@ Function Invoke-ExecSAMSetup { } else { Set-AzKeyVaultSecret -VaultName $kv -Name 'RefreshToken' -SecretValue (ConvertTo-SecureString -String $RefreshToken.refresh_token -AsPlainText -Force) } - + $Results = 'Authentication is now complete. You may now close this window.' try { $SetupPhase = $rows.validated = $true From 1fac3d8e2ccffd2355e0af775355ab534c9f52c5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 6 Jun 2024 20:56:57 -0400 Subject: [PATCH 43/54] Alert webhook tweaks --- .../Push-PublicWebhookProcess.ps1 | 2 +- .../CIPPCore/Public/Get-SlackAlertBlocks.ps1 | 235 ++++++++++++++++++ .../Public/Invoke-CIPPWebhookProcessing.ps1 | 8 +- .../CIPPCore/Public/New-CIPPAlertTemplate.ps1 | 31 +-- Modules/CIPPCore/Public/Send-CIPPAlert.ps1 | 14 +- 5 files changed, 267 insertions(+), 23 deletions(-) create mode 100644 Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 index cb8e46acde61..0669c01fabfa 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 @@ -11,7 +11,7 @@ function Push-PublicWebhookProcess { if ($Webhook.Type -eq 'GraphSubscription') { Invoke-CippGraphWebhookProcessing -Data ($Webhook.Data | ConvertFrom-Json) -CIPPID $Webhook.CIPPID -WebhookInfo ($Webhook.Webhookinfo | ConvertFrom-Json) } elseif ($Webhook.Type -eq 'AuditLog') { - Invoke-CippWebhookProcessing -TenantFilter $Webhook.TenantFilter -Data ($Webhook.Data | ConvertFrom-Json) -CIPPPURL $Webhook.CIPPURL + Invoke-CippWebhookProcessing -TenantFilter $Webhook.TenantFilter -Data ($Webhook.Data | ConvertFrom-Json) -CIPPURL $Webhook.CIPPURL } elseif ($Webhook.Type -eq 'PartnerCenter') { Invoke-CippPartnerWebhookProcessing -Data ($Webhook.Data | ConvertFrom-Json) } diff --git a/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 b/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 new file mode 100644 index 000000000000..ce4dfc21bff0 --- /dev/null +++ b/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 @@ -0,0 +1,235 @@ +function Get-SlackAlertBlocks { + [CmdletBinding()] + Param( + $JSONBody + ) + + $Blocks = [system.collections.generic.list[object]]::new() + + $Payload = $JSONBody | ConvertFrom-Json + + if ($Payload.API -eq 'Alerts') { + foreach ($Entry in $Payload) { + # Alert log alerts + $Blocks.Add([PSCustomObject]@{ + type = 'header' + text = @{ + type = 'plain_text' + text = 'New Alert from CIPP' + emoji = $true + } + }) + $Blocks.Add([PSCustomObject]@{ + type = 'section' + fields = @( + @{ + type = 'mrkdwn' + text = "*Tenant:*`n" + $Entry.Tenant + }, + @{ + type = 'mrkdwn' + text = "*Username:*`n" + $Entry.Username + }, + @{ + type = 'mrkdwn' + text = "*Timestamp:*`n" + ($Entry.Timestamp | Get-Date).ToString('yyyy-MM-dd @ hh:mm:ss tt') + } + ) + }) + + $Blocks.Add([PSCustomObject]@{ + type = 'section' + text = @{ + type = 'mrkdwn' + text = "*Message:*`n" + $Entry.Message + } + }) + } + } elseif ($Payload.TaskInfo -is [object]) { + #Scheduler + $Blocks.Add([PSCustomObject]@{ + type = 'header' + text = @{ + type = 'plain_text' + text = 'New Alert from CIPP' + emoji = $true + } + }) + $Blocks.Add([PSCustomObject]@{ + type = 'section' + text = @{ + type = 'mrkdwn' + text = "*Task Name:*`n" + $Payload.TaskInfo.Name + } + }) + $Blocks.Add([PSCustomObject]@{ + type = 'section' + fields = @( + @{ + type = 'mrkdwn' + text = "*Timestamp:*`n" + ($Payload.TaskInfo.Timestamp | Get-Date).ToString('yyyy-MM-dd @ hh:mm:ss tt') + }, + @{ + type = 'mrkdwn' + text = "*Tenant:*`n" + $Payload.Tenant + } + ) + }) + $Blocks.Add([PSCustomObject]@{ + type = 'divider' + }) + foreach ($Result in $Payload.Results) { + # Check if results is [string] and create text section + if ($Result -is [string]) { + $Blocks.Add([PSCustomObject]@{ + type = 'section' + text = @{ + type = 'mrkdwn' + text = $Result + } + }) + } else { + #Iterate through property names and create fields + $Fields = [system.collections.generic.list[object]]::new() + foreach ($Key in $Result.PSObject.Properties.Name) { + $Fields.Add(@{ + type = 'mrkdwn' + text = "*$($Key):*`n" + $Result.$Key + }) + } + $Blocks.Add([PSCustomObject]@{ + type = 'section' + fields = @($Fields) + + }) + } + } + } elseif ($Payload.RawData -is [object]) { + # Webhook alert + $Blocks.Add([PSCustomObject]@{ + type = 'header' + text = @{ + type = 'plain_text' + text = 'New Alert from CIPP' + emoji = $true + } + }) + $Blocks.Add([PSCustomObject]@{ + type = 'section' + text = @{ + type = 'mrkdwn' + text = "*Title:*`n" + $Payload.Title + } + }) + $Blocks.Add([PSCustomObject]@{ + type = 'section' + text = @{ + type = 'mrkdwn' + text = "*Action URL:*`n<" + $Payload.ActionUrl + '|Go to CIPP>' + } + }) + $Blocks.Add([PSCustomObject]@{ + type = 'divider' + }) + + $Blocks.Add([PSCustomObject]@{ + type = 'section' + text = @{ + type = 'mrkdwn' + text = '*Webhook Data:*' + } + }) + #loop through rawdata properties and create key value fields + $Fields = [system.collections.generic.list[object]]::new() + foreach ($Key in $Payload.RawData.PSObject.Properties.Name) { + # if value is json continue to next property + if ($Payload.RawData.$Key -is [string] -and ![string]::IsNullOrEmpty($Payload.RawData.$Key) -and (Test-Json -Json $Payload.RawData.$Key -ErrorAction SilentlyContinue)) { + continue + } + + if ([string]::IsNullOrEmpty($Payload.RawData.$Key)) { + continue + } + # if value is date object + if ($Payload.RawData.$Key -is [datetime]) { + $Fields.Add(@{ + type = 'mrkdwn' + text = "*$($Key):*`n" + $Payload.RawData.$Key.ToString('yyyy-MM-dd @ hh:mm:ss tt') + }) + } elseif ($Payload.RawData.$Key.PSObject.Properties.Name -is [array] -and $Payload.RawData.$Key.PSObject.Properties.Name.Count -gt 0) { + foreach ($SubKey in $Payload.RawData.$Key.PSObject.Properties.Name) { + if ([string]::IsNullOrEmpty($Payload.RawData.$Key.$SubKey)) { + continue + } elseif ($Payload.RawData.$Key.$SubKey -is [datetime]) { + $Fields.Add(@{ + type = 'mrkdwn' + text = "*$($Key)/$($SubKey):*`n" + $Payload.RawData.$Key.$SubKey.ToString('yyyy-MM-dd @ hh:mm:ss tt') + }) + } else { + $Fields.Add(@{ + type = 'mrkdwn' + text = "*$($Key)/$($SubKey):*`n" + $Payload.RawData.$Key.$SubKey + }) + } + } + } else { + $Fields.Add(@{ + type = 'mrkdwn' + text = "*$($Key):*`n" + $Payload.RawData.$Key + }) + } + } + + $FieldBatch = [system.collections.generic.list[object]]::new() + for ($i = 0; $i -lt $Fields.Count; $i += 10) { + $FieldBatch.Add($Fields[$i..[math]::Min($i + 9, $Fields.Count - 1)]) + } + foreach ($Batch in $FieldBatch) { + $Blocks.Add([PSCustomObject]@{ + type = 'section' + fields = @($Batch) + }) + } + + # if potentiallocationinfo is present + if ($Payload.PotentialLocationInfo) { + # add divider + $Blocks.Add([PSCustomObject]@{ + type = 'divider' + }) + # add text section for location + $Blocks.Add([PSCustomObject]@{ + type = 'section' + text = @{ + type = 'mrkdwn' + text = '*Potential Location Info:*' + } + }) + # loop through location properties and add fields + $LocationFields = [system.collections.generic.list[object]]::new() + foreach ($Key in $Payload.PotentialLocationInfo.PSObject.Properties.Name) { + $LocationFields.Add(@{ + type = 'mrkdwn' + text = "*$($Key):*`n" + $Payload.PotentialLocationInfo.$Key + }) + } + # add fields to section in groups of 10 + $LocationFieldBatch = [system.collections.generic.list[object]]::new() + for ($i = 0; $i -lt $LocationFields.Count; $i += 10) { + $LocationFieldBatch.Add($LocationFields[$i..[math]::Min($i + 9, $LocationFields.Count - 1)]) + } + foreach ($Batch in $LocationFieldBatch) { + $Blocks.Add([PSCustomObject]@{ + type = 'section' + fields = @($Batch) + }) + } + } + } + + if (($Blocks | Measure-Object).Count -gt 0) { + [PSCustomObject]@{ + blocks = $Blocks + } | ConvertTo-Json -Depth 10 + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 index 402e91d285ff..d9a5ec18d4c3 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 @@ -5,7 +5,7 @@ function Invoke-CippWebhookProcessing { $Data, $Resource, $Operations, - $CIPPPURL, + $CIPPURL, $APIName = 'Process webhook', $ExecutingUser ) @@ -62,19 +62,19 @@ function Invoke-CippWebhookProcessing { switch ($action) { 'generatemail' { Write-Host 'Going to create the email' - $GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -ActionResults $ActionResults + $GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -ActionResults $ActionResults -CIPPURL $CIPPURL Write-Host 'Going to send the mail' 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 -ActionResults $ActionResults + $GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -ActionResults $ActionResults -CIPPURL $CIPPURL Send-CIPPAlert -Type 'psa' -Title $GenerateEmail.title -HTMLContent $GenerateEmail.htmlcontent -TenantFilter $TenantFilter } 'generateWebhook' { Write-Host 'Generating the webhook content' $LocationInfo = $Data.CIPPLocationInfo | ConvertFrom-Json -ErrorAction SilentlyContinue - $GenerateJSON = New-CIPPAlertTemplate -format 'json' -data $Data -ActionResults $ActionResults + $GenerateJSON = New-CIPPAlertTemplate -format 'json' -data $Data -ActionResults $ActionResults -CIPPURL $CIPPURL $JsonContent = @{ Title = $GenerateJSON.Title ActionUrl = $GenerateJSON.ButtonUrl diff --git a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 index c502a2ea1bf7..a394435e225f 100644 --- a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 @@ -5,7 +5,8 @@ function New-CIPPAlertTemplate { [Parameter(Mandatory = $true)] $Format, $LocationInfo, - $ActionResults + $ActionResults, + $CIPPURL ) $Appname = '[{"Application Name":"ACOM Azure Website","Application IDs":"23523755-3a2b-41ca-9315-f81f3f566a95"},{"Application Name":"AEM-DualAuth","Application IDs":"69893ee3-dd10-4b1c-832d-4870354be3d8"},{"Application Name":"ASM Campaign Servicing","Application IDs":"0cb7b9ec-5336-483b-bc31-b15b5788de71"},{"Application Name":"Azure Advanced Threat Protection","Application IDs":"7b7531ad-5926-4f2d-8a1d-38495ad33e17"},{"Application Name":"Azure Data Lake","Application IDs":"e9f49c6b-5ce5-44c8-925d-015017e9f7ad"},{"Application Name":"Azure Lab Services Portal","Application IDs":"835b2a73-6e10-4aa5-a979-21dfda45231c"},{"Application Name":"Azure Portal","Application IDs":"c44b4083-3bb0-49c1-b47d-974e53cbdf3c"},{"Application Name":"AzureSupportCenter","Application IDs":"37182072-3c9c-4f6a-a4b3-b3f91cacffce"},{"Application Name":"Bing","Application IDs":"9ea1ad79-fdb6-4f9a-8bc3-2b70f96e34c7"},{"Application Name":"CPIM Service","Application IDs":"bb2a2e3a-c5e7-4f0a-88e0-8e01fd3fc1f4"},{"Application Name":"CRM Power BI Integration","Application IDs":"e64aa8bc-8eb4-40e2-898b-cf261a25954f"},{"Application Name":"Dataverse","Application IDs":"00000007-0000-0000-c000-000000000000"},{"Application Name":"Enterprise Roaming and Backup","Application IDs":"60c8bde5-3167-4f92-8fdb-059f6176dc0f"},{"Application Name":"IAM Supportability","Application IDs":"a57aca87-cbc0-4f3c-8b9e-dc095fdc8978"},{"Application Name":"IrisSelectionFrontDoor","Application IDs":"16aeb910-ce68-41d1-9ac3-9e1673ac9575"},{"Application Name":"MCAPI Authorization Prod","Application IDs":"d73f4b35-55c9-48c7-8b10-651f6f2acb2e"},{"Application Name":"Media Analysis and Transformation Service","Application IDs":"944f0bd1-117b-4b1c-af26-804ed95e767e
0cd196ee-71bf-4fd6-a57c-b491ffd4fb1e"},{"Application Name":"Microsoft 365 Support Service","Application IDs":"ee272b19-4411-433f-8f28-5c13cb6fd407"},{"Application Name":"Microsoft App Access Panel","Application IDs":"0000000c-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Approval Management","Application IDs":"65d91a3d-ab74-42e6-8a2f-0add61688c74
38049638-cc2c-4cde-abe4-4479d721ed44"},{"Application Name":"Microsoft Authentication Broker","Application IDs":"29d9ed98-a469-4536-ade2-f981bc1d605e"},{"Application Name":"Microsoft Azure CLI","Application IDs":"04b07795-8ddb-461a-bbee-02f9e1bf7b46"},{"Application Name":"Microsoft Azure PowerShell","Application IDs":"1950a258-227b-4e31-a9cf-717495945fc2"},{"Application Name":"Microsoft Bing Search","Application IDs":"cf36b471-5b44-428c-9ce7-313bf84528de"},{"Application Name":"Microsoft Bing Search for Microsoft Edge","Application IDs":"2d7f3606-b07d-41d1-b9d2-0d0c9296a6e8"},{"Application Name":"Microsoft Bing Default Search Engine","Application IDs":"1786c5ed-9644-47b2-8aa0-7201292175b6"},{"Application Name":"Microsoft Defender for Cloud Apps","Application IDs":"3090ab82-f1c1-4cdf-af2c-5d7a6f3e2cc7"},{"Application Name":"Microsoft Docs","Application IDs":"18fbca16-2224-45f6-85b0-f7bf2b39b3f3"},{"Application Name":"Microsoft Dynamics ERP","Application IDs":"00000015-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Edge Insider Addons Prod","Application IDs":"6253bca8-faf2-4587-8f2f-b056d80998a7"},{"Application Name":"Microsoft Exchange Online Protection","Application IDs":"00000007-0000-0ff1-ce00-000000000000"},{"Application Name":"Microsoft Forms","Application IDs":"c9a559d2-7aab-4f13-a6ed-e7e9c52aec87"},{"Application Name":"Microsoft Graph","Application IDs":"00000003-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Intune Web Company Portal","Application IDs":"74bcdadc-2fdc-4bb3-8459-76d06952a0e9"},{"Application Name":"Microsoft Intune Windows Agent","Application IDs":"fc0f3af4-6835-4174-b806-f7db311fd2f3"},{"Application Name":"Microsoft Learn","Application IDs":"18fbca16-2224-45f6-85b0-f7bf2b39b3f3"},{"Application Name":"Microsoft Office","Application IDs":"d3590ed6-52b3-4102-aeff-aad2292ab01c"},{"Application Name":"Microsoft Office 365 Portal","Application IDs":"00000006-0000-0ff1-ce00-000000000000"},{"Application Name":"Microsoft Office Web Apps Service","Application IDs":"67e3df25-268a-4324-a550-0de1c7f97287"},{"Application Name":"Microsoft Online Syndication Partner Portal","Application IDs":"d176f6e7-38e5-40c9-8a78-3998aab820e7"},{"Application Name":"Microsoft password reset service","Application IDs":"93625bc8-bfe2-437a-97e0-3d0060024faa"},{"Application Name":"Microsoft Power BI","Application IDs":"871c010f-5e61-4fb1-83ac-98610a7e9110"},{"Application Name":"Microsoft Storefronts","Application IDs":"28b567f6-162c-4f54-99a0-6887f387bbcc"},{"Application Name":"Microsoft Stream Portal","Application IDs":"cf53fce8-def6-4aeb-8d30-b158e7b1cf83"},{"Application Name":"Microsoft Substrate Management","Application IDs":"98db8bd6-0cc0-4e67-9de5-f187f1cd1b41"},{"Application Name":"Microsoft Support","Application IDs":"fdf9885b-dd37-42bf-82e5-c3129ef5a302"},{"Application Name":"Microsoft Teams","Application IDs":"1fec8e78-bce4-4aaf-ab1b-5451cc387264"},{"Application Name":"Microsoft Teams Services","Application IDs":"cc15fd57-2c6c-4117-a88c-83b1d56b4bbe"},{"Application Name":"Microsoft Teams Web Client","Application IDs":"5e3ce6c0-2b1f-4285-8d4b-75ee78787346"},{"Application Name":"Microsoft Whiteboard Services","Application IDs":"95de633a-083e-42f5-b444-a4295d8e9314"},{"Application Name":"O365 Suite UX","Application IDs":"4345a7b9-9a63-4910-a426-35363201d503"},{"Application Name":"Office 365 Exchange Online","Application IDs":"00000002-0000-0ff1-ce00-000000000000"},{"Application Name":"Office 365 Management","Application IDs":"00b41c95-dab0-4487-9791-b9d2c32c80f2"},{"Application Name":"Office 365 Search Service","Application IDs":"66a88757-258c-4c72-893c-3e8bed4d6899"},{"Application Name":"Office 365 SharePoint Online","Application IDs":"00000003-0000-0ff1-ce00-000000000000"},{"Application Name":"Office Delve","Application IDs":"94c63fef-13a3-47bc-8074-75af8c65887a"},{"Application Name":"Office Online Add-in SSO","Application IDs":"93d53678-613d-4013-afc1-62e9e444a0a5"},{"Application Name":"Office Online Client AAD- Augmentation Loop","Application IDs":"2abdc806-e091-4495-9b10-b04d93c3f040"},{"Application Name":"Office Online Client AAD- Loki","Application IDs":"b23dd4db-9142-4734-867f-3577f640ad0c"},{"Application Name":"Office Online Client AAD- Maker","Application IDs":"17d5e35f-655b-4fb0-8ae6-86356e9a49f5"},{"Application Name":"Office Online Client MSA- Loki","Application IDs":"b6e69c34-5f1f-4c34-8cdf-7fea120b8670"},{"Application Name":"Office Online Core SSO","Application IDs":"243c63a3-247d-41c5-9d83-7788c43f1c43"},{"Application Name":"Office Online Search","Application IDs":"a9b49b65-0a12-430b-9540-c80b3332c127"},{"Application Name":"Office.com","Application IDs":"4b233688-031c-404b-9a80-a4f3f2351f90"},{"Application Name":"Office365 Shell WCSS-Client","Application IDs":"89bee1f7-5e6e-4d8a-9f3d-ecd601259da7"},{"Application Name":"OfficeClientService","Application IDs":"0f698dd4-f011-4d23-a33e-b36416dcb1e6"},{"Application Name":"OfficeHome","Application IDs":"4765445b-32c6-49b0-83e6-1d93765276ca"},{"Application Name":"OfficeShredderWacClient","Application IDs":"4d5c2d63-cf83-4365-853c-925fd1a64357"},{"Application Name":"OMSOctopiPROD","Application IDs":"62256cef-54c0-4cb4-bcac-4c67989bdc40"},{"Application Name":"OneDrive SyncEngine","Application IDs":"ab9b8c07-8f02-4f72-87fa-80105867a763"},{"Application Name":"OneNote","Application IDs":"2d4d3d8e-2be3-4bef-9f87-7875a61c29de"},{"Application Name":"Outlook Mobile","Application IDs":"27922004-5251-4030-b22d-91ecd9a37ea4"},{"Application Name":"Partner Customer Delegated Admin Offline Processor","Application IDs":"a3475900-ccec-4a69-98f5-a65cd5dc5306"},{"Application Name":"Password Breach Authenticator","Application IDs":"bdd48c81-3a58-4ea9-849c-ebea7f6b6360"},{"Application Name":"Power BI Service","Application IDs":"00000009-0000-0000-c000-000000000000"},{"Application Name":"SharedWithMe","Application IDs":"ffcb16e8-f789-467c-8ce9-f826a080d987"},{"Application Name":"SharePoint Online Web Client Extensibility","Application IDs":"08e18876-6177-487e-b8b5-cf950c1e598c"},{"Application Name":"Signup","Application IDs":"b4bddae8-ab25-483e-8670-df09b9f1d0ea"},{"Application Name":"Skype for Business Online","Application IDs":"00000004-0000-0ff1-ce00-000000000000"},{"Application Name":"Sway","Application IDs":"905fcf26-4eb7-48a0-9ff0-8dcc7194b5ba"},{"Application Name":"Universal Store Native Client","Application IDs":"268761a2-03f3-40df-8a8b-c3db24145b6b"},{"Application Name":"Vortex [wsfed enabled]","Application IDs":"5572c4c0-d078-44ce-b81c-6cbf8d3ed39e"},{"Application Name":"Windows Azure Active Directory","Application IDs":"00000002-0000-0000-c000-000000000000"},{"Application Name":"Windows Azure Service Management API","Application IDs":"797f4846-ba00-4fd7-ba43-dac1f8f63013"},{"Application Name":"WindowsDefenderATP Portal","Application IDs":"a3b79187-70b2-4139-83f9-6016c58cd27b"},{"Application Name":"Windows Search","Application IDs":"26a7ee05-5602-4d76-a7ba-eae8b7b67941"},{"Application Name":"Windows Spotlight","Application IDs":"1b3c667f-cde3-4090-b60b-3d2abd0117f0"},{"Application Name":"Windows Store for Business","Application IDs":"45a330b1-b1ec-4cc1-9161-9f03992aa49f"},{"Application Name":"Yammer","Application IDs":"00000005-0000-0ff1-ce00-000000000000"},{"Application Name":"Yammer Web","Application IDs":"c1c74fed-04c9-4704-80dc-9f79a2e515cb"},{"Application Name":"Yammer Web Embed","Application IDs":"e1ef36fd-b883-4dbf-97f0-9ece4b576fc6"}]' | ConvertFrom-Json | Where-Object -Property 'Application IDs' -EQ $data.applicationId $HTMLTemplate = Get-Content 'TemplateEmail.HTML' -Raw | Out-String @@ -21,14 +22,14 @@ function New-CIPPAlertTemplate { 'New-InboxRule' { $Title = "$($TenantFilter) - New Rule Detected for $($data.UserId)" $RuleTable = ($Data.CIPPParameters | ConvertFrom-Json | ConvertTo-Html -Fragment | Out-String).Replace('', '
') - + $IntroText = "

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

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

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

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

The (potential) location information for this IP is as follows:

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

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

' } @@ -41,7 +42,7 @@ function New-CIPPAlertTemplate { $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') $IntroText = $IntroText + "

The (potential) location information for this IP is as follows:

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

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

' } @@ -54,7 +55,7 @@ function New-CIPPAlertTemplate { $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') $IntroText = $IntroText + "

The (potential) location information for this IP is as follows:

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

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

' @@ -67,14 +68,14 @@ function New-CIPPAlertTemplate { $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') $IntroText = $IntroText + "

The (potential) location information for this IP is as follows:

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

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

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

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

" } if ($LocationInfo) { $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') @@ -86,7 +87,7 @@ function New-CIPPAlertTemplate { 'Update StsRefreshTokenValidFrom Timestamp.' { $Title = "$($TenantFilter) - $($data.ObjectId) has had all sessions revoked" $IntroText = "$($data.ObjectId) has had their sessions revoked by $($data.UserId)." - $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" + $ButtonUrl = "$CIPPURL/identity/administration/users?customerId=$($data.OrganizationId)" if ($ActionResults) { $IntroText = $IntroText + "

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

" } if ($LocationInfo) { $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') @@ -103,7 +104,7 @@ function New-CIPPAlertTemplate { $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') $IntroText = $IntroText + "

The (potential) location information for this IP is as follows:

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

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

' } @@ -116,7 +117,7 @@ function New-CIPPAlertTemplate { $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') $IntroText = $IntroText + "

The (potential) location information for this IP is as follows:

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

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

' @@ -130,7 +131,7 @@ function New-CIPPAlertTemplate { $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') $IntroText = $IntroText + "

The (potential) location information for this IP is as follows:

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

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

' @@ -145,7 +146,7 @@ function New-CIPPAlertTemplate { $IntroText = $IntroText + "

The (potential) location information for this IP is as follows:

$LocationTable" } $IntroText = "$($data.ObjectId) has been added by $($data.UserId)." - $ButtonUrl = "$CIPPPURL/tenant/administration/enterprise-apps?customerId=?customerId=$($data.OrganizationId)" + $ButtonUrl = "$CIPPURL/tenant/administration/enterprise-apps?customerId=?customerId=$($data.OrganizationId)" $ButtonText = 'Enterprise Apps' } 'Remove service principal.' { @@ -158,7 +159,7 @@ function New-CIPPAlertTemplate { $IntroText = $IntroText + "

The (potential) location information for this IP is as follows:

$LocationTable" } $IntroText = "$($data.ObjectId) has been added by $($data.UserId)." - $ButtonUrl = "$CIPPPURL/tenant/administration/enterprise-apps?customerId=?customerId=$($data.OrganizationId)" + $ButtonUrl = "$CIPPURL/tenant/administration/enterprise-apps?customerId=?customerId=$($data.OrganizationId)" $ButtonText = 'Enterprise Apps' } 'UserLoggedIn' { @@ -171,7 +172,7 @@ function New-CIPPAlertTemplate { $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') $IntroText = $IntroText + "

The (potential) location information for this IP is as follows:

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

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

' } @@ -184,7 +185,7 @@ function New-CIPPAlertTemplate { $LocationTable = ($LocationInfo | ConvertTo-Html -Fragment -As List | Out-String).Replace('
', '
') $IntroText = $IntroText + "

The (potential) location information for this IP is as follows:

$LocationTable" } - $ButtonUrl = "$CIPPPURL/identity/administration/users?customerId=$($data.OrganizationId)" + $ButtonUrl = "$CIPPURL/identity/administration/users?customerId=$($data.OrganizationId)" $ButtonText = 'User Management' } } diff --git a/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 b/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 index 59395211a55a..1d62375b5ada 100644 --- a/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 +++ b/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 @@ -53,12 +53,20 @@ function Send-CIPPAlert { if ($PSCmdlet.ShouldProcess($Config.webhook, 'Sending webhook')) { switch -wildcard ($config.webhook) { '*webhook.office.com*' { - $JSonBody = "{`"text`": `"You've setup your alert policies to be alerted whenever specific events happen. We've found some of these events in the log.

$JSONContent`"}" + $JSONBody = "{`"text`": `"You've setup your alert policies to be alerted whenever specific events happen. We've found some of these events in the log.

$JSONContent`"}" Invoke-RestMethod -Uri $config.webhook -Method POST -ContentType 'Application/json' -Body $JSONBody } - '*discord.com*' { - $JSonBody = "{`"content`": `"You've setup your alert policies to be alerted whenever specific events happen. We've found some of these events in the log. $JSONContent`"}" + $JSONBody = "{`"content`": `"You've setup your alert policies to be alerted whenever specific events happen. We've found some of these events in the log. $JSONContent`"}" + Invoke-RestMethod -Uri $config.webhook -Method POST -ContentType 'Application/json' -Body $JSONBody + } + '*slack.com*' { + $SlackBlocks = Get-SlackAlertBlocks -JSONBody $JSONContent + if ($SlackBlocks.blocks) { + $JSONBody = $SlackBlocks + } else { + $JSONBody = "{`"text`": `"You've setup your alert policies to be alerted whenever specific events happen. We've found some of these events in the log. $JSONContent`"}" + } Invoke-RestMethod -Uri $config.webhook -Method POST -ContentType 'Application/json' -Body $JSONBody } default { From 74044a39294045a1acba0436475e950715837cf5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 6 Jun 2024 21:20:57 -0400 Subject: [PATCH 44/54] alert tweaks --- .../CIPPCore/Public/Get-SlackAlertBlocks.ps1 | 20 +++++++++++++------ .../Public/Invoke-CIPPWebhookProcessing.ps1 | 1 + .../CIPPCore/Public/New-CIPPAlertTemplate.ps1 | 5 +++-- Modules/CIPPCore/Public/Send-CIPPAlert.ps1 | 2 +- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 b/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 index ce4dfc21bff0..8f63195f905f 100644 --- a/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 +++ b/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 @@ -114,6 +114,7 @@ function Get-SlackAlertBlocks { emoji = $true } }) + $Blocks.Add([PSCustomObject]@{ type = 'section' text = @{ @@ -122,11 +123,18 @@ function Get-SlackAlertBlocks { } }) $Blocks.Add([PSCustomObject]@{ - type = 'section' - text = @{ - type = 'mrkdwn' - text = "*Action URL:*`n<" + $Payload.ActionUrl + '|Go to CIPP>' - } + type = 'actions' + elements = @( + @{ + type = 'button' + text = @{ + type = 'plain_text' + text = $Payload.ActionText ?? 'Go to CIPP' + } + url = $Payload.ActionUrl + style = 'primary' + } + ) }) $Blocks.Add([PSCustomObject]@{ type = 'divider' @@ -230,6 +238,6 @@ function Get-SlackAlertBlocks { if (($Blocks | Measure-Object).Count -gt 0) { [PSCustomObject]@{ blocks = $Blocks - } | ConvertTo-Json -Depth 10 + } } } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 index d9a5ec18d4c3..a1787ddbae34 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 @@ -78,6 +78,7 @@ function Invoke-CippWebhookProcessing { $JsonContent = @{ Title = $GenerateJSON.Title ActionUrl = $GenerateJSON.ButtonUrl + ActionText = $GenerateJSON.ButtonText RawData = $Data IP = $data.ClientIP PotentialLocationInfo = $LocationInfo diff --git a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 index a394435e225f..ff9853aca307 100644 --- a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 @@ -197,8 +197,9 @@ function New-CIPPAlertTemplate { } } elseif ($Format -eq 'json') { return [pscustomobject]@{ - title = $Title - buttonurl = $ButtonUrl + title = $Title + buttonurl = $ButtonUrl + buttontext = $ButtonText } } } diff --git a/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 b/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 index 1d62375b5ada..8fc9e31f7bdf 100644 --- a/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 +++ b/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 @@ -63,7 +63,7 @@ function Send-CIPPAlert { '*slack.com*' { $SlackBlocks = Get-SlackAlertBlocks -JSONBody $JSONContent if ($SlackBlocks.blocks) { - $JSONBody = $SlackBlocks + $JSONBody = $SlackBlocks | ConvertTo-Json -Depth 10 -Compress } else { $JSONBody = "{`"text`": `"You've setup your alert policies to be alerted whenever specific events happen. We've found some of these events in the log. $JSONContent`"}" } From ee9bc311475bdfd046c1c74fd6acc48a0cd95f58 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 6 Jun 2024 22:38:57 -0400 Subject: [PATCH 45/54] Update Invoke-PublicWebhooks.ps1 --- .../Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 | 1 + 1 file changed, 1 insertion(+) 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 d4633158b895..36eb80645c89 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 @@ -12,6 +12,7 @@ function Invoke-PublicWebhooks { $Webhooks = Get-CIPPAzDataTableEntity @WebhookTable Write-Host 'Received request' $url = ($request.headers.'x-ms-original-url').split('/API') | Select-Object -First 1 + $CIPPURL = [string]$url Write-Host $url if ($Webhooks.Resource -eq 'M365AuditLogs') { Write-Host "Found M365AuditLogs - This is an old entry, we'll deny so Microsoft stops sending it." From cd5f9cfc1adee20bd49e433abcfcb8f9c5441a71 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 6 Jun 2024 23:54:02 -0400 Subject: [PATCH 46/54] Update Invoke-PublicWebhooks.ps1 --- .../Alerts/Invoke-PublicWebhooks.ps1 | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) 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 36eb80645c89..12f1e78a38c4 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 @@ -94,7 +94,6 @@ function Invoke-PublicWebhooks { continue } - $PreProccessedData = $Data | Select-Object *, CIPPAction, CIPPClause, CIPPGeoLocation, CIPPBadRepIP, CIPPHostedIP, CIPPIPDetected, CIPPLocationInfo, CIPPExtendedProperties, CIPPDeviceProperties, CIPPParameters, CIPPModifiedProperties -ErrorAction SilentlyContinue $LocationTable = Get-CIPPTable -TableName 'knownlocationdb' $ProcessedData = foreach ($Data in $PreProccessedData) { @@ -167,23 +166,25 @@ function Invoke-PublicWebhooks { $Where = $Configuration | ForEach-Object { $conditions = $_.Conditions | ConvertFrom-Json | Where-Object { $_.Input.value -ne '' } $actions = $_.Actions - $conditionStrings = foreach ($condition in $conditions) { + $conditionStrings = [System.Collections.Generic.List[string]]::new() + $CIPPClause = [System.Collections.Generic.List[string]]::new() + foreach ($condition in $conditions) { $value = if ($condition.Input.value -is [array]) { $arrayAsString = $condition.Input.value | ForEach-Object { "'$_'" } "@($($arrayAsString -join ', '))" } else { "'$($condition.Input.value)'" } - "`$(`$_.$($condition.Property.label)) -$($condition.Operator.value) $value" - } - if ($conditionStrings.Count -gt 1) { - $finalCondition = $conditionStrings -join ' -AND ' - } else { - $finalCondition = $conditionStrings + + $conditionStrings.Add("`$(`$_.$($condition.Property.label)) -$($condition.Operator.value) $value") + $CIPPClause.Add("$($condition.Property.label) is $($condition.Operator.label) $value") } + $finalCondition = $conditionStrings -join ' -AND ' + [PSCustomObject]@{ clause = $finalCondition expectedAction = $actions + CIPPClause = $CIPPClause } } @@ -196,7 +197,7 @@ function Invoke-PublicWebhooks { if ($ReturnedData) { $ReturnedData = foreach ($item in $ReturnedData) { $item.CIPPAction = $clause.expectedAction - $item.CIPPClause = ($clause.clause | ForEach-Object { "When $($_.Property.label) is $($_.Operator.label) $($_.input.value)" }) -join ' and ' + $item.CIPPClause = $clause.CIPPClause -join ' and ' $item } } From bd386eb542671e010ec2fddc1e3fe9166e03d480 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 7 Jun 2024 00:09:16 -0400 Subject: [PATCH 47/54] limit configurations to content type --- .../Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 12f1e78a38c4..78cded90ff19 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 @@ -74,7 +74,7 @@ function Invoke-PublicWebhooks { $TenantFilter = (Get-Tenants | Where-Object -Property customerId -EQ $ReceivedItem.TenantId).defaultDomainName Write-Host "Webhook TenantFilter: $TenantFilter" $ConfigTable = get-cipptable -TableName 'WebhookRules' - $Configuration = (Get-CIPPAzDataTableEntity @ConfigTable) | Where-Object { $_.Tenants -match $TenantFilter -or $_.Tenants -match 'AllTenants' } | ForEach-Object { + $Configuration = (Get-CIPPAzDataTableEntity @ConfigTable) | Where-Object { ($_.Tenants -match $TenantFilter -or $_.Tenants -match 'AllTenants') -and $_.Type -eq $ReceivedItem.ContentType } | ForEach-Object { [pscustomobject]@{ Tenants = ($_.Tenants | ConvertFrom-Json).fullValue Conditions = $_.Conditions From 730a940bff3aeb028f3097d878447119932298af Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 7 Jun 2024 12:25:31 +0200 Subject: [PATCH 48/54] adds update policies and driver policies --- .../Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 | 7 +++++++ .../Endpoint/MEM/Invoke-AddPolicy.ps1 | 13 +++++++++++++ .../Public/Entrypoints/Invoke-ListIntunePolicy.ps1 | 3 ++- .../Standards/Invoke-CIPPStandardIntuneTemplate.ps1 | 13 +++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 index d01025dc080a..60d86236ca22 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 @@ -61,6 +61,13 @@ Function Invoke-AddIntuneTemplate { $DisplayName = $Template.name + } + 'windowsDriverUpdateProfiles' { + $Type = 'windowsDriverUpdateProfiles' + $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)/$($ID)" -tenantid $tenantfilter | Select-Object * -ExcludeProperty id, lastModifiedDateTime, '@odata.context', 'ScopeTagIds', 'supportsScopeTags', 'createdDateTime' + Write-Host ($Template | ConvertTo-Json) + $DisplayName = $Template.displayName + $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 100 -Compress } 'deviceConfigurations' { $Type = 'Device' diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 index 2df6b1541877..aca7c04ca269 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 @@ -82,6 +82,19 @@ Function Invoke-AddPolicy { } $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $RawJSON } + 'windowsDriverUpdateProfiles' { + $TemplateTypeURL = 'windowsDriverUpdateProfiles' + $PolicyName = ($RawJSON | ConvertFrom-Json).Name + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant + if ($PolicyName -in $CheckExististing.name) { + $ExistingID = $CheckExististing | Where-Object -Property Name -EQ $PolicyName + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenant -type PUT -body $RawJSON + + } else { + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $RawJSON + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added policy $($PolicyName) via template" -Sev 'info' + } + } } Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added policy $($Displayname)" -Sev 'Info' diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 index dbccf5a4004e..6bbf36a98274 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 @@ -26,7 +26,8 @@ Function Invoke-ListIntunePolicy { $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$ID')" -tenantid $tenantfilter } else { - $GraphURLS = @("https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations?`$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,microsoft.graph.unsupportedDeviceConfiguration/originalEntityTypeName&`$expand=assignments&top=1000", + $GraphURLS = @("https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations?`$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,microsoft.graph.unsupportedDeviceConfiguration/originalEntityTypeName&`$expand=assignments&top=1000" + 'https://graph.microsoft.com/beta/deviceManagement/windowsDriverUpdateProfiles' "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations?`$expand=assignments&top=1000" "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations?`$expand=assignments&`$filter=microsoft.graph.androidManagedStoreAppConfiguration/appSupportsOemConfig%20eq%20true" 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 index 39af5a0f9402..9263b386bb23 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 @@ -98,6 +98,19 @@ function Invoke-CIPPStandardIntuneTemplate { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added policy $($PolicyName) via template" -Sev 'info' } } + 'windowsDriverUpdateProfiles' { + $TemplateTypeURL = 'windowsDriverUpdateProfiles' + $PolicyName = ($RawJSON | ConvertFrom-Json).Name + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant + if ($PolicyName -in $CheckExististing.name) { + $ExistingID = $CheckExististing | Where-Object -Property Name -EQ $PolicyName + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenant -type PUT -body $RawJSON + + } else { + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $RawJSON + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added policy $($PolicyName) via template" -Sev 'info' + } + } } From 8a9e078258d2db4c11327be9b6d6d80809292f93 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 7 Jun 2024 07:08:35 -0400 Subject: [PATCH 49/54] Update Invoke-ExecJITAdmin.ps1 --- .../Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index ad887810061a..1c1c575a598f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -103,6 +103,11 @@ Function Invoke-ExecJITAdmin { } Parameters = $Parameters ScheduledTime = $Request.Body.StartDate + PostExecution = @{ + Webhook = [bool]$Request.Body.PostExecution.Webhook + Email = [bool]$Request.Body.PostExecution.Email + PSA = [bool]$Request.Body.PostExecution.PSA + } } Add-CIPPScheduledTask -Task $TaskBody -hidden $false Set-CIPPUserJITAdminProperties -TenantFilter $Request.Body.TenantFilter -UserId $Request.Body.UserId -Expiration $Expiration From e51d7119d6dba7a285ba831cadff9a927b4c4267 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 7 Jun 2024 13:14:09 +0200 Subject: [PATCH 50/54] adds autobackup CIPP --- .../CIPP/Core/Invoke-ExecListBackup.ps1 | 2 +- .../Core/Invoke-ExecSetCIPPAutoBackup.ps1 | 43 +++++++++++++++++++ .../Scheduler/Invoke-RemoveScheduledItem.ps1 | 2 - Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 18 +++++++- 4 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1 index a0b21d1f8de9..90b7e41bc4c9 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-ExecAddAlert { +Function Invoke-ExecListBackup { <# .FUNCTIONALITY Entrypoint diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 new file mode 100644 index 000000000000..2d04df48933c --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 @@ -0,0 +1,43 @@ +using namespace System.Net + +Function Invoke-ExecSetCIPPAutoBackup { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.Backup.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + $unixtime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds + if ($Request.query.Enabled -eq 'True') { + $Table = Get-CIPPTable -TableName 'ScheduledTasks' + $AutomatedCIPPBackupTask = Get-AzDataTableEntity @table -Filter "Name eq 'Automated CIPP Backup'" + $task = @{ + RowKey = $AutomatedCIPPBackupTask.RowKey + PartitionKey = 'ScheduledTask' + } + Remove-AzDataTableEntity @Table -Entity $task | Out-Null + + $TaskBody = @{ + TenantFilter = 'AllTenants' + Name = 'Automated CIPP Backup' + Command = @{ + value = 'New-CIPPBackup' + label = 'New-CIPPBackup' + } + Parameters = @{ backupType = 'CIPP' } + ScheduledTime = $unixtime + Recurrence = '1d' + } + Add-CIPPScheduledTask -Task $TaskBody -hidden $false + $Result = @{ 'Results' = 'Scheduled Task Successfully created' } + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API 'Alerts' -message $request.body.text -Sev $request.body.Severity + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Result + }) + +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 index 257902f03726..f21b1b88e275 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 @@ -14,8 +14,6 @@ Function Invoke-RemoveScheduledItem { RowKey = $Request.Query.ID PartitionKey = 'ScheduledTask' } - - $Table = Get-CIPPTable -TableName 'ScheduledTasks' Remove-AzDataTableEntity @Table -Entity $task diff --git a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index b0e960829858..3c9acd37e9cd 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -23,10 +23,26 @@ function New-CIPPBackup { ) $CSVfile = foreach ($CSVTable in $BackupTables) { $Table = Get-CippTable -tablename $CSVTable - Get-CIPPAzDataTableEntity @Table | Select-Object *, @{l = 'table'; e = { $CSVTable } } + Get-CIPPAzDataTableEntity @Table } Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created backup' -Sev 'Debug' $CSVfile + $RowKey = 'CIPPBackup' + '_' + (Get-Date).ToString('yyyy-MM-dd-HHmm') + $entity = [PSCustomObject]@{ + PartitionKey = 'CIPPBackup' + RowKey = $RowKey + TenantFilter = 'CIPPBackup' + Backup = [string]($CSVfile | ConvertTo-Json -Compress -Depth 100) + } + $Table = Get-CippTable -tablename 'CIPPBackup' + try { + $Result = Add-CIPPAzDataTableEntity @Table -entity $entity -Force + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created CIPP Backup' -Sev 'Debug' + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup for CIPP: $($_.Exception.Message)" -Sev 'Error' + [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" } + } + } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup: $($_.Exception.Message)" -Sev 'Error' [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" } From 5217464bb21c48b0415260a731a5414241b242bc Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 7 Jun 2024 07:30:18 -0400 Subject: [PATCH 51/54] tweak output for slack --- .../CIPPCore/Public/Get-SlackAlertBlocks.ps1 | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 b/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 index 8f63195f905f..b59ce99a3160 100644 --- a/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 +++ b/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 @@ -151,11 +151,7 @@ function Get-SlackAlertBlocks { $Fields = [system.collections.generic.list[object]]::new() foreach ($Key in $Payload.RawData.PSObject.Properties.Name) { # if value is json continue to next property - if ($Payload.RawData.$Key -is [string] -and ![string]::IsNullOrEmpty($Payload.RawData.$Key) -and (Test-Json -Json $Payload.RawData.$Key -ErrorAction SilentlyContinue)) { - continue - } - - if ([string]::IsNullOrEmpty($Payload.RawData.$Key)) { + if ($Payload.RawData.$Key -is [string] -and ![string]::IsNullOrEmpty($Payload.RawData.$Key)) { continue } # if value is date object @@ -164,6 +160,22 @@ function Get-SlackAlertBlocks { type = 'mrkdwn' text = "*$($Key):*`n" + $Payload.RawData.$Key.ToString('yyyy-MM-dd @ hh:mm:ss tt') }) + } elseif ($Payload.RawData.$Key -is [array] -and $Payload.RawData.$Key.Count -gt 0) { + foreach ($SubKey in $Payload.RawData.$Key) { + if ([string]::IsNullOrEmpty($SubKey)) { + continue + } elseif ($SubKey -is [datetime]) { + $Fields.Add(@{ + type = 'mrkdwn' + text = "*$($Key):*`n" + $SubKey.ToString('yyyy-MM-dd @ hh:mm:ss tt') + }) + } else { + $Fields.Add(@{ + type = 'mrkdwn' + text = "*$($Key):*`n" + $SubKey + }) + } + } } elseif ($Payload.RawData.$Key.PSObject.Properties.Name -is [array] -and $Payload.RawData.$Key.PSObject.Properties.Name.Count -gt 0) { foreach ($SubKey in $Payload.RawData.$Key.PSObject.Properties.Name) { if ([string]::IsNullOrEmpty($Payload.RawData.$Key.$SubKey)) { @@ -173,6 +185,15 @@ function Get-SlackAlertBlocks { type = 'mrkdwn' text = "*$($Key)/$($SubKey):*`n" + $Payload.RawData.$Key.$SubKey.ToString('yyyy-MM-dd @ hh:mm:ss tt') }) + } elseif (Test-Json $Payload.RawData.$Key.$SubKey -ErrorAction SilentlyContinue) { + # parse json and iterate through properties + $SubKeyData = $Payload.RawData.$Key.$SubKey | ConvertFrom-Json + foreach ($SubSubKey in $SubKeyData.PSObject.Properties.Name) { + $Fields.Add(@{ + type = 'mrkdwn' + text = "*$($Key)/$($SubKey)/$($SubSubKey):*`n" + $SubKeyData.$SubSubKey + }) + } } else { $Fields.Add(@{ type = 'mrkdwn' From 83edf14e7de427ead136b5221318c50e765482ba Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 7 Jun 2024 13:43:29 +0200 Subject: [PATCH 52/54] convert BPA to use tables instead of files --- .../Tenant/Standards/Invoke-ListBPA.ps1 | 10 ++++++--- .../Standards/Invoke-ListBPATemplates.ps1 | 22 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) 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 df3e849a19e8..22e078617475 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 @@ -20,14 +20,18 @@ Function Invoke-ListBPA { # Get all possible JSON files for reports, find the correct one, select the Columns $JSONFields = @() $Columns = $null -(Get-ChildItem -Path 'Config\*.BPATemplate.json' -Recurse | Select-Object -ExpandProperty FullName | ForEach-Object { - $Template = $(Get-Content $_) | ConvertFrom-Json + $BPATemplateTable = Get-CippTable -tablename 'templates' + $Filter = "PartitionKey eq 'BPATemplate'" + $Templates = (Get-CIPPAzDataTableEntity @BPATemplateTable -Filter $Filter).JSON | ConvertFrom-Json + + $Templates | ForEach-Object { + $Template = $_ if ($Template.Name -eq $NAME) { $JSONFields = $Template.Fields | Where-Object { $_.StoreAs -eq 'JSON' } | ForEach-Object { $_.name } $Columns = $Template.fields.FrontendFields | Where-Object -Property name -NE $null $Style = $Template.Style } - }) + } if ($Request.query.tenantFilter -ne 'AllTenants' -and $Style -eq 'Tenant') { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPATemplates.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPATemplates.ps1 index 7f7c110fcd9f..376a1f4e592b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPATemplates.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPATemplates.ps1 @@ -14,17 +14,27 @@ Function Invoke-ListBPATemplates { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' Write-Host 'PowerShell HTTP trigger function processed a request.' - Write-Host $Request.query.id + + $Table = Get-CippTable -tablename 'templates' - $Templates = Get-ChildItem 'Config\*.BPATemplate.json' + $Templates = Get-ChildItem 'Config\*.BPATemplate.json' | ForEach-Object { + $Entity = @{ + JSON = "$(Get-Content $_)" + RowKey = "$($_.name)" + PartitionKey = 'BPATemplate' + GUID = "$($_.name)" + } + Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force + } + + $Filter = "PartitionKey eq 'BPATemplate'" + $Templates = (Get-CIPPAzDataTableEntity @Table -Filter $Filter).JSON | ConvertFrom-Json if ($Request.Query.RawJson) { - $Templates = $Templates | ForEach-Object { - $(Get-Content $_) | ConvertFrom-Json - } + $Templates } else { $Templates = $Templates | ForEach-Object { - $Template = $(Get-Content $_) | ConvertFrom-Json + $Template = $_ @{ Data = $Template.fields Name = $Template.Name From c702dd63ce53500951c75e5e8535e05723dacae2 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 7 Jun 2024 15:02:57 +0200 Subject: [PATCH 53/54] added publish to bpa reports --- .../Tenant/Tools/Invoke-AddBPATemplate.ps1 | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-AddBPATemplate.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-AddBPATemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-AddBPATemplate.ps1 new file mode 100644 index 000000000000..15c2d49afc0d --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-AddBPATemplate.ps1 @@ -0,0 +1,41 @@ +using namespace System.Net + +Function Invoke-AddBPATemplate { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.BestPracticeAnalyser.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + try { + + $Table = Get-CippTable -tablename 'templates' + $Table.Force = $true + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$($Request.body | ConvertTo-Json -Depth 10)" + RowKey = $Request.body.name + PartitionKey = 'BPATemplate' + GUID = $Request.body.name + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created BPA named $($Request.body.name)" -Sev 'Debug' + + $body = [pscustomobject]@{'Results' = 'Successfully added template' } + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "BPA Template Creation failed: $($_.Exception.Message)" -Sev 'Error' + $body = [pscustomobject]@{'Results' = "BPA Template Creation failed: $($_.Exception.Message)" } + } + + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $body + }) + +} From ca839d9a0bbd6c1e2ff2e62752e7d869f2f29399 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 7 Jun 2024 15:20:37 +0200 Subject: [PATCH 54/54] version up --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index dfa102a57492..edb1d397cf28 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.7.4 +5.8.0 \ No newline at end of file