Modules/M365DSCLogEngine.psm1

<# This method creates a new error log file for each session,
   whenever an error is encountered, and appends valuable
   troubleshooting information to the file;
#>

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

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

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

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

    try
    {
        $VerbosePreference = 'Continue'
        Write-Host "$($Global:M365DSCEmojiRedX)"

        #region Telemetry
        $driftedData = [System.Collections.Generic.Dictionary[[String], [String]]]::new()
        $driftedData.Add("Event", "Error")
        $driftedData.Add("Category", $Error.CategoryInfo.Category.ToString())
        $driftedData.Add("Exception", $Error.Exception.ToString())
        $driftedData.Add("CustomMessage", $Message)
        $driftedData.Add("Source", $Source)
        $driftedData.Add("StackTrace", $Error.ScriptStackTrace)

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

        # Obtain the ID of the current PowerShell session. While this may
        # not be unique, it will;
        $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"
        $LogContent += "{" + $Error.CategoryInfo.Category.ToString() + "}`r`n"
        $LogContent += $Error.Exception.ToString() + "`r`n"
        $LogContent += "`"" + $Message + "`"`r`n"
        $LogContent += $Error.ScriptStackTrace + "`r`n"
        $LogContent += "`r`n`r`n"

        # Write the error content into the log file;
        $LogFileName = Join-Path -Path (Get-Location).Path -ChildPath $LogFileName
        $LogContent | Out-File $LogFileName -Append
        Write-Host "Error Log created at {$LogFileName}" -ForegroundColor Cyan
    }
    catch
    {
        Write-Warning -Message "An error occured logging an exception: $_"
    }
}

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]
        $TenantId
    )

    $LogName = 'M365DSC'

    try
    {
        if ([System.Diagnostics.EventLog]::SourceExists($Source))
        {
            $sourceLogName = [System.Diagnostics.EventLog]::LogNameFromSourceName($Source, ".")
            if ($LogName -ne $sourceLogName)
            {
                Write-Verbose -Message "[ERROR] Specified source {$Source} already exists on log {$sourceLogName}"
                return
            }
        }
        else
        {
            if ([System.Diagnostics.EventLog]::Exists($LogName) -eq $false)
            {
                #Create event log
                $null = New-EventLog -LogName $LogName -Source $Source
            }
            else
            {
                [System.Diagnostics.EventLog]::CreateEventSource($Source, $LogName)
            }
        }

        Write-EventLog -LogName $LogName -Source $Source `
            -EventId $EventID -Message $Message -EntryType $EntryType
    }
    catch
    {
        Write-Verbose -Message $_
        $MessageText = "Could not write to event log Source {$Source} EntryType {$EntryType} Message {$Message}"
        New-M365DSCLogEntry -Error $_ -Message $MessageText `
            -Source "[M365DSCLogEngine]" `
            -TenantId $TenantId
    }
}

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 (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") -eq $false)
    {
        Write-Host -Object "[ERROR] You need to run this cmdlet with Administrator privileges!" -ForegroundColor Red
        return
    }

    $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 { $_.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-EventLog -LogName 'M365Dsc' -After $afterDate | 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
}