@@ -30,7 +30,17 @@ param(
3030 [string ]$InputJsonPath ,
3131
3232 [Parameter (Mandatory = $true )]
33- [string ]$OutputHtmlPath
33+ [string ]$OutputHtmlPath ,
34+
35+ # Optional secondary report. When supplied, the Network and AI pillars (all
36+ # their tests + the matching summary counts) are overlaid from this report,
37+ # replacing that data in the primary -InputJsonPath. Everything else
38+ # (Identity / Devices / Data / SecOps, tenant identity, dashboard overview)
39+ # is kept from the primary report. Brought tests are fully scrubbed of the
40+ # source tenant's identifiers (emails, IPs, domain, brand, GUID).
41+ [Parameter (Mandatory = $false )]
42+ [ValidateScript ({ Test-Path $_ - PathType Leaf })]
43+ [string ]$SourceJsonPath
3444)
3545
3646# Set up paths
@@ -76,6 +86,125 @@ Write-Host "Loading JSON report from: $InputJsonPath" -ForegroundColor Cyan
7686# Load the JSON report
7787$jsonContent = Get-Content - Path $InputJsonPath - Raw | ConvertFrom-Json - Depth 100
7888
89+ # ---------------------------------------------------------------------------
90+ # Optional: overlay the Network + AI pillars from a secondary source report.
91+ # Real exports often have far richer Network (Global Secure Access / Internet
92+ # Access) and AI pillars than the curated demo input. When -SourceJsonPath is
93+ # supplied we drop the primary report's Network/AI tests, bring in every
94+ # Network/AI test from the source, and copy the matching summary counts. This
95+ # also drives the home-page SWG defense-layer graphic, which keys off specific
96+ # Network TestIds.
97+ #
98+ # Because the source is a *real* tenant, every brought test is fully scrubbed
99+ # here (emails -> demouser<N>@contoso.com, IPv4 -> 203.0.113.x, tenant
100+ # domain/brand/GUID -> Contoso) before it is merged, in addition to the normal
101+ # anonymization passes that run later.
102+ # ---------------------------------------------------------------------------
103+ if ($PSBoundParameters.ContainsKey (' SourceJsonPath' )) {
104+ $bringPillars = @ (' Network' , ' AI' )
105+ Write-Host (" Overlaying {0} pillar(s) from source: {1}" -f ($bringPillars -join ' + ' ), $SourceJsonPath ) - ForegroundColor Cyan
106+ $sourceContent = Get-Content - Path $SourceJsonPath - Raw | ConvertFrom-Json - Depth 100
107+
108+ function Get-PillarSet {
109+ param ($Test )
110+ $p = $Test.TestPillar
111+ if ($null -eq $p ) { return @ () }
112+ if ($p -is [System.Array ]) { return @ ($p ) }
113+ return @ ($p )
114+ }
115+
116+ $isBrought = {
117+ param ($test )
118+ $pillars = Get-PillarSet $test
119+ foreach ($x in $pillars ) { if ($bringPillars -contains $x ) { return $true } }
120+ return $false
121+ }
122+
123+ $keptTests = @ ($jsonContent.Tests | Where-Object { -not (& $isBrought $_ ) })
124+ $broughtTests = @ ($sourceContent.Tests | Where-Object { & $isBrought $_ })
125+
126+ # Re-tag brought tests to the intersection of their pillars and the pillars
127+ # we overlay. This drops non-brought tags (e.g. "Data" on a ["Data","AI"]
128+ # test, so it doesn't surface on the sample-owned Data page) while keeping
129+ # brought tags (e.g. a ["Network","AI"] test stays on both pages).
130+ foreach ($test in $broughtTests ) {
131+ $kept = @ (Get-PillarSet $test | Where-Object { $bringPillars -contains $_ })
132+ $test.TestPillar = if ($kept.Count -eq 1 ) { $kept [0 ] } else { $kept }
133+ }
134+
135+ # --- Full scrub of source-derived (real tenant) content ---
136+ $demoTenantId = ' aaaabbbb-0000-cccc-1111-dddd2222eeee'
137+ $srcTenantId = [string ]$sourceContent.TenantId
138+ $srcDomain = [string ]$sourceContent.Domain
139+ $srcName = [string ]$sourceContent.TenantName
140+ $srcBrand = if ($srcDomain -match ' ^([^.]+)\.' ) { $matches [1 ] } else { $srcName }
141+
142+ $script :__emailAliases = @ {}
143+ $script :__emailNext = 1
144+ $script :__ipAliases = @ {}
145+ $script :__ipNext = 10
146+
147+ $scrub = {
148+ param ([string ]$text )
149+ if ([string ]::IsNullOrEmpty($text )) { return $text }
150+
151+ # Emails -> deterministic demouser<N>@contoso.com (leave demo/Microsoft as-is).
152+ $text = [regex ]::Replace($text , ' [A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}' , {
153+ param ($m )
154+ $e = $m.Value
155+ if ($e -match ' (?i)@(contoso\.com|contoso\.onmicrosoft\.com|microsoft\.com)$' ) { return $e }
156+ if (-not $script :__emailAliases.ContainsKey ($e )) {
157+ $script :__emailAliases [$e ] = " demouser$ ( $script :__emailNext ) @contoso.com"
158+ $script :__emailNext ++
159+ }
160+ return $script :__emailAliases [$e ]
161+ })
162+
163+ # IPv4 -> deterministic TEST-NET (RFC 5737) 203.0.113.x.
164+ $text = [regex ]::Replace($text , ' \b(?:\d{1,3}\.){3}\d{1,3}\b' , {
165+ param ($m )
166+ $ip = $m.Value
167+ if (-not $script :__ipAliases.ContainsKey ($ip )) {
168+ $script :__ipAliases [$ip ] = " 203.0.113.$ ( $script :__ipNext ) "
169+ $script :__ipNext ++
170+ }
171+ return $script :__ipAliases [$ip ]
172+ })
173+
174+ if ($srcTenantId ) { $text = $text -replace [regex ]::Escape($srcTenantId ), $demoTenantId }
175+ # Any onmicrosoft tenant remnants -> contoso.onmicrosoft.com.
176+ $text = $text -replace ' (?i)[A-Za-z0-9-]+\.onmicrosoft\.com' , ' contoso.onmicrosoft.com'
177+ # Remaining references to the source domain (URLs, bare host names) -> contoso.com.
178+ if ($srcDomain ) { $text = $text -replace (' (?i)[A-Za-z0-9.\-]*' + [regex ]::Escape($srcDomain )), ' contoso.com' }
179+ if ($srcName ) { $text = $text -replace (' (?i)' + [regex ]::Escape($srcName )), ' Contoso' }
180+ if ($srcBrand ) { $text = $text -replace (' (?i)\b' + [regex ]::Escape($srcBrand ) + ' \b' ), ' contoso' }
181+
182+ return $text
183+ }
184+
185+ foreach ($test in $broughtTests ) {
186+ foreach ($field in ' TestResult' , ' TestDescription' , ' SkippedReason' ) {
187+ if (($test.PSObject.Properties.Name -contains $field ) -and ($test .$field -is [string ])) {
188+ $test .$field = & $scrub $test .$field
189+ }
190+ }
191+ }
192+
193+ $jsonContent.Tests = @ ($keptTests + $broughtTests )
194+
195+ # Overlay the summary counts for each brought pillar.
196+ foreach ($pil in $bringPillars ) {
197+ $jsonContent.TestResultSummary ." ${pil} Passed" = $sourceContent.TestResultSummary ." ${pil} Passed"
198+ $jsonContent.TestResultSummary ." ${pil} Total" = $sourceContent.TestResultSummary ." ${pil} Total"
199+ }
200+
201+ Write-Host (" Brought {0} test(s) across {1}; scrubbed {2} email(s) and {3} IP(s)" -f `
202+ $broughtTests.Count , ($bringPillars -join ' /' ), $script :__emailAliases.Count , $script :__ipAliases.Count ) - ForegroundColor Cyan
203+ foreach ($pil in $bringPillars ) {
204+ Write-Host (" {0} summary now {1}/{2}" -f $pil , $jsonContent.TestResultSummary ." ${pil} Passed" , $jsonContent.TestResultSummary ." ${pil} Total" ) - ForegroundColor Cyan
205+ }
206+ }
207+
79208Write-Host " Anonymizing report data..." - ForegroundColor Cyan
80209
81210# Anonymize tenant information
0 commit comments