PowerShell Basics

What Formatting cmdlets do to your data

I have seen an increasing number of questions recently where the answer has been to remove Format-Table from the pipeline. As an example consider the names of the processes running on your machine

Get-Process -Name calc | Stop-Process

 

works because you are piping the selected object into the Stop-Process cmdlet.

Now think about this

Get-Process -Name calc | select Name | Stop-Process

 

a bit more long winded (the select isn’t necessary) but the resultant object hitting Stop-Process identifies a process by name which is all Stop-Process needs to work.

 

As a way to approach the reason for the first sentence in the post is there any difference between these two statements?

Get-Process -Name calc

Get-Process -Name calc | Format-Table

 

The output to screen looks identical. So  I should be able to do this:

Get-Process -Name calc | Format-Table | Stop-Process

 

What I get is a bunch of errors of the form:

Stop-Process : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
At line:1 char:41
+ Get-Process -Name calc | Format-Table | Stop-Process
+                                         ~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (Microsoft.Power...FormatStartData:PSObject) [Stop-Process], ParameterB
   indingException
    + FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.StopProcessCommand

 

The reason for this is simple.  The Format cmdlets destroy your pipeline and objects.  They take what they are given and output formatting directives. The only thing you can do with them is display them on screen (you can pipe to a text file but effectively the same thing)

 

If in doubt about what you’re dealing with at any time use Get-member

£> Get-Process | Get-Member

   TypeName: System.Diagnostics.Process

 

£> Get-Process | select name | Get-Member

   TypeName: Selected.System.Diagnostics.Process

 

Notice the slight change in that its now Selected.System.Diagnostics.Process instead of System.Diagnostics.Process

This only applies if you’re selecting a subset of properties. If you want the first N objects

 

£> Get-Process | select -First 5 | Get-Member

   TypeName: System.Diagnostics.Process

 

You still have the original type.

 

However, lets look at formatting

£> Get-Process | Format-Table | Get-Member

TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatStartData

TypeName: Microsoft.PowerShell.Commands.Internal.Format.GroupStartData

TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData

TypeName: Microsoft.PowerShell.Commands.Internal.Format.GroupEndData

TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatEndData

 

You get 5 different objects out – none of which have anything to do with your processes – apart from the values.

The Format cmdlets are designed to format your data for display. Thats all they do. Once your data hits the Format cmdlets that data can only be used fro display – nothing else. Put your Format cmdlets at the end of your pipeline and don’t attempt to do anything else with the data.

The little changes that make a difference

Each version of PowerShell introduces a new headline feature – remoting, workflows, DSC, OneGet in version 2,3,4 and 5 respectively. While this can change the way we work there are also a host of little changes that come along that are often overlooked.

 

One example is a change to Get-ChildItem introduced in PowerShell 3.0.

 

Consider getting a directory listing:

Get-ChildItem -Path C:\Windows

 

This will give all subfolders and file in the given folder.

 

If you just wanted the files you had to do this:

Get-ChildItem -Path C:\Windows | where {$_.PSIsContainer}

 

If you want just the files you use:
Get-ChildItem -Path C:\Windows | where {-not $_.PSIsContainer}

 

or the slightly shorter but not as easy to read:
Get-ChildItem -Path C:\Windows | where {!$_.PSIsContainer}

 

The PSIsContainer property name is not intuitive and I rarely remember the name exactly and try ISPSContainer first or some other variant.

 

Two additional filtering parameters were added to Get-ChildItem

Get-ChildItem -Path C:\Windows –Directory

 

and
Get-ChildItem -Path C:\Windows -File

 

produce listings of folders and files respectively.

 

A small simple change that makes life easier.

 

There are a lot of small changes like this scattered through the later PowerShell versions – I’d recommend going through the release notes to track down the ones that will be useful to you.

Output from jobs

I tripped over a little problem the other day that’s worth reporting.  I was running something like this:

 

$sb = {
$procs = get-service
$procs | Export-Csv test.csv -NoTypeInformation
}

Start-Job -ScriptBlock $sb -Name test

 

I was collecting some data and outputting a CSV.  My problem was more complex but this stands as a good example

 

I didn’t get the data I wanted

 

Thinking about it I put the full path to where I wanted the CSV

 

$sb = {
$procs = get-service
$procs | Export-Csv C:\MyData\scripts\Test\test.csv -NoTypeInformation
}

Start-Job -ScriptBlock $sb -Name test

 

And it works.

 

So where did my data go in the original version?

 

I ran this

 

$sb = {
Get-Location

$procs = get-service
$procs | Export-Csv test.csv -NoTypeInformation
}

Start-Job -ScriptBlock $sb -Name test

 

And then pulled the data from the job

 

£> Receive-Job -Id 10

Path
----
C:\Users\Richard\Documents

 

Obvious really – a job runs in a new powershell process that doesn’t run your profile so it starts in the default location  - which is your home directory. And sure enough the CSV file is there

 

