Monthly Archive

Categories

PowerShell 7

Foreach-Object -parallel

The introduction of Foreach-Object -parallel in PowerShell v7 preview 3 brings some much needed parallelisation options back into PowerShell.

 

PowerShell workflows are available in Windows PowerShell but are quirky (to be kind) and can be difficult to use. Workflows were removed in PowerShell v6.0

 

In PowerShell v7 preview 3 Foreach-Object receives a –parallel parameter that takes a scriptblock as its value.

 

As a simple example consider this simple counting example from the PowerShell team blog:

PS> (Measure-Command -Expression {1..50 | ForEach-Object {Start-Sleep -Milliseconds 100}}).TotalSeconds
5.4960846

 

Now using the parallel option

PS> (Measure-Command -Expression {1..50 | ForEach-Object -Parallel {Start-Sleep -Milliseconds 100}}).TotalSeconds
2.2523929

 

The time to execute is significantly reduced.

 

A few caveats are needed.

Firstly, the parallel option is an experimental feature so must be enabled

Enable-ExperimentalFeature -Name PSForEachObjectParallel

 

Secondly, not all tasks are suitable candidates for parallel execution. Expect more discussion on this topic in the near future including contributions on the PowerShell team blog.

Get-AdUser in PowerShell Core

There has been a problem with Get-ADUser in PowerShell core such that

Get-ADUser -Identity Richard -Properties *

 

Throws an error.

 

The problem is in .NET Core and affects a small number of properties including ProtectedFromAccidentalDeletion

 

The underlying .NET Core issue has been fixed and PowerShell v7 preview 3 on Windows 10.0.18362 will successfully run the command

Experimental features

PowerShell core has recently. v6.2, had the concept of experimental features added. An experimental feature is new or changed functionality that may be a breaking change or about which the PowerShell team want feedback before finalising the code.

My PowerShell v6.2.2 instance has the following experimental features

PSImplicitRemotingBatching
PSUseAbbreviationExpansion
PSCommandNotFoundSuggestion
PSTempDrive

 

of which I’ve enabled PSCommandNotFoundSuggestion and PSTempDrive. Experimental features are disbaled by default in PowerShell v6.2

 

My PowerShell v7 preview 3 instance has the following experimental features

PSImplicitRemotingBatching
PSForEachObjectParallel
PSCommandNotFoundSuggestion

 

of which PSForEachObjectParallel and PSCommandNotFoundSuggestion

PSUseAbbreviationExpansion and PSTempDrive and now full features in PowerShell v7

 

If don’t have any PowerShell v6.2 experimental features enabled OR don’t have v6.2 installed and then install PowerShell v7 preview 3, or later, all experimental features will be enabled by default. If you’ve enabled any experimental features in PowerShell v6.2 then PowerShell v7 respects the settings and doesn’t enable all experimental features.

 

You can view the settings controlling experimental features (among other things) at

Get-Content –Path $home\Documents\PowerShell\powershell.config.json

 

The single settings file is used for PowerShell v6.2 and PowerShell v7 previews

PowerShell v7 preview 3

PowerShell v7 preview 3 is now available from https://github.com/PowerShell/PowerShell/releases

 

Breaking changes seem to be confined to non-Windows platforms with the removal of the kill alias on Stop-Process and support for pwsh as a login shell

 

The big new item is the –parallel parameter on Foreach-Object – more on this later.

 

In this preview version and future preview versions all experimental features will be enabled going forward

Out-GridView is back

Out-GridView is finally back in PowerShell core - https://devblogs.microsoft.com/powershell/out-gridview-returns/.

 

The project is hosted on github - https://github.com/powershell/GraphicalTools

 

Install the module from the gallery -

PS> Install-Module -Name Microsoft.PowerShell.GraphicalTools

 

Currently, Out-GridView is the only command in the module though adding Show-Command and Show-Object are planned.

 

The module works cross-platform not just Windows.

 

You need PowerShell v6.2 or later to build or presumably run the module.

PowerShell v7 preview 2

PowerShell v7 preview 2 arrived a few days ago.

No big ticket items in this preview.

 

Some useful things:

Issue with Get-ChildItem Path with wildcards has been fixed

UseAbbreviationExpansion and TempDrive are moved from experimental features to official features

Foreach-Object is 2 times faster – that’s just the looping remember that any cmdlets executing in the scriptblock( s ) won’t necessarily be faster

 

The Get-ADUser issue hasn’t been fixed but the underlying problem has supposedly been fixed in .NET core so will hopefully bubble through in the next preview

