@@ -2,243 +2,93 @@ function Add-CIPPDbItem {
22 <#
33 . SYNOPSIS
44 Add items to the CIPP Reporting database
5-
6- . DESCRIPTION
7- Adds items to the CippReportingDB table with support for bulk inserts, count mode, and pipeline streaming
8-
9- . PARAMETER TenantFilter
10- The tenant domain or GUID (used as partition key)
11-
12- . PARAMETER Type
13- The type of data being stored (used in row key)
14-
15- . PARAMETER InputObject
16- Items to add to the database. Accepts pipeline input for memory-efficient streaming.
17- Alias: Data (for backward compatibility)
18-
19- . PARAMETER Count
20- If specified, stores a single row with count of items processed
21-
22- . PARAMETER AddCount
23- If specified, automatically records the total count after processing all items
24-
25- . PARAMETER Append
26- If specified, adds items without clearing existing entries for this type/tenant and automatically
27- increments the count. Useful for accumulating report data over time. By default, existing entries are replaced.
28-
29- . EXAMPLE
30- Add-CIPPDbItem -TenantFilter 'contoso.onmicrosoft.com' -Type 'Groups' -Data $GroupsData
31-
32- . EXAMPLE
33- New-GraphGetRequest -uri '...' | Add-CIPPDbItem -TenantFilter 'contoso.onmicrosoft.com' -Type 'Users' -AddCount
34-
35- . EXAMPLE
36- Add-CIPPDbItem -TenantFilter 'contoso.onmicrosoft.com' -Type 'Groups' -Data $GroupsData -Count
37-
38- . EXAMPLE
39- Add-CIPPDbItem -TenantFilter 'contoso.onmicrosoft.com' -Type 'AlertHistory' -Data $AlertData -Append -AddCount
5+ . FUNCTIONALITY
6+ Internal
407 #>
418 [CmdletBinding ()]
429 param (
43- [Parameter (Mandatory = $true )]
10+ [Parameter (Mandatory )]
4411 [string ]$TenantFilter ,
4512
46- [Parameter (Mandatory = $true )]
13+ [Parameter (Mandatory )]
4714 [string ]$Type ,
4815
49- [Parameter (Mandatory = $true , ValueFromPipeline = $true )]
16+ [Parameter (Mandatory , ValueFromPipeline )]
5017 [Alias (' Data' )]
5118 [AllowNull ()]
5219 [AllowEmptyCollection ()]
5320 $InputObject ,
5421
55- [Parameter (Mandatory = $false )]
5622 [switch ]$Count ,
57-
58- [Parameter (Mandatory = $false )]
5923 [switch ]$AddCount ,
60-
61- [Parameter (Mandatory = $false )]
6224 [switch ]$Append
6325 )
6426
6527 begin {
66- # Initialize pipeline processing with state hashtable for nested function access
6728 $Table = Get-CippTable - tablename ' CippReportingDB'
68- $BatchAccumulator = [System.Collections.Generic.List [hashtable ]]::new(500 )
69- $State = @ {
70- TotalProcessed = 0
71- BatchNumber = 0
72- }
29+ $Batch = [System.Collections.Generic.List [hashtable ]]::new()
30+ $TotalProcessed = 0
7331
74- if ($TenantFilter -match ' ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}- [0-9a-f]{12}$' ) {
32+ if ($TenantFilter -match ' ^[0-9a-f]{8}-( [0-9a-f]{4}-){3} [0-9a-f]{12}$' ) {
7533 try {
7634 $TenantFilter = (Get-Tenants - TenantFilter $TenantFilter - IncludeErrors | Select-Object - First 1 ).defaultDomainName
77- } catch {
78- Write-LogMessage - API ' CIPPDbItem' - tenant $TenantFilter - message " Failed to resolve tenant GUID to default domain: $ ( $_.Exception.Message ) " - sev Warning
79- }
80- }
81-
82- # Helper function to format RowKey values by removing disallowed characters
83- function Format-RowKey {
84- param ([string ]$RowKey )
85- $sanitized = $RowKey -replace ' [/\\#?]' , ' _' -replace ' [\u0000-\u001F\u007F-\u009F]' , ' '
86- return $sanitized
87- }
88-
89- # Function to flush current batch
90- function Invoke-FlushBatch {
91- param ($State )
92- if ($BatchAccumulator.Count -eq 0 ) { return }
93-
94- $State.BatchNumber ++
95- $batchSize = $BatchAccumulator.Count
96- $MemoryBeforeGC = [System.GC ]::GetTotalMemory($false )
97- $flushStart = Get-Date
98-
99- try {
100- # Entities are already in the accumulator, just write them
101- $writeStart = Get-Date
102- Add-CIPPAzDataTableEntity @Table - Entity $BatchAccumulator.ToArray () - Force | Out-Null
103- $writeEnd = Get-Date
104- $writeDuration = [math ]::Round(($writeEnd - $writeStart ).TotalSeconds, 2 )
105- $State.TotalProcessed += $batchSize
106-
107- } finally {
108- # Clear and GC
109- $gcStart = Get-Date
110- $BatchAccumulator.Clear ()
111-
112- # Single GC pass is sufficient - aggressive GC was causing slowdown
113- [System.GC ]::Collect()
114-
115- $flushEnd = Get-Date
116- $gcDuration = [math ]::Round(($flushEnd - $gcStart ).TotalSeconds, 2 )
117- $flushDuration = [math ]::Round(($flushEnd - $flushStart ).TotalSeconds, 2 )
118- $MemoryAfterGC = [System.GC ]::GetTotalMemory($false )
119- $FreedMB = [math ]::Round(($MemoryBeforeGC - $MemoryAfterGC ) / 1 MB , 2 )
120- $CurrentMemoryMB = [math ]::Round($MemoryAfterGC / 1 MB , 2 )
121- # Write-Debug "Batch $($State.BatchNumber): ${flushDuration}s total (write: ${writeDuration}s, gc: ${gcDuration}s) | Processed: $($State.TotalProcessed) | Memory: ${CurrentMemoryMB}MB | Freed: ${FreedMB}MB"
122- }
35+ } catch {}
12336 }
12437
12538 if (-not $Count.IsPresent -and -not $Append.IsPresent ) {
126- # Delete existing entries for this type
12739 $Filter = " PartitionKey eq '{0}' and RowKey ge '{1}-' and RowKey lt '{1}0'" -f $TenantFilter , $Type
128- $ExistingEntities = Get-CIPPAzDataTableEntity @Table - Filter $Filter - Property PartitionKey, RowKey, ETag
129- if ($ExistingEntities ) {
130- Remove-AzDataTableEntity @Table - Entity $ExistingEntities - Force | Out-Null
40+ $Existing = Get-CIPPAzDataTableEntity @Table - Filter $Filter - Property PartitionKey, RowKey, ETag
41+ if ($Existing ) {
42+ $null = Remove-AzDataTableEntity @Table - Entity $Existing - Force
13143 }
132- $AllocatedMemoryMB = [math ]::Round([System.GC ]::GetTotalMemory($false ) / 1 MB , 2 )
133- # Write-Debug "Starting $Type import for $TenantFilter | Allocated Memory: ${AllocatedMemoryMB}MB | Batch Size: 500"
13444 }
13545 }
13646
13747 process {
138- # Process each item from pipeline
13948 if ($null -eq $InputObject ) { return }
14049
141- # If Count mode and InputObject is an integer, use it directly as count
142- if ($Count.IsPresent -and $InputObject -is [int ]) {
143- $State.TotalProcessed = $InputObject
144- return
145- }
146-
147- # Handle both single items and arrays (for backward compatibility)
148- $ItemsToProcess = if ($InputObject -is [array ]) {
149- $InputObject
150- } else {
151- @ ($InputObject )
152- }
153-
154- # If Count mode, just count items without processing
15550 if ($Count.IsPresent ) {
156- $itemCount = if ($ItemsToProcess -is [array ]) { $ItemsToProcess.Count } else { 1 }
157- $State.TotalProcessed += $itemCount
51+ if ($InputObject -is [int ]) { $TotalProcessed = $InputObject } else { $TotalProcessed += @ ($InputObject ).Count }
15852 return
15953 }
16054
161- foreach ($Item in $ItemsToProcess ) {
55+ foreach ($Item in @ ( $InputObject ) ) {
16256 if ($null -eq $Item ) { continue }
163-
164- # Convert to entity
165- $ItemId = $Item.ExternalDirectoryObjectId ?? $Item.id ?? $Item.Identity ?? $Item.skuId ?? $Item.userPrincipalName ?? [Guid ]::NewGuid().ToString()
166- $Entity = @ {
167- PartitionKey = $TenantFilter
168- RowKey = Format-RowKey " $Type -$ItemId "
169- Data = [string ]($Item | ConvertTo-Json - Depth 10 - Compress)
170- Type = $Type
171- }
172-
173- $BatchAccumulator.Add ($Entity )
174-
175- # Flush when batch reaches 500 items
176- if ($BatchAccumulator.Count -ge 500 ) {
177- Invoke-FlushBatch - State $State
57+ $ItemId = $Item.ExternalDirectoryObjectId ?? $Item.id ?? $Item.Identity ?? $Item.skuId ?? $Item.userPrincipalName ?? [guid ]::NewGuid().ToString()
58+ $Batch.Add (@ {
59+ PartitionKey = $TenantFilter
60+ RowKey = (" $Type -$ItemId " -replace ' [/\\#?]' , ' _' -replace ' [\u0000-\u001F\u007F-\u009F]' , ' ' )
61+ Data = [string ]($Item | ConvertTo-Json - Depth 10 - Compress)
62+ Type = $Type
63+ })
64+ if ($Batch.Count -ge 500 ) {
65+ $null = Add-CIPPAzDataTableEntity @Table - Entity $Batch.ToArray () - Force
66+ $TotalProcessed += $Batch.Count
67+ $Batch.Clear ()
17868 }
17969 }
18070 }
18171
18272 end {
183- try {
184- # Flush any remaining items in final partial batch
185- if ($BatchAccumulator.Count -gt 0 ) {
186- Invoke-FlushBatch - State $State
187- }
188-
189- if ($Count.IsPresent -or $Append.IsPresent ) {
190- # Store count record
191- if ($Append.IsPresent ) {
192- # When appending, always increment the existing count
193- $Filter = " PartitionKey eq '{0}' and RowKey eq '{1}-Count'" -f $TenantFilter , $Type
194- $ExistingCount = Get-CIPPAzDataTableEntity @Table - Filter $Filter
195- $PreviousCount = if ($ExistingCount -and $ExistingCount.DataCount ) { [int ]$ExistingCount.DataCount } else { 0 }
196- $NewCount = $PreviousCount + $State.TotalProcessed
197- } else {
198- # Normal mode - replace count
199- $NewCount = $State.TotalProcessed
200- }
201-
202- $Entity = @ {
203- PartitionKey = $TenantFilter
204- RowKey = Format-RowKey " $Type -Count"
205- DataCount = [int ]$NewCount
206- }
207- Add-CIPPAzDataTableEntity @Table - Entity $Entity - Force | Out-Null
208- }
209-
210- Write-LogMessage - API ' CIPPDbItem' - tenant $TenantFilter `
211- - message " Added $ ( $State.TotalProcessed ) items of type $Type $ ( if ($Count.IsPresent ) { ' (count mode)' }) $ ( if ($Append.IsPresent ) { ' (append mode)' }) " - sev Debug
73+ if ($Batch.Count -gt 0 ) {
74+ $null = Add-CIPPAzDataTableEntity @Table - Entity $Batch.ToArray () - Force
75+ $TotalProcessed += $Batch.Count
76+ }
21277
213- } catch {
214- Write-LogMessage - API ' CIPPDbItem' - tenant $TenantFilter `
215- - message " Failed to add items of type $Type : $ ( $_.Exception.Message ) " - sev Error `
216- - LogData (Get-CippException - Exception $_ )
217- # Write-Debug "[Add-CIPPDbItem] $TenantFilter - $(Get-CippException -Exception $_ | ConvertTo-Json -Depth 5 -Compress)"
218- throw
219- } finally {
220- # Record count if AddCount was specified
221- if ($AddCount.IsPresent -and $State.TotalProcessed -gt 0 ) {
222- try {
223- $countParams = @ {
224- TenantFilter = $TenantFilter
225- Type = $Type
226- InputObject = $State.TotalProcessed
227- Count = $true
228- }
229- if ($Append.IsPresent ) {
230- $countParams.Append = $true
231- }
232- Add-CIPPDbItem @countParams
233- } catch {
234- Write-LogMessage - API ' CIPPDbItem' - tenant $TenantFilter `
235- - message " Failed to record count for $Type : $ ( $_.Exception.Message ) " - sev Warning
236- }
78+ if ($Count.IsPresent -or $AddCount.IsPresent ) {
79+ $NewCount = $TotalProcessed
80+ if ($Append.IsPresent ) {
81+ $Filter = " PartitionKey eq '{0}' and RowKey eq '{1}-Count'" -f $TenantFilter , $Type
82+ $ExistingCount = Get-CIPPAzDataTableEntity @Table - Filter $Filter
83+ if ($ExistingCount.DataCount ) { $NewCount += [int ]$ExistingCount.DataCount }
23784 }
238-
239- # Final cleanup
240- $BatchAccumulator = $null
241- [System.GC ]::Collect()
85+ $null = Add-CIPPAzDataTableEntity @Table - Entity @ {
86+ PartitionKey = $TenantFilter
87+ RowKey = " $Type -Count"
88+ DataCount = [int ]$NewCount
89+ } - Force
24290 }
91+
92+ Write-LogMessage - API ' CIPPDbItem' - tenant $TenantFilter - message " Added $TotalProcessed items of type $Type " - sev Debug
24393 }
24494}
0 commit comments