£> ls C:\Users\Richard\Documents\*.csv


    Directory: C:\Users\Richard\Documents


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        14/09/2014     11:50      46042 test.csv

 

I can’t remember how many times I’ve told people that PowerShell jobs run in a separate process so I should have realised.  Excellent example of the more you know the more you need to learn

Grains of rice on a chess board

There is a story about the inventor of chess being rewarded by putting 1 grain of rice on the first square of the board; 2 on the second and so on.  How much rice does that come to?

 

The total number of grains is 1.84467440737096E+19

 

At 25mg per grain thats 461168601842739 kilogrammes of rice

 

Which in more understandable terms is:

 

461,168,601,842.739 metric tonnes

 

or


453,886,749,619.642 tons

 

That’s a lot of rice

 

If you want to play around with the numbers the calculations are:

 

[double]$result = 0

1..64 |
foreach {
 
$square = [math]::Pow(2, ($psitem -1))
$result += $square

}

$wt = 0.025

$totalweight = ($wt * $result)/1000
$totalweight

$mtwt = $totalweight /1000
$mtwt

$tons = $totalweight * 0.00098421
$tons

Event Log Providers

An event log provider is writes to an event log.  I’ve used WMI in the past to get these but while looking for somethign else discovered that Get-WinEvent can also find this information

 

Get-WinEvent -ListProvider * | ft Name, LogLinks -AutoSize –Wrap

 

Provides a nice long list of all of the providers and the event logs they write to.

 

Usually I’m only interested in what’s writing to a particular event log. And that’s where things get a bit more messy.

 

The loglinks are supplied as a System.Collections.Generic.IList[System.Diagnostics.Eventing.Reader.EventLogLink] LogLinks  object that doesn’t play nicely with –in or –contains

 

So we need a bit of PowerShell manipulation to get what we want

 

$log = 'System'

Get-WinEvent -ListProvider * |
foreach {
 
if ($log -in ($psitem | select -ExpandProperty Loglinks | select -ExpandProperty Logname)){
    New-Object -TypeName psobject -Property @{
      Name = $psitem.Name
      Log = $log
    }
}
}

 

The trick here is that the loglinks are a collection of objects so you need to expand them twice to get to the name.  Not pretty but it works

Count property

Its frequently said that PowerShell is so big that no one can know everything about it.  I proved that today when I “discovered” a change in PowerShell of which I wasn’t aware.

 

If you create an array:

£> $a = 1,2,3

You can then get the number of members of that array i.e. its length

 

£> $a.count
3

 

£> $a[0]
1

 

In PowerShell 1.0 and 2.0 if you tried that on a variable that only held a single value you would get an error when you tried to access the first value:

£> $b = 1


£> $b.count

The count property returns nothing

 

£> $b[0]
Unable to index into an object of type System.Int32.
At line:1 char:4
+ $b[ <<<< 0]
    + CategoryInfo          : InvalidOperation: (0:Int32) [], RuntimeException
    + FullyQualifiedErrorId : CannotIndex

 

This changed in PowerShell 3.0 and later

£> $b = 1
£> $b.count
1


£> $b[0]
1

 

You can even try other indices
£> $b[1]
£>

 

And just get nothing back rather than an error.

 

This is really useful as you can now safely test on the Count property and if the value is greater than 1 to determine if its a collection.  Alternatively always treat it as a collection and iterate over the number of elements.  I can see this simplifying things for me in quite a few situations

foreach

I was asked about foreach today and responded with a description of who foreach-object works. Thinking about it I should have realised that part of the issue with foreach is the confusion that arises between foreach and foreach - -  that is the difference between the foreach PowerShell statement and the foreach alias of the foreach-object cmdlet.

 

To unravel the confusion there are two different things referred to as foreach. The confusion is that they do very similar things but are used in different ways.

 

The first is the PowerShell statement which is used to step through each value in a collection of values:

 

$procs = Get-Process

foreach ($proc in $procs) {

New-Object -TypeName PSObject -Property @{
   Name = $proc.Name
   SysMen =  $proc.NonpagedSystemMemorySize + $proc.PagedSystemMemorySize64
}

}

 

You create your collection of objects and then use foreach to step through them. It is convention to make the collection plural and the individual member of the collection its singular.  Within the script block you can define what happens to the object.

 

I know I could have a performed this action is a simpler way but I wanted to demonstrate how foreach works. The simpler way would be:

Get-Process |
select Name,
@{Name = 'SysMen';
Expression = {$_.NonpagedSystemMemorySize + $_.PagedSystemMemorySize64}}

 

Now we’ve got that out of the way what about the other foreach which is the alias of foreach-object.  This can be use to iterate over a collection of objects. The main difference is that the objects are usually piped into foreach:

 

Get-Process |
foreach {

New-Object -TypeName PSObject -Property @{
   Name = $_.Name
   SysMen =  $_.NonpagedSystemMemorySize + $_.PagedSystemMemorySize64
}

}

 

If you don’t like using $_ to represent the object on the pipeline try

