A retention program survives an audit when the system, not a person, can prove what was kept, why, for how long, and that disposal happened on schedule with a reviewer attached. Purview gives you three overlapping controls – retention policies, retention labels, and records – and the failure mode is always the same: someone picks the wrong one, then fights precedence for two years. This builds the program end to end in the durable, reviewable place: Security & Compliance PowerShell. Everything has a portal equivalent (purview.microsoft.com -> Solutions -> Records Management / Data Lifecycle Management), but code is what you check into git and re-run across tenants.
Connect once. Note the April 2026 change for Data Lifecycle Management cmdlets:
# Exchange Online Management module v3.9.0+ is required for the DLM cmdlets below
Install-Module ExchangeOnlineManagement -Scope CurrentUser
Import-Module ExchangeOnlineManagement
# Security & Compliance endpoint -- distinct from Connect-ExchangeOnline
Connect-IPPSSession -UserPrincipalName admin@kloudvin.com
1. Retention policy vs. retention label vs. record: choosing the control
These are not three flavors of the same thing. They differ in granularity, capability, and who decides.
| Control | Scope of application | Can it delete? | Disposition review? | Declare a record? | Who applies |
|---|---|---|---|---|---|
| Retention policy | Implicit – a whole location (all Exchange mailboxes, a set of SharePoint sites) | Yes | No | No | System, by container |
| Retention label | Explicit – an individual item (email, document) | Yes | Yes | Yes | User, default, or auto-apply |
| Record (a label setting) | Explicit item, locked | Yes | Yes | n/a | Same as label |
Three rules of thumb that save you from the precedence wars later:
- Retention policy when the requirement is “keep everything in this workload for N years” with no per-item nuance – cheapest to operate, scopes by container.
- Retention label the moment you need any of: disposition review, a record, event-based timing, per-item duration, or file plan metadata. Only a label can do these.
- Record only when immutability is a genuine regulatory requirement. Records carry real operational cost (locked content, restricted edits), so do not default to them.
A constraint that shapes every taxonomy decision: only one retention label applies to an item at a time, and unlike sensitivity labels, retention labels have no priority order. An auto-apply policy never overwrites an existing label and never replaces a record label. Get the label wrong and you are manually relabeling.
2. Label taxonomy, retention actions, and records vs. regulatory records
A retention label is three decisions: an action (Keep, Delete, or KeepAndDelete), a duration, and the clock it runs from (RetentionType). The clock is the part people get wrong.
# Standard label: keep HR content 5 years from last modification, then nothing further
New-ComplianceTag -Name "HR-Keep-5yr" `
-RetentionAction Keep `
-RetentionDuration 1825 `
-RetentionType ModificationAgeInDays `
-Comment "Employee records; retain 5 years from last edit"
RetentionType accepts exactly four values, and the choice is load-bearing:
CreationAgeInDays– starts when the item was created. Fixed endpoint.ModificationAgeInDays– resets on every edit, so a frequently edited doc never ages out. Use deliberately.TaggedAgeInDays– starts when the label was applied. The right choice for cloud attachments.EventAgeInDays– starts when an external event fires (Section 4).
For a contract retained 7 years after creation, then deleted with a review:
New-ComplianceTag -Name "Contract-Keep7-Review" `
-RetentionAction KeepAndDelete `
-RetentionDuration 2555 `
-RetentionType CreationAgeInDays
Records vs. regulatory records
Marking a label as a record sets IsRecordLabel $true: once applied, the label cannot be removed by a user and content is locked against edit/delete (with a versioning story for locked records in SharePoint). It is an unlocked record by default unless you say otherwise, which controls whether record properties stay editable.
New-ComplianceTag -Name "Record-Financials-7yr" `
-RetentionAction KeepAndDelete `
-RetentionDuration 2555 `
-RetentionType CreationAgeInDays `
-IsRecordLabel $true
A regulatory record goes further: you cannot remove the label, cannot unlock it, and cannot shorten or change the retention period after the fact. Because that is effectively irreversible, the option is hidden in the wizard until you enable it tenant-wide:
# Auditable action: logs "Enabled regulatory record option for retention labels"
Set-RegulatoryComplianceUI -Enabled $true
# Now you can create a regulatory record label
New-ComplianceTag -Name "RegRecord-SEC17a4-7yr" `
-RetentionAction KeepAndDelete `
-RetentionDuration 2555 `
-RetentionType CreationAgeInDays `
-IsRecordLabel $true `
-Regulatory $true
Treat
-Regulatory $truelike a one-way door: you cannot relabel your way out of a mistake, and auto-apply is not supported for regulatory records – they must be published and applied deliberately. Most regulated programs need records, not regulatory records. Reserve the latter for the few obligations that genuinely require WORM-like behavior.
Attach file plan descriptors so the label is reportable by department, category, and citation – this turns a flat label list into a defensible file plan:
$fp = [PSCustomObject]@{ Settings = @(
@{ Key = "FilePlanPropertyDepartment"; Value = "Finance" },
@{ Key = "FilePlanPropertyCategory"; Value = "Tax" },
@{ Key = "FilePlanPropertyCitation"; Value = "IRS-6501" }
)}
$fpJson = ConvertTo-Json $fp
New-ComplianceTag -Name "Tax-Record-7yr" -RetentionAction KeepAndDelete `
-RetentionDuration 2555 -RetentionType CreationAgeInDays `
-IsRecordLabel $true -FilePlanProperty $fpJson
3. Publishing vs. auto-apply (SIT, KQL, trainable classifier)
A label does nothing until a policy scopes it to content. Two policy shapes.
Publish makes labels available to users (and as defaults) in Outlook, SharePoint, OneDrive, and Teams – manual application, the user decides.
New-RetentionCompliancePolicy -Name "Publish-Records-Taxonomy" `
-ExchangeLocation All -SharePointLocation All -OneDriveLocation All
New-RetentionComplianceRule -Policy "Publish-Records-Taxonomy" `
-PublishComplianceTag "Contract-Keep7-Review","Tax-Record-7yr"
Auto-apply stamps a label on matching content with no user involvement. The policy is the container; the rule’s ApplyComplianceTag parameter is what makes it auto-apply. The duration lives on the label, not the rule.
Condition 1 – sensitive information types (ContentContainsSensitiveInformation):
New-RetentionCompliancePolicy -Name "Auto-PII-Records" `
-ExchangeLocation All -SharePointLocation All -OneDriveLocation All
New-RetentionComplianceRule -Policy "Auto-PII-Records" `
-ApplyComplianceTag "Record-Financials-7yr" `
-ContentContainsSensitiveInformation @(
@{ Name = "U.S. Social Security Number (SSN)"; minCount = "1" },
@{ Name = "Credit Card Number"; minCount = "1" }
)
Condition 2 – keywords / searchable properties via a KQL (KeyQL) query (ContentMatchQuery):
New-RetentionCompliancePolicy -Name "Auto-Legal-NDA" -SharePointLocation All -ExchangeLocation All
New-RetentionComplianceRule -Policy "Auto-Legal-NDA" `
-ApplyComplianceTag "Contract-Keep7-Review" `
-ContentMatchQuery '(nda OR "non disclosure agreement") AND (filetype:doc* OR filetype:pdf)'
KQL uses the same index as eDiscovery content search. Rules that trip people up: prefix wildcards (cat*) work; suffix/substring (*cat, *cat*) do not. For SharePoint, only predefined managed properties and the refiner set (RefinableString00-99, RefinableDate00-19, etc.) are queryable – crawled/custom properties are not. To target items already carrying a sensitivity label, query its GUID: InformationProtectionLabelId:<GUID> (from Get-Label | Format-Table DisplayName,Guid).
Condition 3 – trainable classifiers. Configure these in the portal auto-apply wizard; the rule’s model parameter is reserved for internal use. Trainable classifiers cannot combine with adaptive scopes – use a static scope.
Run SIT- and query-based auto-apply in simulation mode first. It behaves like
-WhatIf: it reports what would be labeled against data at rest so you can tune confidence and instance counts before anything is stamped. Auto-apply takes up to seven days to propagate and only labels unlabeled items.
4. Event-based retention: wiring asset, employee, and contract triggers
Some clocks cannot start on a date you know in advance: “7 years after the contract expires”, “10 years after the employee leaves”. That is event-based retention – a three-object dance: an event type, a label bound to it, and the event fired later with an asset query that scopes which items it touches.
Step 1 – create the event type (the trigger category):
New-ComplianceRetentionEventType -Name "Employee Departure" `
-Comment "Starts the retention clock when an employee leaves"
Step 2 – create a label whose clock is EventAgeInDays, bound to that event type:
New-ComplianceTag -Name "HR-Keep-7yr-After-Departure" `
-RetentionAction KeepAndDelete `
-RetentionDuration 2555 `
-RetentionType EventAgeInDays `
-EventType "Employee Departure"
Publish and apply that label as usual. The clock stays paused – the item is held indefinitely until the event fires.
Step 3 – when the event happens, fire it. The asset query scopes the event to the right items: a SharePoint/OneDrive Property:Value column match, or an Exchange keyword query.
# Look up the GUIDs the event needs to bind to
Get-ComplianceRetentionEventType | Format-Table Name,Guid
Get-ComplianceTag | Format-Table Name,Guid
New-ComplianceRetentionEvent -Name "Departure - E12345 - 2026-06" `
-EventType "Employee Departure" `
-SharePointAssetIdQuery "EmployeeId:E12345" `
-EventDateTime "06/01/2026"
An asset/contract trigger is identical – a ProductCode or ContractId column queried at event time:
New-ComplianceRetentionEvent -Name "Contract Expiry - KV-4471" `
-EventType "Contract Expiration" `
-SharePointAssetIdQuery "ContractId:KV-4471" `
-ExchangeAssetIdQuery "ContractId:KV-4471" `
-EventDateTime "05/31/2026"
The asset ID is a metadata property on the content – the column must already be populated for the query to match. The event sets the EventAgeInDays start date for every labeled item the query resolves; from there the label’s duration governs. Security & Compliance PowerShell is the only surface that can create events with application permissions – which is what lets you wire New-ComplianceRetentionEvent into an HR/CLM webhook instead of a human clicking a button.
5. How retention resolves across Exchange, SharePoint, OneDrive, and Teams
Retention is not a delete job – it is a copy-on-write preservation layer. When content under a Keep action is deleted or edited, the platform keeps the original out of sight:
- SharePoint / OneDrive – the prior version (or deleted file) lands in the site’s Preservation Hold Library (PHL), invisible to users, swept by a timer job.
- Exchange – deleted/edited mail moves to Recoverable Items (the
Purgessubtree, past the user-visible Deleted Items). - Teams – chat and channel messages are retained via hidden mailbox folders (
SubstrateHolds); Teams retention is a distinct rule type, andApplyComplianceTagplus several rule parameters are not valid for Teams rules.
The consequence: a Keep action never blocks a user from “deleting” – it guarantees the copy survives in PHL/Recoverable Items until the period expires. Storage planning for a records program must budget the PHL, not just live content.
6. The principles of retention: which control wins
When several policies and a label touch one item, Purview resolves the outcome through four ordered principles. Memorize the order – it is the source of most “why didn’t it delete?” tickets:
- Retention wins over deletion. If anything says keep, the item is not permanently deleted, even while a delete action is also in play. Deletion is suspended, not cancelled.
- The longest retention period wins. Among competing Keep durations, the item lives until the longest one expires.
- Explicit wins over implicit for deletions. A label’s delete action beats a policy’s delete action, because a label is applied to the individual item, not inherited from a container. (Among policies only: a scope targeting specific instances beats an org-wide “all” scope.)
- The shortest deletion period wins. Only when the prior levels cannot decide: the earliest applicable deletion date governs.
Worked example: an email under an org-wide policy that keeps 10 years from creation, plus a label that deletes after 7 years. Outcome: retained 10 years (principle 2); at year 7 the label’s delete action moves it to Recoverable Items (principle 3), but it is not purged until year 10 (principle 1 – retention still wins until then). One nuance: Priority Cleanup can intentionally override these principles (and even eDiscovery holds) to force expedited deletion of sensitive content – a deliberate escape hatch, not the default. eDiscovery holds otherwise sit above all four principles: held content cannot be purged until the hold is released.
7. Disposition review: stages, reviewer permissions, proof of disposal
Disposition review is available only on a retention label, never a policy. At end of retention, instead of auto-deleting, the item parks in a review queue and named reviewers decide its fate – the human checkpoint auditors want. Build a two-stage review with the MultiStageReviewProperty JSON; the schema is exact:
$review = '{"MultiStageReviewSettings":[' +
'{"StageName":"Records Manager","Reviewers":[recmgr@kloudvin.com]},' +
'{"StageName":"Legal","Reviewers":[legal@kloudvin.com,gc@kloudvin.com]}' +
']}'
New-ComplianceTag -Name "Contract-Review-7yr" `
-RetentionAction KeepAndDelete `
-RetentionDuration 2555 `
-RetentionType CreationAgeInDays `
-MultiStageReviewProperty $review `
-ReviewerEmail recmgr@kloudvin.com
A label supports up to five consecutive stages. After creation you can rename stages and edit reviewers, but you cannot reorder or remove a stage – design the order before you ship. Each stage allows up to 10 individual users or mail-enabled security groups (Microsoft 365 Groups are not supported).
Reviewers need the Disposition Management role – included in the Records Management role group but not granted to Global Admins by default. Create a least-privilege group for it; add Content Explorer Content Viewer to let reviewers see content (not just metadata) in the mini-preview. A reviewer sees only their own queue by default; to let a records manager see every pending disposition, designate a mail-enabled security group:
Enable-ComplianceTagStorage -RecordsManagementSecurityGroupEmail dispositionreviewers@kloudvin.com
In the queue each reviewer can Approve disposal (advances to the next stage, or at the final/only stage marks for permanent deletion within ~15 days), Relabel (exits this label for a new one’s settings), Extend (suspends review, restarts from stage 1 after the extension), or Add reviewers (which does not grant them permissions – a common trap). Optionally set an auto-approval window of 7-365 days (default 14): if no one acts in time the item advances, and at the final stage it is disposed. There is no separate audit event for auto-approval; the existing Approved disposal event carries the detail.
Your proof of disposal is the audit trail: every disposition action emits an event in the Disposition review activities group, and records deleted without review surface as Records Disposed on the Disposition page (from the unified audit log, which must be on).
Enterprise scenario
A multinational manufacturer ran a SharePoint-based contract repository. Legal’s requirement: retain each contract for 7 years after it expires, then a Legal sign-off before deletion. The platform team’s first build used a fixed CreationAgeInDays label – which would delete contracts 7 years after they were signed, not after they expired. A 12-year master service agreement would have been purged 5 years into its own term. Worse, they had set -Regulatory $true “to be safe”, which made the labels immutable and blocked any corrective relabel.
The fix had three parts. First, rebuild the labels as standard records (not regulatory) with an event clock, so the period starts at expiry:
New-ComplianceRetentionEventType -Name "Contract Expiration"
New-ComplianceTag -Name "MSA-Record-7yrAfterExpiry-Review" `
-RetentionAction KeepAndDelete -RetentionDuration 2555 `
-RetentionType EventAgeInDays -EventType "Contract Expiration" `
-IsRecordLabel $true `
-MultiStageReviewProperty '{"MultiStageReviewSettings":[{"StageName":"Legal","Reviewers":[legal@contoso.com]}]}'
Second, their contract-lifecycle system already stamped a ContractId column on every document set. They wired its “contract closed” webhook to fire the event with application permissions – no human in the loop:
New-ComplianceRetentionEvent -Name "Expiry-$ContractId" `
-EventType "Contract Expiration" `
-SharePointAssetIdQuery "ContractId:$ContractId" `
-EventDateTime $ExpiryDate
Third, they kept Regulatory off. Counsel confirmed the obligation required defensible retention and a disposal review – not WORM immutability – so the irreversibility of regulatory records was a liability, not a feature. The lesson in their runbook: the retention clock (RetentionType) is the design decision; the record flag is a separate, sparing one; and -Regulatory $true is a one-way door you almost never need.
Verify
# Labels and their core settings -- confirm action, duration, clock, record flags
Get-ComplianceTag | Format-Table Name, RetentionAction, RetentionDuration, `
RetentionType, IsRecordLabel, Regulatory
# Where each label is used and the policy distribution status
Get-RetentionCompliancePolicy -DistributionDetail |
Format-Table Name, Mode, DistributionStatus
# Auto-apply rules and their match logic
Get-RetentionComplianceRule | Format-Table Name, Policy, ApplyComplianceTag, ContentMatchQuery
# Event types and any events that have fired
Get-ComplianceRetentionEventType | Format-Table Name, Guid
Get-ComplianceRetentionEvent | Format-Table Name, EventType, EventDateTime
# Confirm the regulatory-records UI state and the disposition reviewer group
Get-RegulatoryComplianceUI
Get-ComplianceTagStorage | Format-List RecordsManagementSecurityGroupEmail
Then validate behavior, not just configuration:
- Simulation – for SIT/KQL auto-apply, open Label policies -> View simulation and confirm matched counts and samples before turning the policy on.
- Content Search – an eDiscovery search filtered by
ComplianceTag:"<label name>"proves the label landed on the items you intended (and nothing else). - Content Explorer – after the seven-day window, confirm labeled item counts per label and location.
- Disposition page – check Pending dispositions populates at end of retention, and Records Disposed / Disposed items show post-disposal with reviewer and timestamp.
- Audit log – search
Approved disposal,Labeled message as a record,Changed retention label for a file, andEnabled regulatory record option for retention labels.