
close
close
Want to know about the security benefits of Microsoft's E5 license?
Whenever I teach about PowerShell scripting I always stress the value of using verbose messages in your functions and scripts. Assuming you are using cmdletbinding, and why wouldn’t you, you can insert Write-Verbose statements throughout your script. These statements won’t do anything unless your command is run with the common -Verbose parameter. However, these statements can be very useful for tracing and troubleshooting. I use these Verbose statements to help me track what my command is doing so that if it fails I have a better idea of where it failed and what it was doing. I don’t enjoy debugging and dealing with the interactive debugger and having Write-Verbose statements makes my life easier. Here are some ideas on how you might want to start using Write-Verbose.
First, if you want to try some of these things right from the command prompt, turn on your verbose pipeline.
$VerbosePreference = "continue"
When you are done testing, set it back to the default.
$VerbosePreference = "silentlycontinue"
Now you can run Write-Verbose commands to see what they look like. Remember that anything you want to write with Write-Verbose must be a string or at least be able to be treated as a string.
Write-Verbose "I am doing something"
You may need to use subexpressions as well. One suggestion is to include a timestamp in your verbose message.
Write-Verbose "[$(Get-Date)] Starting to do something long running"
Write-Verbose "$((get-date).TimeOfDay.ToString()) Initializing array"
Function Get-Foo { [cmdletbinding()] Param( [Parameter(Position = 0, Mandatory, ValueFromPipeline)] [string[]]$Computername ) Begin { Write-Verbose "[BEGIN ] Starting: $($MyInvocation.Mycommand)" Write-Verbose "[BEGIN ] Initializing foo array" [email protected]() } #begin Process { foreach ($computer in $computername) { Write-Verbose "[PROCESS] Getting foo from $($computer.toUpper())" #code happens } } #process End { Write-Verbose "[END ] Ending: $($MyInvocation.Mycommand)" } #end }
The Begin and End blocks include code to display the command name. This is very handy if you have a function that is calling another function. You may also be wondering about the spacing of END and BEGIN. I did that so that the verbose output would align nicely and be easier to read.
Function TryMe { [cmdletbinding()] Param( [string]$Computername ) Begin { Write-Verbose "$((get-date).TimeOfDay.ToString()) [BEGIN ] Starting: $($MyInvocation.Mycommand)" Write-Verbose "$((get-date).TimeOfDay.ToString()) [BEGIN ] Initializing array" $a = @() } #begin Process { Write-Verbose "$((get-date).TimeOfDay.ToString()) [PROCESS] Processing $Computername" # code goes here } #process End { Write-Verbose "$((get-date).TimeOfDay.ToString()) [END ] Ending: $($MyInvocation.Mycommand)" } #end } #function
Function TryMe2 { [cmdletbinding()] Param( [Parameter(Position = 0, Mandatory,ValueFromPipeline)] [string]$Computername ) Begin { $start = Get-Date Write-Verbose "00:00:00.0000000 [BEGIN ] Starting: $($MyInvocation.Mycommand)" Write-Verbose "$((New-TimeSpan -Start $start).ToString()) [BEGIN ] Initializing array" $a = @() } #begin Process { Write-Verbose "$((New-TimeSpan -Start $start).ToString()) [PROCESS] Processing $Computername" # code goes here Start-Sleep -Milliseconds 1234 } #process End { Write-Verbose "$((New-TimeSpan -Start $start).ToString()) [END ] Ending: $($MyInvocation.Mycommand)" } #end } #function
Function TestThis { [cmdletbinding()] Param() Write-Verbose "Execution Metadata:" Write-Verbose "User = $($env:userdomain)\$($env:USERNAME)" $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() $IsAdmin = [System.Security.Principal.WindowsPrincipal]::new($id).IsInRole('administrators') Write-Verbose "Is Admin = $IsAdmin" Write-Verbose "Computername = $env:COMPUTERNAME" Write-Verbose "OS = $((Get-CimInstance Win32_Operatingsystem).Caption)" Write-Verbose "Host = $($host.Name)" Write-Verbose "PSVersion = $($PSVersionTable.PSVersion)" Write-Verbose "Runtime = $(Get-Date)" }
That might be a bit much to include in every script. What could be easier is an external function I could load in my profile.
Function Get-ExecutionMetadata { [cmdletbinding()] Param() $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() $IsAdmin = [System.Security.Principal.WindowsPrincipal]::new($id).IsInRole('administrators') $os = (Get-CimInstance Win32_Operatingsystem).Caption $meta = [pscustomobject]@{ User = "$($env:userdomain)\$($env:USERNAME)" IsAdmin = $IsAdmin Computername = $env:COMPUTERNAME OS = $os Host = $($host.Name) PSVersion = $($PSVersionTable.PSVersion) Runtime = $(Get-Date) } $meta }
I could then call this code in my function.
Function Get-Foo { [cmdletbinding()] Param( [Parameter(Position = 0, Mandatory, ValueFromPipeline)] [string[]]$Computername ) Begin { Write-Verbose "[BEGIN ] Starting: $($MyInvocation.Mycommand)" #don't pass -Verbose to the Get-ExecutionMetadata function $metadata = Get-ExecutionMetadata -Verbose:$false | Out-String Write-Verbose "[BEGIN ] Execution Metadata:" Write-Verbose $metadata Write-Verbose "[BEGIN ] Initializing foo array" [email protected]() } #begin Process { foreach ($computer in $computername) { Write-Verbose "[PROCESS] Getting foo from $($computer.toUpper())" #code happens [pscustomobject]@{Computername = $computer;Time = (Get-Date).TimeOfDay} } } #process End { Write-Verbose "[END ] Ending: $($MyInvocation.Mycommand)" } #end }
I hope you’ll let me know if this was helpful and how you start using it.
More in PowerShell
Most popular on petri