Get-Process |
foreach {

New-Object -TypeName PSObject -Property @{
   Name = $psitem.Name
   SysMen =  $psitem.NonpagedSystemMemorySize + $psitem.PagedSystemMemorySize64
}

}

 

which is exactly equivalent to

Get-Process |
ForEach-Object {

New-Object -TypeName PSObject -Property @{
   Name = $psitem.Name
   SysMen =  $psitem.NonpagedSystemMemorySize + $psitem.PagedSystemMemorySize64
}

}

 

Using the cmdlet or its alias you can set up script blocks to process once when the first object reaches foreach (BEGIN), once per object on the pipeline (PROCESS) and once when the last object has been processed (END)

Get-Process |
ForEach-Object `
-BEGIN {
  Write-Host "First object about to be processed"
} `
-PROCESS {
New-Object -TypeName PSObject -Property @{
   Name = $psitem.Name
   SysMen =  $psitem.NonpagedSystemMemorySize + $psitem.PagedSystemMemorySize64
}
}`
-END {
Write-Host "Last object processed"
}

 

Your ouput looks like this

 

First object about to be processed

Name                                                                      SysMen
----                                                                      ------
armsvc                                                                    164096
concentr                                                                  200400
conhost                                                                   119104
csrss                                                                     153664
csrss                                                                     407760
           <truncated>

WUDFHost                                                                  103696
WWAHost                                                                   778816
WWAHost                                                                   785120
Yammer.Notifier                                                           566304
Last object processed

 

More info is available in the help files for foreach-object and about_foreach

Can it -whatif

One of the nice things about PowerShell is that it can help you prevent mistakes. Many of the cmdlets that make changes to you system have a –whatif parameter that allows you to test your actions:

 

£> Get-Process | Stop-Process -WhatIf
What if: Performing the operation "Stop-Process" on target "armsvc (1564)".
What if: Performing the operation "Stop-Process" on target "audiodg (3004)".
What if: Performing the operation "Stop-Process" on target "concentr (7080)".
What if: Performing the operation "Stop-Process" on target "conhost (3628)".

 

etc

 

 

The –whatif parameter is only present on cmdlets that make changes and then only if the team writing the cmdlet implemented it – they should but you can’t guarantee it happened. So how can you find out which cmdlets implement –whatif?

 

Use Get-Command

 

Compare these 2 commands.

 

£> Get-Command -Module CimCmdlets | select Name

Name
----
Export-BinaryMiLog
Get-CimAssociatedInstance
Get-CimClass
Get-CimInstance
Get-CimSession
Import-BinaryMiLog
Invoke-CimMethod
New-CimInstance
New-CimSession
New-CimSessionOption
Register-CimIndicationEvent
Remove-CimInstance
Remove-CimSession
Set-CimInstance

 

shows the cmdlets in a module

 

£> Get-Command -Module CimCmdlets -ParameterName Whatif | select Name

Name
----
Invoke-CimMethod
New-CimInstance
Remove-CimInstance
Remove-CimSession
Set-CimInstance

 

Now you can test a module to see which cmdlets have –whatif enabled.  You can also test at just the cmdlet level:

£> Get-Command *process -ParameterName Whatif  -CommandType cmdlet | select Name

Name
----
Debug-Process
Stop-Process


£> Get-Command *wmi* -ParameterName Whatif  -CommandType cmdlet | select Name

Name
----
Invoke-WmiMethod
Remove-WmiObject
Set-WmiInstance

Select-Object or Where-Object

Both Select-Object and Where-Object (referred to by their aliases of select and where from now on) are both used to filter data.

 

It is important to know the way these 2 cmdlets are used.

 

Where is used to restrict the objects on the pipeline to those where one or more properties satisfy the filter criteria e.g.

Get-Process | where CPU -gt 20

 

You get a reminder of this if you you use the full syntax

Get-Process | where -FilterScript {$_.CPU -gt 20}

 

As a matter of style you very rarely see anyone using the parameter name –FilterScript.

 

Select is used to cut the number of properties on an object to just those you want to work with e.g.

Get-Process | select Name, Id, CPU

 

If you want just those properties for the processes where CPU time is greater than 20 seconds you need to combine them on the pipeline:

Get-Process | where CPU -gt 20 | select Name, Id, CPU

 

There isn’t a way to embed a where type filter in a select or vice versa. Keep it simple. Use the pipeline and let the cmdlets do the job for which they were designed.

Invoke-Item tips

Invoke-Item is another cmdlet that you don’t see used that often but there is one place where its invaluable – opening files. If you give Invoke-Item the path to a file

Invoke-Item -Path .\procs.txt

 

The file will be opened with the default application associated with that extension. In this case Notepad.

 

If you use a PowerShell script file

Invoke-Item .\t1.ps1

 

It will be opened in Notepad for you to examine.  CSV files automatically open in Excel and other Office files are opened in the correct application.

 

Using the alias ii for Invoke-item makes it even easier.