2

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 "***********************"

MikeZetPL
  • 97
  • 5
  • Does this answer your question? [PowerShell "echo on"](https://stackoverflow.com/questions/2063995/powershell-echo-on) – stackprotector Nov 07 '22 at 18:43
  • Thanks @stackprotector, but its looks its not what I'm looking for: log only some things in created log file with function "WriteLog" + only executed command. – MikeZetPL Nov 07 '22 at 19:54
  • My suggestion if you only want to log select output, is to define your log file and then append onto the end of the lines of the CMDs you want output logged for something like `| Add-Content -Path $log,$log2`. If you want to create a WriteLog function to append in the appropriate places, because it's more concise or for whatever reason, that's fine too. I would just store the output of the CMD in a variable and then pass the variable to the function (that way you don't have to sort out CMDs within the function it's always just whatever the output is). – Dallas Nov 07 '22 at 20:10

1 Answers1

2

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Wow! Now its works :) Thanks very much for that. Can I also with this module log output of the command what has been run? My main idea is to log every command what has been run and it result to one file, or log every command to one file and output (result) to second log file like $DetailLogFile. Of course I can use "Start-Transcript", but its have not perfect view - its hard to use it. – MikeZetPL Nov 08 '22 at 17:06
  • Glad to hear it helped, @MikeZetPL. As for also logging output: please see the two bullet points I've just added at the bottom. – mklement0 Nov 08 '22 at 17:45
  • When is decarated to log output to the same file as main log file its writing some strange symbols - when I open it with NP++ there are many symbols [NUL] where is output part. Configuration Manager Trace Log Tool can read this log file only to begin of this output. When I put logging to another file like $DetailLogFile, then its works. You think its because what had you written in last bullet point? – MikeZetPL Nov 08 '22 at 18:51
  • @MikeZetPL, please see my latest update. The problem in _Windows PowerShell_ is that if you target the same file, `Add-Content` originally creates single-byte ANSI-encoded content, and `Tee-Object` then blindly appends UTF16-LE ("Unicode") content, resulting in a broken file. The simplest solution is to use `Add-Content -Encoding Unicode`, but the last bullet point points to other workarounds. Note that in _PowerShell (Core)_, which consistently defaults to (BOM-less) UTF-8, this problem wouldn't arise. – mklement0 Nov 08 '22 at 19:19