2

I've got a collection of powershell scripts, some of which call others. Some of these subscripts can also be called on their own as needed. How can I quickly add logging to all of the scripts so that any script invocation results in a log file for later examination?

There are a number of questions dealing with logging with some great answers, like this one. But I wanted to see what we could come up with that:

  • required minimal touching of the existing powershell files
  • automatically dealt with script A.ps1 calling script B.ps1. If you call A.ps1, A.ps1 needs to start and finish the logging. But if you call B.ps1 directly, B.ps1 does.

I came up with my answer below, and wanted to share and see if there were other ideas on how to approach this, or suggestions for improvement on my answer.

aggieNick02
  • 2,557
  • 2
  • 23
  • 36

1 Answers1

2

The support code I write (further down) allows for just adding the following to each ps1 file. It automatically gives me logging regardless of if a script is called at top-level or by another script:

#any params for script
. "$PSScriptRoot\ps_support.ps1"
StartTranscriptIfAppropriate
try
{
#all of the original script
}
finally
{
ConditionalStopTranscript
}

The code that powers this is in ps_support.ps1, sitting next to my collection of powershell files that need logging. It uses Get-Variable and Set-Variable to manipulate a couple variables at the caller's scope level:

  • Logging__TranscriptStarted is normal so sub-scopes can see that logging is already happening and not try to start it again.
  • Logging__TranscriptStartedPrivate is private so a scope can know if it is responsible for stopping the logging.

Here is ps_support.ps1:

Set-Variable -name TranscriptStartedPropertyName -opt ReadOnly -value 'Logging__TranscriptStarted'
Set-Variable -name TranscriptStartedPrivatePropertyName -opt ReadOnly -value 'Logging__TranscriptStartedPrivate'

function StartTranscriptIfAppropriate
{
    $transcriptStarted = [bool](Get-Variable -name $TranscriptStartedPropertyName -ErrorAction Ignore)
    if (-not $transcriptStarted)
    {
        $callstack = get-pscallstack
        $fullPath = $callstack[$callstack.count-2].ScriptName
        $name = Split-Path -Path $fullPath -Leaf
        $directory = Split-Path -Path $fullPath
        $logDirectory = [IO.Path]::GetFullPath("$directory\..\scripts_logs")
        md -force $logDirectory | out-null
        $logFinalPath = "$logDirectory\$(Get-Date -Format o | foreach {$_ -replace ":", "."})_$name.log"
        Set-Variable -scope 1 -name $TranscriptStartedPropertyName -value $True
        Set-Variable -scope 1 -option private -name $TranscriptStartedPrivatePropertyName -value $True
        Start-Transcript $logFinalPath | Write-Host
    }
    $immediateCallerPath = Get-Variable -scope 1 -name PSCommandPath -ValueOnly
    Write-Host "Starting script at $immediateCallerPath"
}

function ConditionalStopTranscript
{
    $immediateCallerPath = Get-Variable -scope 1 -name PSCommandPath -ValueOnly
    Write-Host "Stopping script at $immediateCallerPath"
    $transcriptStartedByMe = [bool](Get-Variable -scope 1 -name $TranscriptStartedPrivatePropertyName -ErrorAction Ignore)
    if ($transcriptStartedByMe)
    {
        Stop-Transcript | Write-Host
    }
}   
aggieNick02
  • 2,557
  • 2
  • 23
  • 36