Skip to content

Commit 9d0ae17

Browse files
authored
Merge pull request KelvinTegelaar#1169 from JohnDuprey/dev
bugfixes & durable cleanup
2 parents 2cb07f9 + c744850 commit 9d0ae17

7 files changed

Lines changed: 152 additions & 37 deletions

File tree

CIPPTimers.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
[
2+
{
3+
"Command": "Start-DurableCleanup",
4+
"Description": "Timer function to cleanup durable functions",
5+
"Cron": "0 */15 * * * *",
6+
"Priority": 0,
7+
"RunOnProcessor": true,
8+
"IsSystem": true
9+
},
210
{
311
"Command": "Start-UserTasksOrchestrator",
412
"Description": "Orchestrator to process user scheduled tasks",

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ Function Invoke-ExecBECRemediate {
2727
$Step = 'Disable Account'
2828
Set-CIPPSignInState -userid $username -AccountEnabled $false -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $User
2929
$Step = 'Revoke Sessions'
30-
Revoke-CIPPSessions -userid $SuspectUser -username $request.body.username -ExecutingUser $User -APIName $APINAME -tenantFilter $TenantFilter
31-
30+
Revoke-CIPPSessions -userid $SuspectUser -username $username -ExecutingUser $User -APIName $APINAME -tenantFilter $TenantFilter
31+
$Step = 'Remove MFA methods'
32+
Remove-CIPPUserMFA -UserPrincipalName $username -TenantFilter $TenantFilter -ExecutingUser $User
3233
$Step = 'Disable Inbox Rules'
3334
$Rules = New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $username; IncludeHidden = $true }
3435
$RuleDisabled = 0

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,7 @@ Function Invoke-ExecResetMFA {
1717
$TenantFilter = $Request.Query.TenantFilter
1818
$UserID = $Request.Query.ID
1919
try {
20-
Write-Host "Getting auth methods for $UserID"
21-
$AuthMethods = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$UserID/authentication/methods" -tenantid $TenantFilter -AsApp $true
22-
$Requests = [System.Collections.Generic.List[object]]::new()
23-
foreach ($Method in $AuthMethods) {
24-
if ($Method.'@odata.type' -and $Method.'@odata.type' -ne '#microsoft.graph.passwordAuthenticationMethod') {
25-
$MethodType = ($Method.'@odata.type' -split '\.')[-1] -replace 'Authentication', ''
26-
$Requests.Add(@{
27-
id = "$MethodType-$($Method.id)"
28-
method = 'DELETE'
29-
url = ('users/{0}/authentication/{1}s/{2}' -f $UserID, $MethodType, $Method.id)
30-
})
31-
}
32-
}
33-
if (($Requests | Measure-Object).Count -eq 0) {
34-
$Results = [pscustomobject]@{'Results' = "No MFA methods found for user $($Request.Query.ID)" }
35-
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
36-
StatusCode = [HttpStatusCode]::OK
37-
Body = $Results
38-
})
39-
return
40-
}
41-
42-
$Results = New-GraphBulkRequest -Requests $Requests -tenantid $TenantFilter -asapp $true -erroraction stop
43-
44-
45-
if ($Results.status -eq 204) {
46-
$Results = [pscustomobject]@{'Results' = "Successfully completed request. User $($Request.Query.ID) must supply MFA at next logon" }
47-
} else {
48-
$FailedAuthMethods = (($Results | Where-Object { $_.status -ne 204 }).id -split '-')[0] -join ', '
49-
$Results = [pscustomobject]@{'Results' = "Failed to reset MFA methods for $FailedAuthMethods" }
50-
}
20+
$Results = Remove-CIPPUserMFA -UserPrincipalName $UserID -TenantFilter $TenantFilter -ExecutingUser $request.headers.'x-ms-client-principal'
5121
} catch {
5222
$Results = [pscustomobject]@{'Results' = "Failed to reset MFA methods for $($Request.Query.ID): $(Get-NormalizedError -message $_.Exception.Message)" }
5323
Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to reset MFA for user $($Request.Query.ID): $($_.Exception.Message)" -Sev 'Error' -LogData (Get-CippException -Exception $_)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
function Start-DurableCleanup {
2+
<#
3+
.SYNOPSIS
4+
Start the durable cleanup process.
5+
6+
.DESCRIPTION
7+
Look for orchestrators running for more than the specified time and terminate them. Also, clear any queues that have items for that function app.
8+
9+
.PARAMETER MaxDuration
10+
The maximum duration an orchestrator can run before being terminated.
11+
12+
.FUNCTIONALITY
13+
Internal
14+
#>
15+
16+
[CmdletBinding(SupportsShouldProcess = $true)]
17+
param(
18+
[int]$MaxDuration = 3600
19+
)
20+
$WarningPreference = 'SilentlyContinue'
21+
$StorageContext = New-AzStorageContext -ConnectionString $env:AzureWebJobsStorage
22+
$TargetTime = (Get-Date).ToUniversalTime().AddSeconds(-$MaxDuration)
23+
$Context = New-AzDataTableContext -ConnectionString $env:AzureWebJobsStorage
24+
$InstancesTables = Get-AzDataTable -Context $Context | Where-Object { $_ -match 'Instances' }
25+
26+
$CleanupCount = 0
27+
$QueueCount = 0
28+
foreach ($Table in $InstancesTables) {
29+
$Table = Get-CippTable -TableName $Table
30+
$ClearQueues = $false
31+
$FunctionName = $Table.TableName -replace 'Instances', ''
32+
$Orchestrators = Get-CIPPAzDataTableEntity @Table -Filter "RuntimeStatus eq 'Running'" | Select-Object * -ExcludeProperty Input
33+
$Orchestrators | Where-Object { $_.CreatedTime.DateTime -lt $TargetTime } | ForEach-Object {
34+
$CreatedTime = [DateTime]::SpecifyKind($_.CreatedTime.DateTime, [DateTimeKind]::Utc)
35+
$TimeSpan = New-TimeSpan -Start $CreatedTime -End (Get-Date).ToUniversalTime()
36+
$RunningDuration = [math]::Round($TimeSpan.TotalMinutes, 2)
37+
Write-Information "Orchestrator: $($_.PartitionKey), created: $CreatedTime, running for: $RunningDuration minutes"
38+
$ClearQueues = $true
39+
$_.RuntimeStatus = 'Failed'
40+
if ($PSCmdlet.ShouldProcess($_.PartitionKey, 'Terminate Orchestrator')) {
41+
$Orchestrator = Get-CIPPAzDataTableEntity @Table -PartitionKey $_.PartitionKey -RowKey $_.RowKey
42+
$Orchestrator.RuntimeStatus = 'Failed'
43+
Update-AzDataTableEntity @Table -Entity $Orchestrator
44+
$CleanupCount++
45+
}
46+
}
47+
48+
if ($ClearQueues) {
49+
$Queues = Get-AzStorageQueue -Context $StorageContext -Name ('{0}*' -f $FunctionName) | Select-Object -Property Name, ApproximateMessageCount, QueueClient
50+
$RunningQueues = $Queues | Where-Object { $_.ApproximateMessageCount -gt 0 }
51+
foreach ($Queue in $RunningQueues) {
52+
Write-Information "- Removing queue: $($Queue.Name), message count: $($Queue.ApproximateMessageCount)"
53+
if ($PSCmdlet.ShouldProcess($Queue.Name, 'Clear Queue')) {
54+
$Queue.QueueClient.ClearMessagesAsync() | Out-Null
55+
}
56+
$QueueCount++
57+
}
58+
}
59+
}
60+
Write-Information "Cleanup complete. $CleanupCount orchestrators were terminated. $QueueCount queues were cleared."
61+
}

Modules/CIPPCore/Public/Invoke-CIPPStandardsRun.ps1

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,26 @@ function Invoke-CIPPStandardsRun {
66
[string]$TenantFilter = 'allTenants',
77
[switch]$Force
88
)
9-
Write-Host "Starting process for standards - $($tenantFilter)"
9+
Write-Information "Starting process for standards - $($tenantFilter)"
1010

1111
$AllTasks = Get-CIPPStandards -TenantFilter $TenantFilter
1212

1313
if ($Force.IsPresent) {
14-
Write-Host 'Clearing Rerun Cache'
14+
Write-Information 'Clearing Rerun Cache'
1515
foreach ($Task in $AllTasks) {
1616
$null = Test-CIPPRerun -Type Standard -Tenant $Task.Tenant -API $Task.Standard -Clear
1717
}
1818
}
19+
$TaskCount = ($AllTasks | Measure-Object).Count
1920

21+
if ($TaskCount -eq 0) {
22+
Write-Information "No tasks found for tenant filter '$TenantFilter'"
23+
return
24+
}
25+
26+
Write-Information "Found $TaskCount tasks for tenant filter '$TenantFilter'"
2027
#For each item in our object, run the queue.
21-
$Queue = New-CippQueueEntry -Name "Applying Standards ($TenantFilter)" -TotalTasks ($AllTasks | Measure-Object).Count
28+
$Queue = New-CippQueueEntry -Name "Applying Standards ($TenantFilter)" -TotalTasks $TaskCount
2229

2330
$InputObject = [PSCustomObject]@{
2431
OrchestratorName = 'StandardsOrchestrator'
@@ -31,7 +38,9 @@ function Invoke-CIPPStandardsRun {
3138
}
3239
}
3340

41+
Write-Information 'Starting standards orchestrator'
3442
$InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
35-
Write-Host "Started orchestration with ID = '$InstanceId'"
43+
Write-Information "Started orchestration with ID = '$InstanceId'"
3644
#$Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId
45+
return $InstanceId
3746
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
function Remove-CIPPUserMFA {
2+
<#
3+
.SYNOPSIS
4+
Remove MFA methods for a user
5+
6+
.DESCRIPTION
7+
Remove MFA methods for a user using bulk requests to the Microsoft Graph API
8+
9+
.PARAMETER UserPrincipalName
10+
UserPrincipalName of the user to remove MFA methods for
11+
12+
.PARAMETER TenantFilter
13+
Tenant where the user resides
14+
15+
.EXAMPLE
16+
Remove-CIPPUserMFA -UserPrincipalName testuser@contoso.com -TenantFilter contoso.com
17+
18+
#>
19+
[CmdletBinding(SupportsShouldProcess = $true)]
20+
Param(
21+
[Parameter(Mandatory = $true)]
22+
[string]$UserPrincipalName,
23+
[Parameter(Mandatory = $true)]
24+
[string]$TenantFilter,
25+
[Parameter(Mandatory = $false)]
26+
[string]$ExecutingUser = 'CIPP'
27+
)
28+
29+
Write-Information "Getting auth methods for $UserPrincipalName"
30+
$AuthMethods = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/authentication/methods" -tenantid $TenantFilter -AsApp $true
31+
$Requests = [System.Collections.Generic.List[object]]::new()
32+
foreach ($Method in $AuthMethods) {
33+
if ($Method.'@odata.type' -and $Method.'@odata.type' -ne '#microsoft.graph.passwordAuthenticationMethod') {
34+
$MethodType = ($Method.'@odata.type' -split '\.')[-1] -replace 'Authentication', ''
35+
$Requests.Add(@{
36+
id = "$MethodType-$($Method.id)"
37+
method = 'DELETE'
38+
url = ('users/{0}/authentication/{1}s/{2}' -f $UserPrincipalName, $MethodType, $Method.id)
39+
})
40+
}
41+
}
42+
if (($Requests | Measure-Object).Count -eq 0) {
43+
Write-LogMessage -API 'Remove-CIPPUserMFA' -tenant $TenantFilter -message "No MFA methods found for user $UserPrincipalName" -sev 'Info'
44+
$Results = [pscustomobject]@{'Results' = "No MFA methods found for user $($Request.Query.ID)" }
45+
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
46+
StatusCode = [HttpStatusCode]::OK
47+
Body = $Results
48+
})
49+
return
50+
}
51+
52+
if ($PSCmdlet.ShouldProcess("Remove MFA methods for $UserPrincipalName")) {
53+
$Results = New-GraphBulkRequest -Requests $Requests -tenantid $TenantFilter -asapp $true -erroraction stop
54+
if ($Results.status -eq 204) {
55+
Write-LogMessage -API 'Remove-CIPPUserMFA' -tenant $TenantFilter -message "Successfully removed MFA methods for user $UserPrincipalName" -sev 'Info'
56+
$Results = [pscustomobject]@{'Results' = "Successfully completed request. User $($Request.Query.ID) must supply MFA at next logon" }
57+
} else {
58+
$FailedAuthMethods = (($Results | Where-Object { $_.status -ne 204 }).id -split '-')[0] -join ', '
59+
Write-LogMessage -API 'Remove-CIPPUserMFA' -tenant $TenantFilter -message "Failed to remove MFA methods for $FailedAuthMethods" -sev 'Error'
60+
$Results = [pscustomobject]@{'Results' = "Failed to reset MFA methods for $FailedAuthMethods" }
61+
}
62+
}
63+
64+
return $Results
65+
}

Modules/CippEntrypoints/CippEntrypoints.psm1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ function Receive-CIPPTimerTrigger {
229229
}
230230
} catch {
231231
$Status = 'Failed'
232+
Write-Information "Error in CIPPTimer for $($Function.Command): $($_.Exception.Message)"
232233
}
233234
$FunctionStatus.LastOccurrence = $UtcNow
234235
$FunctionStatus.Status = $Status

0 commit comments

Comments
 (0)