PowerShell

Doing More with PowerShell Verbose Messages

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.

 

 

Sponsored Content

Maximize Value from Microsoft Defender

In this ebook, you’ll learn why Red Canary’s platform and expertise bring you the highest possible value from your Microsoft Defender for Endpoint investment, deployment, or migration.

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"

inserting a timestamp
Inserting a timestamp (Image credit: Jeff Hicks)

Write-Verbose "$((get-date).TimeOfDay.ToString()) Initializing array"

A timestamp alternative
A timestamp alternative (Image credit: Jeff Hicks)

One thing that I tend to do in all my functions is use a verbose message to indicate what scriptblock is being processed.

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.

verbose scriptblocks
Verbose scriptblocks (Image credit: Jeff Hicks)

What about combining some of these things?

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

detailed scripting messages
Detailed scripting messages (Image credit: Jeff Hicks)

How about a variation that shows how much time is elapsing? Here’s a variation that displays a timespan calculated from the first command.

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

Verbose output with a timespan
Verbose output with a timespan (Image credit: Jeff Hicks)

One of the reasons I like to use Write-Verbose is that if someone is running one of my commands and encounters an error, I can have them start a transcript, which now works in the PowerShell ISE, run the command with -Verbose, stop the transcript, and send it to me. I can then see exactly what is happening (or not) for the user. With that in mind, I thought it might be helpful to include some verbose metadata.

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)"

}
<a href="https://petri.com/wp-content/uploads/2017/02/image-5.png"><img style="padding-top: 0px; padding-left: 0px; padding-right: 0px; border-width: 0px;" title="Verbose execution metadata (Image credit: Jeff Hicks)" src="https://petri.com/wp-content/uploads/2017/02/image_thumb-5.png" alt="Verbose execution metadata" width="644" height="145" border="0" /></a>

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

}

verbose metadata in action
Verbose metadata in action (Image credit: Jeff Hicks)

There’s really no limit to what you could include in a verbose message. The only guidance I would offer is to make it meaningful. I also encourage you to make your verbose output easy to read.

Are you intrigued? Here are some other elements you might consider adding to your verbose messaging:

  • Username
  • Computer name
  • Parameter set name
  • PSBoundparameters
  • PowerShell version

I hope you’ll let me know if this was helpful and how you start using it.

Related Topics:

BECOME A PETRI MEMBER:

Don't have a login but want to join the conversation? Sign up for a Petri Account

Register
Comments (0)

Leave a Reply

External Sharing and Guest User Access in Microsoft 365 and Teams

This eBook will dive into policy considerations you need to make when creating and managing guest user access to your Teams network, as well as the different layers of guest access and the common challenges that accompany a more complicated Microsoft 365 infrastructure.

You will learn:

  • Who should be allowed to be invited as a guest?
  • What type of guests should be able to access files in SharePoint and OneDrive?
  • How should guests be offboarded?
  • How should you determine who has access to sensitive information in your environment?

Sponsored by: