Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ function Add-CIPPScheduledTask {
AlertComment = [string]$task.AlertComment
CustomSubject = [string]$task.CustomSubject
PsaTicketStrategy = [string]($task.PsaTicketStrategy.value ?? $task.PsaTicketStrategy)
PsaTicketPriority = [string]($task.PsaTicketPriority.value ?? $task.PsaTicketPriority)
}


Expand Down
5 changes: 5 additions & 0 deletions backend/Modules/CIPPCore/Public/Send-CIPPAlert.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function Send-CIPPAlert {
$RowKey = [string][guid]::NewGuid(),
$Attachments,
$AffectedUser,
$PsaTicketPriority,
[switch]$UseStandardizedSchema
)
Write-Information 'Shipping Alert'
Expand Down Expand Up @@ -348,6 +349,10 @@ function Send-CIPPAlert {
$UserLabel = if ($AffectedUser.UPN) { $AffectedUser.UPN } elseif ($AffectedUser.AzureOID) { "OID:$($AffectedUser.AzureOID)" } else { 'unknown' }
Write-Information "PSA alert AffectedUser: $UserLabel"
}
if ($PsaTicketPriority) {
$Alert.PsaTicketPriority = $PsaTicketPriority
Write-Information "PSA alert priority override: $PsaTicketPriority"
}
$PsaResult = New-CippExtAlert -Alert $Alert
if ($PsaResult) {
Write-Information "PSA result: $PsaResult"
Expand Down
12 changes: 11 additions & 1 deletion backend/Modules/CIPPCore/Public/Send-CIPPScheduledTaskAlert.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ function Send-CIPPScheduledTaskAlert {
'*psa*' {
$PsaSplitSent = $false
$TaskAffectedUser = $null
# Per-task PSA ticket priority (configured on the alert) overrides the global
# HaloPSA.DefaultPriority. Empty on tasks saved before this field existed, in which
# case New-HaloPSATicket falls back to the integration default. Read here rather
# than inside the try so the consolidated fallback path below can use it even when
# the affected-user resolution throws.
$TaskPsaPriority = $TaskInfo.PsaTicketPriority
try {
$ExtConfigTable = Get-CIPPTable -TableName Extensionsconfig
$ExtConfig = (Get-CIPPAzDataTableEntity @ExtConfigTable).config | ConvertFrom-Json -ErrorAction SilentlyContinue
Expand Down Expand Up @@ -304,6 +310,7 @@ function Send-CIPPScheduledTaskAlert {
# task-level affected user if one was resolved.
$GroupParams = @{ Type = 'psa'; Title = $title; HTMLContent = $GroupHTML; TenantFilter = $TenantFilter }
if ($TaskAffectedUser) { $GroupParams.AffectedUser = $TaskAffectedUser }
if ($TaskPsaPriority) { $GroupParams.PsaTicketPriority = $TaskPsaPriority }
Send-CIPPAlert @GroupParams
} else {
$GroupDisplayName = if ($DisplayField) { $Group.Group[0].$DisplayField } else { $null }
Expand All @@ -313,7 +320,9 @@ function Send-CIPPScheduledTaskAlert {
UPN = $GroupKey
DisplayName = $GroupDisplayName
}
Send-CIPPAlert -Type 'psa' -Title $UserTitle -HTMLContent $GroupHTML -TenantFilter $TenantFilter -AffectedUser $AffectedUser
$UserParams = @{ Type = 'psa'; Title = $UserTitle; HTMLContent = $GroupHTML; TenantFilter = $TenantFilter; AffectedUser = $AffectedUser }
if ($TaskPsaPriority) { $UserParams.PsaTicketPriority = $TaskPsaPriority }
Send-CIPPAlert @UserParams
}
}
$PsaSplitSent = $true
Expand All @@ -327,6 +336,7 @@ function Send-CIPPScheduledTaskAlert {
if (-not $PsaSplitSent) {
$PsaParams = @{ Type = 'psa'; Title = $title; HTMLContent = (ConvertTo-PSAHtml -Html $HTML); TenantFilter = $TenantFilter }
if ($TaskAffectedUser) { $PsaParams.AffectedUser = $TaskAffectedUser }
if ($TaskPsaPriority) { $PsaParams.PsaTicketPriority = $TaskPsaPriority }
Send-CIPPAlert @PsaParams
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ function Invoke-CippWebhookProcessing {
if ($AffectedUser) {
$CIPPAlert.AffectedUser = $AffectedUser
}
# Per-alert priority rides on the record rather than a function parameter, the same
# way CustomSubject does above - this function has a second caller
# (Push-PublicWebhookProcess) that has no alert config to pass.
if ($Data.CIPPPsaTicketPriority) {
$CIPPAlert.PsaTicketPriority = $Data.CIPPPsaTicketPriority
}
Send-CIPPAlert @CIPPAlert
}
'generateWebhook' {
Expand Down
31 changes: 17 additions & 14 deletions backend/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,14 @@ function Test-CIPPAuditLogRules {
$ExcludedTenants = @(Expand-CIPPTenantGroups -TenantFilter $ExcludedTenants)
}
[pscustomobject]@{
Tenants = $Tenants
Excluded = $ExcludedTenants
Conditions = $ConfigEntry.Conditions
Actions = $ConfigEntry.Actions
LogType = $ConfigEntry.Type
AlertComment = $ConfigEntry.AlertComment
CustomSubject = $ConfigEntry.CustomSubject
Tenants = $Tenants
Excluded = $ExcludedTenants
Conditions = $ConfigEntry.Conditions
Actions = $ConfigEntry.Actions
LogType = $ConfigEntry.Type
AlertComment = $ConfigEntry.AlertComment
CustomSubject = $ConfigEntry.CustomSubject
PsaTicketPriority = $ConfigEntry.PsaTicketPriority
}
}
}
Expand Down Expand Up @@ -618,13 +619,14 @@ function Test-CIPPAuditLogRules {
}

[PSCustomObject]@{
conditions = $conditions
expectedAction = $actions
CIPPClause = $CIPPClause
AlertComment = $Config.AlertComment
CustomSubject = $Config.CustomSubject
HasGeoCondition = $HasGeoCondition
ExcludedUserKeys = $LocationExcludedUserKeys
conditions = $conditions
expectedAction = $actions
CIPPClause = $CIPPClause
AlertComment = $Config.AlertComment
CustomSubject = $Config.CustomSubject
PsaTicketPriority = $Config.PsaTicketPriority
HasGeoCondition = $HasGeoCondition
ExcludedUserKeys = $LocationExcludedUserKeys
}
}
} catch {
Expand Down Expand Up @@ -685,6 +687,7 @@ function Test-CIPPAuditLogRules {
$item.CIPPClause = $clause.CIPPClause -join ' and '
$item | Add-Member -NotePropertyName 'CIPPAlertComment' -NotePropertyValue $clause.AlertComment -Force -ErrorAction SilentlyContinue
$item | Add-Member -NotePropertyName 'CIPPCustomSubject' -NotePropertyValue $clause.CustomSubject -Force -ErrorAction SilentlyContinue
$item | Add-Member -NotePropertyName 'CIPPPsaTicketPriority' -NotePropertyValue $clause.PsaTicketPriority -Force -ErrorAction SilentlyContinue
$MatchedRules.Add($clause.CIPPClause -join ' and ')
$item
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@ Function Invoke-ExecExtensionMapping {
# Outcomes and priorities are scoped to a ticket type. The settings page sends the
# ticket type currently selected in the form so the lists follow the dropdown; without
# it both fall back to whatever ticket type was last saved.
# @() on each: PowerShell unrolls single-element output, so a ticket type with one outcome
# (or a lookup that answers with a single explanatory row) would otherwise serialise as a
# bare object and break callers that expect a list.
$SelectedTicketType = $Request.Query.TicketType
$TicketTypes = Get-HaloTicketType
$Outcomes = Get-HaloTicketOutcome -TicketType $SelectedTicketType
$Priorities = Get-HaloPriority -TicketType $SelectedTicketType
$TicketTypes = @(Get-HaloTicketType)
$Outcomes = @(Get-HaloTicketOutcome -TicketType $SelectedTicketType)
$Priorities = @(Get-HaloPriority -TicketType $SelectedTicketType)
$Result = @{
'TicketTypes' = $TicketTypes
'Outcomes' = $Outcomes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,18 @@ function Invoke-AddAlert {
$Actions = $Request.Body.actions | ConvertTo-Json -Compress -Depth 10 | Out-String
$RowKey = $Request.Body.RowKey ? $Request.Body.RowKey : (New-Guid).ToString()
$CompleteObject = @{
Tenants = [string]$TenantsJson
excludedTenants = [string]$excludedTenantsJson
Conditions = [string]$Conditions
Actions = [string]$Actions
type = $Request.Body.logbook.value
RowKey = $RowKey
PartitionKey = 'Webhookv2'
AlertComment = [string]$Request.Body.AlertComment
CustomSubject = [string]$Request.Body.CustomSubject
Tenants = [string]$TenantsJson
excludedTenants = [string]$excludedTenantsJson
Conditions = [string]$Conditions
Actions = [string]$Actions
type = $Request.Body.logbook.value
RowKey = $RowKey
PartitionKey = 'Webhookv2'
AlertComment = [string]$Request.Body.AlertComment
CustomSubject = [string]$Request.Body.CustomSubject
# The audit form posts the raw form values, so an autocomplete selection arrives as a
# {label, value} object - unwrap it to the bare Halo priority id before storing.
PsaTicketPriority = [string]($Request.Body.PsaTicketPriority.value ?? $Request.Body.PsaTicketPriority)
}
$WebhookTable = Get-CippTable -TableName 'WebhookRules'
Add-CIPPAzDataTableEntity @WebhookTable -Entity $CompleteObject -Force
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ function Invoke-ListAlertsQueue {
AlertComment = $Task.AlertComment
CustomSubject = $Task.CustomSubject
RawAlert = @{
Conditions = @($Conditions)
Actions = @($($Task.Actions | ConvertFrom-Json -Depth 10 -ErrorAction SilentlyContinue))
Tenants = @($Tenants)
type = $Task.type
RowKey = $Task.RowKey
PartitionKey = $Task.PartitionKey
AlertComment = $Task.AlertComment
CustomSubject = $Task.CustomSubject
Conditions = @($Conditions)
Actions = @($($Task.Actions | ConvertFrom-Json -Depth 10 -ErrorAction SilentlyContinue))
Tenants = @($Tenants)
type = $Task.type
RowKey = $Task.RowKey
PartitionKey = $Task.PartitionKey
AlertComment = $Task.AlertComment
CustomSubject = $Task.CustomSubject
PsaTicketPriority = $Task.PsaTicketPriority
}
}

Expand Down
19 changes: 5 additions & 14 deletions backend/Modules/CippExtensions/Public/Halo/Get-HaloPriority.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,14 @@ function Get-HaloPriority {
}

$Headers = @{ Authorization = "Bearer $($Token.access_token)" }
$TicketTypeRecord = Invoke-RestMethod -Uri "$($Configuration.ResourceURL)/tickettype/$TicketType" -ContentType 'application/json' -Method GET -Headers $Headers

# Halo's /tickettype/{id} response uses different field names for the linked SLA across
# versions. Check the known variants in priority order, take the first non-zero match.
$SlaIdCandidates = @('default_sla', 'default_sla_id', 'sla_id', 'slaid', 'sla')
$SlaId = $null
foreach ($Field in $SlaIdCandidates) {
$Value = $TicketTypeRecord.$Field
if ($Value -and ([int]$Value) -gt 0) {
$SlaId = [int]$Value
break
}
}
$SlaId = Get-HaloTicketTypeSlaId -TicketType $TicketType -Configuration $Configuration -Token $Token

if (-not $SlaId) {
# New-HaloPSATicket applies the same test and omits priority_id entirely for this
# ticket type, so the message describes what will actually happen rather than just
# explaining an empty list.
return @(@{
name = 'The selected Ticket Type has no SLA attached, so there are no priorities to pick from. Attach an SLA to the ticket type in HaloPSA, or leave this blank.'
name = 'The selected Ticket Type has no SLA attached, so there are no priorities to pick from. Tickets will be created without a priority and HaloPSA will apply its own. Attach an SLA to the ticket type in HaloPSA to choose one here.'
priorityid = -1
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
function Get-HaloTicketTypeSlaId {
<#
.SYNOPSIS
Resolve the SLA id attached to a HaloPSA ticket type, or $null when it has none.
.DESCRIPTION
Priorities in HaloPSA are defined per priority per SLA - the same priority_id means a
different thing under a different SLA (response and resolution targets are set on the
SLA/priority pair). A ticket type with no SLA therefore has no priority set that can be
meaningfully chosen from, which is why both the settings dropdown and the ticket writer
need to agree on whether one is attached.

Shared by Get-HaloPriority (to decide whether there is anything to offer) and
New-HaloPSATicket (to decide whether to send priority_id at all), so the two cannot drift
apart and start disagreeing about the same ticket type.
.PARAMETER TicketType
The ticket type id to resolve.
.PARAMETER Configuration
The HaloPSA extension configuration, for ResourceURL.
.PARAMETER Token
An existing Halo token, so callers that already hold one do not fetch a second.
.OUTPUTS
[int] the SLA id, or $null when the ticket type has no SLA or could not be read.
#>
[CmdletBinding()]
param (
$TicketType,
$Configuration,
$Token
)

if (-not $TicketType) { return $null }

try {
$Headers = @{ Authorization = "Bearer $($Token.access_token)" }
$TicketTypeRecord = Invoke-RestMethod -Uri "$($Configuration.ResourceURL)/tickettype/$TicketType" -ContentType 'application/json' -Method GET -Headers $Headers

# Halo's /tickettype/{id} response uses different field names for the linked SLA across
# versions. Check the known variants in order and take the first usable match. Halo uses
# -1 for "none", so anything not greater than zero counts as no SLA.
foreach ($Field in @('default_sla', 'default_sla_id', 'sla_id', 'slaid', 'sla')) {
$Value = $TicketTypeRecord.$Field
if ($Value -and ([int]$Value) -gt 0) {
return [int]$Value
}
}
return $null
} catch {
# Callers treat $null as "no SLA" and omit the priority, which is the safe direction:
# a transient lookup failure should not put an arbitrary priority on a ticket.
Write-Information "Could not resolve the SLA for HaloPSA ticket type $TicketType : $($_.Exception.Message)"
return $null
}
}
Loading
Loading