Monthly Archive

Categories

PowerShell Basics

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.

WMI Filters

A common mistake with WMI/CIM filters is:

PS>  Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceId=C:"
Get-WmiObject : Invalid query "select * from Win32_LogicalDisk where DeviceId=C:"
At line:1 char:1
+ Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceId=C:"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-WmiObject], ManagementException
    + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCo
   mmand

 

The clue is in the invalid query error message

When you use the –Filter parameter and are testing a property of type string the value you are testing against has to be in quotes

PS>  Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceId='C:'"

DeviceID     : C:
DriveType    : 3
ProviderName :
FreeSpace    : 108999528448
Size         : 248480002048
VolumeName   : Windows

 

The filter is defined as a string so you need to use single quotes inside the double quotes. You could mess around with all single quotes but then you have to escape the inner set of quotes – good luck with that – its an unnecessary exercise in frustration

 

How do you know the property is a string?

PS>  (Get-CimClass -ClassName Win32_LogicalDisk).CimClassProperties['DeviceId']

Name               : DeviceID
Value              :
CimType            : String
Flags              : Property, Key, ReadOnly, NullValue
Qualifiers         : {CIM_Key, read, key, MappingStrings...}
ReferenceClassName :

 

The same rules for –Filter apply to Get-CimInstance

Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceId='C:'"

32 or 64 and/or Administrator

When you run the PowerShell console (or ISE) the default icon runs a 32 or 64 bit version that matches your OS. On a 64 bit machine you have the option of running in 32bit (icons have a (x86) suffix on the title.

 

How can you tell whether you’re running in 32 or 64 bit mode?

 

One way is shown in this forum question - http://powershell.org/forums/topic/requires/#post-42226

 

I prefer the simpler:

if ([System.IntPtr]::Size -eq 8) {$size = '64 bit'}
else {$size = '32 bit'}

 

I don’t like automatically kicking into the required bit version if its wrong. I prefer to abort the processing with a message to run as 32 or 64 bit as appropriate

 

You can perform a similar test for administrator privileges (running elevated)

$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
$secprin = New-Object Security.Principal.WindowsPrincipal $currentUser
if ($secprin.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator))
{$admin = 'Administrator'}
else {$admin = 'non-Administrator'}

 

Though on later versions of Powershell its easier to use

#requires –RunAsAdministrator