Ive got a powershell script where I am updating alot of stuff and I am wanting to write everything to a log file. I've used a similar method as this to write a CSV file but I can't get this to work. Can anyone help me out on this? Everything works except the log file does not get created.
$path = Split-Path -parent "\\srv02file\Public\Information Technology\IT Reports\*.*"
$LogDate = Get-Date -f yyyyMMddhhmm
$LogFile = $path + "Remove_Formatting_$LogDate.log"
#Log function with levels of logging and time entry. Default level is INFO when not set
#LogWrite -LEVEL INFO -Message "Log entry"
#HH:MM:SS INFO Log entry
Function LogWrite
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$False)]
[ValidateSet("INFO","WARN","ERROR","FATAL","DEBUG")]
[String]
$Level = "INFO",
[Parameter(Mandatory=$True)]
[string]
$Message
#[Parameter(Mandatory=$False)]
#[string]
#$LogPath
)
$Stamp = (Get-Date).toString("hh:mm:ss")
$Line = "$Stamp $Level $Message"
Add-Content $LogFile -Value $Line
Write-Output $Line
}
You're missing a trailing slash after the folder in $LogFile
Use Join-Path to combine a folder and filename to avoid that issue in future.
You'll also need to uncomment the $LogPath parameter and use that, since $LogFile doesn't exist in the function scope.
Related
When I execute a particular script, the Write-Log function throws an error. This appears to be before I call the function. It is loaded from an external file. This problem is new, it worked previously with no issues.
Error
There are two errors, split by a VERBOSE output statement. The 2nd VERBOSE line is the last script line I included. The format (not colours) is the best I can recreate
C:\Scripts>powershell -file tca_export_tt_ftps.ps1
Test-Path : Cannot bind argument to parameter 'Path' because it is an empty string.
At C:\scripts\Function-Write-Log.ps1:75 char:24
+ if ((Test-Path $Path) -AND $NoClobber) {
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [Test-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAl lowed,Microsoft.PowerShell.Commands.TestPathCommand
VERBOSE: Testing C:\Scripts\log\ exists
Out-File : Cannot bind argument to parameter 'FilePath' because it is an empty string.
At C:\scripts\Function-Write-Log.ps1:110 char:67
+ ... FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [Out-File], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAl lowed,Microsoft.PowerShell.Commands.OutFileCommand
VERBOSE: Logfile is: C:\Scripts\log\kh_tca_out_20210222.log
Start of my Script
Function Upload-TCA
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$false)]
[Alias('StartDate')]
[Alias('SeedDate')]
[DateTime]$dateSeed = (Get-Date),
#[string]$dateSeedStr = (Get-Date -format "yyyy-MM-dd"),
[Parameter(Mandatory=$false)]
[Alias('DisableFtp')]
[bool]$ftpDisabled=$false,
[Parameter(Mandatory=$false)]
[Alias('DisplayConfig')]
[bool]$emitConfig = $false,
[Parameter(Mandatory=$true)]
[string]$orgCode
)
#########################################################################
# Function Imports
#########################################################################
. c:\scripts\Function-Generate-Folder.ps1
. c:\scripts\Function-Write-Log.ps1
. c:\scripts\Function-Upload-WinSCP-FTPS.ps1
. c:\scripts\Function-Create-CredentialFromFile.ps1
#########################################################################
# Logging Setup
#########################################################################
$logDir = "C:\Scripts\log\"
Generate-Folder $logDir
$logName = "$orgCode_tca_out_$(Get-Date -Format "yyyyMMdd").log"
$logFile = Join-Path -Path $logDir -ChildPath $logName
Write-Log "Logfile is: $logFile" -Path $logFile
Write-Log
The Write-Log function is from the Microsoft Script Center Included here in case the link is broken
<#
.Synopsis
Write-Log writes a message to a specified log file with the current time stamp.
.DESCRIPTION
The Write-Log function is designed to add logging capability to other scripts.
In addition to writing output and/or verbose you can write to a log file for
later debugging.
.NOTES
Created by: Jason Wasser #wasserja
Modified: 11/24/2015 09:30:19 AM
Changelog:
* Code simplification and clarification - thanks to #juneb_get_help
* Added documentation.
* Renamed LogPath parameter to Path to keep it standard - thanks to #JeffHicks
* Revised the Force switch to work as it should - thanks to #JeffHicks
To Do:
* Add error handling if trying to create a log file in a inaccessible location.
* Add ability to write $Message to $Verbose or $Error pipelines to eliminate
duplicates.
.PARAMETER Message
Message is the content that you wish to add to the log file.
.PARAMETER Path
The path to the log file to which you would like to write. By default the function will
create the path and file if it does not exist.
.PARAMETER Level
Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational)
.PARAMETER NoClobber
Use NoClobber if you do not wish to overwrite an existing file.
.EXAMPLE
Write-Log -Message 'Log message'
Writes the message to c:\Logs\PowerShellLog.log.
.EXAMPLE
Write-Log -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log
Writes the content to the specified log file and creates the path and file specified.
.EXAMPLE
Write-Log -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error
Writes the message to the specified log file as an error message, and writes the message to the error pipeline.
.LINK
https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
#>
function Write-Log
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true)]
[ValidateNotNullOrEmpty()]
[Alias("LogContent")]
[string]$Message,
[Parameter(Mandatory=$false)]
[Alias('LogPath')]
[string]$Path='C:\Logs\PowerShellLog.log',
[Parameter(Mandatory=$false)]
[ValidateSet("Error","Warn","Info")]
[string]$Level="Info",
[Parameter(Mandatory=$false)]
[switch]$NoClobber
)
Begin
{
# Set VerbosePreference to Continue so that verbose messages are displayed.
$VerbosePreference = 'Continue'
}
Process
{
# If the file already exists and NoClobber was specified, do not write to the log.
if ((Test-Path $Path) -AND $NoClobber) {
Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name."
Return
}
# If attempting to write to a log file in a folder/path that doesn't exist create the file including the path.
elseif (!(Test-Path $Path)) {
Write-Verbose "Creating $Path."
$NewLogFile = New-Item $Path -Force -ItemType File
}
else {
# Nothing to see here yet.
}
# Format Date for our Log File
$FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# Write message to error, warning, or verbose pipeline and specify $LevelText
switch ($Level) {
'Error' {
Write-Error $Message
$LevelText = 'ERROR:'
}
'Warn' {
Write-Warning $Message
$LevelText = 'WARNING:'
}
'Info' {
Write-Verbose $Message
$LevelText = 'INFO:'
}
}
# Write log entry to $Path
"$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append
}
End
{
}
}
I hadn't read the Generate-Folder method correctly. I changed the calls in there for Write-Log to Write-Host. It was trying to use the log before it was fully setup.
Function Generate-Folder()
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true)]
[ValidateNotNullOrEmpty()]
[string]$path='C:\scripts\logs'
)
Write-Host "Testing $path exists" # cant use write-log as not setup yet
$global:foldPath=$null
foreach($foldername in $path.split("\"))
{
$global:foldPath+=($foldername+"\")
if(!(Test-Path $global:foldPath))
{
New-Item -ItemType Directory -Path $global:foldPath
Write-Host "$global:foldPath Folder Created Successfully" #-Path $logFile
}
}
}
[1] I am trying to create Error logs for my PowerShell script. I need to create a function so that instead of using Write-Host I can directly call that function and whatever the error I am getting in the Script can be directly logged into the Log File.
[2] I have used the following method but it doesn't seem that will work for every PowerShell script.
function WriteLog
{
Param ([string]$LogString)
$LogFile = "C:\$(gc env:computername).log"
$DateTime = "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
$LogMessage = "$Datetime $LogString"
Add-content $LogFile -value $LogMessage
}
WriteLog "This is my log message"
[3] Can Anyone Suggest a more easy way to handle logging of Error into the file?
You are not far off for your use case.
You don't need Write-Host at all for this at all. Depending on what PS version you are using, Write-Host is not prudent when you are sending data down the pipe or elsewhere
Write-Host is just bad in legacy, pre-v5x versions if you were sending stuff in the pipeline because it cleared the buffer, so they'd be noting to send. In v5x
and higher. As per the founder/creator of Monad/PowerShell.
• Write-Host Harmful
https://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/
https://devblogs.microsoft.com/scripting/understanding-streams-redirection-and-write-host-in-powershell
https://powershell.org/2012/06/how-to-use-write-host-without-endangering-puppies-or-a-manifesto-for-modularizing-powershell-scripts
PowerShell Best Practice #3: Avoid Write-Host
it now writes to the Information stream, as per the founder/creator of Monad/Powershell.
• However, this thought has been changed since the v5x stuff.
You can use direct logging using either
Tee-Object
Export-Csv -Append
Out-File -Append
... and other redirection options.
You can also, create and write to your own event log...
New-EventLog -LogName LogonScripts -Source ClientScripts
Write-EventLog LogonScripts -Source ClientScripts -Message 'Test Message' -EventId 1234 -EntryType Warning
...then read from it later as you would any other event log.
There are lots of examples (scripts and modules) all of the web to use as is and start with or tweak as needed, even the ones via the Microsoft powershellgallery.com.
ScriptLogger 2.0.0
Write-Log: A Simple Logging Function for your PowerShell - Scripts: Download - Write-Log.ps1:
Write-Log PowerShell Logging Function: Download - Function-Write-Log.ps1:
Logging 2.4.9
Point of note in your post. This...
$LogFile = "C:\$(gc env:computername).log"
... just simplify to this...
$LogFile = "C:\$($Env:COMPUTERNAME).log"
#set logfile-path on global scope
$Logfile = "C:\Temp\Logfile.log" # set your Logfile-Path here
function Write-Log {
param
(
[Parameter(ValueFromPipeline)]
[string]$content
)
$FileExists = Test-Path -Path $LogFile
$DateNow = Get-Date -Format 'dd.MM.yyyy HH:mm'
$FileInp = $DateNow + ' | ' + $content
if ($FileExists -eq $True){
Add-Content -Path $LogFile -Value $FileInp
}
else {
New-Item -Path $Logfile -ItemType file
Add-Content -Path $LogFile -Value $FileInp
}
}
#then you only have to pipe it to Write-log like this:
Write-Output "hello world" | Write-Log
The following sample code correctly reads both the PSD1 and the regular file when the path to the PSD1 is a fully qualified path (or when the current location is the same as the current directory).
It fails when supplied a relative path.
param (
[Parameter(Mandatory=$true)][System.IO.FileInfo]$PsdFile,
[Parameter(Mandatory=$false)][System.IO.FileInfo]$RegularFile = 'testfile'
)
$RegularFileContent = Get-Content $RegularFile
$PsdData = Import-LocalizedData -BaseDirectory $PsdFile.DirectoryName -FileName $PsdFile.Name
echo "regular file contents: $RegularFileContent"
echo "Psd data:"
echo $PsdData.Name
How can I fix this so the user can enter a relative path?
I believe the underlying problem is similar to the problem described in this post about FileInfo but I don't know how to incorporate Resolve-Path or similar into the parameter handling.
A lot of your question hinges on which relative you're referring to as that itself is relative. If you want the path to be relative to a user's working directory versus the script's root, you can accomplish that like so:
param(
[Parameter(Mandatory)]
[string]
$PsdFile,
[Parameter()] # Mandatory is $false by default
[string]
$RegularFile = 'testfile'
)
[System.IO.FileInfo]$psd = Join-Path -Path $PWD.Path -ChildPath $PsdFile
$data = Import-LocalizedData -BaseDirectory $psd.DirectoryName -FileName $PsdFile.Name
$content = Get-Content -Path $RegularFile
"regular file contents: $content"
"Psd data:" + $data.Name
param (
[Parameter(Mandatory = $true)]
[ValidateScript( {
if (-Not (Test-Path -Path $_ ) ) { throw "File not found." }
elseif (-Not (Test-Path -Path $_ -PathType Leaf) ) { throw "PsdFile should be a file." }
return $true
})]
[System.IO.FileInfo] $PsdFile,
[Parameter(Mandatory = $false)]
[ValidateScript( {
if (-Not (Test-Path -Path $_ ) ) { throw "File not found." }
elseif (-Not (Test-Path -Path $_ -PathType Leaf) ) { throw "PsdFile should be a file." }
return $true
})]
[System.IO.FileInfo] $RegularFile = 'testfile'
)
$PsdFile = (Resolve-Path -Path $PsdFile).Path;
$RegularFile = (Resolve-Path -Path $RegularFile).Path;
$RegularFileContent = Get-Content $RegularFile.FullName
$PsdData = Import-LocalizedData -BaseDirectory $PsdFile.DirectoryName -FileName $PsdFile.Name
I use a similar approach to #luri, incorporating ValidateScript to test if the path exists. This example tests specifically for files and throws an error if a directory is passed. This is useful for cases when the user is using <TAB> to quickly autocomplete paths for the -Path parameter.
function PathTestFunction
{
[CmdletBinding()]
param (
[Parameter(Mandatory,
ParameterSetName = 'Path',
ValueFromPipeline,
ValueFromPipelineByPropertyName
)]
[ValidateScript({
# Using Exists() because Test-Path doesn't test for system/hidden files
if ([System.IO.Directory]::Exists($(Resolve-Path -Path $_)))
{
throw "'$(Resolve-Path $_)' is a directory. Please use a file for the -Path parameter."
}
if ([System.IO.File]::Exists($(Resolve-Path -Path $_)) -eq $false)
{
$null = Resolve-Path $_ -ErrorVariable pathError
# Displays a full path name in the error message (vs a relative path if one was passed)
throw $pathError.Exception
}
return $true
})]
[System.IO.FileInfo]$Path
)
Get-Item $Path -Force
}
You could write a second ParameterSet to include a -LiteralPath param for added robustness - this would allow the user to pass a path that included wildcard chars (e.g. 'C:\Test[txt]File.txt').
Using <TAB> to autocomplete paths with wildcards would still work with the example above - but it wouldn't allow the user to type a full path name with unescaped wildcards in it, hence the need for both -Path and -LiteralPath parameters in that case:
'.\Test`[txt`]File.txt' would work with -Path (if using <TAB> to autocomplete path)
'C:\Test`[txt`]File.txt' would work with -Path (if using <TAB> to autocomplete path)
'C:\Test[txt]File.txt' would not work with -Path (if manually typed)
I have the latest Write-Log function from this module installed and wondered if it's possible to apply that function here rather than and or while using the verbose part of the transcript.
I want to now add in a try..catch where if it fails it using the Write-Log function to insert a record into the log file
$varfullpath = "C:\Users\Simon.Evans\Documents\ReferenceData__logfile.txt"
Start-Transcript -Path $varfullpath -Append
Write-Output $varfullpath
[string]$sourceDirectory = "C:\Users\Simon.Evans\Documents\Source Data\LNAT\Code_Mapping.txt"
[string]$destinationDirectory = "I:\Dev\BI\Projects\Powershell\Test Area\Source Data\LNAT\Code_Mapping.txt"
Copy-Item -Force -Verbose $sourceDirectory -Destination $destinationDirectory
Stop-Transcript
For info this is the Write-Log function installed which still not sussed how it works given I've never used functions.
<#
.Synopsis
Write-Log writes a message to a specified log file with the current time stamp.
.DESCRIPTION
The Write-Log function is designed to add logging capability to other scripts.
In addition to writing output and/or verbose you can write to a log file for
later debugging.
.NOTES
Created by: Jason Wasser #wasserja
Modified: 11/24/2015 09:30:19 AM
Changelog:
* Code simplification and clarification - thanks to #juneb_get_help
* Added documentation.
* Renamed LogPath parameter to Path to keep it standard - thanks to #JeffHicks
* Revised the Force switch to work as it should - thanks to #JeffHicks
To Do:
* Add error handling if trying to create a log file in a inaccessible location.
* Add ability to write $Message to $Verbose or $Error pipelines to eliminate
duplicates.
.PARAMETER Message
Message is the content that you wish to add to the log file.
.PARAMETER Path
The path to the log file to which you would like to write. By default the function will
create the path and file if it does not exist.
.PARAMETER Level
Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational)
.PARAMETER NoClobber
Use NoClobber if you do not wish to overwrite an existing file.
.EXAMPLE
Write-Log -Message 'Log message'
Writes the message to c:\Logs\PowerShellLog.log.
.EXAMPLE
Write-Log -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log
Writes the content to the specified log file and creates the path and file specified.
.EXAMPLE
Write-Log -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error
Writes the message to the specified log file as an error message, and writes the message to the error pipeline.
.LINK
https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
#>
function Write-Log
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true)]
[ValidateNotNullOrEmpty()]
[Alias("LogContent")]
[string]$Message,
[Parameter(Mandatory=$false)]
[Alias('LogPath')]
[string]$Path='C:\Logs\PowerShellLog.log',
[Parameter(Mandatory=$false)]
[ValidateSet("Error","Warn","Info")]
[string]$Level="Info",
[Parameter(Mandatory=$false)]
[switch]$NoClobber
)
Begin
{
# Set VerbosePreference to Continue so that verbose messages are displayed.
$VerbosePreference = 'Continue'
}
Process
{
# If the file already exists and NoClobber was specified, do not write to the log.
if ((Test-Path $Path) -AND $NoClobber) {
Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name."
Return
}
# If attempting to write to a log file in a folder/path that doesn't exist create the file including the path.
elseif (!(Test-Path $Path)) {
Write-Verbose "Creating $Path."
$NewLogFile = New-Item $Path -Force -ItemType File
}
else {
# Nothing to see here yet.
}
# Format Date for our Log File
$FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# Write message to error, warning, or verbose pipeline and specify $LevelText
switch ($Level) {
'Error' {
Write-Error $Message
$LevelText = 'ERROR:'
}
'Warn' {
Write-Warning $Message
$LevelText = 'WARNING:'
}
'Info' {
Write-Verbose $Message
$LevelText = 'INFO:'
}
}
# Write log entry to $Path
"$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append
}
End
{
}
}
Thanks for the input chaps finally got there in the end but made a few silly errors on the way.
$varfullpath = "C:\Users\Simon.Evans\Documents\ReferenceData__logfile.txt"
$sourceDirectory = "C:\Users\Simon.Evans\Documents\Source Data\LNAT\Code_Maping.txt"
$destinationDirectory = "I:\Dev\BI\Projects\Powershell\Test Area\Source Data\LNAT\Code_Mapping.txt"
try{
Copy-item -Force -Verbose $sourceDirectory -Destination $destinationDirectory -ErrorAction Stop
Write-log -Message "Copy from $sourceDirectory to $destinationDirectory suceeded" -path $varfullpath
}
catch{
$Error[0] | Write-Log -path $varfullpath ##Example Error 1
Write-log -Message "Copy from $sourceDirectory to $destinationDirectory Failed" -Level Error -path $varfullpath ##Example Error 2
}
Firstly I want to point out that I'm a PowerShell ScrapBooker and not really terribly knowledgeable about PowerShell.
I've been working on a script that installs BGInfo ... I have the actual install and uninstall working perfectly, and now I'm moving on to getting the logging sorted out nicely.
I found this article "Create Log File in Powershell" which was wonderful and have incorporated this Function into my script.
Function Write-Log {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$False)]
[ValidateSet("LABEL","INFO","WARN","ERROR","FATAL","DEBUG")]
[String]
$Level = "INFO",
[Parameter(Mandatory=$True)]
[string]
$Message,
[Parameter(Mandatory=$False)]
[string]
$logfile
)
$Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
IF ($Level -eq "LABEL") {
$Line = "$Message"
}
ELSE {
$Line = "$Stamp $Level $Message"
}
If($logfile) {
Add-Content $logfile -Value $Line
}
Else {
Write-Output $Line
}
}
What I need to know is how to use this to log output from commands.
For Example:
In My script I have this command:
New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' -Name BgInfo -Value """$InstPath\Bginfo.exe"" $InstPath\$BGTemplateFile $InstOptions" -PropertyType 'String' -Force
or this one:
Copy $SourcePath\Bginfo.exe $InstPath
What I would like to know is how I can use my function to capture ANY output from that command and log it in my log file.
I guess that I would also like to use this information and apply it to any other commands where I want to log something.
Hopefully that's all clear and makes sense and that someone can help me out.
Cheers,
Dave.
:)
I believe you are referring to this function:
function Write-Log
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true)]
[ValidateNotNullOrEmpty()]
[Alias("LogContent")]
[string]$Message,
[Parameter(Mandatory=$false)]
[Alias('LogPath')]
[string]$Path='C:\Logs\PowerShellLog.log',
[Parameter(Mandatory=$false)]
[ValidateSet("Error","Warn","Info")]
[string]$Level="Info",
[Parameter(Mandatory=$false)]
[switch]$NoClobber
)
Begin
{
# Set VerbosePreference to Continue so that verbose messages are displayed.
$VerbosePreference = 'Continue'
}
Process
{
# If the file already exists and NoClobber was specified, do not write to the log.
if ((Test-Path $Path) -AND $NoClobber) {
Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name."
Return
}
# If attempting to write to a log file in a folder/path that doesn't exist create the file including the path.
elseif (!(Test-Path $Path)) {
Write-Verbose "Creating $Path."
$NewLogFile = New-Item $Path -Force -ItemType File
}
else {
# Nothing to see here yet.
}
# Format Date for our Log File
$FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# Write message to error, warning, or verbose pipeline and specify $LevelText
switch ($Level) {
'Error' {
Write-Error $Message
$LevelText = 'ERROR:'
}
'Warn' {
Write-Warning $Message
$LevelText = 'WARNING:'
}
'Info' {
Write-Verbose $Message
$LevelText = 'INFO:'
}
}
# Write log entry to $Path
"$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append
}
End
{
}
}
Usage:
Write-Log -Message 'My log Error Message' -Path c:\Logs\Script.log -Level Error
Write-Log -Message 'My log Warning message' -Level Warn
Note: By default its storing in C:\logs\PowershellLog.log. You can change it on the fly during using also and you can hardcode it in function itself.
Hope it helps.