Modules/M365DSCLogEngine.psm1

<#
.SYNOPSIS
    Creates a Microsoft365DSC error log entry and emits diagnostics.
 
.DESCRIPTION
    Builds a detailed error record, writes verbose output, logs to the M365DSC event log, emits telemetry, and appends error details to a session log file.
 
.PARAMETER Source
    Specifies the source name used for event and log entry context.
 
.PARAMETER Message
    Specifies the primary error message to record.
 
.PARAMETER Exception
    Specifies the exception object to include in diagnostics.
 
.PARAMETER Credential
    Specifies credential context used to derive tenant information when needed.
 
.PARAMETER TenantId
    Specifies the tenant identifier to include in log records.
 
.FUNCTIONALITY
    Internal
#>

function New-M365DSCLogEntry
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Source,

        [Parameter(Mandatory = $true)]
        [System.String]
        $Message,

        [Parameter()]
        [System.Object]
        $Exception,

        [Parameter()]
        [PSCredential]
        $Credential,

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

    try
    {
        $tenantIdValue = ''
        if (-not [System.String]::IsNullOrEmpty($TenantId))
        {
            $tenantIdValue = $TenantId
        }
        elseif ($null -ne $Credential)
        {
            $tenantIdValue = $Credential.UserName.Split('@')[1]
        }

        #region Verbose message
        $outputMessage = $Message
        if ($PSBoundParameters.ContainsKey('Exception'))
        {
            $exceptionMessage = '{ ' + $Exception.Exception.Message + ' } \ ' + $Exception.ScriptStackTrace -replace '\n', ' \ '
            $outputMessage += ' ' + $exceptionMessage
        }
        Write-Verbose -Message $outputMessage
        #endregion

        #region Event Log logging
        $errorMessage = $Message
        if ($PSBoundParameters.ContainsKey('Exception'))
        {
            $errorMessage += "`n`n" + $exceptionMessage
        }

        if ($tenantIdValue -eq '')
        {
            Add-M365DSCEvent -Message $errorMessage `
                -EntryType 'Error' `
                -EventID 1 `
                -Source $Source
        }
        else
        {
            Add-M365DSCEvent -Message $errorMessage `
                -EntryType 'Error' `
                -EventID 1 `
                -Source $Source `
                -TenantId $tenantIdValue
        }
        #endregion

        #region Telemetry
        $driftedData = [System.Collections.Generic.Dictionary[[System.String], [System.Object]]]::new()
        $driftedData.Add('CustomMessage', $Message)
        $driftedData.Add('Source', $Source)

        if ($PSBoundParameters.ContainsKey('Exception'))
        {
            $driftedData.Add('Category', $Exception.CategoryInfo.Category.ToString())
            $driftedData.Add('Exception', $Exception.Exception.ToString())
            $driftedData.Add('StackTrace', $Exception.ScriptStackTrace)
        }

        if ($tenantIdValue -ne '')
        {
            $driftedData.Add('TenantId', $tenantIdValue)
        }
        Add-M365DSCTelemetryEvent -Type 'Error' -Data $driftedData
        #endregion

        #region Error log file

        # Obtain the ID of the current PowerShell session. While this may
        # not be unique, it will result in varying file names
        $SessionID = [System.Diagnostics.Process]::GetCurrentProcess().Id.ToString()

        # Generate the Error log file name based on the SessionID;
        $LogFileName = $SessionID + '-M365DSC-ErrorLog.log'

        # Build up the Error message to append to our log file;
        $LogContent = '[' + [System.DateTime]::Now.ToString('yyyy/MM/dd hh:mm:ss') + "]`r`n"
        if ($PSBoundParameters.ContainsKey('Exception'))
        {
            $LogContent += '{' + $Exception.CategoryInfo.Category.ToString() + "}`r`n"
            $LogContent += $Exception.Exception.ToString() + "`r`n"
        }
        $LogContent += "`"" + $Message + "`"`r`n"
        if ($PSBoundParameters.ContainsKey('Exception'))
        {
            $LogContent += $Exception.ScriptStackTrace + "`r`n"
        }
        if ($null -ne $Credential)
        {
            $LogContent += $Credential.UserName + "`r`n"
        }
        if ($tenantIdValue -ne '')
        {
            $LogContent += 'TenantId: ' + $tenantIdValue + "`r`n"
        }
        $LogContent += "`r`n`r`n"

        # Write the error content into the log file;
        $LogFileName = Join-Path -Path $env:TEMP -ChildPath $LogFileName
        $LogFileName = $LogFileName.Replace('\', '/')
        $LogContent | Out-File $LogFileName -Append
        Write-M365DSCHost -Message "Error Log created at {file://$LogFileName}" -ForegroundColor Red
        #endregion
    }
    catch
    {
        Write-Warning -Message "An error occured logging an exception: $_"
    }
}

<#
.SYNOPSIS
    Writes an entry to the Microsoft365DSC event log.
 
.DESCRIPTION
    Creates the event source when needed, truncates long payloads, optionally forwards notifications, and writes the event entry with the requested severity and metadata.
 
.PARAMETER Message
    Specifies the event message text.
 
.PARAMETER Source
    Specifies the event source name.
 
.PARAMETER EntryType
    Specifies the event entry type.
 
.PARAMETER EventID
    Specifies the numeric event identifier.
 
.PARAMETER EventType
    Specifies the notification event category used for endpoint forwarding.
 
.PARAMETER TenantId
    Specifies the tenant identifier appended to the event payload.
 
.FUNCTIONALITY
    Internal
#>

function Add-M365DSCEvent
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Message,

        [Parameter(Mandatory = $true)]
        [System.String]
        $Source,

        [Parameter()]
        [ValidateSet('Error', 'Information', 'FailureAudit', 'SuccessAudit', 'Warning')]
        [System.String]
        $EntryType = 'Information',

        [Parameter()]
        [System.UInt32]
        $EventID = 1,

        [Parameter()]
        [System.String]
        [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift', 'RuleEvaluation')]
        $EventType,

        [Parameter()]
        [System.String]
        $TenantId
    )
    $LogName = 'M365DSC'

    try
    {
        try
        {
            $sourceExists = [System.Diagnostics.EventLog]::SourceExists($Source)
        }
        catch [System.Security.SecurityException]
        {
            Write-Verbose -Message '[WARNING] Not all event logs could be searched. Source might exist in another event log.'
        }

        if ($sourceExists)
        {
            $sourceLogName = [System.Diagnostics.EventLog]::LogNameFromSourceName($Source, '.')
            if ($LogName -ne $sourceLogName)
            {
                Write-Verbose -Message "[ERROR] Specified source {$Source} already exists on log {$sourceLogName}"
                return
            }
        }
        else
        {
            try
            {
                [System.Diagnostics.EventLog]::CreateEventSource($Source, $LogName)
            }
            catch [System.Security.SecurityException]
            {
                Write-Verbose -Message '[WARNING] Not all event logs could be searched. Source might exist in another event log.'
            }
        }

        # Limit the size of the message. Maximum is about 32,766
        $outputMessage = $Message
        if ($PSBoundParameters.ContainsKey('TenantId'))
        {
            $outputMessage += "`n`nTenantId: " + $TenantId
        }

        if ($PSBoundParameters.ContainsKey('Credential'))
        {
            $outputMessage += "`n`nCredential: " + $Credential.UserName
        }

        if ($outputMessage.Length -gt 32766)
        {
            $outputMessage = $outputMessage.Substring(0, 32766)
        }

        if (-not [System.String]::IsNullOrEmpty($EventType))
        {
            Send-M365DSCNotificationEndPointMessage -EventDetails $outputMessage `
                -EventType $EventType
        }

        try
        {
            [System.Diagnostics.EventLog]::WriteEntry($Source, $outputMessage, $EntryType, $EventID)
        }
        catch
        {
            Write-Verbose -Message $_
        }
    }
    catch
    {
        Write-Verbose -Message $_

        $MessageText = "Could not write to event log Source {$Source} EntryType {$EntryType} Message {$Message}"
        # Check call stack to prevent indefinite loop between New-M365DSCLogEntry and this function
        if ((Get-PSCallStack)[1].FunctionName -ne 'New-M365DSCLogEntry' -and `
                -not $_.ToString().Contains('EventLog access is not supported on this platform.'))
        {
            New-M365DSCLogEntry -Exception $_ -Message $MessageText `
                -Source '[M365DSCLogEngine]' `
                -TenantId $TenantId
        }
        else
        {
            Write-Verbose -Message $MessageText
        }
    }
}

