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 whichInvoke
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.