Modules/M365DSCTelemetryEngine.psm1

$Script:TelemetryEnabled = [System.Environment]::GetEnvironmentVariable('M365DSCTelemetryEnabled', `
            [System.EnvironmentVariableTarget]::Machine)

<#
.SYNOPSIS
    Returns whether Microsoft365DSC telemetry is enabled.
 
.DESCRIPTION
    Evaluates the machine-scoped telemetry flag and returns true when telemetry is enabled or not explicitly disabled.
 
.FUNCTIONALITY
    Internal
 
.OUTPUTS
    System.Boolean
#>

function Test-IsM365DSCTelemetryEnabled
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param()

    if ($null -eq $Script:TelemetryEnabled -or $Script:TelemetryEnabled -eq $true)
    {
        return $true
    }
    else
    {
        return $false
    }
}

<#
.DESCRIPTION
    This function gets the Application Insights key to be used for storing telemetry
 
.FUNCTIONALITY
    Internal, Hidden
#>

function Get-M365DSCApplicationInsightsTelemetryClient
{
    [CmdletBinding()]
    param()

    if ($null -eq $Global:M365DSCTelemetryEngine)
    {
        $AI = "$PSScriptRoot/../Dependencies/Microsoft.ApplicationInsights.dll"
        [Reflection.Assembly]::LoadFile($AI) | Out-Null

        $TelClient = [Microsoft.ApplicationInsights.TelemetryClient]::new()

        $connectionString = [System.Environment]::GetEnvironmentVariable('M365DSCTelemetryConnectionString', `
            [System.EnvironmentVariableTarget]::Machine)
        if (-not [System.String]::IsNullOrEmpty($connectionString))
        {
            $TelClient.TelemetryConfiguration.ConnectionString = $connectionString
        }
        else
        {
            $InstrumentationKey = [System.Environment]::GetEnvironmentVariable('M365DSCTelemetryInstrumentationKey', `
                    [System.EnvironmentVariableTarget]::Machine)

            if ($null -eq $InstrumentationKey)
            {
                $InstrumentationKey = 'e670af5d-fd30-4407-a796-8ad30491ea7a'
            }
            $TelClient.InstrumentationKey = $InstrumentationKey
        }

        $Global:M365DSCTelemetryEngine = $TelClient
    }
    return $Global:M365DSCTelemetryEngine
}

<#
.SYNOPSIS
    Stores LCM configuration metadata for telemetry enrichment.
 
.DESCRIPTION
    Saves the Local Configuration Manager configuration object in module scope so telemetry events can include LCM context.
 
.PARAMETER LCMConfig
    Specifies the Local Configuration Manager configuration object.
 
.EXAMPLE
    PS> $lcmConfig = Get-DscLocalConfigurationManager
    PS> Set-M365DSCLCMConfiguration -LCMConfig $lcmConfig
#>

function Set-M365DSCLCMConfiguration
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.Object]
        $LCMConfig
    )

    $Script:LCMConfig = $LCMConfig
}

<#
.SYNOPSIS
    Sends a telemetry event to Application Insights.
 
.DESCRIPTION
    Enriches telemetry data with runtime, environment, connection, and workload metadata, then submits the event through the telemetry client when telemetry is enabled.
 
.PARAMETER Type
    Specifies the telemetry event type.
 
.PARAMETER Data
    Specifies telemetry properties to include with the event.
 
.PARAMETER Metrics
    Specifies numeric telemetry metrics to include with the event.
 
.FUNCTIONALITY
    Internal
#>

function Add-M365DSCTelemetryEvent
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.String]
        $Type,

        [Parameter()]
        [System.Collections.Generic.Dictionary[[System.String], [System.Object]]]
        $Data,

        [Parameter()]
        [System.Collections.Generic.Dictionary[[System.String], [System.Double]]]
        $Metrics
    )

    $dataNew = [System.Collections.Generic.Dictionary[[System.String], [System.String]]]::new()
    foreach ($key in $Data.Keys)
    {
        $dataNew.Add($key, $Data[$key])
    }

    if ($null -eq $Script:TelemetryEnabled -or $Script:TelemetryEnabled -eq $true)
    {
        if ($Type -eq 'DriftEvaluation')
        {
            try
            {
                $hostId = (Get-Host).InstanceId
                if ($null -eq $Script:M365DSCCountResourceInstance -or $hostId -ne $Script:M365DSCExecutionContextId)
                {
                    $Script:M365DSCCountResourceInstance = 1
                }
                else
                {
                    $Script:M365DSCCountResourceInstance++
                }
                if ($null -eq $Script:M365DSCOperationStartTime -or $hostId -ne $Script:M365DSCExecutionContextId)
                {
                    $Script:M365DSCOperationStartTime = [System.DateTime]::Now
                }

                $Script:M365DSCOperationTimeTaken = [System.DateTime]::Now.Subtract($Script:M365DSCOperationStartTime)

                if ($hostId -ne $Script:M365DSCExecutionContextId)
                {
                    $Script:M365DSCExecutionContextId = $hostId
                }
                $dataNew.Add('ResourceInstancesCount', $Script:M365DSCCountResourceInstance)
                $dataNew.Add('M365DSCExecutionContextId', $hostId)
                $dataNew.Add('M365DSCOperationTotalTime', $Script:M365DSCOperationTimeTaken.TotalSeconds)
            }
            catch
            {
                Write-Verbose -Message $_
            }
        }

        try
        {
            $telemetryParameters = Get-M365DSCTelemetryConnectionParameter
            if ($null -eq $telemetryParameters -or $telemetryParameters.Count -eq 0)
            {
                $null = New-M365DSCConnection -Workload 'MicrosoftGraph' -InboundParameters $Data.PSBoundParameters
            }

            if ($null -ne $dataNew.ConnectionMode -and $dataNew.ConnectionMode.StartsWith('Credential'))
            {
                if ($null -eq $Script:M365DSCCurrentRoles -or $Script:M365DSCCurrentRoles.Length -eq 0)
                {
                    $telemetryParameters = Get-M365DSCTelemetryConnectionParameter
                    Connect-M365Tenant -Workload 'MicrosoftGraph' @telemetryParameters -ErrorAction SilentlyContinue
                    $Script:M365DSCCurrentRoles = @()

                    $uri = (Get-MSCloudLoginConnectionProfile -Workload MicrosoftGraph).ResourceUrl + 'v1.0/me?$select=id'
                    $currentUser = Invoke-MgGraphRequest -Uri $uri -Method GET
                    $currentUserId = $currentUser.id

                    $assignments = Get-MgBetaRoleManagementDirectoryRoleAssignment -Filter "principalId eq '$currentUserId'" `
                        -Property @('RoleDefinitionId', 'DirectoryScopeId') -ExpandProperty 'roleDefinition($select=displayName)' -All -ErrorAction 'SilentlyContinue'

                    if ($null -ne $assignments)
                    {
                        foreach ($assignment in $assignments)
                        {
                            if ($null -ne $assignment.RoleDefinition)
                            {
                                $Script:M365DSCCurrentRoles += $assignment.RoleDefinition.DisplayName + '|' + $assignment.DirectoryScopeId
                            }
                        }
                        $dataNew.Add('M365DSCCurrentRoles', $Script:M365DSCCurrentRoles -join ',')
                    }
                }
                else
                {
                    $dataNew.Add('M365DSCCurrentRoles', $Script:M365DSCCurrentRoles -join ',')
                }
            }
            elseif ($null -ne $dataNew.ConnectionMode -and $dataNew.ConnectionMode.StartsWith('ServicePrincipal'))
            {
                if ($null -eq $Script:M365DSCCurrentRoles -or $Script:M365DSCCurrentRoles.Length -eq 0)
                {
                    try
                    {
                        $telemetryParameters = Get-M365DSCTelemetryConnectionParameter
                        Connect-M365Tenant -Workload 'MicrosoftGraph' @telemetryParameters -ErrorAction Stop

                        $Script:M365DSCCurrentRoles = @()
                        $sp = Get-MgServicePrincipal -Filter "AppId eq '$($telemetryParameters.ApplicationId)'" `
                            -ErrorAction 'SilentlyContinue'
                        if ($null -ne $sp)
                        {
                            $assignments = Get-MgBetaRoleManagementDirectoryRoleAssignment -Filter "principalId eq '$($sp.Id)'" `
                                -Property @('RoleDefinitionId', 'DirectoryScopeId') -ExpandProperty 'roleDefinition($select=displayName)' -All -ErrorAction 'SilentlyContinue'
                            if ($null -ne $assignments)
                            {
                                foreach ($assignment in $assignments)
                                {
                                    if ($null -ne $assignment.RoleDefinition)
                                    {
                                        $Script:M365DSCCurrentRoles += $assignment.RoleDefinition.DisplayName + '|' + $assignment.DirectoryScopeId
                                    }
                                }
                                $dataNew.Add('M365DSCCurrentRoles', $Script:M365DSCCurrentRoles -join ',')
                            }
                        }
                    }
                    catch
                    {
                        Write-Verbose -Message $_
                    }
                }
                else
                {
                    $dataNew.Add('M365DSCCurrentRoles', $Script:M365DSCCurrentRoles -join ',')
                }
            }
        }
        catch
        {
            Write-Verbose -Message $_
        }

        $TelemetryClient = Get-M365DSCApplicationInsightsTelemetryClient

        try
        {
            $ProjectName = [System.Environment]::GetEnvironmentVariable('M365DSCTelemetryProjectName', `
                    [System.EnvironmentVariableTarget]::Machine)

            if ($null -ne $ProjectName)
            {
                $dataNew.Add('ProjectName', $ProjectName)
            }

            if (-not $dataNew.ContainsKey('Tenant'))
            {
                if (-not [System.String]::IsNullOrEmpty($dataNew.Principal))
                {
                    if ($dataNew.Principal -like '*@*.*')
                    {
                        $principalValue = $dataNew.Principal.Split('@')[1]
                        $dataNew.Add('Tenant', $principalValue)
                    }
                }
                elseif (-not [System.String]::IsNullOrEmpty($dataNew.TenantId))
                {
                    $principalValue = $dataNew.TenantId
                    $dataNew.Add('Tenant', $principalValue)
                }
            }

            $dataNew.Remove('TenandId') | Out-Null
            $dataNew.Remove('Principal') | Out-Null

            # Capture PowerShell Version Info
            if (-not $dataNew.Keys.Contains('PSMainVersion'))
            {
                $dataNew.Add('PSMainVersion', $PSVersionTable.PSVersion.Major.ToString() + '.' + $PSVersionTable.PSVersion.Minor.ToString())
            }
            if (-not $dataNew.Keys.Contains('PSVersion'))
            {
                $dataNew.Add('PSVersion', $PSVersionTable.PSVersion.ToString())
            }
            if (-not $dataNew.Keys.Contains('PSEdition'))
            {
                $dataNew.Add('PSEdition', $PSVersionTable.PSEdition.ToString())
            }

            if ($null -ne $PSVersionTable.BuildVersion -and -not $dataNew.Keys.Contains('PSBuildVersion'))
            {
                $dataNew.Add('PSBuildVersion', $PSVersionTable.BuildVersion.ToString())
            }

            if ($null -ne $PSVersionTable.CLRVersion -and -not $dataNew.Keys.Contains('PSCLRVersion'))
            {
                $dataNew.Add('PSCLRVersion', $PSVersionTable.CLRVersion.ToString())
            }

            # Capture Console/Host Information
            if ($host.Name -eq 'ConsoleHost' -and $null -eq $env:WT_SESSION -and -not $dataNew.Keys.Contains('PowerShellAgent'))
            {
                $dataNew.Add('PowerShellAgent', 'Console')
            }
            elseif ($host.Name -eq 'Windows PowerShell ISE Host' -and -not $dataNew.Keys.Contains('PowerShellAgent'))
            {
                $dataNew.Add('PowerShellAgent', 'ISE')
            }
            elseif ($host.Name -eq 'ConsoleHost' -and $null -ne $env:WT_SESSION -and -not $dataNew.Keys.Contains('PowerShellAgent'))
            {
                $dataNew.Add('PowerShellAgent', 'Windows Terminal' -and -not $dataNew.Keys.Contains('PowerShellAgent'))
            }
            elseif ($host.Name -eq 'ConsoleHost' -and $null -eq $env:WT_SESSION -and `
                    $null -ne $env:BUILD_BUILDID -and $env:SYSTEM -eq 'build' -and -not $dataNew.Keys.Contains('PowerShellAgent'))
            {
                $dataNew.Add('PowerShellAgent', 'Azure DevOPS')
                $dataNew.Add('AzureDevOPSPipelineType', 'Build')
                $dataNew.Add('AzureDevOPSAgent', $env:POWERSHELL_DISTRIBUTION_CHANNEL)
            }
            elseif ($host.Name -eq 'ConsoleHost' -and $null -eq $env:WT_SESSION -and `
                    $null -ne $env:BUILD_BUILDID -and $env:SYSTEM -eq 'release' -and -not $dataNew.Keys.Contains('PowerShellAgent'))
            {
                $dataNew.Add('PowerShellAgent', 'Azure DevOPS')
                $dataNew.Add('AzureDevOPSPipelineType', 'Release')
                $dataNew.Add('AzureDevOPSAgent', $env:POWERSHELL_DISTRIBUTION_CHANNEL)
            }
            elseif ($host.Name -eq 'Default Host' -and `
                    $null -ne $env:APPSETTING_FUNCTIONS_EXTENSION_VERSION -and -not $dataNew.Keys.Contains('PowerShellAgent'))
            {
                $dataNew.Add('PowerShellAgent', 'Azure Function')
                $dataNew.Add('AzureFunctionWorkerVersion', $env:FUNCTIONS_WORKER_RUNTIME_VERSION)
            }
            elseif ($host.Name -eq 'CloudShell' -and -not $dataNew.Keys.Contains('PowerShellAgent'))
            {
                $dataNew.Add('PowerShellAgent', 'Cloud Shell')
            }

            if ($null -ne $dataNew.Resource -and $dataNew.Keys.Contains('Resource'))
            {
                if ($dataNew.Resource.StartsWith('MSFT_AAD') -or $dataNew.Resource.StartsWith('AAD'))
                {
                    $dataNew.Add('Workload', 'Azure Active Directory')
                }
                elseif ($dataNew.Resource.StartsWith('MSFT_Intune') -or $dataNew.Resource.StartsWith('Defender'))
                {
                    $dataNew.Add('Workload', 'Defender')
                }
                elseif ($dataNew.Resource.StartsWith('MSFT_EXO') -or $dataNew.Resource.StartsWith('EXO'))
                {
                    $dataNew.Add('Workload', 'Exchange Online')
                }
                elseif ($dataNew.Resource.StartsWith('MSFT_Intune') -or $dataNew.Resource.StartsWith('Intune'))
                {
                    $dataNew.Add('Workload', 'Intune')
                }
                elseif ($dataNew.Resource.StartsWith('MSFT_O365') -or $dataNew.Resource.StartsWith('O365'))
                {
                    $dataNew.Add('Workload', 'Office 365 Admin')
                }
                elseif ($dataNew.Resource.StartsWith('MSFT_OD') -or $dataNew.Resource.StartsWith('OD'))
                {
                    $dataNew.Add('Workload', 'OneDrive for Business')
                }
                elseif ($dataNew.Resource.StartsWith('MSFT_Planner') -or $dataNew.Resource.StartsWith('Planner'))
                {
                    $dataNew.Add('Workload', 'Planner')
                }
                elseif ($dataNew.Resource.StartsWith('MSFT_PP') -or $dataNew.Resource.StartsWith('PP'))
                {
                    $dataNew.Add('Workload', 'Power Platform')
                }
                elseif ($dataNew.Resource.StartsWith('MSFT_SC') -or $dataNew.Resource.StartsWith('SC'))
                {
                    $dataNew.Add('Workload', 'Security and Compliance Center')
                }
                elseif ($dataNew.Resource.StartsWith('MSFT_SPO') -or $dataNew.Resource.StartsWith('SPO'))
                {
                    $dataNew.Add('Workload', 'SharePoint Online')
                }
                elseif ($dataNew.Resource.StartsWith('MSFT_Teams') -or $dataNew.Resource.StartsWith('Teams'))
                {
                    $dataNew.Add('Workload', 'Teams')
                }
                $dataNew.Resource = $dataNew.Resource.Replace('MSFT_', '')
            }

            if ($Type -eq 'ExportCompleted')
            {
                if ($null -ne $Global:M365DSCExportResourceInstancesCount)
                {
                    $dataNew.Add('ExportedResourceInstancesCount', $Global:M365DSCExportResourceInstancesCount)
                }
                if ($null -ne $Global:M365DSCExportResourceTypes)
                {
                    $dataNew.Add('ExportedResourceTypes', $Global:M365DSCExportResourceTypes)
                    $dataNew.Add('ExportedResourceTypesCount', $Global:M365DSCExportResourceTypes.Length)
                }
                if ($null -ne $Global:M365DSCExportContentSize)
                {
                    $dataNew.Add('ExportedContentSize', $Global:M365DSCExportContentSize)
                }
            }

            [array]$version = (Get-Module 'Microsoft365DSC').Version | Sort-Object -Descending
            $dataNew.Add('M365DSCVersion', $version[0].ToString())

            # OS Version
            try
            {
                if ($null -eq $Global:M365DSCOSInfo)
                {
                    $Global:M365DSCOSInfo = (Get-CimInstance -ClassName Win32_OperatingSystem -Property Caption -ErrorAction SilentlyContinue).Caption
                }
                $dataNew.Add('M365DSCOSVersion', $Global:M365DSCOSInfo)
            }
            catch
            {
                Write-Verbose -Message $_
            }

            # LCM Metadata Information
            try
            {
                if ($null -eq $Script:M365DSCCurrentPrincipalIsAdmin -and ($PSEdition -eq 'Desktop' -or $IsWindows))
                {
                    $currentPrincipal = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())
                    $Script:M365DSCCurrentPrincipalIsAdmin = $currentPrincipal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
                }

                if ($Script:M365DSCCurrentPrincipalIsAdmin)
                {
                    if ($null -eq $Script:LCMInfo)
                    {
                        $Script:LCMInfo = Get-DscLocalConfigurationManager -ErrorAction Stop
                    }

                    $certificateConfigured = $false
                    if (-not [System.String]::IsNullOrEmpty($LCMInfo.CertificateID))
                    {
                        $certificateConfigured = $true
                    }

                    $partialConfiguration = $false
                    if (-not [System.String]::IsNullOrEmpty($Script:LCMInfo.PartialConfigurations))
                    {
                        $partialConfiguration = $true
                    }
                    $dataNew.Add('LCMUsesPartialConfigurations', $partialConfiguration)
                    $dataNew.Add('LCMCertificateConfigured', $certificateConfigured)
                    $dataNew.Add('LCMConfigurationMode', $Script:LCMInfo.ConfigurationMode)
                    $dataNew.Add('LCMConfigurationModeFrequencyMins', $Script:LCMInfo.ConfigurationModeFrequencyMins)
                    $dataNew.Add('LCMRefreshMode', $Script:LCMInfo.RefreshMode)
                    $dataNew.Add('LCMState', $Script:LCMInfo.LCMState)
                    $dataNew.Add('LCMStateDetail', $Script:LCMInfo.LCMStateDetail)

                    if ([System.String]::IsNullOrEmpty($Type))
                    {
                        if ($Global:M365DSCExportInProgress)
                        {
                            $Type = 'Export'
                        }
                        elseif ($Script:LCMInfo.LCMStateDetail -eq 'LCM is performing a consistency check.' -or `
                                $Script:LCMInfo.LCMStateDetail -eq 'LCM exécute une vérification de cohérence.' -or `
                                $Script:LCMInfo.LCMStateDetail -eq 'LCM führt gerade eine Konsistenzüberprüfung durch.')
                        {
                            $Type = 'MonitoringScheduled'
                        }
                        elseif ($Script:LCMInfo.LCMStateDetail -eq 'LCM is testing node against the configuration.')
                        {
                            $Type = 'MonitoringManual'
                        }
                        elseif ($Script:LCMInfo.LCMStateDetail -eq 'LCM is applying a new configuration.' -or `
                                $Script:LCMInfo.LCMStateDetail -eq 'LCM applique une nouvelle configuration.')
                        {
                            $Type = 'ApplyingConfiguration'
                        }
                        else
                        {
                            $Type = 'Undetermined'
                        }
                    }
                }
            }
            catch
            {
                Write-Verbose -Message $_
            }

            $M365DSCTelemetryEventId = (New-Guid).ToString()
            $dataNew.Add('M365DSCTelemetryEventId', $M365DSCTelemetryEventId)

            if ([System.String]::IsNullOrEMpty($Type))
            {
                if ((-not [System.String]::IsNullOrEmpty($dataNew.Method) -and $dataNew.Method -eq 'Export-TargetResource') -or $Global:M365DSCExportInProgress)
                {
                    $Type = 'Export'
                }
            }

            $TelemetryClient.TrackEvent($Type, $dataNew, $Metrics)
        }
        catch
        {
            try
            {
                $TelemetryClient.TrackEvent('Error', $dataNew, $Metrics)
            }
            catch
            {
                Write-Error $_
            }
        }
    }
}

<#
.SYNOPSIS
    Configures machine-scoped telemetry options.
 
.DESCRIPTION
    Writes telemetry settings such as enabled state, instrumentation key, project name, and connection string to machine environment variables.
 
.PARAMETER Enabled
    Specifies whether telemetry should be enabled.
 
.PARAMETER InstrumentationKey
    Specifies the Application Insights instrumentation key.
 
.PARAMETER ProjectName
    Specifies the telemetry project name value.
 
.PARAMETER ConnectionString
    Specifies the Application Insights connection string.
 
.EXAMPLE
    Set-M365DSCTelemetryOption -Enabled $false
 
.FUNCTIONALITY
    Public
#>

function Set-M365DSCTelemetryOption
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.Boolean]
        $Enabled,

        [Parameter()]
        [System.String]
        $InstrumentationKey,

        [Parameter()]
        [System.String]
        $ProjectName,

        [Parameter()]
        [System.String]
        $ConnectionString
    )

    if ($null -ne $Enabled)
    {
        [System.Environment]::SetEnvironmentVariable('M365DSCTelemetryEnabled', $Enabled, `
                [System.EnvironmentVariableTarget]::Machine)
    }

    if ($null -ne $InstrumentationKey)
    {
        [System.Environment]::SetEnvironmentVariable('M365DSCTelemetryInstrumentationKey', $InstrumentationKey, `
                [System.EnvironmentVariableTarget]::Machine)
    }

    if ($null -ne $ProjectName)
    {
        [System.Environment]::SetEnvironmentVariable('M365DSCTelemetryProjectName', $ProjectName, `
                [System.EnvironmentVariableTarget]::Machine)
    }

    if ($null -ne $ConnectionString)
    {
        [System.Environment]::SetEnvironmentVariable('M365DSCTelemetryConnectionString', $ConnectionString, `
                [System.EnvironmentVariableTarget]::Machine)
    }
}

<#
.SYNOPSIS
    Returns current machine-scoped telemetry configuration.
 
.DESCRIPTION
    Reads telemetry-related environment variables and returns them as a hashtable.
 
.EXAMPLE
    Get-M365DSCTelemetryOption
 
.FUNCTIONALITY
    Public
 
.OUTPUTS
    System.Collections.Hashtable
#>

function Get-M365DSCTelemetryOption
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param()

    try
    {
        return @{
            Enabled            = [System.Environment]::GetEnvironmentVariable('M365DSCTelemetryEnabled', `
                    [System.EnvironmentVariableTarget]::Machine)
            InstrumentationKey = [System.Environment]::GetEnvironmentVariable('M365DSCTelemetryInstrumentationKey', `
                    [System.EnvironmentVariableTarget]::Machine)
            ProjectName        = [System.Environment]::GetEnvironmentVariable('M365DSCTelemetryProjectName', `
                    [System.EnvironmentVariableTarget]::Machine)
            ConnectionString   = [System.Environment]::GetEnvironmentVariable('M365DSCTelemetryConnectionString', `
                    [System.EnvironmentVariableTarget]::Machine)
        }
    }
    catch
    {
        throw $_
    }
}

<#
.SYNOPSIS
    Normalizes resource execution context into telemetry parameters.
 
.DESCRIPTION
    Builds a telemetry dictionary from resource name, command name, and bound parameters.
    It resolves tenant and principal hints and includes authentication mode metadata.
 
.PARAMETER ResourceName
    Specifies the resource name that produced the telemetry event.
 
.PARAMETER CommandName
    Specifies the command or method name associated with the telemetry event.
 
.PARAMETER Parameters
    Specifies bound parameters from the calling command.
 
.FUNCTIONALITY
    Internal
 
.OUTPUTS
    System.Collections.Generic.Dictionary[[System.String], [System.Object]]
#>

function Format-M365DSCTelemetryParameters
{
    [CmdletBinding()]
    [OutputType([System.Collections.Generic.Dictionary[[System.String], [System.Object]]])]
    param(
        [parameter(Mandatory = $true)]
        [System.String]
        $ResourceName,

        [parameter(Mandatory = $true)]
        [System.String]
        $CommandName,

        [parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Parameters
    )
    $data = [System.Collections.Generic.Dictionary[[System.String], [System.Object]]]::new()

    try
    {
        $data.Add('Resource', $ResourceName)
        $data.Add('Method', $CommandName)
        if ($Parameters.Credential)
        {
            try
            {
                $data.Add('Principal', $Parameters.Credential.UserName)
                $data.Add('Tenant', $Parameters.Credential.UserName.Split('@')[1])
            }
            catch
            {
                Write-Verbose -Message $_
            }
        }
        elseif ($Parameters.ApplicationId)
        {
            $data.Add('Principal', $Parameters.ApplicationId)
            $data.Add('Tenant', $Parameters.TenantId)
        }
        elseif (-not [System.String]::IsNullOrEmpty($Parameters.TenantId))
        {
            $data.Add('Tenant', $Parameters.TenantId)
        }
        $connectionMode = Get-M365DSCAuthenticationMode -Parameters $Parameters
        $data.Add('ConnectionMode', $connectionMode)
        $data.Add('PSBoundParameters', $Parameters)
    }
    catch
    {
        Write-Verbose -Message $_
    }
    return $data
}

Export-ModuleMember -Function @(
    'Add-M365DSCTelemetryEvent',
    'Format-M365DSCTelemetryParameters',
    'Get-M365DSCTelemetryOption',
    'Set-M365DSCTelemetryOption',
    'Set-M365DSCLCMConfiguration',
    'Test-IsM365DSCTelemetryEnabled',
    'Get-M365DSCConnectionParametersFromTelemetry'
)