Wicked Smart Data
LearnInsightsAboutContact
Sign InLet's Build
LearnInsightsAboutContact
Sign InLet's Build
Wicked Smart Data

Intelligence, automation, and expert execution — plus an elite library of free knowledge. We turn complexity into competitive advantage.

Start a conversation

Platform

  • Learning Paths
  • Insights
  • RSS Feed

Company

  • About
  • Contact
  • Work With Us

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Wicked Smart Data. All rights reserved.

Intelligence · Automation · Advantage

All Insights
Power BI

Implementing Power BI Information Protection Policies with Microsoft Purview: Automating Sensitivity Label Inheritance, DLP Rules, and Compliance Reporting Across the Enterprise Semantic Layer

Sensitivity labels on a Post-It note aren't governance. This deep-dive lesson teaches you how to architect a complete information protection system in Power BI using Microsoft Purview — automating label inheritance across your semantic layer, enforcing DLP policies at data egress, and building audit-ready compliance reporting pipelines that satisfy real regulatory requirements.

🔥 Expert31 min readNov 2, 2026Updated Nov 2, 2026
Implementing Power BI Information Protection Policies with Microsoft Purview: Automating Sensitivity Label Inheritance, DLP Rules, and Compliance Reporting Across the Enterprise Semantic Layer
On this page
  • Introduction
  • Prerequisites
  • Understanding the Architecture: Where Purview Meets Power BI
  • The Inheritance Chain
  • Licensing Reality Check
  • Configuring the Foundation: Purview Label Taxonomy and Power BI Tenant Settings
  • Designing a Label Hierarchy for the Semantic Layer
  • Enabling Labels in Power BI
  • Automating Sensitivity Label Inheritance via API
  • Using the Power BI Admin REST API for Bulk Labeling
  • Inheriting Labels from Microsoft Purview Data Map
  • Designing and Enforcing DLP Policies for Power BI
  • What Power BI DLP Can and Cannot Do
  • Creating a DLP Policy for Power BI
  • Configuring Policy Tips for Minimal Disruption
  • Handling Complex Scenarios: Composite Models, DirectQuery, and Deployment Pipelines
  • Composite Models and Mixed-Sensitivity Sources
  • DirectQuery and Real-Time Label Inheritance
  • Sensitivity Labels in Deployment Pipelines
  • Building Compliance Reporting: The Audit Data Pipeline
  • Data Sources for Compliance Reporting
  • Building the Compliance Report in Power BI
  • Hands-On Exercise
  • Common Mistakes & Troubleshooting
  • Labels publish to Purview but don't appear in Power BI
  • Inheritance isn't firing on refresh
  • DLP policy is triggering on internal sharing that should be allowed
  • Service principal bulk labeling script returns 403
  • XMLA endpoint bypasses DLP — what do I do?
  • Deployment pipeline promotes content and breaks sharing permissions
  • Summary & Next Steps
  • Implementing Power BI Information Protection Policies with Microsoft Purview: Automating Sensitivity Label Inheritance, DLP Rules, and Compliance Reporting Across the Enterprise Semantic Layer

    Introduction

    Picture this: your enterprise data team has spent six months building a pristine semantic layer in Power BI — certified datasets, carefully modeled star schemas, row-level security locked down tight. Then your compliance officer walks in with a regulatory audit finding. A mid-level analyst exported a report containing salary data to an unencrypted Excel file, emailed it to a vendor, and nobody caught it until the vendor's inbox was compromised. The data was never marked as sensitive. No policy blocked the export. No alert fired. The semantic layer was technically secure, but the information protection layer simply didn't exist.

    This is the gap that Microsoft Purview's information protection integration with Power BI is designed to close. It's not just about putting labels on datasets — it's about creating a system where sensitivity propagates automatically from your most trusted data sources down through dataflows, semantic models, and reports, and where policy enforcement happens at the point of export, not as an afterthought in the SOC. By the end of this lesson, you'll understand not just how to configure this system, but why each architectural decision matters, where the sharp edges are, and how to build compliance reporting that actually satisfies an auditor rather than just looking good in a dashboard.

    What you'll learn:

    • How Microsoft Purview sensitivity labels integrate with the Power BI semantic layer, including the inheritance model and its limitations
    • How to design and enforce Data Loss Prevention (DLP) policies that act on Power BI content without breaking legitimate business workflows
    • How to automate sensitivity label inheritance using Power BI REST API and Microsoft Graph so manual classification doesn't become a governance bottleneck
    • How to build compliance reporting pipelines that surface label coverage, DLP policy violations, and audit events in a structured, auditable format
    • How to handle edge cases: composite models with mixed-sensitivity sources, DirectQuery datasets, third-party export paths, and label conflicts in deployment pipelines

    Prerequisites

    Before diving in, you should be comfortable with:

    • Power BI workspace administration and capacity management (if you need a refresher, see Power BI Service: Setting Up Workspaces, Capacities, and Licensing for Enterprise Deployment)
    • Basic familiarity with Power BI governance concepts, including audit logging and permissions (Power BI Governance: Workspaces, Permissions, and Audit Logging)
    • Hands-on experience with the Power BI REST API (Power BI REST API: Automate Administration and Deployments)
    • Access to Microsoft 365 with a license tier that includes Microsoft Purview Information Protection (formerly Azure Information Protection) — at minimum, Microsoft 365 E3 with the Compliance add-on, though E5 is strongly recommended for full DLP capabilities
    • Global Admin or Compliance Admin role in the Microsoft 365 tenant, plus Power BI Admin, for initial configuration

    Understanding the Architecture: Where Purview Meets Power BI

    Before you configure a single label, you need a clear mental model of what's actually happening under the hood. Microsoft Purview sensitivity labels aren't just metadata tags — they're policy containers. When a label is applied to a Power BI dataset, three things happen simultaneously:

    1. The label metadata is written to the item's properties in the Power BI service, making it visible in the workspace UI and queryable via the REST API and Scanner API.
    2. The label's protection settings are evaluated at content export time. If the label carries encryption, that encryption is applied to exported files (Excel, PDF, CSV) via Azure Rights Management Service (Azure RMS).
    3. The label becomes a signal for DLP policies in Microsoft Purview, which can evaluate it against rules and trigger actions like blocking, alerting, or restricting sharing.

    The key architectural insight is that Power BI is a protected container, not a protected document format. A .pbix file on disk is not natively protected by sensitivity labels the way a Word document is — the protection is enforced by the Power BI service at the point of data egress. This distinction matters enormously for your threat model.

    Key insight

    Sensitivity labels in Power BI protect the export path, not the semantic model itself. A user with direct XMLA endpoint access or a compromised service principal can bypass label-based export controls entirely. Information protection should always be layered with object-level security and row-level security at the model level.

    The Inheritance Chain

    Microsoft Purview supports a concept called label inheritance, which works in a cascading fashion across the Power BI artifact hierarchy:

    Data Source (SQL Server / Synapse / ADLS)
        │
        ▼
    Power BI Dataflow (Gen1 / Gen2)
        │
        ▼
    Power BI Semantic Model (Dataset)
        │
        ▼
    Power BI Report / Dashboard / App
        │
        ▼
    Exported File (Excel / PDF / CSV / PowerPoint)
    

    Inheritance flows downward, and it's governed by a simple rule: the highest-priority label wins, and it propagates to downstream artifacts. If your Synapse source table has a "Highly Confidential" label and your dataflow has "General," the dataset connecting to that dataflow will automatically be promoted to "Highly Confidential" — but only if tenant settings and Purview policies are correctly configured to enable this behavior.

    Warning

    Label inheritance from data sources is not retroactive by default. It fires when a dataset is refreshed. If you have 400 certified datasets and you add a "Confidential" label to a source table today, those datasets won't inherit it until their next scheduled refresh. Plan your rollout timeline accordingly and schedule a forced refresh sweep after major labeling initiatives.

    Licensing Reality Check

    The full information protection stack requires careful license alignment:

    Feature Minimum License
    Apply sensitivity labels in Power BI service Power BI Pro + M365 E3
    Mandatory label enforcement (users must label) M365 E5 Compliance
    DLP policies for Power BI M365 E5 Compliance
    Auto-labeling from data source inheritance Power BI Premium (P SKU or PPU) + M365 E5
    Purview Scanner API for compliance reporting Power BI Premium
    Label encryption on exported files Azure RMS (included in E3+)

    The Premium or Premium Per User requirement for automatic inheritance is the one that catches teams off guard. If you're on Pro licensing only, label inheritance from sources won't fire automatically — users must apply labels manually.


    Configuring the Foundation: Purview Label Taxonomy and Power BI Tenant Settings

    The biggest governance mistake organizations make is deploying sensitivity labels without first designing a coherent taxonomy. You do not want to create 47 labels over 18 months and then discover that half your users apply "Internal" when they mean "Confidential - HR" because the names were ambiguous.

    Designing a Label Hierarchy for the Semantic Layer

    For enterprise Power BI deployments, a four-tier taxonomy maps well to both regulatory frameworks (GDPR, HIPAA, SOX) and practical data classification needs:

    Public
    └── General (Internal Use)
        ├── Confidential
        │   ├── Confidential - Finance
        │   ├── Confidential - HR
        │   └── Confidential - Customer PII
        └── Highly Confidential
            ├── Highly Confidential - Trade Secret
            └── Highly Confidential - Regulated (PHI/PCI)
    

    Each label should encode clear answers to three questions: Who can see this? Where can it go? What happens when it's exported?

    Configure labels in the Microsoft Purview compliance portal (compliance.microsoft.com). Navigate to Information Protection > Labels, create your hierarchy, and for each label, configure:

    • Encryption: For "Highly Confidential" labels, enable Azure RMS encryption with specific user/group assignments. For "Confidential" labels, consider "Do Not Forward" protection on exported files without full encryption, which is less disruptive.
    • Content marking: Headers and footers on exported documents.
    • Auto-labeling: We'll configure this separately for Power BI, but you can also enable it for Office applications here.

    Tip

    Resist the urge to encrypt everything. Applying RMS encryption to a "General" label breaks many legitimate workflows — Power Automate flows, email attachments, SharePoint indexing. Reserve encryption for "Highly Confidential" and evaluate "Confidential" case-by-case. The goal is proportional protection, not maximum friction.

    Enabling Labels in Power BI

    Once your label taxonomy exists in Purview, you need to enable it for Power BI through two mechanisms:

    Step 1: Publish the label policy to Power BI

    In the Purview compliance portal, go to Information Protection > Label policies and either edit your existing policy or create one targeting the security groups that include your Power BI users. In the policy settings, confirm that Power BI is selected as a workload. This is the step most admins miss — labels exist, but they haven't been published to the Power BI workload.

    Step 2: Configure Power BI admin tenant settings

    In the Power BI admin portal (app.powerbi.com/admin-portal), navigate to Tenant settings > Information protection:

    • Allow users to apply sensitivity labels for Power BI content: Enable this for all users or target security groups. Start with a pilot group.
    • Apply sensitivity labels from data sources to their data in Power BI: Enable this — this is the inheritance trigger. It requires Premium capacity.
    • Automatically apply sensitivity labels to downstream content: Enable this to cascade labels from datasets to reports and dashboards automatically.
    • Allow workspace admins to override automatically applied sensitivity labels: Decide whether workspace admins can downgrade labels. In most compliance scenarios, you want this disabled — users should not be able to remove a system-applied label without elevated privileges.
    • Restrict content with protected labels from being shared via link: Enable for "Highly Confidential" labels to prevent "Share with organization" links.

    For a deeper look at these tenant settings in context, see Implementing Power BI Tenant Settings and Sensitivity Labels for Enterprise Data Protection and Compliance.


    Automating Sensitivity Label Inheritance via API

    Manual label application is governance theater. In a mature enterprise with hundreds of datasets spanning multiple workspaces, human-driven labeling creates coverage gaps within weeks of any initiative. The sustainable approach is automation: programmatic labeling at dataset creation, inheritance from upstream sources, and scheduled audits that flag unlabeled assets.

    Using the Power BI Admin REST API for Bulk Labeling

    The setLabels endpoint in the Power BI Admin API allows you to apply sensitivity labels to datasets, reports, dashboards, and dataflows programmatically. Here's a production-ready Python implementation that applies labels based on a classification mapping:

    import requests
    import json
    from msal import ConfidentialClientApplication
    
    # Authentication setup - use a service principal with Power BI Admin rights
    TENANT_ID = "your-tenant-id"
    CLIENT_ID = "your-service-principal-client-id"
    CLIENT_SECRET = "your-client-secret"
    POWER_BI_SCOPE = ["https://analysis.windows.net/powerbi/api/.default"]
    
    # Your Purview label GUIDs - retrieve these from the Purview compliance portal
    LABEL_MAP = {
        "finance": "a1b2c3d4-0001-0001-0001-000000000001",
        "hr": "a1b2c3d4-0002-0002-0002-000000000002",
        "customer_pii": "a1b2c3d4-0003-0003-0003-000000000003",
        "general": "a1b2c3d4-0004-0004-0004-000000000004",
        "highly_confidential": "a1b2c3d4-0005-0005-0005-000000000005",
    }
    
    def get_access_token():
        app = ConfidentialClientApplication(
            CLIENT_ID,
            authority=f"https://login.microsoftonline.com/{TENANT_ID}",
            client_credential=CLIENT_SECRET,
        )
        result = app.acquire_token_for_client(scopes=POWER_BI_SCOPE)
        if "access_token" not in result:
            raise ValueError(f"Token acquisition failed: {result.get('error_description')}")
        return result["access_token"]
    
    def get_all_datasets(token: str) -> list:
        """Retrieve all datasets across the tenant using the admin endpoint."""
        url = "https://api.powerbi.com/v1.0/myorg/admin/datasets"
        headers = {"Authorization": f"Bearer {token}"}
        datasets = []
        
        # The admin endpoint returns up to 5000 items; paginate if needed
        params = {"$top": 5000, "$skip": 0}
        while True:
            response = requests.get(url, headers=headers, params=params)
            response.raise_for_status()
            data = response.json()
            batch = data.get("value", [])
            datasets.extend(batch)
            if len(batch) < 5000:
                break
            params["$skip"] += 5000
        
        return datasets
    
    def classify_dataset(dataset: dict) -> str:
        """
        Determine the appropriate label based on dataset name, workspace, 
        or other metadata. In production, this would query your data catalog
        or classification database.
        """
        name_lower = dataset.get("name", "").lower()
        workspace_id = dataset.get("workspaceId", "")
        
        # Example classification rules - replace with your actual logic
        HR_WORKSPACES = {"ws-001", "ws-002"}  # Replace with actual workspace GUIDs
        
        if workspace_id in HR_WORKSPACES:
            return "hr"
        elif any(term in name_lower for term in ["salary", "compensation", "payroll"]):
            return "hr"
        elif any(term in name_lower for term in ["customer", "pii", "email", "phone"]):
            return "customer_pii"
        elif any(term in name_lower for term in ["revenue", "p&l", "financial", "budget"]):
            return "finance"
        elif dataset.get("sensitivityLabel") is not None:
            return None  # Already labeled - don't overwrite
        else:
            return "general"
    
    def apply_labels_batch(token: str, label_requests: list) -> dict:
        """
        Apply labels in batches of 25 (API limit per call).
        label_requests: list of {"artifactId": "...", "labelId": "..."}
        """
        url = "https://api.powerbi.com/v1.0/myorg/admin/informationProtectionSetLabels"
        headers = {
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        }
        
        results = {"succeeded": [], "failed": []}
        BATCH_SIZE = 25
        
        for i in range(0, len(label_requests), BATCH_SIZE):
            batch = label_requests[i:i + BATCH_SIZE]
            payload = {
                "updateDetails": [
                    {
                        "artifactId": req["artifactId"],
                        "artifactType": req.get("artifactType", "Dataset"),
                        "sensitivityLabelId": req["labelId"],
                        "datasetId": req["artifactId"],  # Required for datasets
                    }
                    for req in batch
                ]
            }
            
            response = requests.post(url, headers=headers, json=payload)
            
            if response.status_code == 200:
                response_data = response.json()
                results["succeeded"].extend(
                    response_data.get("succeededArtifacts", [])
                )
                results["failed"].extend(
                    response_data.get("failedArtifacts", [])
                )
            else:
                # Log the failure but don't abort the entire batch run
                print(f"Batch {i//BATCH_SIZE + 1} failed: {response.status_code} - {response.text}")
                results["failed"].extend([req["artifactId"] for req in batch])
        
        return results
    
    def run_labeling_sweep():
        token = get_access_token()
        datasets = get_all_datasets(token)
        
        label_requests = []
        skipped_count = 0
        
        for dataset in datasets:
            classification = classify_dataset(dataset)
            if classification is None:
                skipped_count += 1
                continue
            
            label_requests.append({
                "artifactId": dataset["id"],
                "artifactType": "Dataset",
                "labelId": LABEL_MAP[classification],
            })
        
        print(f"Processing {len(label_requests)} datasets, skipping {skipped_count} already labeled")
        results = apply_labels_batch(token, label_requests)
        
        print(f"Succeeded: {len(results['succeeded'])}")
        print(f"Failed: {len(results['failed'])}")
        
        return results
    
    if __name__ == "__main__":
        run_labeling_sweep()
    

    Schedule this script as an Azure Function running on a timer trigger — weekly at minimum, daily for high-compliance environments. The classify_dataset function is where your business logic lives; in a mature implementation, it would query a master data catalog, check the dataset's upstream lineage, or call a Microsoft Graph API to inspect the workspace's classification metadata.

    Warning

    The informationProtectionSetLabels admin API endpoint requires that the calling service principal has the Power BI tenant setting "Service principals can use Fabric APIs" enabled, and the service principal must be a member of the security group whitelisted for that setting. Additionally, the service principal needs the Dataset.ReadWrite.All and Content.Create permissions in the Power BI service. Missing either of these is the most common reason bulk labeling scripts fail silently.

    Inheriting Labels from Microsoft Purview Data Map

    If your organization uses Microsoft Purview Data Map (the catalog, not just the compliance tools), you can establish a tighter inheritance loop. Data assets in Purview Data Map — including Azure SQL tables, ADLS Gen2 paths, and Synapse Analytics tables — can carry sensitivity classifications. When Power BI refreshes a dataset connected to these sources, Purview propagates the highest-priority label from the source asset to the dataset.

    The integration works through the Purview connector in Power BI, which is enabled through the tenant settings described earlier. Under the hood, Power BI registers each refresh activity with the Purview lineage API, which allows Purview to trace ColumnMapping relationships from source columns to dataset tables and apply the maximum-sensitivity label found in that lineage chain.

    To verify this is working, after enabling the tenant setting and completing a dataset refresh, navigate to the dataset in the Power BI workspace, select Settings, and check the Sensitivity label field. It should show the inherited label and — critically — show the label as "auto-applied" rather than "manually applied," which affects how DLP policies can treat it.

    This connects naturally to the broader lineage story described in Implementing Power BI Workspace-Level Lineage and Impact Analysis to Manage Dataset Dependencies Across the Enterprise — lineage isn't just an operational tool, it's a compliance mechanism.


    Designing and Enforcing DLP Policies for Power BI

    Data Loss Prevention policies in Microsoft Purview are the enforcement layer. Where sensitivity labels classify data, DLP policies constrain what users can do with it. Power BI DLP support arrived in 2022 and has matured significantly, but it still has meaningful differences from Office DLP that you need to understand before you design your rule set.

    What Power BI DLP Can and Cannot Do

    What DLP policies can do in Power BI:

    • Detect sensitivity labels on datasets and trigger policy actions
    • Detect sensitive information types in dataset content (via Purview's content scanner, which scans actual data values)
    • Block or restrict sharing of datasets that match policy conditions
    • Send policy tip notifications to users attempting to share or export
    • Alert compliance administrators via Microsoft Purview Activity Explorer
    • Integrate with Microsoft Sentinel via the Office 365 Management Activity API

    What Power BI DLP currently cannot do:

    • Block individual report renders (a user can still view data in the Power BI service even if their label would block export)
    • Apply granular column-level DLP (DLP acts at the dataset artifact level, not the column level — for column-level restriction, use object-level security)
    • Retroactively encrypt data already exported before the policy was applied
    • Block XMLA endpoint queries (this is a significant gap — discussed in the troubleshooting section)

    Creating a DLP Policy for Power BI

    In the Microsoft Purview compliance portal, navigate to Data loss prevention > Policies > Create policy. Walk through the wizard:

    Step 1: Template selection

    Start with a custom policy template rather than a prebuilt regulatory template. The prebuilt templates (HIPAA, PCI-DSS, etc.) are designed for document workflows and their condition logic doesn't map cleanly to Power BI dataset metadata.

    Step 2: Locations

    Select Power BI workspaces as the location. You can scope the policy to specific workspaces by name or apply it tenant-wide. For your initial rollout, target your production and certified workspaces first — these are the datasets most likely to contain regulated data and most likely to be shared externally.

    Step 3: Define policy rules

    Here's where the real design work happens. A well-structured Power BI DLP policy for a financial services firm might look like:

    Rule 1: Block External Sharing of Highly Confidential Data

    • Condition: Content contains sensitivity label "Highly Confidential - Regulated (PHI/PCI)"
    • Action: Restrict access — prevent users from sharing the dataset with people outside the organization
    • User notification: Send policy tip with custom message and link to the data handling policy
    • Incident reports: Send alert to compliance team email + Microsoft Sentinel

    Rule 2: Restrict Confidential Financial Data Sharing

    • Condition: Content contains sensitivity label "Confidential - Finance" OR content matches sensitive information type "Credit Card Number"
    • Action: Restrict sharing — require business justification (override with audit log)
    • User notification: Policy tip explaining the restriction
    • Incident reports: Log to Activity Explorer with medium severity

    Rule 3: Detect Unlabeled Datasets with Sensitive Content

    • Condition: Dataset has no sensitivity label AND content matches sensitive information types (SSN, IBAN, Credit Card)
    • Action: Alert only (no blocking — this is a detection rule, not enforcement)
    • Incident reports: High severity alert with dataset details

    Rule 3 is particularly powerful: it catches the "we forgot to label this" scenario by scanning the actual data content, not just the label metadata.

    Note

    Content scanning (inspecting actual data values in datasets) is performed by the Microsoft Purview Scanner, which needs to be configured with access to your Power BI tenant. The scanner runs asynchronously after dataset refresh, so there's a window between refresh completion and when content-based DLP rules are evaluated. Don't design workflows that depend on instantaneous content scanning.

    Configuring Policy Tips for Minimal Disruption

    The single biggest adoption failure mode for DLP is the "everything is blocked" experience. When users encounter policy tips that block legitimate work without explanation, they file support tickets, escalate to their managers, and generate political pressure to disable the policies entirely.

    Design your policy tips as teaching moments, not just walls. Configure custom notification messages that:

    1. State clearly what the policy detected and why it's relevant
    2. Provide a one-sentence plain-English explanation of the risk
    3. Link to your internal data handling policy (host a simple intranet page)
    4. Explain the override process if one is available
    5. Give a compliance team contact for questions

    Example policy tip message for Rule 2:

    "This dataset contains financial data classified as Confidential. Sharing outside your department requires manager approval. To request an exception, submit a ticket at [your-intranet-url]. Questions? Contact dataprivacy@yourcompany.com."

    Configure overrides with business justification for "Confidential" labels but not for "Highly Confidential." The asymmetry is intentional: "Confidential" data sometimes legitimately needs to be shared more broadly, but "Highly Confidential" data should have no self-service override path.


    Handling Complex Scenarios: Composite Models, DirectQuery, and Deployment Pipelines

    The standard documentation covers straightforward Import-mode datasets. The edge cases are where real implementations encounter friction.

    Composite Models and Mixed-Sensitivity Sources

    When a dataset uses a composite model that connects to multiple sources with different sensitivity levels, Power BI applies the highest-sensitivity label from any connected source. This is the correct security behavior, but it creates an operational challenge: a "General" aggregation over a "Highly Confidential" fact table will inherit "Highly Confidential" — even if no actually sensitive columns are exposed in the model.

    The practical implication: if your aggregation layer is pulling pre-aggregated, non-sensitive data from a warehouse that also contains PII in other tables, the dataset-level label will reflect the source system's classification, not the actual content of the model. This is a known limitation of the current architecture.

    Your mitigation options are:

    1. Architectural separation: Route non-sensitive aggregate sources through a separate data tier with an appropriate lower-sensitivity classification. This is the cleanest solution and connects to the Power BI and Azure Synapse enterprise analytics architecture pattern where you maintain separate Gold-layer tables for public vs. restricted data.

    2. Manual label override with documentation: Allow workspace admins to manually downgrade the label with documented justification. This requires enabling the "Allow workspace admins to override" tenant setting, which creates audit risk, so mandate that every override has a corresponding ticket number in the audit log.

    3. Object-level security as a compensating control: If the inherited label is higher than the actual data sensitivity justifies, ensure that the actually sensitive columns from the source are excluded via object-level security, then document this compensation in your compliance evidence.

    DirectQuery and Real-Time Label Inheritance

    DirectQuery datasets present a specific timing problem. Since a DirectQuery dataset doesn't import data — it passes queries to the source at render time — the "refresh" trigger for label inheritance doesn't apply the same way. The label inheritance for DirectQuery is evaluated when the dataset connection metadata is updated, not on a scheduled refresh cycle.

    Key insight

    For DirectQuery datasets connected to Azure Synapse or SQL Server with Purview-classified sources, label inheritance fires when the dataset is published or updated, not on a schedule. If your source classification changes (e.g., a table gets reclassified from "General" to "Confidential"), the downstream DirectQuery dataset won't inherit the new label until someone touches the dataset — republishes it, changes a setting, or you trigger an update via API. Build a monitoring step into your source reclassification workflow.

    Sensitivity Labels in Deployment Pipelines

    When you use Power BI deployment pipelines to promote content from Development through Test to Production, sensitivity labels can create friction that trips up CI/CD workflows if you haven't planned for them.

    The label behavior in deployment pipelines follows these rules:

    • Labels are preserved when content is promoted from one stage to the next — a "Confidential" label on a Dev dataset stays "Confidential" when promoted to Production.
    • If the target stage already has a higher-sensitivity label, the higher label is retained (not downgraded by the incoming content).
    • Service principals running automated deployments must have the ability to read and write sensitivity labels, which requires specific Purview permissions (InformationProtection.ReadWrite.All in Microsoft Graph).

    The most common pipeline failure scenario: a service principal promotes a dataset that has an inherited "Highly Confidential" label to a Production workspace where DLP policies block sharing. The promotion itself succeeds, but the dataset immediately becomes non-shareable, breaking downstream reports that were working in Test. The fix is to ensure your DLP policy scoping explicitly excludes your deployment service principal's operations from blocking actions — use the Exceptions section in the DLP policy to whitelist the deployment service principal's activities.

    For automated CI/CD pipelines using Azure DevOps, see Implementing Power BI Deployment Pipelines with Git Integration and Azure DevOps for Automated CI/CD of Enterprise Reports for the deployment architecture context.


    Building Compliance Reporting: The Audit Data Pipeline

    Sensitivity labels and DLP policies are worthless without visibility into whether they're working. Compliance reporting for Power BI information protection requires pulling from three distinct data sources and synthesizing them into actionable intelligence.

    Data Sources for Compliance Reporting

    Source 1: Power BI Activity Log (via REST API)

    The Power BI activity log captures every label application, label change, export event, and DLP policy match. Extract it daily via the admin API:

    import requests
    from datetime import datetime, timedelta, timezone
    
    def get_activity_log(token: str, date: str) -> list:
        """
        Retrieve Power BI activity events for a specific date.
        date format: "2024-01-15"
        """
        # Activity log uses UTC times
        start_time = f"{date}T00:00:00.000Z"
        end_time = f"{date}T23:59:59.999Z"
        
        url = "https://api.powerbi.com/v1.0/myorg/admin/activityevents"
        headers = {"Authorization": f"Bearer {token}"}
        
        all_events = []
        params = {
            "startDateTime": f"'{start_time}'",
            "endDateTime": f"'{end_time}'",
        }
        
        # Filter for label-relevant event types
        label_event_types = [
            "SensitivityLabelApplied",
            "SensitivityLabelChanged",
            "SensitivityLabelRemoved",
            "ExportReport",
            "ExportDataflow",
            "DownloadReport",
            "ShareReport",
            "PublishedToWebReport",
        ]
        
        filter_clause = " or ".join(
            [f"Activity eq '{e}'" for e in label_event_types]
        )
        params["$filter"] = filter_clause
        
        while True:
            response = requests.get(url, headers=headers, params=params)
            response.raise_for_status()
            data = response.json()
            
            events = data.get("activityEventEntities", [])
            all_events.extend(events)
            
            # Handle pagination via continuationUri
            continuation = data.get("continuationUri")
            continuation_token = data.get("continuationToken")
            
            if not continuation_token:
                break
            
            # Use continuation token for next page
            params = {"continuationToken": f"'{continuation_token}'"}
        
        return all_events
    

    Source 2: Microsoft Purview Activity Explorer (via Graph API)

    DLP policy matches are recorded in the Microsoft Purview Activity Explorer. These events are queryable via the Office 365 Management Activity API:

    def get_purview_dlp_events(token: str, days_back: int = 7) -> list:
        """
        Retrieve DLP policy match events from Office 365 Management Activity API.
        Requires separate authentication with scope: 
        https://manage.office.com/.default
        """
        # Note: This requires a DIFFERENT token scope than Power BI API
        # Use manage.office.com scope, not analysis.windows.net
        
        tenant_id = "your-tenant-id"
        base_url = f"https://manage.office.com/api/v1.0/{tenant_id}/activity/feed"
        
        headers = {"Authorization": f"Bearer {token}"}
        
        # First, start or resume a subscription for DLP content
        # Content type for DLP events
        content_type = "Audit.General"
        
        # Get available content (DLP events are in Audit.General and DLP.All)
        start_time = (
            datetime.now(timezone.utc) - timedelta(days=days_back)
        ).strftime("%Y-%m-%dT%H:%M:%SZ")
        end_time = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
        
        content_url = (
            f"{base_url}/subscriptions/content"
            f"?contentType=DLP.All"
            f"&startTime={start_time}&endTime={end_time}"
        )
        
        response = requests.get(content_url, headers=headers)
        response.raise_for_status()
        
        content_blobs = response.json()
        dlp_events = []
        
        # Retrieve each content blob
        for blob in content_blobs:
            blob_url = blob.get("contentUri")
            blob_response = requests.get(blob_url, headers=headers)
            blob_response.raise_for_status()
            
            events = blob_response.json()
            # Filter for Power BI DLP events
            pbi_dlp_events = [
                e for e in events 
                if e.get("Workload") == "PowerBI" 
                and e.get("Operation", "").startswith("DLP")
            ]
            dlp_events.extend(pbi_dlp_events)
        
        return dlp_events
    

    Source 3: Scanner API for Label Coverage Audit

    The Purview Scanner API (or the Power BI Admin API's metadata scanning endpoints) lets you snapshot the current label state across all artifacts:

    def get_label_coverage_snapshot(token: str) -> dict:
        """
        Get current sensitivity label state for all Power BI artifacts.
        Returns coverage statistics by artifact type.
        """
        headers = {"Authorization": f"Bearer {token}"}
        
        # Trigger metadata scan
        scan_url = "https://api.powerbi.com/v1.0/myorg/admin/workspaces/modified"
        
        # Get all workspaces first
        workspaces_url = "https://api.powerbi.com/v1.0/myorg/admin/groups"
        ws_response = requests.get(
            workspaces_url, 
            headers=headers,
            params={"$top": 5000, "$filter": "type eq 'Workspace'"}
        )
        ws_response.raise_for_status()
        workspaces = ws_response.json().get("value", [])
        
        coverage = {
            "datasets": {"labeled": 0, "unlabeled": 0, "by_label": {}},
            "reports": {"labeled": 0, "unlabeled": 0},
            "dashboards": {"labeled": 0, "unlabeled": 0},
            "dataflows": {"labeled": 0, "unlabeled": 0},
        }
        
        # For each workspace, get artifacts and their labels
        for workspace in workspaces:
            ws_id = workspace["id"]
            
            # Get datasets in this workspace
            ds_url = f"https://api.powerbi.com/v1.0/myorg/admin/groups/{ws_id}/datasets"
            ds_response = requests.get(ds_url, headers=headers)
            
            if ds_response.status_code == 200:
                datasets = ds_response.json().get("value", [])
                for dataset in datasets:
                    label = dataset.get("sensitivityLabel")
                    if label:
                        coverage["datasets"]["labeled"] += 1
                        label_name = label.get("labelId", "unknown")
                        coverage["datasets"]["by_label"][label_name] = (
                            coverage["datasets"]["by_label"].get(label_name, 0) + 1
                        )
                    else:
                        coverage["datasets"]["unlabeled"] += 1
        
        # Calculate coverage percentage
        for artifact_type in coverage:
            total = (
                coverage[artifact_type]["labeled"] + 
                coverage[artifact_type]["unlabeled"]
            )
            coverage[artifact_type]["coverage_pct"] = (
                round(coverage[artifact_type]["labeled"] / total * 100, 1) 
                if total > 0 else 0
            )
        
        return coverage
    

    Building the Compliance Report in Power BI

    The final step is turning this audit data into a living compliance report. Load the three data sources above into a dedicated "Compliance Reporting" dataset — this is itself an excellent candidate for a Power BI dataflow to centralize the extraction logic.

    Your compliance report should contain at minimum:

    Page 1: Label Coverage Dashboard

    • KPI cards: % of datasets labeled (target: 100%), % with auto-inherited labels, % manually labeled
    • Bar chart: Label distribution across the hierarchy
    • Table: Unlabeled datasets with workspace, creator, last refresh date — this is your action list

    Page 2: DLP Policy Activity

    • Line chart: DLP matches over time, by policy rule
    • Matrix: DLP matches by workspace × label combination
    • Table: High-severity DLP incidents with user, artifact, timestamp, and resolution status

    Page 3: Label Change Audit Trail

    • Table: All label application, change, and removal events with actor (user vs. system), timestamp, previous label, and new label
    • Highlight: Any label downgrades (from higher to lower sensitivity) — these require compliance review

    Page 4: Export Risk Analysis

    • Chart: Export events by sensitivity label — how often are "Confidential" or "HC" datasets being exported?
    • Table: Users with high export volumes on sensitive datasets
    • Anomaly detection: Exports outside business hours or from unusual locations (requires enrichment with Azure AD sign-in logs)

    Tip

    Certify this compliance reporting dataset using the endorsement workflow (see Implementing Power BI Dataset Certification and Endorsement Workflows) and apply a "Highly Confidential" label to the dataset itself — it contains details about your security posture, which is sensitive in its own right.


    Hands-On Exercise

    In this exercise, you'll build a complete information protection sweep for a fictional pharmaceutical company, "NovaTech Pharma," which has 80 Power BI datasets across Finance, Clinical Research, and HR domains, with a mix of labeled and unlabeled assets.

    Scenario setup:

    NovaTech has three sensitivity-relevant dataset categories:

    • Clinical trial results (HIPAA-covered PHI) — must be "Highly Confidential - Regulated"
    • Employee compensation data (HR confidential) — must be "Confidential - HR"
    • Sales performance dashboards (internal, no PII) — should be "General"

    Exercise steps:

    Step 1: Audit current state

    Run the get_label_coverage_snapshot() function against the NovaTech tenant (use your lab tenant). Note the current coverage percentage and which datasets are unlabeled.

    Step 2: Build the classification function

    Extend the classify_dataset() function to detect NovaTech's naming conventions:

    • Datasets with "Clinical," "Trial," "Patient," or "PHI" in the name → "highly_confidential"
    • Datasets in workspaces named "HR-*" or containing "Compensation" or "Payroll" → "hr"
    • All other datasets currently unlabeled → "general"

    Step 3: Run the labeling sweep

    Execute run_labeling_sweep() and verify in the Power BI service that labels have been applied correctly. Check three datasets manually in the UI to confirm.

    Step 4: Configure a DLP rule

    In the Purview compliance portal, create a DLP policy that:

    • Targets Power BI workspaces in the "Clinical Research" group
    • Conditions: sensitivity label is "Highly Confidential - Regulated"
    • Action: Block sharing outside the organization
    • Alert: Send email to your lab email address with high severity

    Step 5: Test the DLP rule

    In the Power BI service, navigate to a dataset you labeled "Highly Confidential - Regulated" and attempt to share it externally. Verify that the policy tip fires and the share is blocked (allow up to 15 minutes for policy propagation).

    Step 6: Build the compliance report

    Using the activity log extraction function, pull 7 days of label-related events and load them into a Power BI Desktop model. Create a simple report page showing label changes over time and any DLP matches from your test in Step 5.


    Common Mistakes & Troubleshooting

    Labels publish to Purview but don't appear in Power BI

    Cause: The label policy hasn't been published to the Power BI workload, or the policy publication hasn't propagated yet (can take up to 24 hours).

    Fix: In the Purview compliance portal, open the label policy, go to Locations, confirm Power BI is checked. If it was just enabled, wait the propagation period. You can force-check by having a user log out and back in to the Power BI service.

    Inheritance isn't firing on refresh

    Cause 1: The dataset is not on Premium capacity. Auto-inheritance requires Premium or PPU.

    Cause 2: The source data system isn't registered in Purview Data Map with classifications applied. Inheritance only works from Purview-classified sources, not arbitrary data sources.

    Cause 3: The "Apply sensitivity labels from data sources" tenant setting is off or scoped to a security group that doesn't include the dataset owner.

    DLP policy is triggering on internal sharing that should be allowed

    Cause: The "restrict sharing" action in the DLP policy is too broad — it's blocking sharing with internal users who should have access.

    Fix: In the DLP rule action configuration, use "Restrict sharing with people outside the organization only" rather than blanket "Restrict access." Alternatively, create a security group for approved sharing recipients and add them as an exception in the DLP policy conditions.

    Service principal bulk labeling script returns 403

    Cause 1: The service principal doesn't have the InformationProtection.ReadWrite.All Graph permission with admin consent.

    Cause 2: The service principal is not in the security group whitelisted for the "Service principals can use Fabric APIs" tenant setting.

    Cause 3: The labeling endpoint requires the caller to have an Azure AD Premium license associated — service principals authenticating with client credentials sometimes hit this. Switch to using a managed identity on an Azure Function if you encounter persistent 403s.

    XMLA endpoint bypasses DLP — what do I do?

    This is a genuine architectural gap. Users with XMLA endpoint access (requires Premium) can query the tabular model directly and export results without triggering Power BI's export-path DLP evaluation.

    Mitigation layers:

    1. Restrict XMLA endpoint access to a defined security group in tenant settings — don't leave it open to all users
    2. Enable Azure AD Conditional Access policies that require compliant devices for XMLA endpoint access
    3. Enable XMLA write-back audit logging and route it to your SIEM
    4. Apply row-level security at the model level so that even if DLP is bypassed, XMLA users only see data their role permits

    The XMLA gap is a strong argument for defense-in-depth — DLP is one layer, not the only layer.

    Deployment pipeline promotes content and breaks sharing permissions

    Cause: The promoted dataset inherited a higher-sensitivity label in the source stage, and the target stage's DLP policy blocks sharing for that label.

    Fix: Add the deployment service principal to the DLP policy exceptions list. Navigate to the policy in Purview, select Advanced settings > Exceptions for service accounts, and add your deployment service principal's object ID.


    Summary & Next Steps

    You've now built a complete understanding of how Microsoft Purview's information protection capabilities integrate with the Power BI semantic layer — not just the configuration steps, but the architectural reasoning behind each decision.

    The core principles to carry forward:

    • Labels are policy containers, not stickers. Design your taxonomy around what the labels will enforce, not just how they sound.
    • Inheritance automates classification, but doesn't replace modeling discipline. A composite model pulling from a "Highly Confidential" source will inherit that label even if the model exposes no sensitive data — architectural separation at the source layer is the cleaner fix.
    • DLP policies must be designed with legitimate workflows in mind. A policy that blocks legitimate work generates pressure to disable it. Start with detection-only rules, measure the blast radius, then graduate to blocking.
    • Compliance reporting closes the loop. Labels and DLP without audit visibility are governance theater. Build the reporting pipeline from day one.
    • XMLA endpoints and service principals create bypass paths. Layer in model-level security and conditional access alongside DLP.

    Where to go next:

    Since you've established how information leaves the semantic layer safely, the logical next focus is controlling what data enters and how it's modeled. Consider diving into Implementing Power BI Dataset Sharing and Cross-Workspace Live Connections to Build a Reusable Enterprise Semantic Layer to understand how certified, labeled datasets can be safely shared across workspace boundaries without creating labeling inconsistencies.

    If your compliance requirements extend to encryption at rest for Premium datasets — particularly relevant for PHI or PCI data — explore Implementing Power BI Bring Your Own Key (BYOK) Encryption, which complements information protection by ensuring that even Microsoft cannot access your encrypted dataset storage.

    Finally, to make your compliance reporting operationally robust rather than a one-time effort, the automation patterns in Power BI REST API: Automate Administration and Deployments will help you schedule and maintain the audit pipelines you built in this lesson.

    Work With Us

    From insight to implementation

    Reading is the start. When you're ready to build the data, automation, or AI systems behind it, our team turns strategy into shipped results.

    Let's Build

    Enterprise Power BI

    Previous

    Implementing Power BI Endorsed Dataset Promotion with Deployment Pipeline Stage Rules to Enforce a Governed Data Product Lifecycle Across Enterprise Workspaces

    Related Insights

    Power BIFoundation

    DAX Date Table Patterns: Building a Reusable Calendar Table with CALENDAR, CALENDARAUTO, and Calculated Date Columns for Time Intelligence

    17 min
    Power BIExpert

    Visualizing Data in Power BI Desktop: Choosing, Adding, and Configuring Your First Charts, Tables, and KPI Cards

    33 min
    Power BIPractitioner

    Implementing Power BI Endorsed Dataset Promotion with Deployment Pipeline Stage Rules to Enforce a Governed Data Product Lifecycle Across Enterprise Workspaces

    25 min

    On this page

    • Introduction
    • Prerequisites
    • Understanding the Architecture: Where Purview Meets Power BI
    • The Inheritance Chain
    • Licensing Reality Check
    • Configuring the Foundation: Purview Label Taxonomy and Power BI Tenant Settings
    • Designing a Label Hierarchy for the Semantic Layer
    • Enabling Labels in Power BI
    • Automating Sensitivity Label Inheritance via API
    • Using the Power BI Admin REST API for Bulk Labeling
    • Inheriting Labels from Microsoft Purview Data Map
    • Designing and Enforcing DLP Policies for Power BI
    • What Power BI DLP Can and Cannot Do
    • Creating a DLP Policy for Power BI
    • Configuring Policy Tips for Minimal Disruption
    • Handling Complex Scenarios: Composite Models, DirectQuery, and Deployment Pipelines
    • Composite Models and Mixed-Sensitivity Sources
    • DirectQuery and Real-Time Label Inheritance
    • Sensitivity Labels in Deployment Pipelines
    • Building Compliance Reporting: The Audit Data Pipeline
    • Data Sources for Compliance Reporting
    • Building the Compliance Report in Power BI
    • Hands-On Exercise
    • Common Mistakes & Troubleshooting
    • Labels publish to Purview but don't appear in Power BI
    • Inheritance isn't firing on refresh
    • DLP policy is triggering on internal sharing that should be allowed
    • Service principal bulk labeling script returns 403
    • XMLA endpoint bypasses DLP — what do I do?
    • Deployment pipeline promotes content and breaks sharing permissions
    • Summary & Next Steps