From 0b80ba3fa67d88c5c34b9e500c72050861c46130 Mon Sep 17 00:00:00 2001 From: Andrzej Demski <75534654+AndrewDemski-ad-gmail-com@users.noreply.github.com> Date: Mon, 1 Apr 2024 13:48:03 +0200 Subject: [PATCH 1/5] Create Write-CMTraceLog.ps1 As promised, the CMtrace-compatible logging function. --- .../functions/Write-CMTraceLog.ps1 | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 diff --git a/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 b/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 new file mode 100644 index 0000000..eccd69a --- /dev/null +++ b/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 @@ -0,0 +1,318 @@ +# Obtain UTC offset (think about moving it to the parent method) + $DateTime = New-Object -ComObject WbemScripting.SWbemDateTime; + $DateTime.SetVarDate($(Get-Date)); + $UtcValue = $DateTime.Value; + $global:CMTraceLog_UtcOffset = $UtcValue.Substring(21, $UtcValue.Length - 21); + [System.Runtime.InteropServices.Marshal]::ReleaseComObject($DateTime) | Out-Null; + +# Set context of process which writes a message + $global:CMTraceLog_Context = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name; + +# set string templates for formating later +[string]$global:logline_part1_template_nonerror = ""; +[string]$global:logline_part1_template_error = ""; +[string]$global:logline_part2_template = ""; + +# (full help at the end of file) + +enum CMTraceLogSeverity +{ + Warning = 2 + Error = 3 + Verbose = 4 + Debug = 5 + Information = 6 +} + +Function Write-CMTraceLog +{ + # Define and validate parameters + [CmdletBinding()] + Param( + + #Path to the log file + [parameter(Mandatory=$False)] + [String]$Logfile = "$Script:WorkingDir\logs\updates.log", + + #The information to log + [parameter(Mandatory=$True)] + $Message, + + #The severity (Error, Warning, Verbose, Debug, Information) + [parameter(Mandatory=$True)] + [ValidateSet('Warning','Error','Verbose','Debug', 'Information', IgnoreCase=$True)] + [String]$Type, + + #Write back to the console or just to the log file. By default it will write back to the host. + [parameter(Mandatory=$False)] + [switch]$WriteBackToHost = $True + + )#Param + + # Get the info about the calling script, function etc + $callinginfo = (Get-PSCallStack)[1]; + + # Set Source Information + $Source = (Get-PSCallStack)[1].Location; + + # Set Component Information + $Component = (Get-Process -Id $PID).ProcessName; + + # Set PID Information + $ProcessID = $PID; + + # Set date/time of message + $dt = Get-Date; + [string]$time = [string]::Format("{0:HH:mm:ss.fff}", $dt); + [string]$date = [string]::Format("{0:MM-dd-yyyy}", $dt); + + # Set the order + <# + switch($Type) + { + 'Warning' {$Severity = 2;} #Warning + 'Error' {$Severity = 3;} #Error + 'Verbose' {$Severity = 4;} #Verbose + 'Debug' {$Severity = 5;} #Debug + 'Information' {$Severity = 6;} #Information + } + #> + $severity = [int]([CMTraceLogSeverity]::$Type); + + #region set the 1st part of logged entry (templates for formatting) + if($Type -eq 'Error') + { + if($Message.exception.Message) + { + # cool! we have an exception, we can use it + } + else + { + # we do not have an exception, we need to prepare out own cutom error to use later + [System.Exception]$Exception = $Message; + [String]$ErrorID = 'Custom Error'; + [System.Management.Automation.ErrorCategory]$ErrorCategory = [Management.Automation.ErrorCategory]::WriteError; + $ErrorRecord = [System.Management.Automation.ErrorRecord]::new($Exception, $ErrorID, $ErrorCategory, $Message); + $Message = $ErrorRecord; + } + [string]$logline_part1 = [string]::Format( + $global:logline_part1_template_error, + $Type.ToUpper(), + $Message.exception.message, + $Message.InvocationInfo.MyCommand, + $Message.InvocationInfo.Scriptname, + $Message.InvocationInfo.ScriptLineNumber, + $Message.InvocationInfo.OffsetInLine, + $Message.InvocationInfo.Line + ); + } + else + { + [string]$logline_part1 = [string]::Format($global:logline_part1_template_nonerror, $Type.ToUpper(), $message); + } + #endregion set the 1st part of logged entry (templates for formatting) + + #region set the 2nd part of logged entry + [string]$logline_part2 = [string]::Format( + $global:logline_part2_template, + $time, + $global:CMTraceLog_UtcOffset, + $date, + $Component, + $global:CMTraceLog_Context, + $Severity, + $ProcessID, + $Source + ); + #endregion set the 2nd part of logged entry + + #region Switch statement to write out to the log and/or back to the host. + + # Write the log entry in the CMTrace Format. + $logline = $logline_part1 + $logline_part2; + $logline | Out-File -Append -Encoding utf8 -FilePath $Logfile; + + switch ($severity) + { + #region Warning + 2{ + #Write back to the host if $Writebacktohost is true. + if(($WriteBackToHost)){ + Switch($PSCmdlet.GetVariableValue('WarningPreference')){ + 'Continue' {$WarningPreference = 'Continue';Write-Warning -Message "$Message";$WarningPreference=''} + 'Stop' {$WarningPreference = 'Stop';Write-Warning -Message "$Message";$WarningPreference=''} + 'Inquire' {$WarningPreference ='Inquire';Write-Warning -Message "$Message";$WarningPreference=''} + 'SilentlyContinue' {} + } + } + } + #endregion Warning + + #region Error + 3{ + #This if statement is to catch the two different types of errors that may come through. A normal terminating exception will have all the information that is needed, if it's a user generated error by using Write-Error, + #then the else statment will setup all the information we would like to log. + + #Write back to the host if $Writebacktohost is true. + if(($WriteBackToHost)){ + #Write back to Host + Switch($PSCmdlet.GetVariableValue('ErrorActionPreference')) + { + 'Stop'{$ErrorActionPreference = 'Stop';$Host.Ui.WriteErrorLine("ERROR: $([String]$Message.Exception.Message)");Write-Error $Message -ErrorAction 'Stop';$ErrorActionPreference=''} + 'Inquire'{$ErrorActionPreference = 'Inquire';$Host.Ui.WriteErrorLine("ERROR: $([String]$Message.Exception.Message)");Write-Error $Message -ErrorAction 'Inquire';$ErrorActionPreference=''} + 'Continue'{$ErrorActionPreference = 'Continue';$Host.Ui.WriteErrorLine("ERROR: $([String]$Message.Exception.Message)");$ErrorActionPreference=''} + 'Suspend'{$ErrorActionPreference = 'Suspend';$Host.Ui.WriteErrorLine("ERROR: $([String]$Message.Exception.Message)");Write-Error $Message -ErrorAction 'Suspend';$ErrorActionPreference=''} + 'SilentlyContinue'{} + } + + } + } + #endregion Error + + #region Verbose + 4{ + #Write back to the host if $Writebacktohost is true. + if(($WriteBackToHost)){ + Switch ($PSCmdlet.GetVariableValue('VerbosePreference')) { + 'Continue' {$VerbosePreference = 'Continue'; Write-Verbose -Message "$Message";$VerbosePreference = ''} + 'Inquire' {$VerbosePreference = 'Inquire'; Write-Verbose -Message "$Message";$VerbosePreference = ''} + 'Stop' {$VerbosePreference = 'Stop'; Write-Verbose -Message "$Message";$VerbosePreference = ''} + } + } + + } + #endregion Verbose + + #region Debug + 5{ + #Write back to the host if $Writebacktohost is true. + if(($WriteBackToHost)){ + Switch ($PSCmdlet.GetVariableValue('DebugPreference')){ + 'Continue' {$DebugPreference = 'Continue'; Write-Debug -Message "$Message";$DebugPreference = ''} + 'Inquire' {$DebugPreference = 'Inquire'; Write-Debug -Message "$Message";$DebugPreference = ''} + 'Stop' {$DebugPreference = 'Stop'; Write-Debug -Message "$Message";$DebugPreference = ''} + } + } + + } + #endregion Debug + + #region Information + 6{ + #Write back to the host if $Writebacktohost is true. + if(($WriteBackToHost)){ + Switch ($PSCmdlet.GetVariableValue('InformationPreference')){ + 'Continue' {$InformationPreference = 'Continue'; Write-Information -Message "INFORMATION: $Message";$InformationPreference = ''} + 'Inquire' {$InformationPreference = 'Inquire'; Write-Information -Message "INFORMATION: $Message";$InformationPreference = ''} + 'Stop' {$InformationPreference = 'Stop'; Write-Information -Message "INFORMATION: $Message";$InformationPreference = ''} + 'Suspend' {$InformationPreference = 'Suspend';Write-Information -Message "INFORMATION: $Message";$InformationPreference = ''} + } + } + } + #endregion Information + + } + #endregion Switch statement to write out to the log and/or back to the host. +} + +<# +.SYNOPSIS + Write to a log file in a format that takes advantage of the CMTrace.exe log viewer that comes with SCCM. + Found @ https://wolffhaven.gitlab.io/wolffhaven_icarus_test/powershell/write-cmtracelog-dropping-logs-like-a-boss/ + heavily modified for the purpose of WAU project + +.DESCRIPTION + Output strings to a log file that is formatted for use with CMTRace.exe and also writes back to the host. + + The severity of the logged line can be set as: + + 2-Error + 3-Warning + 4-Verbose + 5-Debug + 6-Information + + + Warnings will be highlighted in yellow. Errors are highlighted in red. + + The tools to view the log: + + SMS Trace - http://www.microsoft.com/en-us/download/details.aspx?id=18153 + CM Trace - https://www.microsoft.com/en-us/download/details.aspx?id=50012 or the Installation directory on Configuration Manager 2012 Site Server - \tools\ + + With current atomization of the code, the component parameter should be passed as one of parameters + [string]$component_name = (Get-PSCallStack)[0].FunctionName + If logger function is not called in the function, then we could designate a decriptive name for it. + +.EXAMPLE +Try{ + Get-Process -Name DoesnotExist -ea stop +} +Catch{ + Write-CMTraceLog -Logfile "C:\output\logfile.log -Message $Error[0] -Type Error +} + + This will write a line to the logfile.log file in c:\output\logfile.log. It will state the errordetails in the log file + and highlight the line in Red. It will also write back to the host in a friendlier red on black message than + the normal error record. + +.EXAMPLE + $VerbosePreference = Continue + Write-CMTraceLog -Message "This is a verbose message." -Type Verbose + + This example will write a verbose entry into the log file and also write back to the host. The Write-CMTraceLog will obey + the preference variables. + +.EXAMPLE +Write-CMTraceLog -Message "This is an informational message" -Type Information -WritebacktoHost:$false + + This example will write the informational message to the log but not back to the host. + +.EXAMPLE +Function Test{ + [cmdletbinding()] + Param() + Write-CMTraceLog -Message "This is a verbose message" -Type Verbose +} +Test -Verbose + +This example shows how to use write-cmtracelog inside a function and then call the function with the -verbose switch. +The write-cmtracelog function will then print the verbose message. + +.NOTES + + ########## + Change Log + ########## + + v1.6 - 2024-04-01 - reorganized com handling triggered by UTC Offset calculation, reuced the paralellism by moving err/non-err strings generation to the front + + ########## + + v1.5 - 2015-03-12 - Found bug with Error writing back to host twice. Fixed. + + ########## + + v1.4 - 2015-03-12 - Found bug with Warning writebackto host duplicating warning error message. + Fixed. + + ########## + + v1.3 - 2015-02-23 - Commented out line 224 and 249 as it was causing a duplicaton of the message. + + ########## + + v1.2 - Fixed inheritance of preference variables from child scopes finally!! Changed from using + using get-variable -scope 1 (which doesn't work when a script modules calls a function: + See this Microsoft Connect bug https://connect.microsoft.com/PowerShell/feedback/details/1606119.) + Anyway now now i use the $PSCmdlet.GetVariableValue('VerbosePreference') command and it works. + + ########## + + v1.1 - Found a bug with the get-variable scope. Need to refer to 2 parent scopes for the writebacktohost to work. + - Changed all Get-Variable commands to use Scope 2, instead of Scope 1. + + ########## + + v1.0 - Script Created +#> From f3ed2968bcde9abefb9f259b6be53822b143bacf Mon Sep 17 00:00:00 2001 From: Andrzej Demski <75534654+AndrewDemski-ad-gmail-com@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:09:39 +0200 Subject: [PATCH 2/5] Update Write-CMTraceLog.ps1 --- .../WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 b/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 index eccd69a..a81ca27 100644 --- a/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 +++ b/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 @@ -202,10 +202,10 @@ Function Write-CMTraceLog #Write back to the host if $Writebacktohost is true. if(($WriteBackToHost)){ Switch ($PSCmdlet.GetVariableValue('InformationPreference')){ - 'Continue' {$InformationPreference = 'Continue'; Write-Information -Message "INFORMATION: $Message";$InformationPreference = ''} - 'Inquire' {$InformationPreference = 'Inquire'; Write-Information -Message "INFORMATION: $Message";$InformationPreference = ''} - 'Stop' {$InformationPreference = 'Stop'; Write-Information -Message "INFORMATION: $Message";$InformationPreference = ''} - 'Suspend' {$InformationPreference = 'Suspend';Write-Information -Message "INFORMATION: $Message";$InformationPreference = ''} + 'Continue' {$InformationPreference = [System.Management.Automation.ActionPreference]::Continue; Write-Information "INFORMATION: $Message" -InformationAction Continue ; $InformationPreference = ''} + 'Inquire' {$InformationPreference = [System.Management.Automation.ActionPreference]::Inquire; Write-Information "INFORMATION: $Message" -InformationAction Inquire; $InformationPreference = ''} + 'Stop' {$InformationPreference = [System.Management.Automation.ActionPreference]::Stop; Write-Information "INFORMATION: $Message" -InformationAction Stop; $InformationPreference = ''} + 'Suspend' {$InformationPreference = [System.Management.Automation.ActionPreference]::Suspend; Write-Information "INFORMATION: $Message" -InformationAction Suspend; $InformationPreference = ''} } } } From 77bb54e60589347e02dc06e1b5e1fb5f6a23abae Mon Sep 17 00:00:00 2001 From: Andrzej Demski <75534654+AndrewDemski-ad-gmail-com@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:18:33 +0200 Subject: [PATCH 3/5] Update Write-CMTraceLog.ps1 --- Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 b/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 index a81ca27..ede1fa8 100644 --- a/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 +++ b/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 @@ -45,7 +45,7 @@ Function Write-CMTraceLog #Write back to the console or just to the log file. By default it will write back to the host. [parameter(Mandatory=$False)] - [switch]$WriteBackToHost = $True + [switch]$WriteBackToHost = $False )#Param From 35c4d206ac0bab3a9232711fe72a439acf017ae0 Mon Sep 17 00:00:00 2001 From: Andrzej Demski <75534654+AndrewDemski-ad-gmail-com@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:40:37 +0200 Subject: [PATCH 4/5] Update Write-CMTraceLog.ps1 Applying formatting suggestions from CSPELL and GIT_DIFF linters --- .../functions/Write-CMTraceLog.ps1 | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 b/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 index ede1fa8..f0d6de9 100644 --- a/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 +++ b/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 @@ -1,5 +1,5 @@ # Obtain UTC offset (think about moving it to the parent method) - $DateTime = New-Object -ComObject WbemScripting.SWbemDateTime; + $DateTime = New-Object -ComObject 'WbemScripting.SWbemDateTime'; $DateTime.SetVarDate($(Get-Date)); $UtcValue = $DateTime.Value; $global:CMTraceLog_UtcOffset = $UtcValue.Substring(21, $UtcValue.Length - 21); @@ -8,7 +8,7 @@ # Set context of process which writes a message $global:CMTraceLog_Context = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name; -# set string templates for formating later +# set string templates for formatting later [string]$global:logline_part1_template_nonerror = ""; [string]$global:logline_part1_template_error = ""; [string]$global:logline_part2_template = ""; @@ -31,16 +31,16 @@ Function Write-CMTraceLog Param( #Path to the log file - [parameter(Mandatory=$False)] + [parameter(Mandatory=$False)] [String]$Logfile = "$Script:WorkingDir\logs\updates.log", - #The information to log - [parameter(Mandatory=$True)] + #The information to log + [parameter(Mandatory=$True)] $Message, #The severity (Error, Warning, Verbose, Debug, Information) [parameter(Mandatory=$True)] - [ValidateSet('Warning','Error','Verbose','Debug', 'Information', IgnoreCase=$True)] + [ValidateSet('Warning','Error','Verbose','Debug', 'Information', IgnoreCase=$True)] [String]$Type, #Write back to the console or just to the log file. By default it will write back to the host. @@ -88,7 +88,7 @@ Function Write-CMTraceLog } else { - # we do not have an exception, we need to prepare out own cutom error to use later + # we do not have an exception, we need to prepare out own custom error to use later [System.Exception]$Exception = $Message; [String]$ErrorID = 'Custom Error'; [System.Management.Automation.ErrorCategory]$ErrorCategory = [Management.Automation.ErrorCategory]::WriteError; @@ -100,7 +100,7 @@ Function Write-CMTraceLog $Type.ToUpper(), $Message.exception.message, $Message.InvocationInfo.MyCommand, - $Message.InvocationInfo.Scriptname, + $Message.InvocationInfo.ScriptName, $Message.InvocationInfo.ScriptLineNumber, $Message.InvocationInfo.OffsetInLine, $Message.InvocationInfo.Line @@ -114,14 +114,14 @@ Function Write-CMTraceLog #region set the 2nd part of logged entry [string]$logline_part2 = [string]::Format( - $global:logline_part2_template, - $time, - $global:CMTraceLog_UtcOffset, - $date, + $global:logline_part2_template, + $time, + $global:CMTraceLog_UtcOffset, + $date, $Component, - $global:CMTraceLog_Context, - $Severity, - $ProcessID, + $global:CMTraceLog_Context, + $Severity, + $ProcessID, $Source ); #endregion set the 2nd part of logged entry @@ -137,7 +137,8 @@ Function Write-CMTraceLog #region Warning 2{ #Write back to the host if $Writebacktohost is true. - if(($WriteBackToHost)){ + if(($WriteBackToHost)) + { Switch($PSCmdlet.GetVariableValue('WarningPreference')){ 'Continue' {$WarningPreference = 'Continue';Write-Warning -Message "$Message";$WarningPreference=''} 'Stop' {$WarningPreference = 'Stop';Write-Warning -Message "$Message";$WarningPreference=''} @@ -151,10 +152,11 @@ Function Write-CMTraceLog #region Error 3{ #This if statement is to catch the two different types of errors that may come through. A normal terminating exception will have all the information that is needed, if it's a user generated error by using Write-Error, - #then the else statment will setup all the information we would like to log. + #then the else statement will setup all the information we would like to log. #Write back to the host if $Writebacktohost is true. - if(($WriteBackToHost)){ + if(($WriteBackToHost)) + { #Write back to Host Switch($PSCmdlet.GetVariableValue('ErrorActionPreference')) { @@ -178,22 +180,21 @@ Function Write-CMTraceLog 'Inquire' {$VerbosePreference = 'Inquire'; Write-Verbose -Message "$Message";$VerbosePreference = ''} 'Stop' {$VerbosePreference = 'Stop'; Write-Verbose -Message "$Message";$VerbosePreference = ''} } - } - + } } #endregion Verbose #region Debug 5{ - #Write back to the host if $Writebacktohost is true. - if(($WriteBackToHost)){ + #Write back to the host if $Writebacktohost is true. + if(($WriteBackToHost)) + { Switch ($PSCmdlet.GetVariableValue('DebugPreference')){ 'Continue' {$DebugPreference = 'Continue'; Write-Debug -Message "$Message";$DebugPreference = ''} 'Inquire' {$DebugPreference = 'Inquire'; Write-Debug -Message "$Message";$DebugPreference = ''} 'Stop' {$DebugPreference = 'Stop'; Write-Debug -Message "$Message";$DebugPreference = ''} } - } - + } } #endregion Debug @@ -285,7 +286,7 @@ The write-cmtracelog function will then print the verbose message. Change Log ########## - v1.6 - 2024-04-01 - reorganized com handling triggered by UTC Offset calculation, reuced the paralellism by moving err/non-err strings generation to the front + v1.6 - 2024-04-01 - reorganized com handling triggered by UTC Offset calculation, reduced the parallelism by moving err/non-err strings generation to the front ########## From 78624e46842b26d7e7ed7d993b46747fce55042c Mon Sep 17 00:00:00 2001 From: Andrzej Demski <75534654+AndrewDemski-ad-gmail-com@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:46:04 +0200 Subject: [PATCH 5/5] Update Write-CMTraceLog.ps1 --- .../WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 b/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 index f0d6de9..62d815a 100644 --- a/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 +++ b/Sources/WAU/Winget-AutoUpdate/functions/Write-CMTraceLog.ps1 @@ -96,9 +96,9 @@ Function Write-CMTraceLog $Message = $ErrorRecord; } [string]$logline_part1 = [string]::Format( - $global:logline_part1_template_error, - $Type.ToUpper(), - $Message.exception.message, + $global:logline_part1_template_error, + $Type.ToUpper(), + $Message.exception.message, $Message.InvocationInfo.MyCommand, $Message.InvocationInfo.ScriptName, $Message.InvocationInfo.ScriptLineNumber,