A Safe Purview DSPM for AI Lab: Sensitivity Labels, RCD, and Synthetic EDM Data
1. Study log: the salary file problem
The scenario for this entry is a common Copilot readiness worry: a payroll export sits in an HR site that too many people can open, and nobody wants a Copilot answer to quote someone’s salary. Copilot works inside existing Microsoft 365 permissions, so the fix starts with access and classification.[15]
I didn’t connect to a Microsoft 365 tenant, create a label, upload an EDM table, or turn on any policy. I wrote a PowerShell generator for synthetic HR data and ran it locally on PowerShell 7.4.6 (section 4). Every name, employee ID and salary in this article is fake. The tenant steps are a lab plan built from Microsoft Learn. I have no tenant results to report.
Blueprint version: the AB-900 study guide lists skills measured as of October 14, 2026, and the credential page says the English exam updates on that date.[1][2] This article targets the October 14, 2026 version, because the PR may publish after it takes effect. Exams taken before October 14, 2026 use the prior version; the study guide’s change log lists only a minor change, in “Identify the core security features of Microsoft 365 services”.[1] In the October 14 version, “Understand data protection and governance tasks for Microsoft 365 and Copilot” carries 35-40% of the exam.[1]
| Default approach | AI-hardened approach |
|---|---|
| Hide the HR site from Copilot and call it done. | Use Restricted Content Discovery as a temporary hold while owners fix permissions. It doesn’t change access.[5] |
| Detect salary data with a broad number pattern. | Match exact values from a hashed reference table with an EDM classifier.[6] |
| Turn on an auto-labeling policy straight away. | Run simulation first. Microsoft requires at least one simulation before labels apply.[12] |
| Test with a copy of real payroll data. | Use synthetic rows. The EDM sample file isn’t hashed when you upload it.[7] |
| Trust a label to stop Copilot by itself. | Know what a label does: encryption needs EXTRACT and VIEW rights for Copilot to return data, and a DLP policy can block labeled items from Copilot processing.[15][16] |
2. Lab architecture and prerequisites
The data path for the lab looks like this:
Synthetic HR site → DSPM data risk assessment → Restricted Content Discovery (temporary) → EDM classifier from a hashed synthetic table → auto-labeling policy in simulation → label → optional DLP rule for Copilot
The exam objective says “DSPM for AI”.[1] Microsoft Learn now marks the DSPM for AI page as the classic version, replaced by a new Data Security Posture Management experience that covers AI apps and agents.[3][4] The portal path in the classic docs is Solutions > DSPM for AI (classic).[3] Learn both names; your tenant may show either.
Prerequisites checklist:
- A non-production tenant with a documented owner. Don’t run this in production first.
- Licensing confirmed for each feature. Restricted Content Discovery needs a Microsoft Copilot license and SharePoint Advanced Management available.[5] Auto-labeling is gated by license; Microsoft points to its licensing guidance for the per-SKU matrix.[12]
- Microsoft 365 auditing turned on. Simulation requires it.[12]
- Sensitivity labels enabled for Office files in SharePoint and OneDrive, or auto-labeling can’t label files there.[12][18]
- At least one sensitivity label created and published to at least one user. It can’t be a parent label, and its scope must include Files & other data assets.[12]
- Roles kept small. EDM SIT creation needs Compliance administrator (or Global administrator, which Microsoft recommends against).[8] Viewing matched content in simulation needs Data Classification Content Viewer, which Global admins don’t have by default. Turning a policy on needs Compliance Administrator or Compliance Data Administrator.[12]
- A security group named EDM_DataUploaders with the upload account as a member. Microsoft lists Groups Administrator as the least-privilege role to create it.[10]
- A Windows 10 or later machine (or Windows Server 2019 or later) for the EDM Upload Agent.[10]
3. Implementation steps
Steps 1 and 2 are read-only. Each step ends with its most likely failure point.
-
Review the DSPM data risk assessment. DSPM for AI runs a weekly default assessment for the top 100 SharePoint sites by usage, and the first results take 4 days to appear.[3] Open the synthetic HR site from the list, or run a custom assessment if it isn’t there.[3] The Protect tab offers the same controls this lab uses: restrict access by label (DLP), restrict all items (Restricted Content Discovery), and create an auto-labeling policy.[3] Failure point: a brand-new lab tenant may show nothing for days. That’s expected.
-
Confirm label support in SharePoint and OneDrive before you build anything. Microsoft enables the capability with
Set-SPOTenant -EnableAIPIntegration $true; in this lab, read the current value and only change it under an approved change.[18] Failure point: a silent no-op. Microsoft’s pre-flight checklist says a missing prerequisite typically leaves the policy running without labeling any files, with no error in the portal.[12] -
Put the HR site on a temporary hold. In the SharePoint admin center, go to Sites > Active sites, select the site, open Settings, and turn on Restrict content from Microsoft Copilot. The PowerShell equivalent is below.[5]
# Lab plan, not executed. SharePoint Online Management Shell. Get-SPOSite -Identity <site-url> | Select RestrictContentOrgWideSearch Set-SPOSite -Identity <site-url> -RestrictContentOrgWideSearch $true # Rollback: Set-SPOSite -Identity <site-url> -RestrictContentOrgWideSearch $falseFailure point: expecting it to work like a permission change. Site permissions stay the same, users with access can still open the files, and summarizing a document the user already has open isn’t affected. It applies to SharePoint sites only, not OneDrive.[5] Propagation depends on site size; for sites over 500,000 items, Microsoft says an update could take more than a week.[5]
-
Create a custom SIT for the employee ID. EDM needs a primary field that an existing SIT can detect, and the field should be unique per row.[6][8] There’s no built-in SIT for a made-up ID format, so create one under Information Protection > Classifiers > Sensitive info types > Create sensitive info type.[17] For the synthetic
ZKLABIDs, a regex such as\b(ZKLAB[0-9]{6})\bfits Microsoft’s rules: no^or$anchors and a single capturing group.[17] I have not validated this pattern in a tenant. Failure point: a loose pattern. Microsoft warns that mapping a primary field to a loosely defined SIT can cause false negatives and slow matching.[7] -
Generate the synthetic files with the script in section 4. It writes a 15-row sample file and an identical synthetic source table as comma-separated files, plus a positive and a negative test document. Failure point: field names. They must start with a letter, be at least three characters, and use only letters and digits.[8][9]
-
Create the EDM classifier. Go to EDM classifiers, set New EDM experience to On, choose + Create EDM classifier, and upload
edm-sample-file.csv.[8] SelectEmployeeIdas the primary element and map it to your custom SIT. LeaveFirstName,LastName,AnnualSalaryandCostCenteras supporting elements. The default proximity is 300 characters.[8] Failure point: pickingFirstNameas the primary element. Microsoft says to pick fields that make each row unique.[8] -
Hash and upload the synthetic table. Wait at least one hour after creating the schema, then download it, validate the file, and upload. Microsoft recommends the two-computer method for real data; with synthetic rows the single-computer method is fine for a lab.[8][10] Authorize the agent with its
/Authorizeswitch, signed in as a member of EDM_DataUploaders, then run:[10]# Lab plan, not executed. Run from the EDM Upload Agent install folder. EdmUploadAgent.exe /SaveSchema /DataStoreName <schema name> /OutputDir <path to output folder> EdmUploadAgent.exe /ValidateData /DataFile [data file] /Schema [schema file] EdmUploadAgent.exe /UploadData /DataStoreName [DS Name] /DataFile [data file] /HashLocation [hash file location] /Schema [Schema file] /AllowedBadLinesPercentage [value] EdmUploadAgent.exe /GetDataStoreThe agent expects .csv by default, which is why the generator writes CSV.[10] Failure point: editing the downloaded schema and uploading it again. Microsoft says that produces an error.[8]
-
Test the classifier before any policy. Test from EDM classifiers with the Test icon, using
test-positive-salary.txtand thentest-negative-control.txt.[11] Wait an hour after upload and indexing finish. Microsoft also suggestsTest-DataClassificationand waiting 24 hours before testing in DLP.[8][11]# Lab plan, not executed. Security & Compliance PowerShell. Test-DataClassification -ClassificationNames "<EDM SIT name>" -TextToClassify (Get-Content .\test-positive-salary.txt -Raw)Failure point: a SIT that extracts only part of the ID. Microsoft lists substring and extra-character extraction as common problems.[11]
-
Create the auto-labeling policy in simulation. Create new test documents in the HR site after the EDM SIT exists. Auto-labeling only evaluates content created or modified after the SIT was created or modified.[12] Then go to Solutions > Information Protection > Policies > Auto-labeling policies > + Create auto-labeling policy, choose Custom, pick your label, limit SharePoint to the HR test site, and add a rule on Content contains > Sensitive info types using the EDM SIT.[12] On the Decide if you want to test out the policy now or later page, select Run policy in simulation mode and leave the 7-day automatic turn-on unselected for this lab.[12]
The PowerShell version uses
-Mode TestWithoutNotifications. The-WhatIfswitch doesn’t work in Security & Compliance PowerShell, so simulation mode is your only dry run.[13][14]# Lab plan, not executed. Security & Compliance PowerShell. New-AutoSensitivityLabelPolicy -Name "AB900-03 Synthetic Salary" -SharePointLocation "<hr-test-site-url>" -ApplySensitivityLabel "<label name>" -Mode TestWithoutNotifications New-AutoSensitivityLabelRule -Name "AB900-03 EDM salary rule" -Policy "AB900-03 Synthetic Salary" -Workload SharePoint -ContentContainsSensitiveInformation @(@{operator="And"; groups=@(@{name="Default"; operator="Or"; sensitivetypes=@(@{id="<<EDM SIT Id>>"; name="<<EDM SIT name>>"; maxcount="-1"; classifiertype="ExactMatch"; mincount="1"; confidencelevel="Medium"})})})EDM SITs go in a
groupsblock in that parameter, per the cmdlet reference.[14] Separately, Microsoft notes that when you configure a label with EDM conditions, you must also include at least one non-EDM SIT, or auto-labeling turns off for that label.[12] Failure points: simulation can take 12 hours, management actions stay greyed out for about 24 hours after creation, and simulation still fires any alert policy scoped to these activities.[12] -
Decide on a Copilot DLP rule. If the label should keep items out of Copilot answers, create a DLP policy on the Microsoft 365 Copilot and Copilot Chat location with Content contains > Sensitivity labels. Labeled items still appear in citations, but Copilot doesn’t use their content in the response.[16] You can’t combine sensitive info type and sensitivity label conditions in one rule.[16]
-
Roll back in reverse order. Turn off or delete the DLP rule and the auto-labeling policy, set
RestrictContentOrgWideSearchback to$false, then run the generator with-Rollbackto remove the local files. Removing a label with an auto-labeling policy also removes encryption that label applied, so think before you automate removal.[12]
4. Engineering deliverable: synthetic HR data generator
This script creates the EDM sample file, a matching synthetic source table, and two test documents. It makes no tenant connection.
What I tested, on Linux with PowerShell 7.4.6 and synthetic output only: -WhatIf created nothing; a normal run wrote four files, a manifest and an audit log; a second identical run made no changes; a different -Seed without -Force stopped with an error, and with -Force it regenerated the files; -RowCount 5 failed validation; and -Rollback removed the generated files but skipped one I had edited, logging a hash mismatch. PSScriptAnalyzer reported only information-level positional-parameter notes. I didn’t test it on Windows PowerShell 5.1, and the script requires 7.2 or later.
#Requires -Version 7.2
<#
.SYNOPSIS
Generates synthetic HR salary files for an EDM and auto-labeling lab. Local files only.
.NOTES
Makes no tenant connection. Every name, ID and salary is fake.
#>
[CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Generate')]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$OutputPath,
[Parameter(ParameterSetName = 'Generate')]
[ValidateRange(10, 20)]
[int]$RowCount = 15,
[Parameter(ParameterSetName = 'Generate')]
[ValidateRange(1, 999999)]
[int]$Seed = 900003,
[Parameter(ParameterSetName = 'Generate')]
[switch]$Force,
[Parameter(Mandatory, ParameterSetName = 'Rollback')]
[switch]$Rollback
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$root = [System.IO.Path]::GetFullPath($OutputPath)
$manifestPath = Join-Path $root 'lab-manifest.json'
$auditPath = Join-Path $root 'lab-audit.jsonl'
function Write-Audit {
param([string]$Action, [string]$Target, [string]$Result)
if (-not (Test-Path -LiteralPath $root)) { return }
$entry = [ordered]@{
timeUtc = (Get-Date).ToUniversalTime().ToString('o')
user = [Environment]::UserName
action = $Action
target = $Target
result = $Result
whatIf = [bool]$WhatIfPreference
}
if (-not $WhatIfPreference) {
($entry | ConvertTo-Json -Compress) | Add-Content -LiteralPath $auditPath -Encoding utf8
}
}
function Get-Sha256([string]$Path) {
(Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash
}
if ($Rollback) {
if (-not (Test-Path -LiteralPath $manifestPath)) {
Write-Warning "No manifest at $manifestPath. Nothing to roll back."
return
}
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
foreach ($file in $manifest.files) {
$full = Join-Path $root $file.name
if (-not (Test-Path -LiteralPath $full)) { continue }
if ((Get-Sha256 $full) -ne $file.sha256) {
Write-Warning "Skipping $($file.name): content changed since generation."
Write-Audit 'rollback-skip' $file.name 'hash-mismatch'
continue
}
if ($PSCmdlet.ShouldProcess($full, 'Remove generated lab file')) {
Remove-Item -LiteralPath $full
Write-Audit 'rollback-remove' $file.name 'removed'
}
}
if ($PSCmdlet.ShouldProcess($manifestPath, 'Remove lab manifest')) {
Remove-Item -LiteralPath $manifestPath
Write-Audit 'rollback-remove' 'lab-manifest.json' 'removed'
}
return
}
# Idempotency: same seed and row count with unchanged files means nothing to do.
if (Test-Path -LiteralPath $manifestPath) {
$existing = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
$unchanged = $existing.seed -eq $Seed -and $existing.rowCount -eq $RowCount -and
-not ($existing.files | Where-Object {
$p = Join-Path $root $_.name
-not (Test-Path -LiteralPath $p) -or (Get-Sha256 $p) -ne $_.sha256
})
if ($unchanged) {
Write-Verbose 'Lab files already match the manifest. No changes made.'
Write-Audit 'generate' $root 'no-op'
return
}
if (-not $Force) {
throw "Existing lab files differ from the requested run. Use -Rollback first, or -Force to overwrite."
}
}
$rng = [System.Random]::new($Seed)
$first = 'Avery','Blake','Casey','Devon','Emery','Finley','Harper','Jordan','Kai','Logan','Morgan','Parker','Quinn','Riley','Sage','Taylor','Rowan','Skyler','Reese','Hayden'
$last = 'Synthwood','Testfield','Labrook','Mockley','Sampleton','Fakehurst','Demoford','Trialmont'
$costCenters = 'CC1001','CC1002','CC2001','CC3005'
$rows = foreach ($i in 1..$RowCount) {
[pscustomobject]@{
EmployeeId = 'ZKLAB{0:D6}' -f (100000 + $i)
FirstName = $first[$rng.Next($first.Count)]
LastName = $last[$rng.Next($last.Count)]
AnnualSalary = 50000 + ($rng.Next(0, 900) * 100)
CostCenter = $costCenters[$rng.Next($costCenters.Count)]
}
}
# Guardrails that mirror Microsoft's sample-file rules for the new EDM experience.
foreach ($name in $rows[0].PSObject.Properties.Name) {
if ($name -notmatch '^[A-Za-z][A-Za-z0-9]{2,}$') { throw "Invalid EDM field name: $name" }
}
if (($rows.EmployeeId | Sort-Object -Unique).Count -ne $RowCount) { throw 'Primary field EmployeeId must be unique per row.' }
# Values contain no commas or quotes, so plain comma-separated output is safe.
$csv = @(($rows[0].PSObject.Properties.Name) -join ',')
$csv += $rows | ForEach-Object { @($_.EmployeeId, $_.FirstName, $_.LastName, $_.AnnualSalary, $_.CostCenter) -join ',' }
$match = $rows | Select-Object -First 3
$positive = @(
'SYNTHETIC LAB DOCUMENT - NOT REAL DATA'
'FY27 compensation adjustments (draft)'
$match | ForEach-Object { "$($_.FirstName) $($_.LastName), employee $($_.EmployeeId), annual salary $($_.AnnualSalary)." }
)
$negative = @(
'SYNTHETIC LAB DOCUMENT - NOT REAL DATA'
'Negative control: ID format matches but the value is not in the table.'
'Morgan Nobody, employee ZKLAB999999, annual salary 61000.'
)
$outputs = [ordered]@{
'edm-sample-file.csv' = $csv
'edm-source-table.csv' = $csv
'test-positive-salary.txt' = $positive
'test-negative-control.txt' = $negative
}
if (-not (Test-Path -LiteralPath $root)) {
if ($PSCmdlet.ShouldProcess($root, 'Create lab folder')) {
New-Item -ItemType Directory -Path $root | Out-Null
}
}
$written = foreach ($name in $outputs.Keys) {
$full = Join-Path $root $name
if ($PSCmdlet.ShouldProcess($full, 'Write synthetic lab file')) {
Set-Content -LiteralPath $full -Value $outputs[$name] -Encoding utf8NoBOM
Write-Audit 'write' $name 'ok'
[pscustomobject]@{ name = $name; sha256 = Get-Sha256 $full }
}
}
if ($written -and $PSCmdlet.ShouldProcess($manifestPath, 'Write lab manifest')) {
[ordered]@{ seed = $Seed; rowCount = $RowCount; createdUtc = (Get-Date).ToUniversalTime().ToString('o'); files = @($written) } |
ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $manifestPath -Encoding utf8NoBOM
Write-Audit 'manifest' 'lab-manifest.json' 'ok'
}
Usage:
./New-SyntheticSalaryEdmLab.ps1 -OutputPath ./ab900-03-lab -WhatIf
./New-SyntheticSalaryEdmLab.ps1 -OutputPath ./ab900-03-lab -Verbose
./New-SyntheticSalaryEdmLab.ps1 -OutputPath ./ab900-03-lab -Rollback
The first rows of the generated sample file, from my local run:
EmployeeId,FirstName,LastName,AnnualSalary,CostCenter
ZKLAB100001,Blake,Mockley,55000,CC3005
ZKLAB100002,Sage,Testfield,75000,CC3005
ZKLAB100003,Jordan,Labrook,137900,CC3005
Why it’s built this way:
- The row count is capped at 10 to 20 because Microsoft suggests about 10 to 20 rows for the sample file.[9] A real source table can be far larger; EDM supports up to 100 million rows.[6]
- The
ZKLABprefix can’t collide with a real employee number. The negative control uses the same ID format with a value that isn’t in the table. Microsoft says EDM test results include matches for the primary element’s SIT as well as the EDM SIT, so expect the custom pattern SIT to fire on the negative control and the EDM SIT not to.[11] - Rollback deletes only what the script created. The manifest stores a SHA-256 hash per file, and rollback skips anything that changed.
- The audit log records every write and removal as JSON lines with UTC timestamps.
-WhatIfruns log nothing.
5. Public-objective concepts to retain
These map to the October 14, 2026 AB-900 skills list.[1]
- DSPM for AI appears twice in the objectives: as a capability to understand and as the tool to “Discover and manage AI activity by using DSPM for AI”.[1] Know that data risk assessments look for oversharing and that the Protect tab links to labels, DLP and Restricted Content Discovery.[3]
- Sensitivity labels show sensitivity to users, can apply encryption, and pass to new content that Copilot in Word, PowerPoint and Outlook creates from a labeled file.[15]
- Classification comes in levels. A regular SIT finds a pattern; an EDM SIT matches exact values from your own hashed table.[6] EDM goes deeper than the AB-900 list, so treat steps 6 to 8 as lab practice, not exam scope.
- Copilot returns only data the user can access, and for encrypted content the user also needs EXTRACT and VIEW usage rights.[15]
- The SharePoint objective names SharePoint Advanced Management “including restricted access control”.[1] Restricted access control limits a site to members of a control group, and people outside that group can’t open the site even with earlier permissions.[19] Restricted Content Discovery leaves permissions alone and only limits discovery.[5] I’d learn that difference cold.
This is objective-aligned study material. It doesn’t reproduce exam questions.
6. Next step in the study journey
Next in the series is the agent lifecycle: approvals, security boundaries, and Power Platform admin center controls. Until then, the related reading on ZakitPro is the AB-900 Day One Conditional Access lab blueprint, the AB-900 certification review, and the Purview for Copilot Applied Skills review.
Sources
[1] https://learn.microsoft.com/en-us/credentials/certifications/resources/study-guides/ab-900 (Study guide for Exam AB-900) [2] https://learn.microsoft.com/en-us/credentials/certifications/copilot-and-agent-administration-fundamentals (Copilot and Agent Administration Fundamentals credential page) [3] https://learn.microsoft.com/en-us/purview/dspm-for-ai (DSPM for AI, classic) [4] https://learn.microsoft.com/en-us/purview/data-security-posture-management-learn-about (Learn about Data Security Posture Management) [5] https://learn.microsoft.com/en-us/sharepoint/restricted-content-discovery (Restrict discovery of SharePoint sites and content) [6] https://learn.microsoft.com/en-us/purview/sit-learn-about-exact-data-match-based-sits (Learn about EDM-based sensitive information types) [7] https://learn.microsoft.com/en-us/purview/sit-get-started-exact-data-match-based-sits-overview (Get started with EDM SITs) [8] https://learn.microsoft.com/en-us/purview/sit-create-edm-sit-unified-ux-schema-rule-package (Create an EDM SIT schema and rule package, new experience) [9] https://learn.microsoft.com/en-us/purview/sit-create-edm-sit-unified-ux-sample-file (Create the EDM SIT sample file, new experience) [10] https://learn.microsoft.com/en-us/purview/sit-get-started-exact-data-match-hash-upload (Hash and upload the sensitive information source table) [11] https://learn.microsoft.com/en-us/purview/sit-get-started-exact-data-match-test (Test an EDM SIT) [12] https://learn.microsoft.com/en-us/purview/apply-sensitivity-label-automatically (Automatically apply a sensitivity label) [13] https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/new-autosensitivitylabelpolicy (New-AutoSensitivityLabelPolicy) [14] https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/new-autosensitivitylabelrule (New-AutoSensitivityLabelRule) [15] https://learn.microsoft.com/en-us/purview/ai-m365-copilot (Use Microsoft Purview to manage data security and compliance for Microsoft 365 Copilot) [16] https://learn.microsoft.com/en-us/purview/dlp-microsoft365-copilot-location-learn-about (DLP for Microsoft 365 Copilot) [17] https://learn.microsoft.com/en-us/purview/create-a-custom-sensitive-information-type (Create a custom sensitive information type) [18] https://learn.microsoft.com/en-us/purview/sensitivity-labels-sharepoint-onedrive-files (Enable sensitivity labels for files in SharePoint and OneDrive) [19] https://learn.microsoft.com/en-us/sharepoint/restricted-access-control (Restrict SharePoint site access with Microsoft 365 groups and Microsoft Entra security groups)