How to Log Errors in Powershell? - powershell

[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

Related

Powershell bind argument path

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
}
}
}

How to I apply the Write-Log function to a script

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
}

Catch Exception from replace method in powershell v5

I am trying to write a script to find and replace a string in a file. How do I catch an exception if the script fails to replace the string for some reason and log it in an external file? Here is what I have so far.
Write-Host "Checking Execution Policy"
$currentExecutionPolicy = Get-ExecutionPolicy
if( $currentExecutionPolicy -eq "RemoteSigned")
{
Write-Host "Execution policy check passed"
}
else
{
"Setting Execution policy to RemoteSigned as per https://msdn.microsoft.com/powershell/reference/5.1/Microsoft.PowerShell.Core/about/about_Execution_Policies"
Set-ExecutionPolicy Remotesigned
}
Write-Host "Starting Script"
#Recurse through all the file shares and find the file.
$rootPath='\\do.main.name\shared\Information Technology\IT\u.name'
$hotspotFile = Get-ChildItem -Path $rootPath -Recurse -Include "hotspot.mac"
Write-Host "Found file" $hotspotFile
$logstring = "Found file" + $hotspotFile
WriteLog $logstring
try
{
(Get-Content $hotspotFile).Replace("olddomain.com","do.main.name") | Set-Content $hotspotFile
}
catch
{
Write-Host "Failed to replace string -" $file
$logstring = "Failed to replace string -" + $file
WriteLog $logstring
}
#Logging
$Logfile = "F:\u.name\Documents\Logs\SCR_To_Find_And_Replace_Old_Domain_String.log"
Function WriteLog
{
Param ([string]$logstring)
Add-content $Logfile -value $logstring
}
Per the comments, I think a failed replace does not generate an exception so you can't use try catch.
Instead you could use an if test, e.g.:
If ($hotspotfile -notcontains "do.main.name") { }
Or test the file for the presence of the old string. You'd probably be wise to also put in an earlier if statement testing if the string was present in the file in the first place and skipping the replace and check if it wasn't.

Multiple PowerShell Scripts Writing to Same File

I have a condition that kicks off a PowerShell script to append a short string to a text file. This condition can fire rapidly, so the file is being written multiple times by the same script. Additionally, a separate script is importing from that text file in batches (less frequently).
Whenever the condition fires very rapidly, I get the error: "The process can not access the file 'file_name' because it is being used by another process." When I do the same append in Python (my main language), I don't get the same error, but I could use some help fixing this in PowerShell.
$action = $args[0]
$output_filename = $args[1]
$item = $args[2]
if ($action -eq 'direct'){
$file_path = $output_filename
$sw = New-Object -typename System.IO.StreamWriter($file_path, "true")
$sw.WriteLine($item)
$sw.Close() }
I have also tried the following instead of StreamWriter, but apparently the performance is weak for Add-Content and Out-File (http://sqlblog.com/blogs/linchi_shea/archive/2010/01/04/add-content-and-out-file-are-not-for-performance.aspx):
out-file -Append -FilePath $file_path -InputObject $item }
Might try something like this:
while ($true)
{
Try {
[IO.File]::OpenWrite($file_path).close()
Add-Content -FilePath $file_path -InputObject $item
Break
}
Catch {}
}

Capturing errors in this Powershell script

I have this test script to change the Administrator password on a list of servers.
I have set the script to log errors if the server can't be ping'd or account can't be found etc. However in addtion to this i'd like to capture any other errors that take place and also add those to the log file. I know you can use the "Try and Catch" for error handling but havn't had any luck so far.
Would someone be kind enough to show how to do it?
Here is the script
$date = Get-Date
$user = "Administrator"
$newpwd = "MyPassword"
$servers = gc C:\servers.txt
foreach ($server in $servers)
{
$ping = new-object System.Net.NetworkInformation.Ping
$Reply = $null
$Reply = $ping.send($server)
if($Reply.status -like 'Success')
{
$Admin=[adsi]("WinNT://" + $server + "/$user, user")
if($?)
{
$Admin.SetPassword($newpwd)
if($?)
{Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Succsess the $user password was changed. , $date"}
else
{Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: FAILED to change the password. , $date"}
}
else
{
Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: The $user user account was not found on the server. , $date"}
}
else
{
Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: Ping FAILED could not connect. , $date"
}
If you want to write exceptions to the log right after they were thrown, you could use a trap. Add something like this to you script:
trap [Exception] {
#Add message to log
Add-Content -Path test.csv -Value "$server, $($_.Exception.Message), $(Get-Date)"
#Continue script
continue;
}
That will log all exceptions (not all errors).
If you want all errors, you can access them using $Error. It's an arraylist containing every error during your sessions(script). The first item $Error[0] is the latest error. This however, is not something that fits directly into an csv file without formatting it.