Home > OS >  Log PowerShell commands to LogFile
Log PowerShell commands to LogFile

Time:11-09

I have basic PowerShell script with logging function and some commands to run. I'm looking for a solution of logging into log file commands what are executed.

For now I only know this, but its quite annoying to copy paste all command to have been logged:


$LogPath        = "C:\Logs"
$FileName       = (Get-Item $PSCommandPath).Basename                                                        
$LogFile        = $LogPath   "\"   $FileName   ".log"

Function WriteLog
{
Param ([string]$LogString)
$Stamp      = (Get-Date).toString("yyyy-MM-dd HH:mm:ss")
$LogMessage = "$Stamp $LogString"
Add-content $LogFile -value $LogMessage
}

WriteLog "***********************"
WriteLog ""

WriteLog "Command1"
Command1

WriteLog "Command2"
Command2

WriteLog "Command3"
Command3

WriteLog "Command4"
Command4

WriteLog "Command5"
Command5

WriteLog ""
WriteLog "***********************"

CodePudding user response:

I suggest the following:

  • Modify your function to alternatively accept a script block ({ ... }) representing the command to execute.

  • If a script block is given, use its string representation as the log message, and then execute it.

# Create the logging function in a *dynamic module* (see below).
# Place this at the top of your script.
$null = New-Module {

  # Define the log-file path.
  $LogPath        = 'C:\Logs'
  $FileName       = (Get-Item $PSCommandPath).Basename                                                        
  $LogFile        = $LogPath   '\'   $FileName   '.log'
  
  # Create / truncate the file.
  New-Item -Force -ErrorAction Stop $LogFile

  function Add-LogMessage {
    [CmdletBinding(DefaultParameterSetName = 'String')]
    param(
      [Parameter(Position = 0, Mandatory, ParameterSetName = 'ScriptBlock')]
      [scriptblock] $ScriptBlock
      ,
      [Parameter(Position = 0, ParameterSetName = 'String')]
      [string] $String
    )

    # If a script block was given, use its string representation
    # as the log string.
    if ($ScriptBlock) {
      # Make the string representation single-line by replacing newlines
      # (and line-leading whitespace) with "; " 
      $String = $ScriptBlock.ToString().Trim() -replace '\r?\n *', '; '
    }

    # Create a timestamped message and append it to the log file.
    $stamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
    $logMessage = "$stamp $String"
    Add-Content -LiteralPath $LogFile -Value $logMessage

    # If a script block was given, execute it now.
    if ($ScriptBlock) {
      # Because this function is defined in a (dynamic) module,
      # its invocation doesn't create a child scope of the *caller's* scope,
      # and invoking the given script block, which is bound to the caller's scope,
      # with . (dot-sourcing) runs it *directly in the caller's scope*.
      . $ScriptBlock
    }
  }
}

Note:

  • The function name adheres to PowerShell's verb-noun convention, using Add, which is an approved verb; however, for brevity the aspect of situationally also performing execution (for which Invoke would be the approved verb) is not reflected in the name.

Your script would then look something like this:

Add-LogMessage "***********************"
Add-LogMessage ""

Add-LogMessage { Command1 }

Add-LogMessage { Command2 }

# ... 

Add-LogMessage "***********************"

Note:

  • By placing the function inside a (dynamic, transient) module created via New-Module, its invocation does not create a child scope of the caller's scope.

  • When a script block created by a literal ({ ... }) in the caller's scope is passed, it can then be invoked with ., the dot-sourcing operator, which executes it directly in the caller's scope, which means that the script block's code is free to modify the script's variables, the same way that placing that code directly in the script would.

  • If you want the function to also log a given script block's output (while still printing it to the display), you can use Tee-Object as follows (for simplicity I'm assuming the same target log file, adjust as needed):

    . $ScriptBlock | Tee-Object -Append -FilePath $LogFile
    
    • Caveat: As of PowerShell 7.2.x, Tee-Object uses a fixed character encoding, namely UTF-16LE ("Unicode") in Windows PowerShell and BOM-less UTF-8 in PowerShell (Core) 7 . GitHub issue #11104 suggests adding an -Encoding parameter (which only future PowerShell (Core) versions would benefit from).

    • Therefore, if you're using Windows PowerShell and you're targeting the same log file for capturing the output, be sure to modify the Add-Content call with -Encoding Unicode as follows:

      Add-Content -Encoding Unicode -LiteralPath $LogFile -Value $logMessage
      
    • Alternatively, if you want to avoid UTF-16LE ("Unicode") files for their size (with all-ASCII characters, they're twice the size of ANSI and UTF-8 files), you can use one of the workarounds discussed in this answer.

  • Related