Public/Write-Log.ps1

function Write-Log {
<#
    .Synopsis
       Writes a message to one of several streams/targetd. The default "info" messages use Write-Information and set the informationprefer
    .Description
        Write-Log is the central function for the FC_Log module. It is designed to wrap the several ways to log data into one easy to use function. Using Write-Log in your script, you can then decide at run time if you want your messages to display or not, and to what stream you want them written. A common use is to Set-logLevel to Error when the scripts are running in production, and then if an error occurs reruning the scripts with Debug level, and possible write to the Windows event log to help you in troubleshooting.
 
        Messages can be written to the windows event log in addition with being returned via a powershell stream based on how the logger has been configured.
         
        There is also limited formatting that will be applied. The formating is set in the Set-LogFormattingOptions function
    .PARAMETER Messages
        Default: $null
        Position: 0
 
        The $Messages parameter is an array of strings that will be logged. This will accept pipeline input.
    .PARAMETER EventLevel
        Position: 2
        Specifies the entry type of the event. Position=1
        Valid values are "Debug","Verbose","Info","Warning","Error"
 
        The default level of Info is used if not specified. Write-Log checks against the script scoped logLevel, if the EventLevel is lower than the script scoped $logLevel then the message will be written to the appropriate stream (Write-Debug,Write-Verbose,Write-Information,Write-Warning, or Write-Error) depending on the caller's preference and the messages EventLevel.
    .PARAMETER eventID
        Position: 3
        Only used when you Set-logTargetWinEvent on. The Event ID for the windows event log. Can be any number between 0 and 65535, defaults to 10.
     .EXAMPLE
        Writes a message as a Information event level. Good for general logging to the screen. The Information preference variable is set to 'Continue' for the scope of the call so that the message will be deployed regardless of the caller's preference.
 
       Write-Log "Basic information message"
    .EXAMPLE
        Uses the pipeline to log all the running process names
 
       Get-Process | select -ExpandProperty name | Write-Log
  
    .EXAMPLE
        Writing a error message to the Windows Event Log a message as a Error event level, with the max eventID value of 65535. This will call Write-Error to raise an error on the PowerShell host, and write the log to the Windows Event log with the event ID: 65535
 
        Set-logTargetWinEvent $true
 
       Write-Log "Testing out a error message" Error -eventID 65535
    .INPUTS
        Accepts a array of string values for the message that will be written
    #>

  [CmdletBinding()]
  param(
    [Parameter(ValueFromPipeline = $True,Position = 0)] [string[]]$Messages = "",
    [Parameter(Position = 1)][ValidateSet("Debug","Verbose","Info","Warning","Error","Disable")] [string]$EventLevel = "Info",
    [Parameter(Position = 2)] [int]$eventID = 10,
    [Parameter(Position = 3)] [int]$tabLevel = 0

  )
  begin {
    if ($script:LogLevel -eq 100) {
      return
    }
    $msgLevel = $script:logLevelOptions[$EventLevel]
    #Set the Verbose preference to the value in the calling script
    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $isWindowsEventLogTarget = $script:logTargets['WindowsEventLog']
    if( $PSVersionTable.PSEdition -eq 'core' -and $isWindowsEventLogTarget -eq 1 ){
        Write-Error "I cannot log to the Windows Event Log on pwsh core without some workarounds. See https://github.com/brandonmcclure/friendly-chainsaw/issues/61"
        $isWindowsEventLogTarget = 0
    }

    $tabs = ''
    if ($script:logFormattingOptions['PrefixScriptName'] -eq 1) {
      $scriptName = ""
      if([string]::IsnullOrEmpty($scriptName)){
        $scriptName = Get-CallingScript
    }
    if([string]::IsnullOrEmpty($scriptName)){
        Write-Error "Cannot set the scriptName" -ErrorAction Stop
    }
  }
  $callingFunction = ""
    if ($script:logFormattingOptions['PrefixCallingFunction'] -eq 1) {
      $callingFunction = Get-CallingFunction

    }
    if ($script:logFormattingOptions['AutoTabCallsFromFunctions'] -eq 1) {
      $callingFunction = (Get-PSCallStack | Select-Object -Skip 1 -First 1 | Where-Object { $_.Command -ne '<ScriptBlock>' }).FunctionName 
      if (!([string]::IsNullOrEmpty($callingFunction))) {
        $tabLevel++
      }
    }
    for ($i = 1; $i -le $tabLevel | Where-Object { $_ -ne 0 }; $i++) {
      $tabs = $tabs + ' '
    }

    $timeStamp = ''
    if ($script:logFormattingOptions['PrefixTimestamp'] -eq 1) {
      $timeStamp = "$(Get-Date) - "
    }
  }
  process {
    foreach ($msg in $Messages) {
      #Debug Messages
      if ($msgLevel -eq 0 -and $script:LogLevel -eq 0) {
        if ($script:logFormattingOptions['PrefixCallingFunction'] -eq 1 -and !([string]::IsNullOrEmpty($callingFunction))) {
            $FormatMessage = "$tabs$timeStamp[$callingFunction][DEBUG] $msg"
        }
        elseif ($script:logFormattingOptions['PrefixScriptName'] -eq 1 -and !([string]::IsNullOrEmpty($scriptName))) {
          $FormatMessage = "$tabs$timeStamp[$scriptName][DEBUG] $msg"
        }
        else {
          $FormatMessage = "$tabs$timeStamp[DEBUG] $msg"
        }

        if($script:logTargets['Console'] -eq 1){
            if ($DebugPreference -eq "Inquire" -or $DebugPreference -eq "Continue") {
              Write-Debug "$msg"
            }
            else {
              $VerbosePreference = 'Continue'
              Write-Verbose "$FormatMessage"
            }
        }
        if ($isWindowsEventLogTarget -eq 1) {
          Write-EventLog -LogName Application -Source "$script:LogSource" -EntryType "Information" -EventId $eventID -Message "$FormatMessage"
        }
        if ($script:logTargets['Speech'] -eq 1) {
          Add-Type -AssemblyName System.speech
          $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
          $speak.Speak($msg)
        }
      }
      #Verbose Messages
      elseif ($msgLevel -eq 5 -and $script:LogLevel -le 5) {
        if ($script:logFormattingOptions['PrefixCallingFunction'] -eq 1 -and !([string]::IsNullOrEmpty($callingFunction))) {
          $FormatMessage = "$tabs$timeStamp[$callingFunction] $msg"
        }
        elseif ($script:logFormattingOptions['PrefixScriptName'] -eq 1 -and !([string]::IsNullOrEmpty($scriptName))) {
          $FormatMessage = "$tabs$timeStamp[$scriptName] $msg"
        }
        else {
          $FormatMessage = "$tabs$timeStamp $msg"
        }
        if($script:logTargets['Console'] -eq 1){
            $VerbosePreference = 'Continue'
            Write-Verbose "$FormatMessage"
        }
        if ($isWindowsEventLogTarget -eq 1) {
          Write-EventLog -LogName Application -Source "$script:LogSource" -EntryType "Information" -EventId $eventID -Message "$FormatMessage"
        }
        if ($script:logTargets['Speech'] -eq 1) {
          Add-Type -AssemblyName System.speech
          $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
          $speak.Speak($msg)
        }
      }
      #Info Messages
      elseif ($msgLevel -eq 10 -and $script:LogLevel -le 10) {
        if ($script:logFormattingOptions['PrefixCallingFunction'] -eq 1 -and !([string]::IsNullOrEmpty($callingFunction))) {
          $FormatMessage = "$tabs$timeStamp[$callingFunction] $msg"
        }
        elseif ($script:logFormattingOptions['PrefixScriptName'] -eq 1 -and !([string]::IsNullOrEmpty($scriptName))) {
          $FormatMessage = "$tabs$timeStamp[$scriptName] $msg"
        }
        else {
          $FormatMessage = "$tabs$timeStamp$msg"
        }
        if($script:logTargets['Console'] -eq 1){
            $InformationPreference = 'Continue'
            Write-Information $FormatMessage
        }
        if ($isWindowsEventLogTarget -eq 1) {
          Write-EventLog -LogName Application -Source "$script:LogSource" -EntryType "Information" -EventId $eventID -Message "$FormatMessage"
        }
        if ($script:logTargets['Speech'] -eq 1) {
          Add-Type -AssemblyName System.speech
          $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
          $speak.Speak($msg)
        }

      }
      #Warning Messages
      elseif ($msgLevel -eq 20 -and $script:LogLevel -le 20) {
        if ($script:logFormattingOptions['PrefixCallingFunction'] -eq 1 -and !([string]::IsNullOrEmpty($callingFunction))) {
          $FormatMessage = "$tabs$timeStamp[$callingFunction][WARNING] $msg"
        }
        elseif ($script:logFormattingOptions['PrefixScriptName'] -eq 1 -and !([string]::IsNullOrEmpty($scriptName))) {
          $FormatMessage = "$tabs$timeStamp[$scriptName][WARNING] $msg"
        }
        else {
          $FormatMessage = "$tabs$timeStamp[WARNING] $msg"
        }
        if($script:logTargets['Console'] -eq 1){
            Write-Warning "$FormatMessage"
        }
        if ($isWindowsEventLogTarget -eq 1) {
          Write-EventLog -LogName Application -Source "$script:LogSource" -EntryType "Warning" -EventId $eventID -Message "$FormatMessage"
        }

        if ($script:logTargets['Speech'] -eq 1) {
          Add-Type -AssemblyName System.speech
          $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
          $speak.Speak($msg)
        }

      }
      #Error Messages
      elseif ($msgLevel -eq 30 -and $script:LogLevel -le 30) {
        if ($script:logFormattingOptions['PrefixCallingFunction'] -eq 1 -and !([string]::IsNullOrEmpty($callingFunction))) {
          $FormatMessage = "$tabs$timeStamp[$callingFunction] $msg"
        }
        elseif ($script:logFormattingOptions['PrefixScriptName'] -eq 1 -and !([string]::IsNullOrEmpty($scriptName))) {
          $FormatMessage = "$tabs$timeStamp[$scriptName] $msg"
        }
        else {
          $FormatMessage = "$tabs$timeStamp$msg"
        }
        if($script:logTargets['Console'] -eq 1){
            Write-Error "$FormatMessage"
        }
        if ($isWindowsEventLogTarget -eq 1) {
          Write-EventLog -LogName Application -Source "$script:LogSource" -EntryType "Error" -EventId $eventID -Message "$FormatMessage"
        }
        if ($script:logTargets['Speech'] -eq 1) {
          Add-Type -AssemblyName System.speech
          $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
          $speak.Speak($msg)
        }


      }

      if ($script:logTargets['File'] -eq 1) {
        foreach($file in $script:logTargetFileNames){
            $fileParentDir = Split-Path $file -Parent
                if (-not (Test-Path $fileParentDir)){
                    try{
                        New-Item -Path $fileParentDir -ItemType Directory -Force
                    }
                    catch{
                        throw "Could not set log target to: $fileParentDir. Path does not exist"
                    }
                }
        $i = 0;
        $repeat = $true
        while($repeat){
          $i++;
          if($i -gt 9){
            $repeat = $false
          }
        try{
            $FormatMessage | Add-Content $file -ErrorAction Stop
            $repeat = $false
        }
        catch{

        }

        
      }
        }
      }
    }
  }
} Export-ModuleMember -Function Write-Log