public/Get-MsecPurviewRetention.ps1

function Get-MsecPurviewRetention {
    <#
    .SYNOPSIS
        Retention labels and retention policies, with whether either is actually in force.

    .DESCRIPTION
        Records management has two halves that are easy to mistake for one. A retention LABEL
        says what to do with an item; a retention POLICY says where the rule applies. A label
        with no policy publishing it does nothing at all, and the label list gives no hint of
        that - which is how a tenant ends up appearing to have retention while retaining
        nothing. Measured on one tenant: one label, published by nothing, and zero policies.

        BOTH KINDS COME BACK IN ONE STREAM, tagged by Kind ('Label' or 'Policy'), because the
        question is almost always "what retention do we have" rather than one or the other. Use
        -Kind to take a side. IsInForce is the column that matters: false on an unpublished
        label, false on a disabled policy.

        THE DEFAULT FOR A TENANT WITH NOTHING CONFIGURED IS AN EMPTY RESULT, and that is a real
        answer rather than a failure - which is exactly why the command does not throw on it.
        Callers writing a report should say "no retention is configured" rather than omitting
        the section.

    .PARAMETER Kind
        Limit to 'Label' or 'Policy'. Both by default.

    .EXAMPLE
        Connect-Msec -KeyVaultName kv-msec
        Get-MsecPurviewRetention | Format-Table Kind, Name, Action, Duration, IsInForce

    .EXAMPLE
        # Labels that exist but are published by no policy, so retain nothing.
        Get-MsecPurviewRetention -Kind Label | Where-Object { -not $_.IsInForce }

    .OUTPUTS
        One PSCustomObject per label or policy, PSTypeName 'MsecPurviewRetention'.

    .NOTES
        Needs Connect-Msec. The compliance session is opened automatically on first use - that
        handshake takes a few seconds and imports a few hundred cmdlets, so it is reported rather
        than done silently. Call Connect-MsecPurview yourself to control -Organization, or to
        choose when those 102 cmdlet names land in your runspace.

        Read-only.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [ValidateSet('Label', 'Policy')]
        [string] $Kind
    )

    Initialize-MsecExoSession -Endpoint Compliance

    # Checked per half, and only for the half actually being read: a tenant that exposes one and
    # not the other can still answer -Kind for the side that works, and the message says so.
    if (-not $Kind -or $Kind -eq 'Label') {
        Assert-MsecExoCmdlet -Name 'Get-ComplianceTag' -Feature 'retention labels' `
            -Hint 'Use -Kind Policy to read the half that is available.'
    }
    if (-not $Kind -or $Kind -eq 'Policy') {
        Assert-MsecExoCmdlet -Name 'Get-RetentionCompliancePolicy' -Feature 'retention policies' `
            -Hint 'Use -Kind Label to read the half that is available.'
    }

    if (-not $Kind -or $Kind -eq 'Label') {
        foreach ($tag in @(Get-ComplianceTag -ErrorAction Stop)) {
            [PSCustomObject]@{
                PSTypeName    = 'MsecPurviewRetention'
                Kind          = 'Label'
                Name          = [string] $tag.Name
                Action        = [string] $tag.RetentionAction
                Duration      = [string] $tag.RetentionDuration
                RetentionType = [string] $tag.RetentionType
                IsRecordLabel = [bool] $tag.IsRecordLabel
                # A label nothing publishes cannot apply to anything.
                IsInForce     = [bool] $tag.Published
                Enabled       = $null
                Locations     = $null
                Notes         = [string] $tag.Notes
                WhenChangedUtc = $tag.WhenChanged
            }
        }
    }

    if (-not $Kind -or $Kind -eq 'Policy') {
        foreach ($policy in @(Get-RetentionCompliancePolicy -ErrorAction Stop)) {
            $locations = @(
                foreach ($n in 'ExchangeLocation', 'SharePointLocation', 'OneDriveLocation',
                               'ModernGroupLocation', 'TeamsChannelLocation', 'TeamsChatLocation') {
                    $resolved = Resolve-MsecPurviewLocation $policy.$n
                    if ($resolved.Scope -ne 'None') { "$($n -replace 'Location','')=$($resolved.Scope)" }
                }
            )

            [PSCustomObject]@{
                PSTypeName    = 'MsecPurviewRetention'
                Kind          = 'Policy'
                Name          = [string] $policy.Name
                Action        = $null
                Duration      = $null
                RetentionType = $null
                IsRecordLabel = $null
                IsInForce     = [bool] $policy.Enabled
                Enabled       = [bool] $policy.Enabled
                Locations     = $locations
                Notes         = [string] $policy.Comment
                WhenChangedUtc = $policy.WhenChanged
            }
        }
    }
}