Monthly Archive

Categories

Monthly Archives: June 2018

PowerShell if not

When you’re using an if statement you’re usually testing for a positive so how do you do a PowerShell if not

There a few scenarios to cover. The simplest is if you’re testing a boolean:

PS> $x = $true

if ($x) {'Yes'} else {'No'}
Yes

 

In an if statement the test ($x) is evaluated and must give a true or false answer. If true then Yes else No

Let’s make turn the test into a not test. You can use ! or –not as you prefer

PS> $x = $true

if (!$x) {'Yes'} else {'No'}
if (-not $x) {'Yes'} else {'No'}
No
No

 

If the value is already false

PS> $x = $false

if (!$x) {'Yes'} else {'No'}
if (-not $x) {'Yes'} else {'No'}
Yes
Yes

 

Be careful as you’re getting into double negative territory which is always a headache when you come to review the code at some time in the future.

If you’re dealing with numeric values

PS> $x = 5

if ($x -ne 5) {'Yes'} else {'No'}
if ($x -lt 5) {'Yes'} else {'No'}
if ($x -gt 5) {'Yes'} else {'No'}
No
No
No

 

Be careful with the first one as you’ll only get Yes if $x –ne 5. Back to double negative thinking.

Notice you can’t do this

if ($x -not -lt 5) {'Yes'} else {'No'}

 

Double operators don’t work. You get an error about

At line:1 char:8
+ if ($x -not -lt 5) {'Yes'} else {'No'}
+ ~~~~
Unexpected token '-not' in expression or statement.
At line:1 char:8

among other things.

Nulls can be tricky

PS> $x = $null

if ($x) {'Yes'} else {'No'}
if (!$x) {'Yes'} else {'No'}

No
Yes

 

A null value will evaluate as false unless you –not it – another double negative situation.

 

Using if not is relatively straight forward. Just make sure you have the logic thought through to deal with double negatives correctly.

PowerShell sleep

Getting PowerShell to sleep or pause during execution can sometimes be useful. This is how you can do a PowerShell sleep.

The starting point is Start-Sleep

PS> Get-Command Start-Sleep -Syntax

Start-Sleep [-Seconds] <int> [<CommonParameters>]

Start-Sleep -Milliseconds <int> [<CommonParameters>]

 

You can set a time in seconds or milliseconds that you want PowerShell to pause

PS> 1..5 | foreach {
Get-Date
Start-Sleep -Seconds 30
}

30 June 2018 20:08:02
30 June 2018 20:08:32
30 June 2018 20:09:02
30 June 2018 20:09:32
30 June 2018 20:10:02

 

If you need your script to sleep for more than a minute you still need to use seconds. So to sleep for 3 minutes use:

Start-Sleep -Seconds 180

 

If you want the pause to be under manual control use the pause function:

PS> 1..5 | foreach {
Get-Date
pause
}

30 June 2018 20:17:57
Press Enter to continue...:
30 June 2018 20:18:01
Press Enter to continue...:
30 June 2018 20:18:16
Press Enter to continue...:
30 June 2018 20:18:33
Press Enter to continue...:
30 June 2018 20:18:43
Press Enter to continue...:

 

The drawback is that it’s a manual process so not good for soemthing running in the middle of the night and you get the display contaminated with “press enter to continue…” statements.

Pause is a function that PowerShell automatically creates. It has a simple definition:

$null = Read-Host 'Press Enter to continue...'

PowerShell string concatenation

PowerShell string concatenation is performed using the concatenation operator which is +

PS> $a = '1234'
PS> $b = '5678'
PS> $c = $a + $b
PS> $c
12345678

 

When you concatenate 2 strings you’re creating a new string not adding extra characters to the end of the first string.

My preference is to use variable substitution rather than straight concatenation

PS> $d = "$a$b"
PS> $d
12345678

 

If your string has double quote delimiters you can substitute the value of variables as shown. Be careful though because substitution doesn’t work if you use single quotes:

PS> $e = '$a$b'
PS> $e
$a$b

 

One last trick with strings is the ability to use the multiply operator to generate strings

PS> '123' * 5
123123123123123

PS> 1..10 | foreach {'*' * $_}
*
**
***
****
*****
******
*******
********
*********
**********

Write-Host

Write-Host has had a bad press over the years culminating in the infamous saying “if you use Write-Host a puppy will die” or words to that effect.

So what’s the fuss about?

Let’s take some code