Load format file in a module

Staying with the test-connection function I thought I’d show how to turn the function and format file into a module. This includes how to load the format file in a module.

 

Create a folder TestConnection

 

Copy the format file and the script file that contains the test-connection function into the folder.

 

Rename the script file to TestConnection.psm1

 

Create a module manifest file:

PS> New-ModuleManifest -Path TestConnection.psd1 –RootModule testconnection.psm1 -FormatsToProcess RSPing.Format.ps1xml -CompatiblePSEditions Core

The manifest file should look like this:

#
# Module manifest for module 'TestConnection'
#
# Generated by: Richard
#
# Generated on: 30/06/2019
#

@{

# Script module or binary module file associated with this manifest.
RootModule = 'testconnection.psm1'

# Version number of this module.
ModuleVersion = '0.0.1'

# Supported PSEditions
CompatiblePSEditions = 'Core'

# ID used to uniquely identify this module
GUID = 'e260cd41-8023-4a38-8dab-ac8ffbf20163'

# Author of this module
Author = 'Richard'

# Company or vendor of this module
CompanyName = 'Unknown'

# Copyright statement for this module
Copyright = '(c) Richard. All rights reserved.'

# Description of the functionality provided by this module
# Description = ''

# Minimum version of the PowerShell engine required by this module
# PowerShellVersion = ''

# Name of the PowerShell host required by this module
# PowerShellHostName = ''

# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''

# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''

# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# CLRVersion = ''

# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()

# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()

# Format files (.ps1xml) to be loaded when importing this module
FormatsToProcess = 'RSPing.Format.ps1xml'

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = '*'

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = '*'

# Variables to export from this module
VariablesToExport = '*'

# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = '*'

 

To load the module into your PowerShell session:

PS> Import-Module -Name C:\Scripts\Modules\TestConnection\TestConnection.psd1

 

Substitute the path to your module.

Even simpler than updating the format data manually!

Test-Connection formatting

Last time I showed how to write a function that replaces the current PowerShell 6/7 Test-Connection. This time I’ll show you how to do the Test-Connection formatting.

Using just the function you get something like this:

PS> test-connection -computername 127.0.0.1

Success : True
Source : W10PROIP
Destination : 127.0.0.1
IPV4Address : 10.10.54.5
Bytes : 32
Time : 0

etc

 

All of the properties on the object are displayed and because there are more than four properties PowerShell defaults to a list display. What we want is to drop the Success property and display the remaining fields as a table – exactly like Windows PowerShell v5.1. I’ve deliberately dropped the IPv6 data because I don’t use IPV6 for anything.

Start by exporting the format data from Windows PowerShell

PS> Get-FormatData -TypeName 'System.Management.ManagementObject#root\cimv2\Win32_PingStatus' | Export-FormatData –Path C:\test\ping.xml

 

You have to use the full name for the type which you can find via

Test-Connection 127.0.0.1 | get-member

 

If you open the xml file you’ll see something like this:

<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<ViewDefinitions><View>
<Name>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</Name>
<ViewSelectedBy>
<TypeName>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader><Label>Source</Label><Width>13</Width></TableColumnHeader>
<TableColumnHeader><Label>Destination</Label><Width>15</Width></TableColumnHeader>
<TableColumnHeader><Label>IPV4Address</Label><Width>16</Width></TableColumnHeader>
<TableColumnHeader><Label>IPV6Address</Label><Width>40</Width></TableColumnHeader>
<TableColumnHeader><Label>Bytes</Label><Width>8</Width></TableColumnHeader>
<TableColumnHeader><Label>Time(ms)</Label><Width>9</Width></TableColumnHeader>
</TableHeaders>
<TableRowEntries><TableRowEntry><TableColumnItems>
<TableColumnItem><PropertyName>__Server</PropertyName>
</TableColumnItem><TableColumnItem><PropertyName>Address</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>IPV4Address</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>IPV6Address</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>BufferSize</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>ResponseTime</PropertyName></TableColumnItem>
</TableColumnItems></TableRowEntry></TableRowEntries>
</TableControl>
</View></ViewDefinitions>
</Configuration>

 

Initially the XML will be all on one line. You’ll need to use a pretty printer of manually edit the XML to get into a more readable format. I’ve deliberately put a few entries on the same line where they should probably be separated on their own lines.

 

The test-connection function sets the object type to RSping – you can change that if you want.

 

The XML needs the type changing in the name and typename fields. You also need to change the tableColumnHeader and the TableColumnItem fields so they look like this:

<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<ViewDefinitions>
<View>
<Name>RSPing</Name>
<ViewSelectedBy>
<TypeName>RSPing</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader><Label>Source</Label><Width>13</Width></TableColumnHeader>
<TableColumnHeader><Label>Destination</Label><Width>15</Width></TableColumnHeader>
<TableColumnHeader><Label>IPV4Address</Label><Width>16</Width></TableColumnHeader>
<TableColumnHeader><Label>Bytes</Label><Width>8</Width></TableColumnHeader>
<TableColumnHeader><Label>Time(ms)</Label><Width>9</Width></TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem><PropertyName>Source</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>Destination</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>IPV4Address</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>Bytes</PropertyName></TableColumnItem>
<TableColumnItem><PropertyName>Time</PropertyName></TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>

 

Save the format data as RSPing.Format.ps1xml

 

To use the format data you need to load it

Update-FormatData -PrependPath .\RSPing.Format.ps1xml

 

By prepending the format data your format data will be used before any system formatting.

 

Now when you use test-connection you’ll get the formatting you want.

 

The new formatting data is session specific. It stays in the PowerShell session until you close it. If you want the formatting data in all sessions either create a module or dot source the test-connection function and load the formatting data in your profile.

PowerShell 6/7 Test-Connection

The PowerShell 6/7 Test-Connection implementation as of PowerShell v6.2.1 and PowerShell v7.0 preview 1 is in my opinion a horrible example of how not to create output. the cmdlet shows each ping and then wraps all of the results in the Replies property so you need to do something like this:

Test-Connection 127.0.0.1 | select -ExpandProperty Replies

 

And on top of all this there’s also a progress bar displayed!

What I really want is a version of test-connection in PowerShell 6/7 that works like test connection does in Windows PowerShell v5.1.

 

There’s a lot of discussion going round in circles about Test-Connection but in the mean time I decided to write my own. I’m only concerned with Windows systems so I can use a CIM class. If I wanted it to work cross-platform I’d need to use a based dot net solution.

function test-connection {
[CmdletBinding()]
param (
[string]$computername,
[int]$count=4,
[switch]$quiet
)

$filter = "Address='$computername'"

$pingresults = 1..$count | ForEach-Object {
$ping = Get-CimInstance -ClassName Win32_PingStatus -Filter $filter
$props = [ordered]@{
Success = if ($ping.StatusCode -eq 0) {$true}else {$false}
Source = $env:COMPUTERNAME
Destination = $ping.Address
IPV4Address = $ping.IPV4Address
Bytes = $ping.BufferSize
Time = $ping.ResponseTime
}

$result = New-Object -TypeName PSobject -Property $props
$result.PSTypeNames[0] = "RSPing"

$result
}

if ($quiet) {
foreach ($pingresult in $pingresults){
$pingresult.Success
}
}
else {
$pingresults
}
}

 

I’ve given the function the name test-connection. If you have a function and a cmdlet with the same name the function takes precedence.

 

The function has three parameters – computername that can take a computer name or an IP address; count = number of pings defaults to 4 and a quiet switch that will return a boolean to indicate success of failure.

 

The work is done by Win32_PingStatus CIM class with a filter that uses the computername for the Address property.

 

The relevant properties are collected into the output object whose type is set to RSPing.

 

If the quiet switch is selected the Success property for each object is displayed otherwise the whole object is output.

 

I don’t want the success property in the full display so I’m going to write a format file to manage the display which I’ll show you next time.

 

PowerShell doesn’t always supply the functionality in the way that you want it but there’s nothing to stop you creating your own version of the command.

Quotes in CSVs

Up to and including PowerShell v6.2.x converting or exporting data to a csv has automatically put quotes round each field. In PowerShell v7 you can control quotes in CSVs

Using

Get-Service | ConvertTo-Csv

as an example.

 

The current behaviour is to put quotes round everything

"XboxNetApiSvc","System.ServiceProcess.ServiceController[]","False","False","False","Xbox Live Networking Service","Syst
em.ServiceProcess.ServiceController[]",".","XboxNetApiSvc","System.ServiceProcess.ServiceController[]",,"Stopped","Win32
ShareProcess","Manual",,

 

This is still the default behaviour in PowerShell v7 but there’s now a –usequotes parameter to control the quotes. Its default value is always to match previous behaviour.

 

Other options are AsNeeded and Never

AsNeeded consistently threw an error but Never seems to work