Monthly Archive

Categories

Monthly Archives: October 2019

Resolve-Path

Resolve-Path is a cmdlet I haven’t used much – if at all – so I thought I should have a look at it.

At an arbitrary point in the file system:

PS> Get-Location

Path
----
C:\Scripts\Modules\Coordinates

 

.. Indicates the next level up

PS> Get-ChildItem -Path ..

Directory: C:\Scripts\Modules

etc

 

..\.. indicates two levels up

PS> Get-ChildItem -Path ..\..

Directory: C:\Scripts

etc

 

Resolve-Path will resolve any use of wildcards and other characters with meaning into the full path

PS> Resolve-Path -Path ..

Path
----
C:\Scripts\Modules

 

PS> Resolve-Path -Path ..\..

Path
----
C:\Scripts

 

Note that

PS> Resolve-Path -Path .

Path
----
C:\Scripts\Modules\Coordinates

 

is effectively the same as Get-Location

Ad hoc development

I was having a discussion about how people can learn PowerShell at the recent UK PowerShell day and mentioned ad hoc development. Surprisingly, no-one really knew what I meant.

 

Ad hoc development is a concept more than a development type. It was used extensively back in the days of PowerShell v1 and v2 but seems to have dropped off the radar these days.

 

Many people seem to learn the PowerShell language – either from a class or a book – but then don’t have any idea how to put that into practice. This is a failing of our teaching methods. Too many times I’ve seen people asking for help because they’ve dived into trying to create huge complicated scripts, or modules, and don’t have the background knowledge or experience to actually get the code to work.

 

Ad hoc development is one approach to moving from a basic knowledge of the PowerShell language to coding production level scripts and modules.

 

The starting point is the command line and working interactively. Use individual cmdlets or even a pipeline of cmdlets. If you find you’re using the same pipeline a lot then save as a script. Later, as you learn more you can parameterise the script, add all the production bells and whistles and even turn it into a module.

 

PowerShell is a huge beast these days with many parts you probably don’t need to start with. Use what you need now and add to your code as you learn rather then trying to jump right into a big complicated project. In the long run you’ll learn faster and end up getting more done with less frustration.

Pipeline Chain operators

Another experimental feature from PowerShell v7 preview 5 brings pipeline chain operators to PowerShell.

PS> Get-ExperimentalFeature -Name PSPipelineChainOperators | Format-List Name, Description

Name : PSPipelineChainOperators
Description : Allow use of && and || as operators between pipeline invocations

 

The operators work as follows

<command1> && <command2> means that command2 will fire if command1 completes without errors

 

You could write that in PowerShell now as

<command1>; if ($?) { <command2> }

 

If command1 works and $? is $true (no errors) fire command2

 

<command1> || <command2> means that command2 will fire if command1 has errors

i.e.

<command1>; if (-not $?) { <command2> }

 

Like all experimental features it has to be enabled and PowerShell restarted

PS> Enable-ExperimentalFeature -Name PSPipelineChainOperators
WARNING: Enabling and disabling experimental features do not take effect until next start of PowerShell.

 

The best way to explain these operators is to show some examples

This is from the RFC

PS> 1,2,3 | ForEach-Object { $_ + 1 } && Write-Output 'Hello'
2
3
4
Hello

 

PS> Get-Item -Path c:\nosuchfile -ErrorAction SilentlyContinue || Write-Output 'ERROR'
ERROR

The message is written because the file isn’t found

 

PS> $path = 'C:\test\DebugJob2.ps1'
PS> Get-Item -Path $path -ErrorAction SilentlyContinue && Remove-Item -Path $path

results in the file being deleted

 

Subsequently running

PS> Get-Item -Path $path -ErrorAction SilentlyContinue || Write-Output 'No such file'
No such file

 

generates the message because the file isn’t there.

 

The chain operators work if there’s an error or not with the execution of command1 so you can’t use the test cmdlets such as Test-Path because they return booleans.

 

These operators follow the bash model of working on errors which isn’t necessarily the way PowerShell will work for you. I’m in two minds as to whether these operators are useful or not as the examples above feel contrived and I’m not convinced at the moment that the mental gymnastics required to accommodate these operators in my code are worth it. Time will tell.

Get-Error

One of the experimental features new PowerShell v7 preview 5 is the Get-Error cmdlet. The features description states:

Enable Get-Error cmdlet that displays detailed information about ErrorRecords included nested objects

 

Enable the feature:

PS> Enable-ExperimentalFeature -Name Microsoft.PowerShell.Utility.PSGetError
WARNING: Enabling and disabling experimental features do not take effect until next start of PowerShell.

 

