A few weeks ago I posted an entry on creating customized debug messages that you can use in your scripting. After some more testing and tweaking I revised and renamed the function. Write-DebugMessage can now be used within your script to send messages to the debug pipeline. I moved the $debugpreference setting to the function itself. If you set it to Continue,the message will be written to the debug pipeline.
The other major change to the function is that I reformatted the date time to reflect milliseconds.
$timestamp=get-date -f ‘MM/dd/yyyy HH:mm:ss:ffff’
It all works great. But one of my online PowerShell students asked about saving debug messages to a file. That seemed like a reasonable request. Unfortunately, PowerShell v1.0 doesn’t appear to have any provision for redirecting the debug pipeline. Undaunted I dug in and came up with this solution.
When you record a PowerShell transcript, everything, including debug messages is written to it. My solution was to create a transcript file, parse out the debug messages and save them to a new file.
1: $debuglog=$TRUE
2:
3: if ($debuglog) {
4: #define a temporary name for the transcript file
5: $transcriptName="{0}_transcript.txt" -f $myinvocation.mycommand.name
6: $transcript=(Join-Path "C:\" $transcriptName )
7: Start-Transcript $transcript | Out-Null
8:
9: }
Set the $debuglog variable to $True if you want to log your debug messages. If so, I define a transcript name generated from the script name which I pull from $myinvocation.mycommand.name. I could probably parse it further to drop the extension but it works and it is temporary for the most part. You must start the transcript before you have any debug message code.
At the end of the script, I have similar code to create a debug log file based on the script name.
1: if ($debuglog) {
2: $filename="{0}_debug.txt" -f $myinvocation.mycommand.name
3: $debuglogfile=(Join-Path "c:\" $filename)
4:
5: Stop-Transcript | Out-Null
6:
7: Get-Content $transcript | Select-String -Pattern "DEBUG:" | Out-File $debuglogfile
8:
9: notepad $debuglogfile
10:
11: $rc=Read-Host "Do you want to delete $transcript [YN]?"
12: if ($rc -match "y") {
13: Remove-Item $transcript
14: }
15:
16: }
The Get-Content expression parses out the debug lines and writes them to the debug log file. At the end, I’ve added code to prompt you to delete the temporary transcript file.
Here’s a sample script that uses the Write-DebugMessage function and my debug logging code.
1: Function Write-DebugMessage {
2: Param([string]$text)
3:
4: #set to Continue to turn on debug messages
5: $debugpreference="continue"
6:
7: # build a text string of information that can be passed
8: # to the Debug pipeline
9:
10: #here are some variables you might want to work with
11: $computer=$env:computername
12: $domain=$env:userdomain
13: $user=$env:username
14:
15: #return a timestamp value with milliseconds
16: #like 08/06/2008 11:08:37:4361
17: #Or you can use an expression like this
18: #(Get-Date -Format g).toString()
19: $timestamp=get-date -f 'MM/dd/yyyy HH:mm:ss:ffff'
20:
21: $line=$($myinvocation.scriptlinenumber)
22: $script=$($myinvocation.invocationname)
23:
24: [string]$msg="[{0}:{1}\{2}] {3} Line:{4} {5}" -f $computer,$domain,$user,$timestamp,$line,$text
25:
26: Write-Debug $msg
27:
28: }
29:
30: Function Foo-Me {
31:
32: Write-DebugMessage "in function $($myinvocation.invocationname)"
33: Write-DebugMessage "getting services"
34: Get-Service
35: Write-DebugMessage "exiting function"
36: }
37:
38: #setting this to $TRUE doesn't do any good if the debug pipeline is
39: #turned off
40: debuglog=$TRUE
41:
42: if ($debuglog) {
43: #define a temporary name for the transcript file
44: $transcriptName="{0}_transcript.txt" -f $myinvocation.mycommand.name
45: $transcript=(Join-Path "C:\" $transcriptName )
46: Start-Transcript $transcript | Out-Null
47:
48: write-DebugMessage "starting $($myinvocation.invocationname)"
49:
50: sleep 4
51:
52: write-DebugMessage "Getting Date"
53: get-Date
54:
55: write-DebugMessage "Getting powershell process"
56: get-Process powershell
57:
58: write-DebugMessage "Calling Foo-Me"
59: foo-Me
60:
61: write-DebugMessage "Getting WMI Information"
62: $d=Get-WmiObject win32_logicaldisk -filter "deviceid='c:'"
63:
64: write-DebugMessage ("Drive C size is {0}GB" -f ([int]($d.Size/1GB)))
65:
66: a=@(1,2,3,4,5)
67: write-DebugMessage $a
68:
69: write-DebugMessage "finished"
70:
71: if ($debuglog) {
72: $filename="{0}_debug.txt" -f $myinvocation.mycommand.name
73: $debuglogfile=(Join-Path "c:\" $filename)
74:
75: Stop-Transcript | Out-Null
76:
77: Get-Content $transcript | Select-String -Pattern "DEBUG:" | Out-File $debuglogfile
78:
79: notepad $debuglogfile
80:
81: $rc=Read-Host "Do you want to delete $transcript [YN]?"
82: if ($rc -match "y") {
83: Remove-Item $transcript
84: }
85:
86:
There is one major caveat to this technique which may or not be relevant to you. PowerShell only allows one transcript per session. If you are running a transcript in your PowerShell console and try to run this script you’ll get an error message and no debug log will be created.
If you have any suggestions on this topic, I’d love to hear them.
You can download the debug demo script here
For debug logging, how about writing out the debug messages as they are received by Write-DebugMessage. You have an instant when you know what the debug message is, you know if the user wants it logged, and you have the opportunity to write the message to a file. You could even write out the messages in an XML format to include all the information separately, in addition to the full message.
This would also allow you to log debug messages to a file separately from displaying them on the host.
And if you used my previous example, you could do this for any script/function that did not call the Write-Debug cmdlet directly.
But… by putting the $DebugPreference inside the function, debug output is always turned on. This eliminates the ability to leave your Write-DebugMessage commands in your script, but to suppress output by setting $DebugPreference = “SilentlyContinue.” If you plan to simply copy and paste the Write-DebugMessage function, that may be fine – you could change it in the function itself. But if you plan to dot-source the function into a script from an external “utilities” script, then having the variable hardcoded within the function reduces flexibility. I’d suggest leaving $DebugPreference in the actual script, not inside the function. That way each script utilizing Write-DebugMessage can turn debugging on or off. The function would inherit the $DebugPreference from any script it’s dot-sourced or pasted into.
Thanks for the feedback. I went back and forth on where to control $DebugPreference. I tend to write stand-alone scripts where I’m likely to have copied and pasted the function. In a production environment it definitely makes more sense to control the debug pipeline from your script.