Write-Host -Object "starting"

function t1 {
Write-Host -Object "In the function"

2+2
}

Write-Host -Object "pre-function"

t1

Write-Host -Object "post-function"

It’s not quite the world’s most fantastic piece of code but it’ll do for now

When you run this code you see output like this:

starting
pre-function
In the function
4
post-function

The problem is that you can’t easily separate the code output from the Write-Host messages as Write-Host does what it says and writes your message direct to the host.

What you should be doing is using one of the more specific Write cmdlets:

Write-Debug
Write-Error
Write-Host
Write-Information
Write-Progress
Write-Verbose
Write-Warning

Maybe in this case Write-Information

Write-Information -MessageData "starting" -InformationAction Continue

function t1 {
Write-Information -MessageData "In the function" -InformationAction Continue

2+2
}

Write-Information -MessageData "pre-function" -InformationAction Continue

t1

Write-Information -MessageData "post-function" -InformationAction Continue

with output of

starting
pre-function
In the function
4
post-function

But this looks just like Write-Host. In PowerShell v5 and later Write-Host is a wrapper for Write-Information

To see the difference remove the –InformationAction parameter and use this horribly contrived code

$inf = @()

Write-Information -MessageData "starting" -InformationVariable a
$inf += $a

function t1 {
Write-Information -MessageData "In the function" -InformationVariable a
$inf += $a

2+2
}

Write-Information -MessageData "pre-function" -InformationVariable a
$inf += $a

t1

Write-Information -MessageData "post-function" -InformationVariable a
$inf += $a

If you look at the contents of $inf you see the messages

starting
pre-function
post-function

Notice you’ve lost the message from within the function – we’ll come back to that another time

You have a record with some interesting data

PS> $inf | gm


TypeName: System.Management.Automation.InformationRecord

Name MemberType Definition 
---- ---------- ---------- 
Equals Method bool Equals(System.Object obj) 
GetHashCode Method int GetHashCode() 
GetType Method type GetType() 
ToString Method string ToString() 
Computer Property string Computer {get;set;} 
ManagedThreadId Property uint32 ManagedThreadId {get;set;} 
MessageData Property System.Object MessageData {get;} 
NativeThreadId Property uint32 NativeThreadId {get;set;} 
ProcessId Property uint32 ProcessId {get;set;} 
Source Property string Source {get;set;} 
Tags Property System.Collections.Generic.List[string] Tags {get;}
TimeGenerated Property datetime TimeGenerated {get;set;} 
User Property string User {get;set;}

For instance

PS> $inf | select TimeGenerated, MessageData

TimeGenerated MessageData 
------------- ----------- 
29/06/2018 20:25:44 starting 
29/06/2018 20:25:44 pre-function 
29/06/2018 20:25:44 post-function

could be useful for tracing progress

There’s a lot more in the write cmdlets that we’ll come back to another time

Hyper-V switches

I need to do some work on the Hyper-V switches in my lab so need to see which VMs are on which switch.

Easier than I thought:

Get-VM |
Get-VMNetworkAdapter |
select VMname, Name, Switchname

PowerShell versions

PowerShell has gone through a number of versions since it was first released in November 2006. These are the major features of the PowerShell versions:

PowerShell v1 – initial release with 137 cmdlets. (released with Windows Vista / Server 2008 – not Server Core). Only way to work remotely was Get-WmiObject. no longer available.

PowerShell v2 (Windows 7 / Server 2008 R2) – introduced PowerShell remoting, PowerShell jobs and extended WMI cmdlets. Deprecated.

 

PowerShell v3 (Windows 8 / Server 2012) – Workflows, CIM cmdlets

 

PowerShell v4 (Windows 8.1 / server 2012 r2) – DSC

 

PowerShell v5 (Windows 10) – PowerShell classes

PowerShell v5.1 (Windows 10 Update / Server 2016) – mainly bug fixes for v5

 

PowerShell v6.0 (open source) – SSH remoting, installs for Linux and mac

PowerShell v6.1 (in development) – mainly fixes and updates to v6.0

 

Documentation has become weaker over time and is currently available from - https://docs.microsoft.com/en-gb/powershell/scripting/powershell-scripting?view=powershell-6

Which version should you use?

If you’re on Windows use the version installed OR download and install the latest WMF package for your version of Windows. PowerShell v6 is useful for non-domain remoting or remoting to Linux servers.

