PowerBI/Get-PowerBISecurityConfig.ps1

<#
.SYNOPSIS
    Collects Power BI security and tenant configuration settings.
.DESCRIPTION
    Queries Power BI tenant settings for security-relevant configuration including
    guest access, external sharing, publish to web, sensitivity labels, and service
    principal restrictions. Returns a structured inventory of settings with current
    values and CIS benchmark recommendations.
 
    Requires the MicrosoftPowerBIMgmt PowerShell module.
    Uses Invoke-PowerBIRestMethod to query the admin API.
.PARAMETER OutputPath
    Optional path to export results as CSV. If not specified, results are returned to the pipeline.
.EXAMPLE
    PS> . .\Common\Connect-Service.ps1
    PS> Connect-Service -Service PowerBI
    PS> .\PowerBI\Get-PowerBISecurityConfig.ps1
 
    Displays Power BI security configuration settings.
.EXAMPLE
    PS> .\PowerBI\Get-PowerBISecurityConfig.ps1 -OutputPath '.\powerbi-security-config.csv'
 
    Exports the security configuration to CSV.
.NOTES
    Author: Daren9m
    Settings checked are aligned with CIS Microsoft 365 Foundations Benchmark v6.0.1 recommendations.
#>

[CmdletBinding()]
param(
    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [string]$OutputPath
)

# Stop on errors: API failures should halt this collector rather than produce partial results.
$ErrorActionPreference = 'Stop'

# Verify Power BI connection by attempting to get an access token
try {
    Get-PowerBIAccessToken -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null
}
catch {
    Write-Error "Power BI connection check failed: $($_.Exception.Message). Ensure you have run Connect-PowerBIServiceAccount."
    return
}

$settings = [System.Collections.Generic.List[PSCustomObject]]::new()
$checkIdCounter = @{}

function Add-Setting {
    param(
        [string]$Category,
        [string]$Setting,
        [string]$CurrentValue,
        [string]$RecommendedValue,
        [string]$Status,
        [string]$CheckId = '',
        [string]$Remediation = ''
    )
    $subCheckId = $CheckId
    if ($CheckId) {
        if (-not $checkIdCounter.ContainsKey($CheckId)) { $checkIdCounter[$CheckId] = 0 }
        $checkIdCounter[$CheckId]++
        $subCheckId = "$CheckId.$($checkIdCounter[$CheckId])"
    }
    $settings.Add([PSCustomObject]@{
        Category         = $Category
        Setting          = $Setting
        CurrentValue     = $CurrentValue
        RecommendedValue = $RecommendedValue
        Status           = $Status
        CheckId          = $subCheckId
        Remediation      = $Remediation
    })
    if ($CheckId -and (Get-Command -Name Update-CheckProgress -ErrorAction SilentlyContinue)) {
        Update-CheckProgress -CheckId $subCheckId -Setting $Setting -Status $Status
    }
}

# ─── Retrieve all tenant settings ────────────────────────────────
$script:settingsError = $null
try {
    $tenantSettings = Invoke-PowerBIRestMethod -Url 'admin/tenantSettings' -Method Get -WarningAction SilentlyContinue | ConvertFrom-Json
    $allSettings = $tenantSettings.tenantSettings
}
catch {
    $errMsg = $_.Exception.Message
    if ($errMsg -match '404|Not Found') {
        $script:settingsError = 'Power BI admin API not available -- ensure the calling account has Power BI Service Administrator role'
    }
    elseif ($errMsg -match '403|Forbidden|Unauthorized') {
        $script:settingsError = 'Access denied -- insufficient permissions for Power BI tenant settings'
    }
    else {
        $script:settingsError = "Could not retrieve Power BI tenant settings: $errMsg"
    }
    Write-Warning $script:settingsError
    $allSettings = @()
}

# Helper: look up a setting by settingName and return its isEnabled value
function Get-TenantSetting {
    param([string]$SettingName)
    $match = $allSettings | Where-Object { $_.settingName -eq $SettingName }
    if ($match) { return $match.isEnabled }
    return $null
}

