Have you ever run a script or function you received from someone else and wondered if it worked? It ran without showing an error, but then again, it returned nothing at all! You don’t have a clue what it did nor could see its progress as it was executing.
Because it didn’t return any object to the pipeline, you also can’t use its outcome in other commands. You are forced to write more code to check whether it did its job which results in wasted time and added complexity.
In this chapter, you’re going to learn some tips on what to return to your user, how often, and how to return output in many different ways.
Use Progress Bars Wisely
PowerShell has a handy cmdlet called Write-Progress. This cmdlet displays a progress bar in the PowerShell console. It’s never a good idea to leave the user of your script staring at a blinking cursor. The user has no idea if the task should take 10 seconds or 10 minutes. They also might think the script has halted or crashed in some manner. It’s important you provide some visual cues as to what’s going on.
Use Write-Progress for any task that takes more than 10 seconds or so. The importance increases with time. For more complicated scripts with many transactions, be sure to use Write-Progress at a higher level. Use the progress bar as the main indicator of progress and leave the smaller steps to a verbose message, for example.
Perhaps you have a script that connects to a list of servers and parses through a bunch of files on each server. Each server has a few thousand files that take 10–20 seconds each to read and parse through. Take a look here for an example of what this script might look like:
foreach ($file in Get-ChildItem -Path "\$namec$SomeFolderWithABunchOfFiles" -File) {
## Read and do stuff to each one of these files
}
}
In the preceding example, you essentially have two “progress layers” represented with a foreach loop – processing a server at a time and also processing a file at a time. Try to add a progress bar at the “higher-level layer” or processing the server. You wouldn’t want tens of thousands of file paths flying by on the progress bar. Instead, a messaging saying “Processing files on ABC server” that continually updates would be better.
For any script that returns some kind of output, a well-developed script contains two “layers” – processing and presentation. The processing “layer” contains all of the code necessary to perform whatever task is at hand. The “presentation” layer displays what the scripts outputs to the console. Never combine the two.
If you need to change up what the output looks like in the console, do it outside the script. For example, never use a Format-* cmdlet inside of a script. You should treat scripts like reusable tools. Unless you are 100% certain that script will never need to send output to another script or function, don’t attempt to format the output in the script. Instead, pipe objects from the script into a formatting command at the console or perhaps another “formatting” script.
Perhaps you have a script that reads some files. To make the output easier to look at, you decide to pipe the contents of Get-ChildItem to Format-Table.
## readsomefiles.ps1
Get-ChildItem -Path 'somepathhere' | Format-Table
The output looks fine but you then need to perform some action on each of those files; maybe it’s removing them. Knowing that you can pipe the contents of Get-ChildItem to Remove-Item, you add Remove-Item onto the end.
You’ll soon find out that PowerShell doesn’t like that at all. Why? Because Format-Table, just like any of the Format-* cmdlets, does not return objects to the pipeline. Instead of using a formatting cmdlet inside of the script, remove the Format-Table reference from the script and instead use the cmdlet in the console.
Verbose messaging comes in handy in many different scenarios from troubleshooting, script progress indication, and debugging. Use the Write-Verbose cmdlet as much as possible to return granular information about what’s happening in a script. Use verbose messages to display variable values at runtime, indicate what path code takes in a condition statement like if/then, or indicate when a function starts and stops.
There are no defined rules to indicate when to return a verbose message. Since verbose messaging is off by default, you don’t need to worry about spewing text to the console. It’s better to have more information than less when it comes to verbose messaging.
Perhaps you have a script that gathers ACLs for DNS records stored in Active Directory (AD).
When you run this script, it doesn’t return any messaging letting you know what’s going on. It only returns ACLs matching the DnsHostNameparameter. You could add some messaging to this function to make it better using some verbose messages.
You can see in the following some verbose messaging was added to the script. To see this verbose messaging when you run this script, you would use the Verboseparameter like .Get-DnsAdAcl.ps1 -Verbose.
PowerShell has six streams. You can think about three of those streams in terms of verbosity – Debug, Verbose, and Information. The debug stream is at the bottom and should return lots of granular information about code activity while the information stream should contain high-level messages.
Use the Write-Informationcmdlet to display top-level information similar to what would be down in a progress bar. The user doesn’t need to see variables, if/then logic, or any of that. Use Write-Information to display basic, high-level status messages about script activity.
Using the example from the “Use Write-Verbose” tip, you could improve that a bit by using Write-Information instead of Write-Verbose when the script starts and stops.
Write-Verbose -Message "Finished getting ACL for [$($Record.Name)]."
}
}
Write-Information -MessageData 'Script ending...'
When and where to use Write-Information vs. Write-Verbose is completely up to you. The use cases vary wildly. Just remember to use Write-Information to display higher-level activity and use Write-Verbose to display more granular activity.
Nothing will confuse a PowerShell developer more than when a script or function returns different types of objects. Keep it simple and ensure regardless of the circumstances, the command only returns one type.
When a script or function returns different types of objects based on various scenarios, it becomes hard to write code that takes input from that command.
Perhaps you’re writing a server inventory script. You have a script that queries a remote server and returns information like service status and user profiles on that server.
$servers = ('SRV1','SRV2','SRV3','SRV4')
foreach ($server in $servers) {
Get-Service -ComputerName $server
Get-ChildItem -Path "\$serverc$Users" -Directory
}
The preceding script returns two different types of objects – a System.Service.ServiceController object via Get-Service and a System.IO.DirectoryInfo object via Get-ChildItem.
Even though you want to see the output of each cmdlet, do not leave the script as is. Instead, consolidate these two types of output into your own object type, preferably a PSCustomObject. You can see an example in the following of returning one PSCustomObject object type for each server:
Allowing your scripts and functions to only return one object type forces standardization. It also makes it easier to chain functions together. If you can expect a certain object type to be returned for each function in a module, for example, you can write other functions that accept that input much easier than if you had to code around a lot of different types.
If a command returns information you have no use for, don’t allow it to return objects to the pipeline. Instead, assign the output to $null or pipe the output to the Out-Null cmdlet.
If the command should return the output sometimes but not all the time, create a PassThruparameter. By creating a PassThru parameter, you give the user the power to decide to return information or not.
Maybe you have a script that, as a part of it, creates a directory using New-Item. Some cmdlets that do not have the Get verb return unnecessary information; the New-Item cmdlet is one of them.
You can see in Figure 15-2 when you run New-Item, it returns an object. If you just need to create a directory, you probably don’t need that object. If you’d leave this command in your script, the script would return this object. Ensure that doesn’t happen.
Instead of allowing cmdlets and other functions to place unnecessary objects on the pipeline, send the output to $null. Assigning output to $null essentially removes it entirely and prevents anything from going to the pipeline.
You can also pipe output toOut-Nullbut I prefer assigning output to the$nullvariable. Why? Because when working with large collections, you should not use the pipeline at all, if possible for performance reasons.