<#
.SYNOPSIS
    Exports Microsoft365DSC diagnostic artifacts into a zip archive.
 
.DESCRIPTION
    Collects DSC verbose logs, M365DSC event log entries, PowerShell and OS details, and LCM information.
    Optionally anonymizes selected values before packaging the output.
 
.PARAMETER ExportFilePath
    Specifies the destination zip file path.
 
.PARAMETER NumberOfDays
    Specifies how many days of logs should be included.
 
.PARAMETER Anonymize
    Indicates that sensitive values should be anonymized in exported artifacts.
 
.PARAMETER Server
    Specifies the server value to replace during anonymization.
 
.PARAMETER Domain
    Specifies the domain value to replace during anonymization.
 
.PARAMETER Url
    Specifies the URL value to replace during anonymization.
 
.EXAMPLE
    Export-M365DSCDiagnosticData -ExportFilePath C:\Temp\DSCLogsExport.zip -NumberOfDays 3
 
.EXAMPLE
    Export-M365DSCDiagnosticData -ExportFilePath C:\Temp\DSCLogsExport.zip -Anonymize -Server spfe -Domain contoso.com -Url sharepoint.contoso.com
 
.FUNCTIONALITY
    Internal
#>

function Export-M365DSCDiagnosticData
{
    [CmdletBinding(DefaultParametersetName = 'None')]
    param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [System.String]
        $ExportFilePath,

        [Parameter()]
        [System.UInt32]
        $NumberOfDays = 7,

        [Parameter(ParameterSetName = 'Anon')]
        [Switch]
        $Anonymize,

        [Parameter(ParameterSetName = 'Anon', Mandatory = $true)]
        [System.String]
        $Server,

        [Parameter(ParameterSetName = 'Anon', Mandatory = $true)]
        [System.String]
        $Domain,

        [Parameter(ParameterSetName = 'Anon', Mandatory = $true)]
        [System.String]
        $Url
    )
    Write-Host 'Exporting logging information' -ForegroundColor Yellow

    if (($PSEdition -eq 'Desktop' -or $IsWindows) -and ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator') -eq $false)
    {
        throw 'You need to run this cmdlet with Administrator privileges!'
    }

    $afterDate = (Get-Date).AddDays(($NumberOfDays * -1))

    # Create Temp folder
    $guid = [Guid]::NewGuid()
    $tempPath = Join-Path -Path $env:TEMP -ChildPath $guid
    $null = New-Item -Path $tempPath -ItemType 'Directory'

    # Copy DSC Verbose Logs
    Write-Host ' * Copying DSC Verbose Logs' -ForegroundColor Gray
    $logPath = Join-Path -Path $tempPath -ChildPath 'DSCLogs'
    $null = New-Item -Path $logPath -ItemType 'Directory'

    $sourceLogPath = Join-Path -Path $env:windir -ChildPath 'System32/Configuration/ConfigurationStatus'
    $items = Get-ChildItem -Path "$sourceLogPath/*.json" | Where-Object -Property LastWriteTime -GT $afterDate
    Copy-Item -Path $items -Destination $logPath -ErrorAction 'SilentlyContinue' #-ErrorVariable $err

    if ($Anonymize)
    {
        Write-Host ' * Anonymizing DSC Verbose Logs' -ForegroundColor Gray
        foreach ($file in (Get-ChildItem -Path $logPath))
        {
            $content = Get-Content -Path $file.FullName -Raw -Encoding Unicode
            $content = $content -replace $Domain, '[DOMAIN]' -replace $Url, 'fqdn.com' -replace $Server, '[SERVER]'
            Set-Content -Path $file.FullName -Value $content
        }
    }

    # Export M365Dsc event log
    Write-Host ' * Exporting DSC Event Log' -ForegroundColor Gray
    $evtExportLog = Join-Path -Path $tempPath -ChildPath 'M365Dsc.csv'

    try
    {
        Write-Host ' * Anonymizing DSC Event Log' -ForegroundColor Gray
        Get-WinEvent -FilterHashtable @{ LogName = 'M365Dsc'; StartTime = $afterDate } -ErrorAction SilentlyContinue | Export-Csv $evtExportLog -NoTypeInformation
        if ($Anonymize)
        {
            $newLog = Import-Csv $evtExportLog
            foreach ($entry in $newLog)
            {
                $entry.MachineName = '[SERVER]'
                $entry.UserName = '[USER]'
                $entry.Message = $entry.Message -replace $Domain, '[DOMAIN]' -replace $Url, 'fqdn.com' -replace $Server, '[SERVER]'
            }

            $newLog | Export-Csv -Path $evtExportLog -NoTypeInformation
        }
    }
    catch
    {
        $txtExportLog = Join-Path -Path $tempPath -ChildPath 'M365Dsc.txt'
        Add-Content -Value 'M365Dsc event log does not exist!' -Path $txtExportLog
    }

    # PowerShell Version
    Write-Host ' * Exporting PowerShell Version info' -ForegroundColor Gray
    $psInfoFile = Join-Path -Path $tempPath -ChildPath 'PSInfo.txt'
    $PSVersionTable | Out-File -FilePath $psInfoFile

    # OS Version
    Write-Host ' * Exporting OS Version info' -ForegroundColor Gray
    $computerInfoFile = Join-Path -Path $tempPath -ChildPath 'OSInfo.txt'

    Get-ComputerInfo -Property @(
        'OsName',
        'OsOperatingSystemSKU',
        'OSArchitecture',
        'WindowsVersion',
        'WindowsBuildLabEx',
        'OsLanguage',
        'OsMuiLanguages') | Out-File -FilePath $computerInfoFile

    # LCM settings
    Write-Host ' * Exporting LCM Configuration info' -ForegroundColor Gray
    $lcmInfoFile = Join-Path -Path $tempPath -ChildPath 'LCMInfo.txt'
    Get-DscLocalConfigurationManager | Out-File -FilePath $lcmInfoFile

    # Creating export package
    Write-Host ' * Creating Zip file with all collected information' -ForegroundColor Gray
    Compress-Archive -Path $tempPath -DestinationPath $ExportFilePath -Force

    # Cleaning up temporary data
    Write-Host ' * Removing temporary data' -ForegroundColor Gray
    Remove-Item $tempPath -Recurse -Force -Confirm:$false

    Write-Host ('Completed with export. Information exported to {0}' -f $ExportFilePath) -ForegroundColor Yellow
}

<#
.SYNOPSIS
    Registers a notification endpoint for M365DSC events.
 
.DESCRIPTION
    Adds a URL and event type pair to the machine-level endpoint registration list.
    The function validates duplicates before persisting the new registration.
 
.PARAMETER Url
    Specifies the notification endpoint URL.
 
.PARAMETER EventType
    Specifies the event type that should trigger notifications.
 
.FUNCTIONALITY
    Public
#>

function New-M365DSCNotificationEndpointRegistration
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Url,

        [Parameter(Mandatory = $true)]
        [System.String]
        [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift', 'RuleEvaluation')]
        $EventType
    )

    # Get current notification endpoint registrations from registry and parse them into an object.
    $CurrentNotificationEndpoints = ([System.Environment]::GetEnvironmentVariable('M365DSCNotificationEndPointRegistrations', `
                [System.EnvironmentVariableTarget]::Machine))
    if ($null -ne $CurrentNotificationEndpoints)
    {
        $template = '{Url*:https://contoso.com}|{EventType:Error};{Url*:https://contoso.com}|{EventType:Error};'
        $CurrentNotificationEndpointsAsObject = ConvertFrom-String $CurrentNotificationEndpoints -TemplateContent $template
    }

    # Check to see if a notification endpoint registration with the same url and for the same event type already exists or not
    $existingInstance = $CurrentNotificationEndpointsAsObject | Where-Object -FilterScript { $_.Url -eq $url -and $_.EventType -eq $EventType }
    if ($null -ne $existingInstance)
    {
        throw "An existing endpoint notification registration on {$url} for {$EventType} already exists."
    }

    # If no exisiting instances, add the current one to the registry entry
    $CurrentNotificationEndpoints += "$($Url.Replace('|','').Replace(';',''))|$EventType;"
    [System.Environment]::SetEnvironmentVariable('M365DSCNotificationEndPointRegistrations', $CurrentNotificationEndpoints, `
            [System.EnvironmentVariableTarget]::Machine)
}

