PowerShell: How to easily create log files for your scripts

Important Update

As of September 2015, I have released version 2 of my PowerShell Logging solution, and is now known as PSLogging.

There has been a number of significant improvements with the most notable being that the PowerShell Logging function library has now been converted into a fully fledged PowerShell module. All of the details about the new version, the improvements and how to use and integrate the new version into your new and existing scripts can be found here – PSLogging PowerShell module.

Note: This version of the logging solution is no longer supported or maintained, so please upgrade to the new version today!


Today I thought I would share some thing that I have developed over the last year or so as I have been working more and more with PowerShell…. some standard functions that can simply be dot sourced and called at any time so that all my scripts have some awesome logs to go along with the awesome script.

Why have logging?

Well personally I feel that this is a very important part of a script or tool that is developed, particularly if it is going to be used regularly and provides some sort of valuable functionality.

Having a log that people can follow, and see what is happening while the script is executing or if the script runs into errors is very useful. Not only that, but I feel people will respect your developed scripts \ tools as they see it as a mature product, not some dodgey backyard operation that was just copied from the web and had the crap hacked through it.

The other reason is that it makes troubleshooting a hell of a lot easier… particularly 6 – 12 months down the track when lets say you are running the script in a different environment than what it was originally developed for. Having a log and being able to write out the result of each step of your script allows you to easily pin-point where the problem lies, especially if you pipe out error messages to the log (both custom or system errors).

Another great reason is having compassion on the guy the might be having to update or troubleshoot your script \ tool in the future. Its sort of like having well commented code (which I know everyone does… lol), it makes it easier for someone to be able to track your script and know what is happening and why its happening.

Finally, the reason why I personally like logging is because the way I layout all of my scripts is a seperate function per major task that the script completes. At the end of my scripts I then have an execution section where I call all of my functions in order and pass the required variables.

Using logging with this approach to development is great because it allows you to break your log into sections… each function has its own mini log section if you like, so it makes tracking the script really really easy and plus it looks awesome.

Logging Function Library: The contents

Becuase of the reasons above, I decided to create a logging function library which is essentially just a .ps1 file with a bunch of standard log functions. In each of my scripts I just dot source the .ps1 file and then call the each of the functions as required. The functions included in logging library are:

  • Log-Start: Creates the .log file and initialises the log with date and time, etc
  • Log-Write: Writes a informational line to the log
  • Log-Error: Writes an error line to the log (formatted differently)
  • Log-Finish: Completes the log and writes stop date and time
  • Log-Email: Emails the content of the log

Logging Function Library: Log Formatting

All of my scripts use this logging library and therefore all the logs look kinda the same. This is actually a good thing because it allows your user base to get used to a particular log format and so its just another incentive for them to use and enjoy your script that you worked so hard on.

Here is a screenshot of one of my logs that has been written using this logging function library

PowerShell Log File Example

Logging Function Library: The Code

Ok guys, enough rambling… here it is in all of its raw beauty:

Logging Function Library: Installation Instructions

Here are the instructions to start using this logging function library in your scripts:

  1. Copy the code above and save it in a new .ps1 file called Logging_Functions.ps1. Alternatively you can download the file from here: PowerShell Logging Function Library
  2. Copy the file and store it in a central location that everyone has access to (Note: This is very important otherwise your script will fail for people who don’t have access to the function library)
  3. In your script dot source the Logging_Functions.ps1 file. You should do this at the top of your PowerShell script and it should look something like this:
    
    . "C:\Scripts\Functions\Logging_Functions.ps1"
    
  4. Call the relevant functions as required (see the examples found in the meta information of each of the functions on how to do this)

PowerShell Script Template

If you are unsure on how to do step 4 above, or you would like to fill-out a standard template that have everything you need to create a new PowerShell script, then check out this post: PowerShell Script Template

If you have any problems using my PowerShell Logging Function Library then please let me know in the comments below or send me an email and I will try my hardest to get you all fixed up. Also, if you have any cool ideas or have some thoughts on how to improve the library then I am always intersted in hearing what others are doing and how I can improve myself.

Thanks guys and happy scripting 🙂

Luca

Comments

  1. Hello, i am trying to use it in other script so i state:
    . “D:\scripts\Functions\Logging_Functions.ps1”
    Log-Start -logPath “D:\scripts\log” -LogName “service_check.log”

    but immediately when the script starts i get this error message:

    New-Item : Access to the path ‘D:\scripts\log’ is denied.
    At D:\scripts\Functions\Logging_Functions.ps1:53 char:17
    + New-Item <<<< -Path $LogPath -Value $LogName -ItemType File
    + CategoryInfo : PermissionDenied: (D:\scripts\log:String) [New-Item], UnauthorizedAccessException
    + FullyQualifiedErrorId : NewItemUnauthorizedAccessError,Microsoft.PowerShell.Commands.NewItemCommand

    I wonder why it has no access as it already written the log header.

    Thank you,
    Kosta

    1. Hi Kosta,

      Thanks for the comment and I am glad that you are using the logging script.

      I had a look and line 53 is this “New-Item -Path $LogPath -Value $LogName –ItemType File”, which is the line that will create the log file, so in your case it is trying to create service_check.log file in D:\Scripts\Logs directory. Can you confirm that the context in which the script is running has at least modify permissions to this directory? Also can you have a look in the directory and see if the script actually creates the log file?

      The other thing I would try is create a test script using my PowerShell Script Template and see if you get the same error again.

      Hope this helps. If you still can’t figure it out, let me know the answers to the above questions and we can go from there.

      Thanks
      Luca

      1. The problem is with the line below and the -Value parameter
        New-Item -Path $LogPath -Value $LogName –ItemType File

        it should be -Name as below.
        New-Item -Path $LogPath -Name $LogName –ItemType File

        Other than that. Great re-usable scripts

        1. Hi Andy,
          You are correct it should be -Name and now -Value. Strangely enough I am pretty sure that the Value parameter did work for me in the past but that might have been on a previous version of PowerShell (such as version 2).
          In any case, thanks very much for picking that up, I have now updated the code in the article.
          Thanks again
          Luca

  2. Hi Andy,

    Love the script. i’m using in most of mine scripts, Thanks
    Would it be possible to make it so that each time the script that calls the log function it adds to a previous log file and does not overwrite it?

    1. Hi Caleb,

      Sorry about the late reply. Yes this is possible. In the Logging Function Library file just remove the following lines from the Log-Start function:


      #Check if file exists and delete if it does
      If((Test-Path -Path $sFullPath)){
      Remove-Item -Path $sFullPath -Force
      }

      Hope this helps

      Luca

  3. Thanks for the template and library. Is there a way with this to output into the log a stream? I”m trying to backup GPO’s and want it to actually output the results of the command using your template:

    Backup-GPO -All -Path $sPath

    Thanks. (I’m new to Powershell so still learning the ropes).

    1. Hi Matthew,

      I am not 100% sure what you are asking but if you would like to print the log items out to screen as opposed to a text file you can use the cmdlet Write-Debug.

      As an example you can try this >>> Write-Debug "Hellow World". This should print this to the screen.

      Let me know if this is what you are after.

      Thanks
      Luca

  4. Hi Luca,

    And i’m sorry about my last message, Accidentally attention-ed it to the wrong person.
    Thank you for that, i am using this in every one of my scripts now!!!

    Regards
    Caleb

    1. Hi Caleb,

      Thanks very much for the positive feedback and glad that you are using it… that is awesome!

      Regards
      Luca

  5. Hi, that’s a wonderful, but i’m not pretty sure how to implement this script in my function in order to output the possible errors that could appear while executing the function. Could you help me??

    For example, If I want to include your function in a for each, how I have to call to your function??

    Thanks,

    1. Hi Albert,

      Thanks for commenting. Take a look at my PowerShell Script Template because this gives you a template to build your scripts on using the PowerShell Logging Function Library. So for example if you want to write an information line into your log you would use the following Log-Write -LogPath $sLogFile -LineValue "...".

      If you would like to write an error to the log then use this Log-Error -LogPath $sLogFile -ErrorDesc "Error description goes here" -ExitGracefully $True. The best way to use the error logging function would be with using one of the error handling options available in PowerShell. The one that I find most useful and that I use (and is documented in the PowerShell Script Template) is Try… Catch. Have a look at the PowerShell Script Template post and it is all documented there.

      If you still stuck, just let me know and I can help you out.

      Thanks
      Luca

  6. Hi Luca,

    I would like to know how to use Log-Error. For Example I got line in script:

    Import-Clixml d:\something.xml and when imported file doeasnt existx i got error. I can use

    Log-Write -LogPath $LogFile -LineValue ($error[0] | out-string)

    But how to use Log-Error? All my tryes end with:

    Log-Error : Cannot bind argument to parameter ‘ErrorDesc’ because it is an empty string.
    At D:\SKRIPTS\POWERSHELL\Create_User_in_AD.ps1:19 char:40
    + Log-Error -LogPath $LogFile -ErrorDesc $_.Exception -ExitGracefully $True
    + ~~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Log-Error], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Log-Error

    Thank you.

    1. Hi Casper,

      Can you show me the exact line of code you are having trouble with?

      What I would do is try something like this:


      $sError = $Error[0] | Out-String
      Log-Error -LogPath $sLogFile -ErrorDesc $sError -ExitGracefully $True

      Give that a go and see if that works.

      Thanks
      Luca

      1. This works great Luca! Thanks! I’m using this inside my foreach loop and it logs the correct message the first time, but then it retains the error message for each server it connects to until a new error message comes along and then that error message will log for each server until a different error comes. This loop is going out to 250 servers so is there anyway to reset the error message on each loop? Thanks!!!

        1. An indirect way would be to track your $error.count and not log $error[0] on a “good” server that doesn’t bump up your error count.

  7. I have this error
    Log-Error : Cannot bind argument to parameter ‘ErrorDesc’ because it is an empty string.

    i tried to use $Error and $error[0] and $sError = $Error[0] | Out-String
    but no Luke could you please help

    1. Hi Mohammad,

      Can you please send through via email or reply to this post and extract of your code so I can see what is happening?

      Thanks
      Luca

  8. Lucas,
    Even after removing the below script, I am still not able to append the data to the log file. It fails saying the log file already exists.

    #Check if file exists and delete if it does
    If((Test-Path -Path $sFullPath)){
    Remove-Item -Path $sFullPath -Force
    }
    Also I am trying to capture the error and using the function log-error, but i cannot capture the error…
    try{
    Invoke-Sqlcmd -query “select top 10 * from sales”
    Log-Write -LogPath $sLogFile -LineValue “step 2-altered the table : $tblname”
    }
    catch
    {
    write-output(“starting catch block”)
    Log-Error -LogPath $sLogFile -ErrorDesc $sError -ExitGracefully $True
    Break
    }
    If that table doesn’t exist i want to capture that error. But it’s displaying on the ps screen without capturing it.

  9. Also note on line 53 the dash (–ItemType File) isn’t correct, I believe it is the em dash and will cause a “missing the terminator” parse error at the end of the script. In the above script notice the highlight of the dash. Also with the downloaded version. Other than a few other minor errors noted, a great script, thanks for sharing Luca!

    1. Thanks Don for pointing that out. Not sure how that happened. Must of converted when I moved all of my scripts to GitHub. I have fixed that and should all be ok now. Thanks again.

  10. Hi Luca,
    Thanks for sharing your Logging_Functions. what are the pro/ and cons of using this as a ps1 file, and “dot source” it like you show in your template, rather than include your functions in a module (Logging_Functions.psm1), then import-module in your profile? Which way you would recommand?

    Thanks again for this useful library.

    1. Hi Thanh,

      Either way works, but there are two reasons why I don’t import the and my other functions as modules:

      1. If I am just using PowerShell to do small things, I generally don’t want to output to a log file – the results on screen are good enough.

      2. Usually when I am writing a script that requires logging I will be sharing it to be run by a number of people on different machines (e.g. IT admins). For this reason if I store my functions in a central accessible location (like a file share), then it is available to everyone running the script without them having to do anything fancy.

      Let me know your thoughts and how you would use the logging functions.

      Thanks
      Luca

  11. New-Item : En del af stien ‘C:\Temp\’ blev ikke fundet.
    At C:\Users\Ricco\Desktop\Logfile.ps1:56 char:13
    + New-Item <<<< -Path $LogPath -Value $LogName –ItemType File
    + CategoryInfo : WriteError: (C:\Temp\:String) [New-Item], DirectoryNotFoundException
    + FullyQualifiedErrorId : NewItemIOError,Microsoft.PowerShell.Commands.NewItemCommand

    The library is created c:\temp and It creates the file, but nothing else.

    1. Hi Ricco,

      Can you please try download another version of the PowerShell Logging Function? I just made a change to line 53 (courtesy of the comment by Don) which will resolve that issue.

      Thanks
      Luca

  12. @sagar
    Just change the logic slightly

    If((Test-Path -Path $sFullPath) -eq $false){
    #Create file and start logging
    New-Item -Path $LogPath -Name $LogName -ItemType File

    }

  13. Luca
    Great Template + Logging! I will be using these going forward.
    What I’d like to do is log all verbose output in my functions to the log file that your template is using. Do you know of a way to redirect verbose output to this log? For example, on a command if I add the common parameter -Verbose it displays the output in the shell, but I’d like that information logged into the .log file that your template is using. It doesn’t appear that your Log-Write cmdlet accepts pipeline input.

    1. Hi Leroy,

      What you can do is something like this .... | Log-Write -LogPath "C:\Windows\Temp\Test_Script.log" -LineValue $_

      $_ is a special variable in PowerShell and is the variable that is passed via pipeline.

      Give that a go… hope that solves the problem.

      Thanks
      Luca

  14. Hello,
    I’m new to powershell and I was looking for “how to log in powershell” and found your script and your script template.
    But I think it doesn’t work completly how I’m using it.
    The logfile is created and the starting event and the closing event are in the logfile. But I didn’t get any output on the screen and the log entries from my functions are also not in the file (the write-host is printed).
    Here is one of my functions
    Function Import_Suppliers{

    [CmdletBinding()]

    Param ([Parameter(Mandatory=$true)][string] $fFile, [Parameter(Mandatory=$true)][string] $fDelimiter)

    Begin{
    Log-Write -LogPath $sLogFile -LineValue “Oeffnen der Datei” + $fFile
    }
    Process{
    Try{
    $Dataset = Import-Csv -path $fFile -Delimiter $fDelimiter
    return ,$Dataset
    }
    Catch{
    Log-Error -LogPath $sLogFile -ErrorDesc $_.Exception -ExitGracefully $True
    Break
    }
    }
    End{
    If($?){
    Log-Write -LogPath $sLogFile -LineValue “Oeffnen der Datei ” + $fFile + ” Erfolgreich beendet.”
    Log-Write -LogPath $sLogFile -LineValue ” ”
    }
    }
    }

    And this is the content of the log
    ***************************************************************************************************
    Started processing at [09/06/2014 13:14:51].
    ***************************************************************************************************

    Running script version [1.0].

    ***************************************************************************************************

    ***************************************************************************************************
    Finished processing at [09/06/2014 13:14:52].
    ***************************************************************************************************

    Kind regars from germany
    Andreas

    1. Hi Andreas,

      The reason why it isn’t writing any lines to your log file is because you cannot do string concatenation in the -LineValue attribute. To resolve this can you do either of the following:

      1. Create a variable with the line value you want to write to the log and then use that variable for the -LineValue attribute. See example below:


      $Line = “Oeffnen der Datei” + $fFile
      Log-Write -LogPath $sLogFile -LineValue $Line

      2. Move the variable directly into the “” quotes. When PowerShell executes it will insert the variable data in as opposed to the variable name. This is a standard PowerShell feature and works in all double quotes “” (Note: This does not work with singles quotes – ”). See example below:


      Log-Write -LogPath $sLogFile -LineValue “Oeffnen der Datei $fFile”

      Hope this helps.
      Luca

  15. Hi Luca,
    I have implemented solution 2 and it works.
    But I have another question I execute a .exe in “batchmode”. When I execute the command directly in the shell it generates output in the shell. Livelog would discribe it, I think.
    The command is called via “cmd /c …”.
    I tried to redirect the output via cmd /c …. | Log-Write -LogPath $sLogFile -LineValue $_
    But I get the following error:
    “Error: An error has occurred [System.Management.Automation.ParameterBindingValidationException: Das Argument kann nicht an den
    Parameter “LineValue” gebunden werden, da es sich um eine leere Zeichenfolge handelt.”
    In english:
    The argument can not be bind to the parameter “LineValue” because it is an empty string…”
    Would be nice if this is possible.

    Output Example:
    FlowHeater (R) Batch Modul: Version 3.3.4

    Start: 08.09.2014 08:03:44
    Set Parameter [file], Wert [Lieferanten.csv]

    Rows Read : 31
    Rows Fitter : 31
    Rows Write : 31

    End: 08.09.2014 08:03:46

    Kind regars from germany
    Andreas

  16. Luca
    Thanks for your reply earlier.
    I guess what I’m getting at is:
    How would you edit your script to accept pipeline input? I’ve tried adding the parameter for ValueFromPipeline=$true, but that produces errors.
    For example, i’d like the ability to do something like:
    get-service | Log-Write -LogPath $sLogFile -LineValue $_
    Thanks!

  17. Great !
    One detail : I *do* hate code duplication,
    so : is it possible in my scripts to create an ‘alias’ or a ‘wrapper function’ to replace all the calls to :

    Log-Write -LogPath $log -LineValue “blah blha”
    by :
    logw “blah”

    I’ve tried the function, but can’t set it up correctly,
    I’m new to powershell and still learning, (with heavy nix background that does not help in this case).

    1. Hi Skai,

      Unfortunately it isn’t that easy as you can’t really create alias for PowerShell functions. You could essentially convert my function library into a PowerShell module and then import the module into your script. You could then attempt to build an alias around the function within the module, as per this article here. I haven’t ever tried it so I am not sure exactly how it would work, but give it a go.

      Another solution you could use is create another function in your script called logw and have one parameter which is the line value. Then essentially you could run logw “blah” and it would work. Below is a function that you could use:


      Function logw{
      Param ([Parameter(Mandatory=$true)][string]$Data)

      Log-Write -LogPath "YOUR LOG FILE PATH OR VARIABLE CONTAINING FILE PATH" -LineValue $Data
      }

      Hope this helps.

      Thanks
      Luca

  18. In fact I would propose a change :

    in a script or profile, define :
    function log { Log-Write -LogPath $log -LineValue $args }

    that can be called like this :
    log “blah blah”
    log “a” “b” “c”

    And in your code :

    Function Log-Write{
    [CmdletBinding()]
    Param ([Parameter(Mandatory=$true)][string]$LogPath, [Parameter(Mandatory=$true)][array]$LineValue)
    Process{

    $LineValue | ForEach-Object{
    Add-Content -Path $LogPath -Value $_.toString()
    Write-Debug $_.toString();
    }

    }
    }

    something along this line

  19. I’m having the same issue as several other people here with the log-error line, returning ‘ErrorDesc’ as an empty string. Does ‘$_.Exception’ reference any previous error, or does the line need to be placed where an error would likely occur? Can we simply code it as:
    Log-Error -LogPath “C:\Windows\Temp\Test_Script.log” -ErrorDesc $_.Exception -ExitGracefully $True
    Perhaps $_.Exception needs to be changed to something like [system.exception] and placed in a try-catch loop?

    Perhaps you wouldn’t mind posting an example code where the Log-error is used successfully?

    1. Hi Joe,

      If you would like to see how to use it then take a look at my PowerShell Script Template. This is a standard template I use for all of my scripts which includes the dot sourcing the PowerShell Logging Function .ps1 file and includes the Try…Catch blocks to capture errors and push them to the log via Log-Error.

      Let me know if you need any more help

      Thanks
      Luca

  20. Great script Luca! Still playing with it to make sure I understand it completely. I’ve downloaded your template and plan to use it as well once I take a look at it.
    Line 186 in your Functions_Logging.ps1 has an extra accented a in the Break command.
    I expect that will cause issues at some point.

    1. Hi Kevin,

      Thanks glad you like it. Yeah I am aware of that, I have tried to get that fixed but I don’t know why it keeps coming back – it might have to do with GitHub formatting. I will investigate. Thanks for the heads up.

      Luca

  21. Luca! Wow! I have trashed my own logging methods and inserted yours. It’s the first piece of code that I gotten from the web and didn’t modify a single thing. Awesome job!

  22. Interesting functions. I highly recommend you to build a PowerShell Module. This is the proper way to distribute such a tool and will make it much handle. Also, make sure the include which version is required. Many people might be in process of moving to PowerShell 4.0 or greater.

    Thanks for the contribution,
    Max

    1. Hi Max,

      Good idea – I am wanting to improve my logging functions, so I most likely will look into a PS module at that stage.

      Thanks for the tip
      Luca

  23. Luca,
    great Logging functions!

    I am using them in one of my scripts to migrate a bunch of SharePoint site collections from one farm to another.
    I have a strange behavior that I cannot explain as I am reusing the same code on every site collection.

    Basically when I call the logging function, for some site collections it gives me the following error:

    Add-Content : Cannot use interface. The IContentCmdletProvider interface is not implemented by this provider.
    At C:\Program Files\WindowsPowerShell\Modules\PSLogging\PSLogging.psm1:210 char:5
    + Add-Content -Path $LogPath -Value $Message
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotImplemented: (:) [Add-Content], PSNotSupportedException
    + FullyQualifiedErrorId : NotSupported,Microsoft.PowerShell.Commands.AddContentCommand

    The code I use to add lines to the log is:
    Write-LogInfo -LogPath $Log -Message “PHASE 3 – CREATING new Site Collection $site” -ToScreen -TimeStamp
    Write-LogInfo -LogPath $Log -Message ” SITE COLLECTION: $site” -ToScreen
    Write-LogInfo -LogPath $Log -Message ” WEB APPLICATION: $webapp” -ToScreen
    Write-LogInfo -LogPath $Log -Message ” DATABASE: $dbname $OFS” -ToScreen

  24. Thanks! This is exactly what I needed. It took me a little while to get it to work with what I needed (Mostly a powershell noob), but I got it to work for the manual use that I need. Now to see if I can get this to work with an SCCM deployment of a package containing this powershell script.

    I’m trying to create a required deployment in SCCM to remove a folder of log files (stupid CBS.log files growing out of control). Hopefully this will shed some light on why my original script wasn’t working.

  25. Luca –

    First off, these functions look great.

    One small change that I would make though is to do a test-path for the logging directory for start-log, as it looks like the function throws an exception of a directory is passed to it that doesn’t exist.

    Something like:

    #Check if Directory exists and create if it doesn't
    If(!(Test-Path -Path $LogPath )){
    md $LogPath
    }

  26. The problem is with the line below and the -Value parameter
    New-Item -Path $LogPath -Value $LogName –ItemType File

    Replace with below the line.
    Out-File -Filepath $sFullPath

    Working Fine

  27. can somebody post the original Logging_Functions.ps1 script here ,still facing an issue in downloading from the github link..

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.