Monthly Archive

Categories

PowerShell Basics

Filtering of Objects and Properties

Saw a post on the forum today that suggests people are still confused about how to perform filtering of objects and properties in PowerShell.

As with so much in PowerShell explanations are always better with examples.

 

Let’s start with the physical disks in a computer:

PS> Get-PhysicalDisk

FriendlyName               SerialNumber    CanPool OperationalStatus HealthStatus Usage            Size 
------------               ------------    ------- ----------------- ------------ -----            ---- 
Toshiba USB 2.0 Ext. HDD   WD-WCAMR3209671 False   OK                Healthy      Auto-Select 298.09 GB 
ST916082 1A                DEF10E8D9B36    False   OK                Healthy      Auto-Select 149.05 GB 
Samsung SSD 840 PRO Series S1AXNSAF329511V False   OK                Healthy      Auto-Select 476.94 GB

 

If you want to objects that match a specific criteria – for instance disk is larger than 300GB:

PS> Get-PhysicalDisk | Where-Object Size -gt 300GB

FriendlyName               SerialNumber    CanPool OperationalStatus HealthStatus Usage            Size 
------------               ------------    ------- ----------------- ------------ -----            ---- 
Samsung SSD 840 PRO Series S1AXNSAF329511V False   OK                Healthy      Auto-Select 476.94 GB

 

Where-Object is your friend.

 

What you’re actually doing – though very few people actually write it like this – is

Get-PhysicalDisk | Where-Object -Property Size -GT -Value 300GB

 

The help file for Where-Object lists the possible operators.

 

You can also show the original style syntax

Get-PhysicalDisk | Where-Object -FilterScript {$_.Size -gt 300GB}

 

Normal usage is to not write the –FilterScript parameter so it becomes

Get-PhysicalDisk | Where-Object {$_.Size -gt 300GB}

 

$_ represents the object currently on the pipeline. If you need to use multiply conditions in your filter you’ll need to use the older style syntax.

 

So far you’ve seen how reduce the number of objects on the pipeline. Where-Object filters out those that don’t match the given criteria.

 

If you want to reduce the number of properties that the objects on the pipeline possess you’ll need to use Select-Object

PS> Get-PhysicalDisk | Select-Object -Property FriendlyName, HealthStatus, Size

FriendlyName               HealthStatus         Size 
------------               ------------         ---- 
Toshiba USB 2.0 Ext. HDD   Healthy      320072933376 
ST916082 1A                Healthy      160041885696 
Samsung SSD 840 PRO Series Healthy      512110190592

 

More commonly written as

Get-PhysicalDisk | Select FriendlyName, HealthStatus, Size

 

PowerShell best practice is always to use the full cmdlet and parameter names in your scripts. The *-Object cmdlets and in particular Where-Object, Sort-Object and Select-Object are often abbreviated to Where, Sort and Select and the parameters only used where necessary.  This was the way I was advised to use them by Jeffrey Snover – who invented PowerShell -  when I wrote PowerShell in Practice. Good enough for me.

Append data to a file

A question on the forums - the user wanted to append data to a file. This is a common scenario when you’re creating a log file.

 

There’s 2 easy ways to do this.

 

Lets create a couple of variables with multi-line data

PS> $data = @'
>> This is
>> multiline data
>>
>> '@
PS> $data
This is
multiline data

 

PS> $data2 = @'
>> This is
>> more multiline
>> data
>> '@
PS> $data2
This is
more multiline
data

 

First you could use Out-File

PS> Out-File -FilePath of.txt -InputObject $data
PS> Out-File -FilePath of.txt -InputObject $data2 -Append
PS> Get-Content -Path of.txt
This is
multiline data

This is
more multiline
data

 

First time you call Out-File you don’t have to use –Appemd but you can. On subsequent calls use -Append to add the data – if you don’t the file will be overwritten with the new data.

 

Second option is one you don’t see so much – Add-Content. In earlier versions of PowerShell this was your only option

PS> Add-Content -Path ac.txt -Value $data
PS> Add-Content -Path ac.txt -Value $data2
PS> Get-Content -Path ac.txt
This is
multiline data

This is
more multiline
data

 

If the file doesn’t exist Add-Content will create it.

 

Two ways to append data to a file

Preserving property order

This is a very common pattern:

 

$os = Get-CimInstance -ClassName Win32_OperatingSystem
$comp = Get-CimInstance -ClassName Win32_ComputerSystem

$props = @{
OS = $os.Caption
InstallDate = $os.InstallDate
LastBoot = $os.LastBootUpTime
Make = $comp.Manufacturer
Model = $comp.Model
}

New-Object -TypeName PSObject -Property $props

 

Get some data – in this case a couple of WMI classes and create an output object.