<#
.SYNOPSIS
    Removes a notification endpoint registration.
 
.DESCRIPTION
    Removes a URL and event type pair from the machine-level endpoint registration list.
    The function throws when the registration does not exist.
 
.PARAMETER Url
    Specifies the notification endpoint URL.
 
.PARAMETER EventType
    Specifies the registered event type to remove.
 
.FUNCTIONALITY
    Public
#>

function Remove-M365DSCNotificationEndpointRegistration
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Url,

        [Parameter(Mandatory = $true)]
        [System.String]
        [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift', 'RuleEvaluation')]
        $EventType
    )

    # Get current notification endpoint registrations from registry and parse them into an object.
    $CurrentNotificationEndpoints = ([System.Environment]::GetEnvironmentVariable('M365DSCNotificationEndPointRegistrations', `
                [System.EnvironmentVariableTarget]::Machine))
    if ($null -ne $CurrentNotificationEndpoints)
    {
        $template = '{Url*:https://contoso.com}|{EventType:Error};{Url*:https://contoso.com}|{EventType:Error};'
        $CurrentNotificationEndpointsAsObject = ConvertFrom-String $CurrentNotificationEndpoints -TemplateContent $template
    }

    # Check to see if a notification endpoint registration with the same url and for the same event type already exists or not
    $existingInstance = $CurrentNotificationEndpointsAsObject | Where-Object -FilterScript { $_.Url -eq $url -and $_.EventType -eq $EventType }
    if ($null -eq $existingInstance)
    {
        throw "No existing endpoint notification registration on {$url} for {$EventType} were found."
    }

    $valueToRemove += "$($Url.Replace('|','').Replace(';',''))|$EventType;"
    $CurrentNotificationEndpoints = $CurrentNotificationEndpoints.Replace($valueToRemove, '')

    # If we found an exisiting instance, remove it from registry entry's value.
    [System.Environment]::SetEnvironmentVariable('M365DSCNotificationEndPointRegistrations', $CurrentNotificationEndpoints, `
            [System.EnvironmentVariableTarget]::Machine)
}

