Skip to content

Commit 243050e

Browse files
authored
Merge pull request #577 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 9baac5e + 3f6dd43 commit 243050e

File tree

8 files changed

+295
-5
lines changed

8 files changed

+295
-5
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action
2+
# More GitHub Actions for Azure: https://github.com/Azure/actions
3+
4+
name: Build and deploy Powershell project to Azure Function App - cippwubam
5+
6+
on:
7+
push:
8+
branches:
9+
- dev
10+
workflow_dispatch:
11+
12+
env:
13+
AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root
14+
15+
jobs:
16+
deploy:
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: 'Checkout GitHub Action'
21+
uses: actions/checkout@v4
22+
23+
- name: 'Run Azure Functions Action'
24+
uses: Azure/functions-action@v1
25+
id: fa
26+
with:
27+
app-name: 'cippwubam'
28+
slot-name: 'Production'
29+
package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}
30+
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_1EF590059B3740EF954AFF32CF485EF0 }}
31+
sku: 'flexconsumption'
32+

Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandard.ps1

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,28 @@ function Push-CIPPStandard {
4949
$Settings = $Item.Settings
5050
}
5151

52-
& $FunctionName -Tenant $Item.Tenant -Settings $Settings -ErrorAction Stop
52+
# Prepare telemetry metadata for standard execution
53+
$metadata = @{
54+
Standard = $Standard
55+
Tenant = $Tenant
56+
TemplateId = $Item.templateId
57+
FunctionName = $FunctionName
58+
TriggerType = 'Standard'
59+
}
60+
61+
# Add template-specific metadata
62+
if ($Standard -eq 'IntuneTemplate' -and $Item.Settings.TemplateList.value) {
63+
$metadata['IntuneTemplateId'] = $Item.Settings.TemplateList.value
64+
}
65+
if ($Standard -eq 'ConditionalAccessTemplate' -and $Item.Settings.TemplateList.value) {
66+
$metadata['CATemplateId'] = $Item.Settings.TemplateList.value
67+
}
68+
69+
# Wrap the standard execution with telemetry
70+
Measure-CippTask -TaskName $Standard -EventName 'CIPP.StandardCompleted' -Metadata $metadata -Script {
71+
& $FunctionName -Tenant $Item.Tenant -Settings $Settings -ErrorAction Stop
72+
}
73+
5374
Write-Information "Standard $($Standard) completed for tenant $($Tenant)"
5475
} catch {
5576
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Error running standard $($Standard) for tenant $($Tenant) - $($_.Exception.Message)" -sev Error -LogData (Get-CippException -Exception $_)

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/New-CippCoreRequest.ps1

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,31 @@ function New-CippCoreRequest {
6969
Write-Information "Access: $Access"
7070
Write-LogMessage -headers $Headers -API $Request.Params.CIPPEndpoint -message 'Accessed this API' -Sev 'Debug'
7171
if ($Access) {
72-
$Response = & $FunctionName @HttpTrigger
72+
# Prepare telemetry metadata for HTTP API call
73+
$metadata = @{
74+
Endpoint = $Request.Params.CIPPEndpoint
75+
FunctionName = $FunctionName
76+
Method = $Request.Method
77+
TriggerType = 'HTTP'
78+
}
79+
80+
# Add tenant filter if present
81+
if ($Request.Query.TenantFilter) {
82+
$metadata['Tenant'] = $Request.Query.TenantFilter
83+
} elseif ($Request.Body.TenantFilter) {
84+
$metadata['Tenant'] = $Request.Body.TenantFilter
85+
}
86+
87+
# Add user info if available
88+
if ($Request.Headers.'x-ms-client-principal-name') {
89+
$metadata['User'] = $Request.Headers.'x-ms-client-principal-name'
90+
}
91+
92+
# Wrap the API call execution with telemetry
93+
$Response = Measure-CippTask -TaskName $Request.Params.CIPPEndpoint -Metadata $metadata -Script {
94+
& $FunctionName @HttpTrigger
95+
}
96+
7397
# Filter to only return HttpResponseContext objects
7498
$HttpResponse = $Response | Where-Object { $_.PSObject.TypeNames -eq 'Microsoft.Azure.Functions.PowerShellWorker.HttpResponseContext' }
7599
if ($HttpResponse) {
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
function Measure-CippTask {
2+
<#
3+
.SYNOPSIS
4+
Measure and track CIPP task execution with Application Insights telemetry
5+
.DESCRIPTION
6+
Wraps task execution in a timer, sends custom event to Application Insights with duration and metadata
7+
.PARAMETER TaskName
8+
The name of the task being executed (e.g., "New-CIPPTemplateRun")
9+
.PARAMETER Script
10+
The scriptblock to execute and measure
11+
.PARAMETER Metadata
12+
Optional hashtable of metadata to include in telemetry (e.g., Command, Tenant, TaskInfo)
13+
.PARAMETER EventName
14+
Optional custom event name (default: "CIPP.TaskCompleted")
15+
.FUNCTIONALITY
16+
Internal
17+
.EXAMPLE
18+
Measure-CippTask -TaskName "ApplyTemplate" -Script {
19+
# Task logic here
20+
} -Metadata @{
21+
Command = "New-CIPPTemplateRun"
22+
Tenant = "contoso.onmicrosoft.com"
23+
}
24+
.EXAMPLE
25+
Measure-CippTask -TaskName "DisableGuests" -EventName "CIPP.StandardCompleted" -Script {
26+
# Standard logic here
27+
} -Metadata @{
28+
Standard = "DisableGuests"
29+
Tenant = "contoso.onmicrosoft.com"
30+
}
31+
#>
32+
[CmdletBinding()]
33+
param(
34+
[Parameter(Mandatory = $true)]
35+
[string]$TaskName,
36+
37+
[Parameter(Mandatory = $true)]
38+
[scriptblock]$Script,
39+
40+
[Parameter(Mandatory = $false)]
41+
[hashtable]$Metadata,
42+
43+
[Parameter(Mandatory = $false)]
44+
[string]$EventName = 'CIPP.TaskCompleted'
45+
)
46+
47+
# Initialize tracking variables
48+
$sw = [System.Diagnostics.Stopwatch]::StartNew()
49+
$result = $null
50+
$errorOccurred = $false
51+
$errorMessage = $null
52+
53+
try {
54+
# Execute the actual task
55+
$result = & $Script
56+
} catch {
57+
$errorOccurred = $true
58+
$errorMessage = $_.Exception.Message
59+
# Re-throw to preserve original error behavior
60+
throw
61+
} finally {
62+
# Stop the timer
63+
$sw.Stop()
64+
$durationMs = [int]$sw.Elapsed.TotalMilliseconds
65+
66+
# Send telemetry if TelemetryClient is available
67+
if ($global:TelemetryClient) {
68+
try {
69+
# Build properties dictionary for customDimensions
70+
$props = New-Object 'System.Collections.Generic.Dictionary[string,string]'
71+
$props['TaskName'] = $TaskName
72+
$props['Success'] = (-not $errorOccurred).ToString()
73+
74+
if ($errorOccurred) {
75+
$props['ErrorMessage'] = $errorMessage
76+
}
77+
78+
# Add all metadata to properties
79+
if ($Metadata) {
80+
foreach ($key in $Metadata.Keys) {
81+
$value = $Metadata[$key]
82+
# Convert value to string, handling nulls
83+
if ($null -ne $value) {
84+
$props[$key] = [string]$value
85+
} else {
86+
$props[$key] = ''
87+
}
88+
}
89+
}
90+
91+
# Metrics dictionary for customMeasurements
92+
$metrics = New-Object 'System.Collections.Generic.Dictionary[string,double]'
93+
$metrics['DurationMs'] = [double]$durationMs
94+
95+
# Send custom event to Application Insights
96+
$global:TelemetryClient.TrackEvent($EventName, $props, $metrics)
97+
$global:TelemetryClient.Flush()
98+
99+
Write-Verbose "Telemetry sent for task '$TaskName' to event '$EventName' (${durationMs}ms)"
100+
} catch {
101+
Write-Warning "Failed to send telemetry for task '${TaskName}': $($_.Exception.Message)"
102+
}
103+
} else {
104+
Write-Verbose "TelemetryClient not initialized, skipping telemetry for task '$TaskName'"
105+
}
106+
}
107+
108+
return $result
109+
}

Modules/CippEntrypoints/CippEntrypoints.psm1

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,52 @@ function Receive-CippActivityTrigger {
294294

295295
if ($Item.FunctionName) {
296296
$FunctionName = 'Push-{0}' -f $Item.FunctionName
297+
298+
# Prepare telemetry metadata
299+
$taskName = if ($Item.Command) { $Item.Command } else { $FunctionName }
300+
$metadata = @{
301+
Command = if ($Item.Command) { $Item.Command } else { $FunctionName }
302+
FunctionName = $FunctionName
303+
}
304+
305+
# Add tenant information if available
306+
if ($Item.TaskInfo) {
307+
if ($Item.TaskInfo.Tenant) {
308+
$metadata['Tenant'] = $Item.TaskInfo.Tenant
309+
}
310+
if ($Item.TaskInfo.Name) {
311+
$metadata['JobName'] = $Item.TaskInfo.Name
312+
}
313+
if ($Item.TaskInfo.Recurrence) {
314+
$metadata['Recurrence'] = $Item.TaskInfo.Recurrence
315+
}
316+
}
317+
318+
# Add tenant from other common fields
319+
if (-not $metadata['Tenant']) {
320+
if ($Item.TenantFilter) {
321+
$metadata['Tenant'] = $Item.TenantFilter
322+
} elseif ($Item.Tenant) {
323+
$metadata['Tenant'] = $Item.Tenant
324+
}
325+
}
326+
327+
# Add queue information
328+
if ($Item.QueueId) {
329+
$metadata['QueueId'] = $Item.QueueId
330+
}
331+
if ($Item.QueueName) {
332+
$metadata['QueueName'] = $Item.QueueName
333+
}
334+
297335
try {
298336
Write-Warning "Activity starting Function: $FunctionName."
299-
$Output = Invoke-Command -ScriptBlock { & $FunctionName -Item $Item }
337+
338+
# Wrap the function execution with telemetry
339+
$Output = Measure-CippTask -TaskName $taskName -Metadata $metadata -Script {
340+
Invoke-Command -ScriptBlock { & $FunctionName -Item $Item }
341+
}
342+
300343
Write-Warning "Activity completed Function: $FunctionName."
301344
if ($TaskStatus) {
302345
$QueueTask.Status = 'Completed'
@@ -389,7 +432,31 @@ function Receive-CIPPTimerTrigger {
389432
$Parameters = $Function.Parameters | ConvertTo-Json | ConvertFrom-Json -AsHashtable
390433
}
391434

392-
$Results = Invoke-Command -ScriptBlock { & $Function.Command @Parameters }
435+
# Prepare telemetry metadata
436+
$metadata = @{
437+
Command = $Function.Command
438+
Cron = $Function.Cron
439+
FunctionId = $Function.Id
440+
TriggerType = 'Timer'
441+
}
442+
443+
# Add parameters if available
444+
if ($Parameters.Count -gt 0) {
445+
$metadata['ParameterCount'] = $Parameters.Count
446+
# Add specific known parameters
447+
if ($Parameters.Tenant) {
448+
$metadata['Tenant'] = $Parameters.Tenant
449+
}
450+
if ($Parameters.TenantFilter) {
451+
$metadata['Tenant'] = $Parameters.TenantFilter
452+
}
453+
}
454+
455+
# Wrap the timer function execution with telemetry
456+
$Results = Measure-CippTask -TaskName $Function.Command -Metadata $metadata -Script {
457+
Invoke-Command -ScriptBlock { & $Function.Command @Parameters }
458+
}
459+
393460
if ($Results -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') {
394461
$FunctionStatus.OrchestratorId = $Results -join ','
395462
$Status = 'Started'

Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1654,7 +1654,7 @@ function Invoke-NinjaOneTenantSync {
16541654
$ParsedAdmins = [PSCustomObject]@{}
16551655

16561656
$AdminUsers | Select-Object displayname, userPrincipalName -Unique | ForEach-Object {
1657-
$ParsedAdmins | Add-Member -NotePropertyName $_.displayname -NotePropertyValue $_.userPrincipalName
1657+
$ParsedAdmins | Add-Member -NotePropertyName $_.displayname -NotePropertyValue $_.userPrincipalName -Force
16581658
}
16591659

16601660
$TenantDetailsItems = [PSCustomObject]@{
377 KB
Binary file not shown.

profile.ps1

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,42 @@
11
Write-Information '#### CIPP-API Start ####'
22

3+
# Load Application Insights SDK for telemetry
4+
Set-Location -Path $PSScriptRoot
5+
try {
6+
$AppInsightsDllPath = Join-Path $PSScriptRoot 'Shared\AppInsights\Microsoft.ApplicationInsights.dll'
7+
if (Test-Path $AppInsightsDllPath) {
8+
[Reflection.Assembly]::LoadFile($AppInsightsDllPath) | Out-Null
9+
Write-Information 'Application Insights SDK loaded successfully'
10+
} else {
11+
Write-Warning "Application Insights DLL not found at: $AppInsightsDllPath"
12+
}
13+
} catch {
14+
Write-Warning "Failed to load Application Insights SDK: $($_.Exception.Message)"
15+
}
16+
17+
# Initialize global TelemetryClient
18+
if (-not $global:TelemetryClient) {
19+
try {
20+
$connectionString = $env:APPLICATIONINSIGHTS_CONNECTION_STRING
21+
if ($connectionString) {
22+
# Use connection string (preferred method)
23+
$config = [Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration]::CreateDefault()
24+
$config.ConnectionString = $connectionString
25+
$global:TelemetryClient = [Microsoft.ApplicationInsights.TelemetryClient]::new($config)
26+
Write-Information 'TelemetryClient initialized with connection string'
27+
} elseif ($env:APPINSIGHTS_INSTRUMENTATIONKEY) {
28+
# Fall back to instrumentation key
29+
$global:TelemetryClient = [Microsoft.ApplicationInsights.TelemetryClient]::new()
30+
$global:TelemetryClient.InstrumentationKey = $env:APPINSIGHTS_INSTRUMENTATIONKEY
31+
Write-Information 'TelemetryClient initialized with instrumentation key'
32+
} else {
33+
Write-Warning 'No Application Insights connection string or instrumentation key found'
34+
}
35+
} catch {
36+
Write-Warning "Failed to initialize TelemetryClient: $($_.Exception.Message)"
37+
}
38+
}
39+
340
# Import modules
441
@('CIPPCore', 'CippExtensions', 'Az.KeyVault', 'Az.Accounts', 'AzBobbyTables') | ForEach-Object {
542
try {

0 commit comments

Comments
 (0)