It would be better for users if the naming of experimental features became more consistent and ideally a single word rather than a fully qualified name including the module as this and some other new experimental features have.

 

The cmdlet has a simple syntax:

PS> Get-Command Get-Error -Syntax

Get-Error [-Newest <int>] [<CommonParameters>]

Get-Error [[-InputObject] <psobject>] [<CommonParameters>]

 

Generate some errors:

PS> 1/ 0
RuntimeException: Attempted to divide by zero.
PS> Get-ChildItem -Path c:\nosuchfile
Get-ChildItem: Cannot find path 'C:\nosuchfile' because it does not exist.

 

Using Get-Error gives a very comprehensive view of the errors

PS> Get-Error

Exception :
ErrorRecord :
Exception :
Message : Cannot find path 'C:\nosuchfile' because it does not exist.
HResult : -2146233087
TargetObject : C:\nosuchfile
CategoryInfo : ObjectNotFound: (C:\nosuchfile:String) [], ParentContainsErrorRecordException
FullyQualifiedErrorId : PathNotFound
ItemName : C:\nosuchfile
SessionStateCategory : Drive
TargetSite :
Name : GetChildItems
DeclaringType : System.Management.Automation.SessionStateInternal
MemberType : Method
Module : System.Management.Automation.dll
StackTrace :
at System.Management.Automation.SessionStateInternal.GetChildItems(String path, Boolean recurse, UInt32 depth,
CmdletProviderContext context)
at System.Management.Automation.ChildItemCmdletProviderIntrinsics.Get(String path, Boolean recurse, UInt32 depth,
CmdletProviderContext context)
at Microsoft.PowerShell.Commands.GetChildItemCommand.ProcessRecord()
Message : Cannot find path 'C:\nosuchfile' because it does not exist.
Source : System.Management.Automation
HResult : -2146233087
TargetObject : C:\nosuchfile
CategoryInfo : ObjectNotFound: (C:\nosuchfile:String) [Get-ChildItem], ItemNotFoundException
FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
InvocationInfo :
MyCommand : Get-ChildItem
ScriptLineNumber : 1
OffsetInLine : 2
HistoryId : 5
Line : Get-ChildItem -Path c:\nosuchfile
PositionMessage : At line:1 char:2
+ Get-ChildItem -Path c:\nosuchfile
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
InvocationName : Get-ChildItem
CommandOrigin : Internal
ScriptStackTrace : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo :

 

This is the latest error in the error collection. You’ll see the same information if you use

PS> $error[0]

 

If you use the Newest parameter you’ll also see the Error Index

PS> Get-Error -Newest 2

ErrorIndex: 0

Exception :
ErrorRecord :
Exception :
Message : Cannot find path 'C:\nosuchfile' because it does not exist.
HResult : -2146233087
TargetObject : C:\nosuchfile
CategoryInfo : ObjectNotFound: (C:\nosuchfile:String) [], ParentContainsErrorRecordException
FullyQualifiedErrorId : PathNotFound
ItemName : C:\nosuchfile
SessionStateCategory : Drive
TargetSite :
Name : GetChildItems
DeclaringType : System.Management.Automation.SessionStateInternal
MemberType : Method
Module : System.Management.Automation.dll
StackTrace :
at System.Management.Automation.SessionStateInternal.GetChildItems(String path, Boolean recurse, UInt32 depth,
CmdletProviderContext context)
at System.Management.Automation.ChildItemCmdletProviderIntrinsics.Get(String path, Boolean recurse, UInt32 depth,
CmdletProviderContext context)
at Microsoft.PowerShell.Commands.GetChildItemCommand.ProcessRecord()
Message : Cannot find path 'C:\nosuchfile' because it does not exist.
Source : System.Management.Automation
HResult : -2146233087
TargetObject : C:\nosuchfile
CategoryInfo : ObjectNotFound: (C:\nosuchfile:String) [Get-ChildItem], ItemNotFoundException
FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
InvocationInfo :
MyCommand : Get-ChildItem
ScriptLineNumber : 1
OffsetInLine : 2
HistoryId : 5
Line : Get-ChildItem -Path c:\nosuchfile
PositionMessage : At line:1 char:2
+ Get-ChildItem -Path c:\nosuchfile
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
InvocationName : Get-ChildItem
CommandOrigin : Internal
ScriptStackTrace : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo :

ErrorIndex: 1

