Pages

Wednesday, June 15, 2016

PowerShell v3 in a Year Day 1

I am finally getting around to working through PowerShells Get-Help for version 3. As was previously done for version 2, I will explore the current versions help topics in pairs. Each day I will cover two topics. At the moment, there are 523 items on my machine. For reference, I used this script to get a list of what I needed to write on:
$counter = 1
Get-Help * |
Sort-Object Name |
ForEach-Object {
      Write-Output "$($counter): $($_.Name)"
      $counter++
} |
Out-File-FilePath C:PowershellProjectsPowerShellInAYearPowershellInAYear.txt-Force -EncodingASCII
Note, there should be a few more than a standard PowerShell instance, because I have some other PowerShell products installed. SQL Server particularly comes to mind. That being said, I will explore my first item today: %.Some days I will cover more, such as the Set-Location aliases, A:, B:, C:, etc. But, for now, I am going to tackle one by itself since it will force me to cover a good bit of ground.

%: ForEach-Object Alias

The % character is actually an alias for the ForEach-Object cmdlet. Without rehashing every single line in the help, you can get that yourself with Get-Help % -Full. Basically, ForEach-Object, for which % is an alias, "Performs an operation against each item in a collection of input objects." So, if you have a collection, lets say an array of 5 numbers, you can enumerate the collection one by one. ForEach-Object is one way to do just that. In this case, it could look like this:
1..5 |
% {
      $_
}
When you run this simple command, it returns the numbers, 1 through 5, one at a time:
1
2
3
4
5
In this scenario the block contains a symbol, $_, which indicates the pipeline operator. % passes the objects in the collection to the block one at a time. When it loops, the $_ symbol indicates the item currently being processed.

There are two ways to get information to iterate:

  1. pipeline 
  2. -InputObject
In our previous example we use the pipeline, |, to pass items in the collection to the loop. The alternative, -InputObject, could be used this way,
% -InputObject (1..5) {
      $_
}
This particular demonstration is a very unusual syntactical way to do it, but, when it is run, it produces the same exact sequence of 5 numbers as shown above.

One thing to note about version 3s implementation of ForEach-Object is that you can now drop the surrounding braces and the $_ variable. For example, in version 2, you had to explicitly reference the pipeline variable and wrap the expression in an opening and closing brace, as shown below:
Get-Process| ForEach-Object { $_.ProcessName }
Version 3, however, allows you to drop the braces and the pipeline variable ($_), like this
  Get-Process  | foreach name
This syntactical shortcut makes it easier for folks new to PowerShell as it eliminates a variety of complex constructs, such as the required braces, and, the $_ variable.

Also, noteworthy in version 3 is the introduction of the the $PSItem variable. As outlined in this link,
New V3 Language Features
you can substitute the $_ variable (required for v2) with the $PSItem variable (in v3). Unfortunately, there is no on-the-box help I can find about this item yet, but, it is documented in a few spots on the PowerShell Team blog.

If you want to take full advantage of the ForEach-Object cmdlet, the next idea to focus on is the two parameter sets:
  1. ForEach-Object [-Process] [-Begin ] [-End ] [-InputObject < PSObject > ][-RemainingScripts ] [-Confirm []] [-WhatIf []][]
  2. ForEach-Object [-MemberName] [-ArgumentList < Object [] >] [-InputObject ] [-Confirm []] [-WhatIf []] []
The first parameter set is the one I find I take advantage of most with its -Begin, -Process and -End blocks. These blocks represent opportunities, when working with collection iteration, to perform a one-time, pre-processing step (with -Begins block), to handle objects passed to the cmdlet one at a time (with the -Process block), and, a one-time, post-processing block (with -End). For example, if I want to print a folder path, iterate over a file collection and get file names, then, provide a total, I could do it this way:
Get-ChildItem -Path ($Path = C:Windows) |
Select-Object -First 5 |
Where-Object {$_.PSIsContainer} |
ForEach-Object -Begin {"Processing $path."} `
      -Process {$_.name} `
      -End{"$path has $([IO.Directory]::GetFiles($Path).Count) files."}
When I run it, I get a header line, a folder set (of the first 5 folders), and, a file count to tag the footer.
Processing C:Windows.
addins
AppCompat
AppPatch
assembly
Boot
C:Windows has 39 files.
This is just one, simple example of how you can utilize ForEach-Object to provide some more interesting processing than basic, collection iteration.

The -RemainingScripts parameter offers you the ability to call any additional commands, functions, scripts, etc, as a secondary part of the -Process block. So, lets say you needed to make a change in the -Process block, but, could not perform an additional step until that block had exited. You could use the -Remaining Scripts parameter as a way to perform any secondary processing independent of the -Process block, but, sequential to -Process and prior to the execution of the -End block. In a way, this gives you two -Process blocks, or, a -Process1 and -Process2 block.

-Confirm and -Whatif are two switches to be used in conjunction with commands that potentially alter the machine. -Confirm indicates that a prompt will be displayed prior to execution of the command. In a way, its a safety valve, meant to stop you from unintentionally hurt yourself, i.e., permanently deleting files you shouldnt delete. Similarly, -WhatIf is there to demonstrate what will happen if you were to run a given command, but, not actually make the change. I use this one a lot with Rename-Item, Copy-Item, Remove-Item etc. Both of these are the "your job depends on this being right" safety measures. Use them judiciously, but, use them.

One of the most interesting things, probably because it is new, and, as of yet unexplored, items with ForEach-Object is the second parameter set. It really only has two parameters of note: -MemberName and ArgumentList. After looking for examples online and in help, I had to come up with my own. In this case, I found this. If I want to check a collection, lets say an array of 1 to 5, to see if it has a given member (2 in this case), I can use this approach:
ForEach-Object-InputObject (1..5) -MemberName contains -ArgumentList 2
When I run this command, it returns true. So, as far as I can tell, this parameter set iterates over a collection and analyzes the member (either a property or a method) one by one. Here is iterates over a collection 1 to 5, and, test the method Contains to see if each item matches 2 or not. When it finds a match, it returns $true. So, this use of the cmdlet is a way to test if the cmdlet has a specific member.

As far as I can tell, the -ArgumentList parameter only works in conjunction with methods. It doesnt make any sense for a property to require arguments. Another example might demonstrate how to use the -MemberName parameter only.
ForEach-Object-InputObject (dir.) -MemberName count
This usage focuses on a property, count, and does not need arguments. So, when it runs, it iterates over the collection, and, returns a count of the number of items in the InputObjects collection, dir .. To validate the result, I ran this command and got the same value, 13:
(dir.).Count
I am still looking to find a really great way to take advantage of this cmdlet, particularly with this parameter set, but, it is good to know it is out there.

EDIT: Thanks to @Alexandair, I corrected a few errors. First,  Get-Process  | foreach processname  does not work. It has to be  Get-Process  | foreach name  or Get-Process  | foreach -MemberName name. Second, I incorrectly referred to the $_ symbol as the pipeline operator, when, in fact, it is the pipeline variable. 

Related Posts by Categories

0 comments:

Post a Comment