Seeing the red in black and white
The Idea
I'm trying to build an after-OS install deployment script that creates a log file when initiated and tracks the changes, successes and failures, during the deployment process, peeking back to see how far it's got so I can basically select from a defined set of configurations and leave the script to finish the job. The key requirements are that the script is highly compatible, so PowerShell v2 is the target. The script has to bypass common security dialogs for installers so the script is launched with the command PowerShell -ExecutionPolicy Bypass -File "%~dp0%thisScript%" and is lanuched as an administrator.The Issue
The script output must be shown both in the command window and copied to a text file simply so I can see if anything went wrong without having to carefully examine the output of a command line window precariously left on a "press any key...", but can still see both the command line output in action in the window explaining where I am in the deployment process while the script is in action and check the log file at the end for error. Due to the fact that the script itself is designed to deploy various individual installers, it should record errors but not break on them unless critical. This can be handled through both capturing exit codes from installers and handling WMI queries in try catch code blocks.I'm using command line to start the script and although I can redirect the output of the script launching command to a text file using something like:
PowerShell -ExecutionPolicy Bypass -File "%~dp0%thisScript%" > C:\Logs\ThisScript.txt
This would stop the console displaying the output of the script in the command window. The script would still run and be logged completely, but you wouldn't be able to see it processing the tasks.
I Did This
Being that PowerShell allows for "modules" (either scripts or compiled dlls), it seems logical to create a logging script module that I can throw objects down the pipeline which I can reuse in other scripts by calling the Import-Module cmdlet. This is a little complicated module and is still a work in progress.Pretty nasty code alert:
[bool]$LogToFile = $False$cDT = Get-Date
[string]$LogFilePath = ('{0}\Logs\ANOVO\{1}_ScriptLog.log' -f "$env:windir", ('{0}-{1:D2}-{2:D2}_{3:D2}-{4:D2}' -f $cDT.Year, $cDT.Month, $cDT.Day, $cDT.Hour, $cDT.Minute))
[bool]$EchoLog = $True
[bool]$LogMessagePassThru = $False
function Write-LogFile {
param(
[parameter(ValueFromPipeline=$True,
HelpMessage='Enter message to write to log file.')][string]$logMessage,
[parameter(Mandatory=$False,
HelpMessage='Enter title for log file message.')][string]$logTitle = 'Log Entry',
[parameter(Mandatory=$False,
HelpMessage='Enter full path for log file.')][AllowEmptyString()][string]$logPath,
[parameter(Mandatory=$False,
HelpMessage='Allow pipeline message to pass through.')][bool]$logPassThru = $script:LogMessagePassThru
)
Begin {
}
Process {
if($LogToFile)
{
if(($logPath -eq $null) -OR ($logPath.Length -lt 1)) { $logPath = $script:LogFilePath }
if(!(Test-Path (Split-Path -Parent $logPath))) {
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $logPath) | Write-LogFile -logTitle 'Logs Directory Created' -logPath $logPath -logPassThru $False
}
if(!(Test-Path $logPath)){
New-Item -ItemType File -Force -Path $logPath | Write-LogFile -logTitle 'Log File Created' -logPath $logPath -logPassThru $False
}
('[{0} - {1}] {2}: {3}' -f (Get-Date).ToShortDateString(), (Get-Date).ToLongTimeString(), $logTitle, $logMessage) | Out-File $logPath -Append -Force
}
if($logPassThru) { $logMessage }
else
{
if($EchoLog)
{
('[{0} - {1}] {2}: {3}' -f (Get-Date).ToShortDateString(), (Get-Date).ToLongTimeString(), $logTitle, $logMessage) | Out-Host
}
}
}
End{
}
}
Export-ModuleMember -Function Write-LogFile
Export-ModuleMember -Variable LogFilePath
Export-ModuleMember -Variable LogToFile
Export-ModuleMember -Variable EchoLog
Export-ModuleMember -Variable LogPassThru
The Problem
The key issue I wanted to resolve was how to get the PowerShell red failure text to copy to the log file. It's actually quite readable and if nothing more, reports the point of failure the script quite noticeably when reading through a large log file. Also, I needed to have the text captured on commands and installers that may fail. Errors in PowerShell can come from multiple angles (installers, powershell commands, WMI object functions and more) and handling these isn't uniform. Sometimes, a function call may fail, but not output an error or may completely fail and stop the script.My Solution
I set up a quick check using the $? operator, $Error[] array and catching the last output.Write-LogFile "Beginning function..."
Preform some object function that may fail but not report it here
if($?)
{
Write-LogFile 'This was successful.'
}
else
{
Write-LogFile "This was not: $Error[0]"
} And...
Write-LogFile "Something may break here..."
try
{
Preform something that could cause powershell to red text
}
catch
{
Write-LogFile 'You broke it!'
Write-LogFile 'Powershell Error Message:'
Write-LogFile $_
}
When we do this, the full error text shows up in both the command line window and in the log file, allowing the script to continue. If you don't wan to capture the fully qualified error, you can just caputre the general message with $Error[0].Exception.Message
Code and output:
PS C:\>
try
{
MakingSomething-Up
Write-Output 'Successfull.'
}
catch
{
# $thisError = $Error[0].Exception.Message
Write-Output 'Failed.'
Write-Output 'Powershell Error Message:'
Write-Output $_
}
Write-Output 'Reached this too...'
Failed.
Powershell Error Message:
MakingSomething-Up : The term 'MakingSomething-Up' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a
path was included, verify that the path is correct and try again.
At line:4 char:3
+ MakingSomething-Up
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (MakingSomething-Up:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Reached this too...
PS C:\>