Exception :
ErrorRecord :
Exception :
Message : Attempted to divide by zero.
HResult : -2146233087
CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
FullyQualifiedErrorId : RuntimeException
InvocationInfo :
ScriptLineNumber : 1
OffsetInLine : 2
HistoryId : -1
Line : 1/ 0
PositionMessage : At line:1 char:2
+ 1/ 0
+ ~~~~
CommandOrigin : Internal
ScriptStackTrace : at <ScriptBlock>, <No file>: line 1
TargetSite :
Name : Divide
DeclaringType : System.Management.Automation.IntOps
MemberType : Method
Module : System.Management.Automation.dll
StackTrace :
at System.Management.Automation.IntOps.Divide(Int32 lhs, Int32 rhs)
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at System.Management.Automation.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
Message : Attempted to divide by zero.
Data : System.Collections.ListDictionaryInternal
InnerException :
Message : Attempted to divide by zero.
HResult : -2147352558
Source : System.Management.Automation
HResult : -2146233087
CategoryInfo : NotSpecified: (:) [], RuntimeException
FullyQualifiedErrorId : RuntimeException
InvocationInfo :
ScriptLineNumber : 1
OffsetInLine : 2
HistoryId : -1
Line : 1/ 0
PositionMessage : At line:1 char:2
+ 1/ 0
+ ~~~~
CommandOrigin : Internal
ScriptStackTrace : at <ScriptBlock>, <No file>: line 1

 

This seems to give a concise view of the Index and the error

PS> Get-Error -Newest 2 | Select PSErrorIndex, @{N='Error'; E={$_}} | Format-List

PSErrorIndex : 0
Error : Cannot find path 'C:\nosuchfile' because it does not exist.

PSErrorIndex : 1
Error : Attempted to divide by zero.

 

I think I’d rather see Get-Error by itself supply a list of current errors and their index. There should also be an Index parameter that enables you to pick a specific error. Otherwise this looks like a useful addition.

Returning cmdlets

If I’m interpreting the email updates coming from the PowerShell project the next code release of PowerShell v7 should see the following returning cmdlets:

Get-Counter

Update-List

Clear-RecycleBin

Out-Printer

All but Update-List are Windows only as far as I can ell

Error view

Error view is another experimental feature introduced with PowerShell v7 preveiw 5. The experimental feature needs to be enabled and PowerShell restarted.

PS> Enable-ExperimentalFeature -Name PSErrorView
WARNING: Enabling and disabling experimental features do not take effect until next start of PowerShell.

 

At the PowerShell prompt you’d normally see an error in this form

PS> 1 / 0
Attempted to divide by zero.
At line:1 char:1
+ 1 / 0
+ ~~~~~
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

 

The PSErrorview feature changes this to a more concise one line error

PS> 1 / 0
RuntimeException: Attempted to divide by zero.

 

Enabling the experimental feature sets the $errorview preference variable to ConciseView

PS> $errorview
ConciseView

 

You can set the error view manually

PS> $errorview = [System.Management.Automation.ErrorView]::NormalView
PS> 1/0
Attempted to divide by zero.
At line:1 char:1
+ 1/0
+ ~~~
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

 

Possible values for $errorview are

PS> [enum]::GetValues([System.Management.Automation.ErrorView])
NormalView
CategoryView
ConciseView

 

In Normal view an error in a script looks like this

PS> .\test.ps1
Attempted to divide by zero.
At C:\Scripts\test.ps1:1 char:2
+ 1 / 0
+ ~~~~~
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

 

In Concise view you get this

PS> $errorview = [System.Management.Automation.ErrorView]::ConciseView
PS> .\test.ps1
RuntimeException: C:\Scripts\test.ps1
Line |
1       | 1 / 0
| ^ Attempted to divide by zero.

 

The concise view supplies a more obvious indication of where the error is occurring.

 

This is a new feature that looks useful especially if you spend a lot of time working interactively.

Test-Connection

In PowerShell v7 preview Test-Connection sees some improvements:

PS> Test-Connection -TargetName 127.0.0.1 | Format-List

Source : W510W10
Destination : 127.0.0.1
Replies : {System.Net.NetworkInformation.PingReply, System.Net.NetworkInformation.PingReply,
System.Net.NetworkInformation.PingReply, System.Net.NetworkInformation.PingReply}

 

The progress bar has been removed as has the unrequired text displayed with each ping.

The replies are still wrapped up in a collection of objects each of which looks like this

PS> Test-Connection -TargetName 127.0.0.1 -Count 1 | select -ExpandProperty Replies

Status : Success
Address : 127.0.0.1
RoundtripTime : 0
Options : System.Net.NetworkInformation.PingOptions
Buffer : {97, 98, 99, 100…}

 

Using the Quiet parameter