Unfortunately, the output looks like this

 

Make        : Microsoft Corporation
Model       : Surface Pro 2
LastBoot    : 30/12/2016 09:41:52
OS          : Microsoft Windows 10 Pro Insider Preview
InstallDate : 08/12/2016 13:20:04

 

The property order is NOT preserved because you’re working with a hash table. If you absolutely have to preserve the property order use an ordered hash table

 

$os = Get-CimInstance -ClassName Win32_OperatingSystem
$comp = Get-CimInstance -ClassName Win32_ComputerSystem

$props = [ordered]@{
OS = $os.Caption
InstallDate = $os.InstallDate
LastBoot = $os.LastBootUpTime
Make = $comp.Manufacturer
Model = $comp.Model
}

New-Object -TypeName PSObject -Property $props

 

All you do is add [ordered] in front of the hashtable definition and your output becomes

 

OS          : Microsoft Windows 10 Pro Insider Preview
InstallDate : 08/12/2016 13:20:04
LastBoot    : 30/12/2016 09:41:52
Make        : Microsoft Corporation
Model       : Surface Pro 2

 

exactly what you defined

Calculating Standard Deviation – the class

You’ve seen how to calculate standard deviation and how to turn that calculation into a PowerShell function. This time we’ll use the calculation to create a class:

class stats {

static [double] StandardDeviation ([double[]]$numbers) {

$mean = $numbers | Measure-Object -Average | select -ExpandProperty Average
$sqdiffs = $numbers | foreach {[math]::Pow(($psitem - $mean), 2)}

$sigma = [math]::Sqrt( ($sqdiffs | Measure-Object -Average | select -ExpandProperty Average) )

return [math]::Round($sigma, 3)

}
}

 

Name the class stats. We’ll create a static method on the class that will calculate the Standard Deviation. The method takes an array of doubles as input – integers are converted automatically to double.

 

The calculation is the same as before. Just add the return keyword to ensure you actually get the output. Using return is mandatory in PowerShell classes.

 

Because its a static class you don’t need to create an instance of the class – just use it directly:

$numbers = 1..10
[stats]::StandardDeviation($numbers)
[stats]::StandardDeviation(1..10)

Calculating Standard Deviation – the function

Last time I showed how to calculate the standard deviation of a set of numbers and said the code could easily be turned into a function

function Measure-StandardDeviation {
[CmdletBinding()]
param (
[double[]]$numbers
)
$mean = $numbers | Measure-Object -Average | select -ExpandProperty Average
$sqdiffs = $numbers | foreach {[math]::Pow(($psitem - $mean), 2)}


$sigma = [math]::Sqrt( ($sqdiffs | Measure-Object -Average | select -ExpandProperty Average) )
[math]::Round($sigma, 3)
}

 

I’ve called the function Measure-StandardDeviation based on Measure-Object producing the mean.

The parameter is set to accept an array of doubles – floating point numbers – integers will be automatically converted to doubles.

To use the function

$numbers = 1..10
Measure-StandardDeviation -numbers $numbers

 

or create the input array directly

Measure-StandardDeviation -numbers (1..10)

Calculating Standard Deviations–the calculation

A while ago I saw something asking about calculating standard deviations for a set of numbers in PowerShell. You can calculate the the mean (average) of a set of numbers using Measure-Object

 

$numbers = 1..10


$mean = $numbers | Measure-Object -Average | select -ExpandProperty Average
$mean
5.5

 

To calculate the standard deviation you:

Subtract the mean of the set from each number in the set and square the difference. Then calculate the mean of those squared differences and finally take the square root of the result.

We already have the mean

 

so lets calculate the differences and square them

$sqdiffs = $numbers | foreach {[math]::Pow(($psitem - $mean), 2)}

 

The Pow static method of the math class is used to create the squares. The standard deviation (normally referred to as sigma) is calculated:

$sigma = [math]::Sqrt( ($sqdiffs | Measure-Object -Average | select -ExpandProperty Average) )
[math]::Round($sigma, 3)

 

You can calculate the mean of the squares of the differences using measure-object again. I used the Round static method of the math class to give my answer 3 decimal places.

 

Now that we can calculate the standard deviation we could turn this into a method, or a PowerShell class or even create a proxy function for Measure-Object that enables you to calculate standard deviation.

using help proactively

I was reading a thread on the forum about using a particular cmdlet and it occurred to me that the person posing the question hadn’t actually looked at the help file for the cmdlet.

 

One of the first things I do when coming across a new cmdlet – either in some code or when I need to perform a new task - is to read the FULL help file for the cmdlet.

Either 

Get-Help Get-Process –Full

 

or

Get-Help Get-Process –Online

 