<#
.SYNOPSIS
    Retrieves notification endpoint registrations.
 
.DESCRIPTION
    Returns all or filtered endpoint registrations from the machine-level registration value based on URL and event type filters.
 
.PARAMETER Url
    Specifies an optional URL filter.
 
.PARAMETER EventType
    Specifies an optional event type filter.
 
.FUNCTIONALITY
    Public
#>

function Get-M365DSCNotificationEndpointRegistration
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $Url,

        [Parameter()]
        [System.String]
        [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift', 'RuleEvaluation')]
        $EventType
    )

    # Get current notification endpoint registrations from registry and parse them into an object.
    $CurrentNotificationEndpoints = ([System.Environment]::GetEnvironmentVariable('M365DSCNotificationEndPointRegistrations', `
                [System.EnvironmentVariableTarget]::Machine))
    if ($null -ne $CurrentNotificationEndpoints)
    {
        $template = '{Url*:https://contoso.com}|{EventType:Error};{Url*:https://contoso.com}|{EventType:Error};'
        $CurrentNotificationEndpointsAsObject = ConvertFrom-String $CurrentNotificationEndpoints -TemplateContent $template

        # If both Url and EventType are null, return all endpoints
        if ([System.String]::IsNullOrEmpty($Url) -and [System.String]::IsNullOrEmpty($EventType))
        {
            return $CurrentNotificationEndpointsAsObject
        }
        elseif ([System.String]::IsNullOrEmpty($EventType) -and -not[System.String]::IsNullOrEmpty($Url))
        {
            # If Url is specified but EventType is null, return all endpoints on the given Url, no matter the EventType.
            return $CurrentNotificationEndpointsAsObject | Where-Object -Property Url -EQ $Url
        }
        elseif (-not [System.String]::IsNullOrEmpty($EventType) -and [System.String]::IsNullOrEmpty($Url))
        {
            # If EventType is specified but $Url is null, return all endpoints matching the specified EventType.
            return $CurrentNotificationEndpointsAsObject | Where-Object -Property EventType -EQ $EventType
        }
        elseif (-not [System.String]::IsNullOrEmpty($EventType) -and -not [System.String]::IsNullOrEmpty($Url))
        {
            # If both Url and EventType are specified, return endpoints that match both.
            return $CurrentNotificationEndpointsAsObject | Where-Object -FilterScript { $_.EventType -eq $EventType -and $Url -eq $Url }
        }
    }
    return $null
}

<#
.Description
This function receives an event, will loop through all registered notification endpoints
and will send a message to them if they have a registration on the given EventType of
the message.
 
.Functionality
Private
#>

function Send-M365DSCNotificationEndPointMessage
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $EventDetails,

        [Parameter()]
        [System.String]
        [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift', 'RuleEvaluation')]
        $EventType
    )

    # Get all notification endpoints that are registered for the given EventType
    [Array]$endpointsToContact = Get-M365DSCNotificationEndpointRegistration -EventType $EventType

    $messageBody = @{
        Details   = $EventDetails
        EventType = $EventType
    }

    $Parameters = @{
        Method      = 'POST'
        Uri         = ''
        Body        = ($messageBody | ConvertTo-Json)
        Headers     = $null
        ContentType = 'application/json'
    }

    foreach ($endpoint in $endpointsToContact)
    {
        try
        {
            $variableValue = Get-Variable -Name ('M365DSCNotificationEndpointBearer' + $domain) -Scope Global -ErrorAction SilentlyContinue

            if (-not [System.String]::IsNullOrEMpty($variableValue))
            {
                $domain = $endpoint.Url.Replace('https://', '').Replace('http://', '').Split('/')[0].Split('.')[0]
                $bearerTokenValue = (Get-Variable -Name ('M365DSCNotificationEndpointBearer' + $domain) -Scope Global).Value
                $Parameters.Headers = @{'Authorization' = "Bearer $bearerTokenValue" }
            }
            else
            {
                $Parameters.Headers = $null
            }
            $Parameters.Uri = $endpoint.url
            Invoke-RestMethod @Parameters | Out-Null
        }
        catch
        {
            Write-Verbose -Message "$_"
        }
    }
}

<#
.SYNOPSIS
    Determines whether the current shell is non-interactive.
 
.DESCRIPTION
    Checks command-line arguments and host interaction mode to identify unattended execution contexts.
 
.FUNCTIONALITY
    Private
#>

function Assert-M365DSCIsNonInteractiveShell
{
    param ()

    # Test each Arg for match of abbreviated '-NonInteractive' command.
    $NonInteractive = [Environment]::GetCommandLineArgs() -like '-NonI*'

    if ([Environment]::UserInteractive -and -not $NonInteractive)
    {
        # We are in an interactive shell.
        return $false
    }

    return $true
}

<#
.SYNOPSIS
    Configures M365DSC event logging options.
 
.DESCRIPTION
    Updates machine-scoped logging behavior for drift reporting.
 
.PARAMETER IncludeNonDrifted
    Specifies whether non-drifted resources should also be logged.
 
.FUNCTIONALITY
    Public
#>

function Set-M365DSCLoggingOption
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.Boolean]
        $IncludeNonDrifted
    )

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

<#
.SYNOPSIS
    Returns current M365DSC event logging options.
 
.DESCRIPTION
    Reads machine-scoped logging options and returns them as a hashtable.
 
.FUNCTIONALITY
    Public
 
.OUTPUTS
    System.Collections.Hashtable
#>

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

    try
    {
        return @{
            IncludeNonDrifted = [Boolean]([System.Environment]::GetEnvironmentVariable('M365DSCEventLogIncludeNonDrifted', `
                        [System.EnvironmentVariableTarget]::Machine))
        }
    }
    catch
    {
        throw $_
    }
}

Export-ModuleMember -Function @(
    'Add-M365DSCEvent',
    'Assert-M365DSCIsNonInteractiveShell',
    'Export-M365DSCDiagnosticData',
    'Get-M365DSCLoggingOption',
    'New-M365DSCLogEntry',
    'Get-M365DSCNotificationEndpointRegistration',
    'New-M365DSCNotificationEndpointRegistration',
    'Remove-M365DSCNotificationEndpointRegistration',
    'Set-M365DSCLoggingOption'
)