PS> Test-Connection -TargetName 127.0.0.1 -Quiet
True

Works exactly as expected

 

TargetName is called ComputerName in Windows PowerShell versions of the cmdlet though the PowerShell v7 versions supplies ComputerName as an alias.

 

The preview 5 version of Test-Connection is starting to become usable though to be considered truly fit for purpose the Replies objects need to be unravelled and the output should consist of one object per ping NOT just a single object.

Null coalescing with Object properties

The Null coalescing operators were introduced in PowerShell v7 preview 5. last time you saw how to use Null coalescing with variables. You can also use Null coalescing with Object properties.

Let’s first create an object.

PS> $prop = @{Nullprop = $null; NonNullProp = 'something'}
PS> $obj = New-Object -TypeName PSobject -Property $prop

 

Yes, I know there other ways but I prefer this approach.

Using the Null coalescing operator

PS> $obj.NonNullProp ?? 'is null'
something
PS> $obj.NullProp ?? 'is null'
is null

 

is very similar to working with variables.

The Null coalescing assignment operator

PS> $obj.NonNullProp ??= 'is null'
PS> $obj.NullProp ??= 'is null'
PS> $obj | Format-List

NonNullProp : something
Nullprop : is null

 

If the property is non-null nothing happens. If the property is null the property is assigned the value on the right hand side of the operator.

 

To my mind the Null coalescing operator and the Ternary operator introduced in PowerShell v7 preview 4 fall into the same category of great if you need to perform these operations on a regular basis and are used to thinking in these terms but otherwise not something to bother with. We’ve managed to survive through 6 versions of PowerShell without them and I don’t see them as a great step forward or particularly useful for IT administrators.

Null coalescing operators

Powershell v7 preview 5 introduces a new experimental feature that supports Null coalescing operators and null coalescing assignment operstors.

As its an experimental feature:

PS> Get-ExperimentalFeature -Name PSCoalescingOperators | Format-List

Name : PSCoalescingOperators
Enabled : False
Source : PSEngine
Description : Support the null coalescing operator and null coalescing assignment operator in PowerShell language

 

You have to enable it:

PS> Enable-ExperimentalFeature -Name PSCoalescingOperators
WARNING: Enabling and disabling experimental features do not take effect until next start of PowerShell.

 

And then restart PowerShell.

Create a variable with $null as its value

PS> $nvar = $null

Using the coalescing operator
PS> $nvar ?? 'is null'
is null

 

if $nvar is null the string is returned

By contrast a non-null value won’t return the ‘is null’ string

PS> $num = 1
PS> $num ?? 'is null'
1
PS> $str = 'aaa'
PS> $str ?? 'is null'
aaa

 

The ?? operator checks the left hand side to be null or undefined and returns the value of right hand side if null, else return the value of left hand side.

The coalescing operator is equivalent to

PS> if ($nvar -eq $null) {'is null'}else {$nvar}
is null

 

The Null assignment operator ??= operator check the left hand side to be null or undefined and assigns the value of right hand side if null, else the value of left hand side is not changed.

PS> $date1 = Get-Date

PS> $date2 ??= Get-Date
PS> $date1

25 October 2019 21:19:48

PS> $date2

25 October 2019 21:22:03

 

Using the null coalescing assignment operator is equivalent to

PS> if ($date2 -eq $null) {$date2 = Get-Date}
PS> $date2

25 October 2019 21:24:09

PowerShell v7 preview 5 experimental features

PowerShell v7 preview 5 has a number of new, and some modified experimental features. The full list of PowerShell v7 preview 5 experimental features is:

PSCoalescingOperators - - Support the null coalescing operator and null coalescing
assignment operator in PowerShell language

PSCommandNotFoundSuggestion - - Recommend potential commands based on fuzzy search on a CommandNotFoundException

PSErrorView - - New formatting for ErrorRecord

PSForEachObjectParallel - - New parameter set for ForEach-Object to run script blocks
in parallel

PSImplicitRemotingBatching - - Batch implicit remoting proxy commands to improve
performance

PSPipelineChainOperators - - Allow use of && and || as operators between pipeline
invocations

PSTernaryOperator - - Support the ternary operator in PowerShell language

PSUpdatesNotification - - Print notification message when new releases are available

Microsoft.PowerShell.Utility.PSGetError - - Enable Get-Error cmdlet that displays detailed information about ErrorRecords included nested objects

Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace - - Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger.

PSDesiredStateConfiguration.InvokeDscResource - - Enables the Invoke-DscResource cmdlet and related features.

Its an interesting set of features that I’ll work through in subsequent posts.