If you’re on Linux or mac the your only choice in v6.x

PowerShell v6.1 preview 3

PowerShell v6.1 preview 3 became available a couple of weeks ago.

https://github.com/PowerShell/PowerShell/releases

To the user its a minor set of fixes from preview 2 though if you read the release notes there’s a lot of work going on in the background.

If the 6 months between releases idea holds we can expect to see the v6.1 release within the next month.

OpenSSH is still a bit of a mess with options to install some, or all, as Windows optional features or install from the github project. This needs to be sorted with definitive guidelines a preferably a roadmap.

PowerShell commands

When you think of PowerShell commands most people think of cmdlets but that’s not the full story.

PowerShell commands encompass:

Aliases

Functions

Cmdlets

Native Windows commands

When you type a command name the command types listed above are checked in order to determine the command to run – specifically aliases, then functions, then cmdlets and finally native commands.

 

If you want to se this in action try

PS> function get-date {get-process}
PS> get-date

And you’ll get a list of processes!

 

Get-command by default will only show the function! To see all commands

PS> Get-Command get-date -All

CommandType Name
----------- ----
Function get-date
Cmdlet Get-Date

 

You can still execute the cmdlet version:

PS> & (Get-Command get-date -All)[1]

25 June 2018 14:53:43

or

PS> & (Get-Command get-date -CommandType cmdlet)

by using the call operator & on the cmdlet

or alternatively you can use the module name to qualify the command

PS> Microsoft.PowerShell.Utility\get-date

25 June 2018 14:54:16

Creating collections for output

There are many times when you end up creating a collection – either for output or use for performing further processing. There’s a common pattern that’s used when creating collections for output that’s very inefficient.

 

That pattern is:

$data = @()
97..122 | foreach {
$props = @{
Number = $_
Letter = [char][byte]$_
}

$data += New-Object -TypeName PSobject -Property $props

}

$data

Create an empty array. Build a pipeline to get the data you want and for each object on that pipeline create an output object and add it to the array. Output the array.

This is inefficient because each time you think you’re adding an object to the array you’re really creating a new array from the current contents of the array and the new object.

I used to use this approach a lot and it wasn’t until I sat and thought about it while working on PowerShell in Action that I realised what I was doing wrong.

 

A more efficient approach is to let PowerShell automatically create the collection for you.

$data = 97..122 | foreach {
$props = @{
Number = $_
Letter = [char][byte]$_
}

New-Object -TypeName PSobject -Property $props

}

$data

 

Just output your results at the end of the pipeline and PowerShell will create the array for you.

It also works with a foreach loop

$data = foreach ($num in 97..122 ) {
$props = @{
Number = $num
Letter = [char][byte]$num
}

New-Object -TypeName PSobject -Property $props

}

$data

Creating objects

In my recent post about avoiding Add-Member I showed how to create objects. As with most things in PowerShell there are a number of different ways of creating objects.

I showed this code:

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

$props = @{
OS = $os.Caption
Version = $os.Version
Name = $comp.Caption
Manufacturer = $comp.Manufacturer
Model = $comp.Model
}

$op = New-Object -TypeName PSObject -Property $props

$op

 

A number of comments objected to this saying I should have used:

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

$op = [pscustomobject]@{
OS = $os.Caption
Version = $os.Version
Name = $comp.Caption
Manufacturer = $comp.Manufacturer
Model = $comp.Model
}

$op

 

In PowerShell v3 s you gained the option to directly create the object by using a type declaration as shown. There’s a couple of advantages – it’s slightly less typing and it preserves the order of the properties as if you’d used an ordered hashtable.

 

The disadvantage to my mind is that you have to remember to use [pscustomobject] which is a placeholder for PSObject. You have to do it this way because you’re effectively using the PSObject constructor with no parameters.

 

There are a number of reasons I didn’t use this technique. Firstly, a lot of my posts are for beginners and using New-Object is simpler to grasp then the type declaration on a hash table method. Secondly, remembering to use pscustomobject is a pain. if you forget it and use [psobject] you end up with a hash table as the output rather than the object you thought you were going to get.

 

Both techniques work but I prefer using New-Object because it’s easier to understand what’s going on in the code especially for people relatively new to PowerShell.

 

Where there are multiple good ways to complete a task in PowerShell I think its best to find a method that you’re comfortable with and use that. In reality both methods end up with the same result and comes down to personal preference. Don’t let anyone tell you one is better than the other!