The online version tends to be updated quicker than the downloadable version.

 

Two areas to concentrate on.

The parameter list shows you what parameters are available, the input and relevant information

 

The examples section is possibly the most useful as you’ll find how you can actually use the cmdlet.

 

The detailed description and notes a re worth a read – especially for a cmdlet you’ve not used before

 

Think about the PowerShell experts you’ve come across. How did they get to be experts? A lot of it was reading the help files.

Update-Help errors

Updatable help brings the benefit of up to date help with typos fixed and new edge cases described. The down side is that it sometimes fails:

 

PS> Update-Help -Force
Update-Help : Failed to update Help for the module(s) 'Microsoft.PowerShell.Operation.Validation'
with UI culture(s) {en-GB} : Unable to retrieve the HelpInfo XML file for UI culture en-GB. Make sure the HelpInfoUri property in the module manifest is valid or check your network connection and then try the command again.
At line:1 char:1
+ Update-Help -Force
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (:) [Update-Help], Exception
    + FullyQualifiedErrorId : UnableToRetrieveHelpInfoXml,Microsoft.PowerShell.Commands.UpdateHelpCo
   mmand

 

Update-Help : Failed to update Help for the module(s) 'PSScriptAnalyzer' with UI culture(s) {en-US} : The Help content at the specified location is not valid. Specify a location that contains valid Help Content.
At line:1 char:1
+ Update-Help -Force
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Update-Help], Exception
    + FullyQualifiedErrorId : HelpContentXmlValidationFailure,Microsoft.PowerShell.Commands.UpdateHe
   lpCommand

 

The failure messages are relatively self-explanatory if you read the details. The problem usually boils down to the needed help files not being available at the URI provided in the module manifest. This often occurs with new modules – the creation of the help files lags well behind the creation of the module usually.

 

Unfortunately there isn’t much you can do apart from try to find examples of the modules use online

Boolean in Where-Object filter

I was testing some code yesterday and realised there was a quirk in the way the original where syntax (with {}) worked and the way the newer syntax worked.

 

To demonstrate this I created a set of objects

$i = 0

$tests = while ($i -lt 25){
  New-Object -TypeName PSObject -Property @{
    Index = $i
    Current = if (-not($i % 2)){$true} else {$false}
  }
  $i++
}

 

Object properties are a numeric index and a boolean value

 

If you want just the $true values many people write this

$tests | where {$_.Current -eq $true}

 

or if using the newer syntax use this

$tests | where Current -eq $true

 

This is unnecessary typing as you can do this

$tests | where {$_.Current}

$tests | where Current

 

The reason is that the filter you are creating tests a property of the current object against your criteria and passes is if the result is true. A boolean property will by definition either be true of false so just need to test directly

 

if you want to double negative type test i.e. – not $true (which I don’t recommend as its very easy to get into  logic mess) then you have to do this

$tests | where {-not $_.Current}

 

as this fails
$tests | where –not Current

 

You could do this

$tests | where Current -ne $true

 

but it negates the whole code simplification objective

Months

It would be nice to be able to do this:

PS>  Get-Date -Day 25 -Month December -Year 2016
Get-Date : Cannot bind parameter 'Month'. Cannot convert value "December" to type "System.Int32".
Error: "Input string was not in a correct format."
At line:1 char:25
+ Get-Date -Day 25 -Month December -Year 2016
+                         ~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-Date], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.GetDateCo
   mmand

 

You have to do this:

PS>  Get-Date -Day 25 -Month 12 -Year 2016

25 December 2016 11:00:56

 

The time is set to current time automatically.

 

One of the gaps in .NET is an enum for the months of the year. You get an enum for days of the week:

PS>  [System.DayOfWeek]::Friday
Friday

 

PS>  0..6 | foreach {[System.DayOfWeek]$psitem}
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

 

PowerShell v5 gives you the capability to make your own enums:

enum MonthsOfYear {
January = 1
February = 2
March = 3
April = 4
May = 5
June = 6
July = 7
August = 8
September = 9
October = 10
November = 11
December = 12
}

 

PS>  [MonthsOfYear]10
October

PS>  [MonthsOfYear]::April
April

 

PS>  1..12 | foreach {[MonthsoFYear]$PSItem}
January
February
March
April
May
June
July
August
September
October
November
December

 

PS>  [System.Enum]::GetNames([MonthsOfYear])
January
February
March
April
May
June
July
August
September
October
November
December

 

Now you can use the name of the month

PS>  Get-Date -Day 25 -Month ([MonthsOfYear]::December) -Year 2016

25 December 2016 11:19:38

 

Enums are one of those pieces of functionality that you don’t necessarily think about but are very useful. The ability to create your own in PowerShell is very useful and will become more widespread as people realise its available.