Table of Contents
When I first started learning PowerShell, if I couldn’t accomplish a task with a PowerShell one-liner, I went back to the GUI. Over time, I built my skills up to writing scripts, functions, and modules. Don’t allow yourself to become overwhelmed by some of the more advanced examples you may see on the internet. No one is a natural expert with PowerShell. We were all beginners at one time.
I have a bit of advice to offer those of you who are still using the GUI for administration: install the management tools on your admin workstation and manage your servers remotely. This way it won’t matter if the server is running a GUI or Server Core installation of the operating system. It’s going to help prepare you for managing servers remotely with PowerShell.
One-Liners
A PowerShell one-liner is one continuous pipeline and not necessarily a command that’s on one physical line. Not all commands that are on one physical line are one-liners.
Even though the following command is on multiple physical lines, it’s a PowerShell one-liner because it’s one continuous pipeline. It could be written on one physical line, but I’ve chosen to line break at the pipe symbol. The pipe symbol is one of the characters where a natural line break is allowed in PowerShell.
Get-Service |
Where-Object CanPauseAndContinue -eq $true |
Select-Object -Property *
Natural line breaks can occur at commonly used characters including comma (,
) and opening brackets ([
), braces ({
), and parenthesis ((
). Others that aren’t so common include the semicolon (;
), equals sign (=
), and both opening single and double quotes ('
,"
).
PS C:\> Get-Service |
>> Where-Object CanPauseAndContinue -eq $true |
>> Select-Object -Property *
Name : dptftcs
RequiredServices : {}
CanPauseAndContinue : True
CanShutdown : False
CanStop : True
DisplayName : Intel(R) Dynamic Tuning Technology Telemetry Service
DependentServices : {}
MachineName : .
ServiceName : dptftcs
ServicesDependedOn : {}
ServiceHandle : SafeServiceHandle
Status : Running
ServiceType : Win32OwnProcess
StartType : Automatic
Site :
Container :
...
This next example isn’t a PowerShell one-liner because it’s not one continuous pipeline. It’s two separate commands on one line, separated by a semicolon.
PS C:\> $Service = 'w32time'; Get-Service -Name $Service
Status Name DisplayName
------ ---- -----------
Stopped w32time Windows Time
Many programming and scripting languages require a semicolon at the end of each line. While they can be used that way in PowerShell, it’s not recommended because they’re not needed.
Filtering Left
The results of the commands shown in this chapter have been filtered down to a subset. For example, Get-Service was used with the Name parameter to filter the list of services that were returned to only the Windows Time service.
In the pipeline, you always want to filter the results down to what you’re looking for as early as possible. This is accomplished using parameters on the first command or, the one to the far left. This is sometimes called filtering left.
The following example uses the Name parameter of Get-Service to immediately filter the results to the Windows Time service only.
PS C:\> Get-Service -Name w32time
Status Name DisplayName
------ ---- -----------
Stopped w32time Windows Time
It’s not uncommon to see examples where the command is piped to Where-Object to perform the filtering.
PS C:\> Get-Service | Where-Object Name -eq w32time
Status Name DisplayName
------ ---- -----------
Stopped W32Time Windows Time
The first example filters at the source and only returns the results for the Windows Time service. The second example returns all the services then pipes them to another command to perform the filtering.
While this may not seem like a big deal in this example, imagine if you were querying a list of Active Directory users. Do you really want to return the information for many thousands of user accounts from Active Directory only to pipe them to another command that filters them down to a tiny subset? My recommendation is to always filter left even when it doesn’t seem to matter. You’ll be so use to it that you’ll automatically filter left when it really does matter.
The order that the commands are specified in does indeed matter when performing filtering. For example, consider the scenario where you are using Select-Object to select only a few properties and Where-Object to filter on properties that won’t be in the selection. In that scenario, the filtering must occur first, otherwise the property won’t exist in the pipeline when try to perform the filtering.
Get-Service |
Select-Object -Property DisplayName, Running, Status |
Where-Object CanPauseAndContinue
The command in the previous example doesn’t return any results because the CanStopAndContinue property doesn’t exist when the results of Select-Object are piped to Where-Object. That particular property wasn’t “selected”. In essence, it was filtered out. Reversing the order of Select-Object and Where-Object produces the desired results.
Get-Service |
Where-Object CanPauseAndContinue |
Select-Object -Property DisplayName, Status
DisplayName Status
----------- ------
Intel(R) Dynamic Tuning Technology Telemetry Service Running
Intel(R) Graphics Command Center Service Running
Intel(R) Innovation Platform Framework Service Running
Workstation Running
Lenovo Smart Standby Running
Secondary Logon Running
Windows Image Acquisition (WIA) Running
Web Threat Defense Service Running
Web Threat Defense User Service_79cbb Running
Windows Management Instrumentation Running
The Pipeline
Depending on how thorough a commands help is, it may include an INPUTS and OUTPUTS section.
help Stop-Service -Full
Only the relevant section of the help is shown in the previous results. As you can see, the INPUTS section states that a ServiceController or a String object can be piped to the Stop-Service cmdlet. It doesn’t tell you which parameters accept that type of input. One of the easiest ways to determine that information is to look through the different parameters in the full version of the help for the Stop-Service cmdlet.
...
INPUTS
System.ServiceProcess.ServiceController, System.String
You can pipe a service object or a string that contains the name of a service
to this cmdlet.
OUTPUTS
None, System.ServiceProcess.ServiceController
This cmdlet generates a System.ServiceProcess.ServiceController object that
represents the service, if you use the PassThru parameter. Otherwise, this
cmdlet does not generate any output.
...
Once again, I’ve only shown the relevant portion of the help in the previous set of results. Notice that the DisplayName parameter doesn’t accept pipeline input, the InputObject parameter accepts pipeline input by value for ServiceController objects, and the Name parameter accepts pipeline input by value for string objects. It also accepts pipeline input by property name.
...
-DisplayName <String[]>
Specifies the display names of the services to stop. Wildcard characters are
permitted.
Required? true
Position? named
Default value None
Accept pipeline input? False
Accept wildcard characters? false
-InputObject <ServiceController[]>
Specifies ServiceController objects that represent the services to stop. Enter a
variable that contains the objects, or type a command or expression that gets the
objects.
Required? true
Position? 0
Default value None
Accept pipeline input? True (ByValue)
Accept wildcard characters? false
-Name <String[]>
Specifies the service names of the services to stop. Wildcard characters are
permitted.
Required? true
Position? 0
Default value None
Accept pipeline input? True (ByPropertyName, ByValue)
Accept wildcard characters? false
...
When a parameter accepts pipeline input by both property name and by value, it always tries by value first. If by value fails, then it tries by property name. By value is a little misleading. I prefer to call it by type. This means if you pipe the results of a command that produces a ServiceController object type to Stop-Service, it binds that input to the InputObject parameter. But if you pipe the results of a command that produces String output to Stop-Service, it binds it to the Name parameter. If you pipe the results of a command that doesn’t produce a ServiceController or String object to Stop-Service, but it does produce output containing a property called Name, then it binds the Name property from the output to the Name parameter of Stop-Service.
Determine what type of output the Get-Service command produces.
PS C:\> Get-Service -Name w32time | Get-Member
TypeName: System.ServiceProcess.ServiceController
Name MemberType Definition
---- ---------- ----------
Name AliasProperty Name = ServiceName
RequiredServices AliasProperty RequiredServices = ServicesDependedOn
Get-Service produces a ServiceController object type.
As you previously saw in the help, the InputObject parameter of Stop-Service accepts ServiceController objects via the pipeline by value (by type). This means that when the results of the Get-Service cmdlet are piped to Stop-Service, they bind to the InputObject parameter of Stop-Service.
Get-Service -Name w32time | Stop-Service
Now to try string input. Pipe w32time to Get-Member just to confirm that it’s a string.
PS C:\> 'w32time' | Get-Member
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
As previously shown in the help, piping a string to Stop-Service binds it by value to the Name parameter of Stop-Service. Test this by piping w32time to Stop-Service.
'w32time' | Stop-Service
Notice that in the previous example, I used single quotes around the string w32time. In PowerShell, you should always use single quotes instead of double quotes unless the contents of the quoted string contains a variable that needs to be expanded to its actual value. By using single quotes, PowerShell doesn’t have to parse the contents contained within the quotes so your code runs a little faster.
Create a custom object to test pipeline input by property name for the Name parameter of Stop-Service.
$CustomObject = [pscustomobject]@{
Name = 'w32time'
}
The contents of the CustomObject variable is a PSCustomObject object type and it contains a property named Name.
PS C:\> $CustomObject | Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Name NoteProperty string Name=w32time
Finding pipeline input the easy way
The MrToolkit module contains a function named Get-MrPipelineInput. This cmdlet can be used to easily determine which parameters of a command accept pipeline input, what type of object they accept, and if they accept pipeline input by value or by property name.
Find-Module -Name MrToolkit | Install-Module
PS C:\> Get-MrPipelineInput -Name Stop-Service
ParameterName ParameterType ValueFromPipeline ValueFromPipelineByPropertyName
------------- ------------- ----------------- -------------------------------
InputObject System.ServiceProcess.ServiceController[] True False
Name System.String[] True True