# ─── CIS 9.1.1: Guest user access restricted ────────────────────
# When AllowGuestLookup is disabled, guest users cannot browse the tenant directory
$guestLookup = Get-TenantSetting -SettingName 'AllowGuestLookup'
$guestStatus = if ($guestLookup -eq $false) { 'Pass' } elseif ($null -eq $guestLookup) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Guest Access'
    Setting          = 'Guest User Access Restricted'
    CurrentValue     = if ($null -eq $guestLookup) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$(-not $guestLookup)" }
    RecommendedValue = 'True'
    Status           = $guestStatus
    CheckId          = 'POWERBI-GUEST-001'
    Remediation      = 'Power BI Admin Portal > Tenant settings > Export and sharing > Allow guest users to browse and access Power BI content > Disabled'
}
Add-Setting @settingParams

# ─── CIS 9.1.2: External user invitations restricted ────────────
$guestInvite = Get-TenantSetting -SettingName 'ElevatedGuestsTenant'
$inviteStatus = if ($guestInvite -eq $false) { 'Pass' } elseif ($null -eq $guestInvite) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Guest Access'
    Setting          = 'External User Invitations Restricted'
    CurrentValue     = if ($null -eq $guestInvite) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$(-not $guestInvite)" }
    RecommendedValue = 'True'
    Status           = $inviteStatus
    CheckId          = 'POWERBI-GUEST-002'
    Remediation      = 'Power BI Admin Portal > Tenant settings > Export and sharing > Invite external users to your organization > Disabled'
}
Add-Setting @settingParams

# ─── CIS 9.1.3: Guest access to content restricted ──────────────
$guestContent = Get-TenantSetting -SettingName 'AllowGuestUserToAccessSharedContent'
$contentStatus = if ($guestContent -eq $false) { 'Pass' } elseif ($null -eq $guestContent) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Guest Access'
    Setting          = 'Guest Access to Content Restricted'
    CurrentValue     = if ($null -eq $guestContent) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$(-not $guestContent)" }
    RecommendedValue = 'True'
    Status           = $contentStatus
    CheckId          = 'POWERBI-GUEST-003'
    Remediation      = 'Power BI Admin Portal > Tenant settings > Export and sharing > Allow Azure Active Directory guest users to access Power BI > Disabled'
}
Add-Setting @settingParams

# ─── CIS 9.1.4: Publish to web restricted ───────────────────────
$publishToWeb = Get-TenantSetting -SettingName 'WebDashboardsPublishToWebDisabled'
$publishStatus = if ($publishToWeb -eq $true) { 'Pass' } elseif ($null -eq $publishToWeb) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Sharing'
    Setting          = 'Publish to Web Restricted'
    CurrentValue     = if ($null -eq $publishToWeb) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$publishToWeb" }
    RecommendedValue = 'True'
    Status           = $publishStatus
    CheckId          = 'POWERBI-SHARING-001'
    Remediation      = 'Power BI Admin Portal > Tenant settings > Export and sharing > Publish to web > Disabled'
}
Add-Setting @settingParams

# ─── CIS 9.1.5: R and Python visuals disabled ───────────────────
$rPython = Get-TenantSetting -SettingName 'RScriptVisuals'
$rPythonStatus = if ($rPython -eq $false) { 'Pass' } elseif ($null -eq $rPython) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Sharing'
    Setting          = 'R and Python Visuals Disabled'
    CurrentValue     = if ($null -eq $rPython) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$(-not $rPython)" }
    RecommendedValue = 'True'
    Status           = $rPythonStatus
    CheckId          = 'POWERBI-SHARING-002'
    Remediation      = 'Power BI Admin Portal > Tenant settings > R and Python visuals > Interact with and share R and Python visuals > Disabled'
}
Add-Setting @settingParams

# ─── CIS 9.1.6: Sensitivity labels enabled ──────────────────────
$sensitivityLabels = Get-TenantSetting -SettingName 'UseSensitivityLabels'
$labelsStatus = if ($sensitivityLabels -eq $true) { 'Pass' } elseif ($null -eq $sensitivityLabels) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Information Protection'
    Setting          = 'Sensitivity Labels Enabled'
    CurrentValue     = if ($null -eq $sensitivityLabels) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$sensitivityLabels" }
    RecommendedValue = 'True'
    Status           = $labelsStatus
    CheckId          = 'POWERBI-INFOPROT-001'
    Remediation      = 'Power BI Admin Portal > Tenant settings > Information protection > Allow users to apply sensitivity labels for content > Enabled'
}
Add-Setting @settingParams

# ─── CIS 9.1.7: Shareable links restricted ──────────────────────
$shareLinks = Get-TenantSetting -SettingName 'ShareLinkToEntireOrg'
$shareStatus = if ($shareLinks -eq $false) { 'Pass' } elseif ($null -eq $shareLinks) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Sharing'
    Setting          = 'Shareable Links Restricted'
    CurrentValue     = if ($null -eq $shareLinks) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$(-not $shareLinks)" }
    RecommendedValue = 'True'
    Status           = $shareStatus
    CheckId          = 'POWERBI-SHARING-003'
    Remediation      = 'Power BI Admin Portal > Tenant settings > Export and sharing > Allow shareable links to grant access to everyone in your organization > Disabled'
}
Add-Setting @settingParams

# ─── CIS 9.1.8: External data sharing restricted ────────────────
$extDataSharing = Get-TenantSetting -SettingName 'AllowExternalDataSharingReceiverWorksWithShare'
$extStatus = if ($extDataSharing -eq $false) { 'Pass' } elseif ($null -eq $extDataSharing) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Sharing'
    Setting          = 'External Data Sharing Restricted'
    CurrentValue     = if ($null -eq $extDataSharing) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$(-not $extDataSharing)" }
    RecommendedValue = 'True'
    Status           = $extStatus
    CheckId          = 'POWERBI-SHARING-004'
    Remediation      = 'Power BI Admin Portal > Tenant settings > Export and sharing > Allow external data sharing > Disabled'
}
Add-Setting @settingParams

# ─── CIS 9.1.9: Block ResourceKey Authentication ────────────────
$blockResKey = Get-TenantSetting -SettingName 'BlockResourceKeyAuthentication'
$resKeyStatus = if ($blockResKey -eq $true) { 'Pass' } elseif ($null -eq $blockResKey) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Authentication'
    Setting          = 'Block ResourceKey Authentication'
    CurrentValue     = if ($null -eq $blockResKey) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$blockResKey" }
    RecommendedValue = 'True'
    Status           = $resKeyStatus
    CheckId          = 'POWERBI-AUTH-001'
    Remediation      = 'Power BI Admin Portal > Tenant settings > Developer settings > Block ResourceKey Authentication > Enabled'
}
Add-Setting @settingParams

# ─── CIS 9.1.10: Service Principal API access restricted ────────
$spAccess = Get-TenantSetting -SettingName 'ServicePrincipalAccess'
$spStatus = if ($spAccess -eq $false) { 'Pass' } elseif ($null -eq $spAccess) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Authentication'
    Setting          = 'Service Principal API Access Restricted'
    CurrentValue     = if ($null -eq $spAccess) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$(-not $spAccess)" }
    RecommendedValue = 'True'
    Status           = $spStatus
    CheckId          = 'POWERBI-AUTH-002'
    Remediation      = 'Power BI Admin Portal > Tenant settings > Developer settings > Allow service principals to use Power BI APIs > Disabled or restricted to specific security groups'
}
Add-Setting @settingParams

# ─── CIS 9.1.11: Service Principal profiles restricted ──────────
$spProfiles = Get-TenantSetting -SettingName 'CreateServicePrincipalProfile'
$spProfileStatus = if ($spProfiles -eq $false) { 'Pass' } elseif ($null -eq $spProfiles) { 'Review' } else { 'Fail' }
$settingParams = @{
    Category         = 'Power BI - Authentication'
    Setting          = 'Service Principal Profiles Restricted'
    CurrentValue     = if ($null -eq $spProfiles) { if ($script:settingsError) { $script:settingsError } else { 'Not found' } } else { "$(-not $spProfiles)" }
    RecommendedValue = 'True'
    Status           = $spProfileStatus
    CheckId          = 'POWERBI-AUTH-003'
    Remediation      = 'Power BI Admin Portal > Tenant settings > Developer settings > Allow service principals to create and use profiles > Disabled'
}
Add-Setting @settingParams

# ─── Output ──────────────────────────────────────────────────────
$report = @($settings)
Write-Verbose "Collected $($report.Count) Power BI security configuration settings"

if ($OutputPath) {
    $report | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
    Write-Output "Exported Power BI security config ($($report.Count) settings) to $OutputPath"
}
else {
    Write-Output $report
}