PowerShell for Windows Admins

June 21, 2018  10:19 AM

Creating objects

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

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


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


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!

June 20, 2018  4:48 AM

Windows server 2019 preview build 17962

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Windows Server

The availability of Windows server 2019 preview build 17962 has been announced.

Also available is:

A preview of Hyper-V Server 2019

System Insights – a new feature bringing local predictive analytical capabilities to Windows Server. Systems Insights is managed through Windows Admin Center or PowerShell

Server Core App Compatibility on Demand – includes ability to install system components that need some of the GUI capabilities not present on server core including MMC, perfmon, sysinternals tools and PowerShell ISE

A new preview of Windows Admin Center (WAC was formerly known as project Honolulu)

I’m not a big fan of WAC – its perpetuating the GUI based focus of admin tools. One good thing in this preview is that the underlying PowerShell scripts are know visible. This may just make it useful as a learning tool in the same way that the AD Adninistrative center is a way to learn how to manage AD with PowerShell

June 19, 2018  10:57 AM

Avoid Add-Member

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

There’s a huge amount of code still being created that looks like this:

$os = Get-WmiObject -Class Win32_OperatingSystem
$comp = Get-WmiObject -Class Win32_ComputerSystem

$op = New-Object -TypeName PSObject |
Add-Member -MemberType NoteProperty -Name OS -Value $os.Caption -PassThru |
Add-Member -MemberType NoteProperty -Name Version -Value $os.Version -PassThru |
Add-Member -MemberType NoteProperty -Name Name -Value $comp.Caption -PassThru |
Add-Member -MemberType NoteProperty -Name Manufacturer -Value $comp.Manufacturer -PassThru |
Add-Member -MemberType NoteProperty -Name Model -Value $comp.Model -PassThru


Some data is acquired and a custom object is created for the output. In this case an empty object is created and Add-Member is used to create and populate the object’s properties.

The best thing that can be said about this method is that it works. This method is a hang over from the early days of PowerShell. It’s about time that people moved on and learned how to do this properly.

NOTE: Get-CimInstance should be used these days instead of Get-WmiObject as the WMI cmdlets aren’t available in PowerShell v6.0 and are effectively deprecated.

Instead of using Add-Member use a hash table to create and populate the properties:

$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


This is much cleaner, involves less typing (therefore reducing errors) and is easier to understand.

Many people (including those that should know better) whine about the fact that using New-Object means that the order of the properties when displayed isn’t the same as input and so prefer the Add-Member method.

If you absolutely have to force the order of properties then use an ordered hash table:

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

$props = [ordered]@{
OS = $os.Caption
Version = $os.Version
Name = $comp.Caption
Manufacturer = $comp.Manufacturer
Model = $comp.Model

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


So, should Add-Member be totally avoided. Not quite. It does have a use – when you want to add a very small number of properties to an existing object. Otherwise don’t use it.

May 31, 2018  3:43 AM

Splitting into pairs

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

During my session on scriptblocks at Summit 2018 I showed how to split a string into pairs of values

Start with this string

PS> $str = ‘Jack,Jill,Bill,Ben,Eric,Ernie,Cagney,Lacey’

You want the string split in the commas – but every other comma so you get pairs if values separated by a comma.

You need to set a counter
PS> $count=@(0)
PS> $count

Then use a scriptblock to control the split
PS> $str -split {$_ -eq ‘,’ -AND ++$count[0] % 2 -eq 0}
PS> $count

You need to use an array because the scriptblock executes in its own context and a scalar value wouldn’t be updated as you need.


PS> $count2 = 0
PS> $str -split {$_ -eq ‘,’ -AND ++$count2 % 2 -eq 0}
PS> $count2

and nothing happens.

May 31, 2018  3:28 AM

Summit 2018 sessions

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

The code and slides from my Summit 2018 sessions can be found at




May 31, 2018  3:13 AM

PowerShell functionality

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

PowerShell functionality – in the form of modules – comes from a number of different sources.

These are:

Powershell itself including the Microsoft.PowerShell.* modules and CIM cmdlets

Windows Team – this includes the modules such as NetAdapter, NetTCPIP, Storage that were first introduced with Windows 8 and have been part of all subsequent Windows client and server versions. These modules are often CDXML (CIM based) which means they won’t be found, and can’t be installed, on versions of Windows earlier than Windows 8

Windows feature installs – these modules are installed when you install a windows feature or role such as DNS, AD, IIS or Hyper-V. Many of then are also available as part the RSAT tools. Their presence on your system is dependent on what features are installed.

Modules you have written – totally unique to you

Modules you’ve down loaded from the PowerShell gallery or other repository. Again the presence of a particular module depends on what you’ve downloaded.

Modules from third party vendors – such as NetApp or VMWare. Their presence depends on what you’ve bought and installed.

If you can’t find a cmdlet or module on your system check that its available for that version of windows then check to see where it comes from. You may, depending on the source, be able to download and/or install it.

May 28, 2018  9:48 AM

Hyper-V book

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

The Month of Lunches Hyper-V book I was working on was cancelled by the publisher.

The good news is that it’s most likely going to be resurrected with another publisher and will hopefully be available later this year.

More to follow when the details are finalised

May 26, 2018  10:11 AM

WMI and CIM accelerators

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
CIM, Powershell, WMI

In PowerShell an accelerator is a shortcut to a .NET type. The WMI accelerators have been around since PowerShell v1. The WMI accelerators were heavily used in v1 fill some of the gaps in cmdlet coverage. The CIM accelerators appeared in PowerShell v3 (I think – only discovered them recently!). This is how you use the WMI and CIM accelerators.

There are three WMI accelerators

wmiclass is shortcut for System.Management.ManagementClass
wmi is shortcut for System.Management.ManagementObject
wmisearcher is shortcut for System.Management.ManagementObjectSearcher

And four CIM accelerators are

ciminstance is shortcut for Microsoft.Management.Infrastructure.CimInstance
cimclass is shortcut for Microsoft.Management.Infrastructure.CimClass
cimtype is shortcut for Microsoft.Management.Infrastructure.CimType
cimconverter is shortcut for Microsoft.Management.Infrastructure.CimConverter


CimSession which is a shortcut for Microsoft.Management.Infrastructure.CimSession. Use this to set a parameter type.

Notice that there isn’t a direct correspondence between the WMI and CIM accelerators.

PowerShell v6 only has the CIM accelerators

The WMI accelerators are used like this:


This can be used for creating new instances of CIM classes

PS> $p = [wmiclass]’Win32_Process’
PS> $p.Create(“notepad.exe”)

This is easily replicated using the CIM cmdlets

PS> Invoke-CimMethod -ClassName Win32_Process -MethodName create -Arguments @{CommandLine=’notepad.exe’}


The [wmi] accelerator is used to find an instance BUT you have to use the class key!

PS> [wmi]”root\cimv2:Win32_Process.Handle=’7264′”

NOTE the handle has to be the one reported by CIM NOT the one reported by Get-Process!

Its much easier to use Get-CimInstance and filter on the name

Get-CimInstance -ClassName Win32_Process -Filter “Name=’pwsh.exe'”


This is used to find CIM instances:

PS> $query = [wmisearcher]”SELECT * FROM Win32_Process WHERE Name=’pwsh.exe'”
PS> $query.Get()

Its easier to use Get-CIMinstance these days

PS> Get-CimInstance -ClassName Win32_Process -Filter “Name=’pwsh.exe'”

Looking at the CIM accelerators


This one doesn’t seem to be usable for anything but a type decorator on a parameter.


Using Get-CimClass

PS> Get-CimClass -ClassName Win32_process | fl

CimSuperClassName : CIM_Process
CimSuperClass : ROOT/cimv2:CIM_Process
CimClassProperties : {Caption, Description, InstallDate, Name…}
CimClassQualifiers : {Locale, UUID, CreateBy, DeleteBy…}
CimClassMethods : {Create, Terminate, GetOwner, GetOwnerSid…}
CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties
CimClassName : Win32_Process

The output is of type Microsoft.Management.Infrastructure.CimClass for which cimclass is an accelerator BUT there doesn’t seem to be a way to use the accelerator to access a class. I think this one is only usable as a type on a parameter for a function where you want to pass in a CIM class object.


Microsoft.Management.Infrastructure.CimType is an enum that contains the CIM (and WMI) datatypes:

PS> [cimtype]::Boolean
PS> [cimtype]::UInt32

The full set of CIM data types is

PS> [enum]::GetNames([cimtype])


Some of the CIM data types shown above don’t directly correspond to .NET types you’re used to from PowerShell. You can use [cimconvertor] which is shortcut for Microsoft.Management.Infrastructure.CimConverter to discover the corresponding .NET or CIM data type


PS> [cimconverter]::GetCimType([int32])
PS> [cimconverter]::GetCimType([double])


PS> [cimconverter]::GetDotNetType([cimtype]::SInt32)

IsPublic IsSerial Name BaseType
——– ——– —- ——–
True True Int32 System.ValueType

PS> [cimconverter]::GetDotNetType([cimtype]::Instance)

IsPublic IsSerial Name BaseType
——– ——– —- ——–
True True CimInstance System.Object

For the most part I think the WMI and CIM accelerators are best ignored. Use the CIM cmdlets instead. The cimtype and cimconverter accelerators are useful when developing code to check on types between CIM and .NET

May 22, 2018  1:01 PM

PowerShell parameter sets

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

PowerShell parameter sets allow you to control which parameters can be used together. If a parameter isn’t in the parameter set you’re trying to use you’ll get an error message.

PS> Get-VM -Name XYZ -id (New-Guid)
Get-VM : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:1
+ Get-VM -Name XYZ -id (New-Guid)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-VM], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.HyperV.PowerShell.Commands.GetVM

You can also use parameter sets to help control the processing within your function as this example shows:

function Convert-Temperature {
param (



$temps = New-Object -TypeName psobject -Property @{
'Temperature-Celsius' = 0.0
'Temperature-Fahrenheit' = 0.0
'Temperature-Kelvin' = 0.0

switch ($psCmdlet.ParameterSetName) {
"Celsius" {
$temps.'Temperature-Celsius' = $degreeC
$temps.'Temperature-Fahrenheit' = [math]::Round((32 + ($degreeC * 1.8)), 2)
$temps.'Temperature-Kelvin' = $degreeC + 273.15

"Fahrenheit" {
$temps.'Temperature-Celsius' = [math]::Round((($degreeF - 32) / 1.8), 2)
$temps.'Temperature-Fahrenheit' = $degreeF
$temps.'Temperature-Kelvin' = $temps.'Temperature-Celsius' + 273.15

"Kelvin" {
$temps.'Temperature-Celsius' = $degreeK - 273.15
$temps.'Temperature-Fahrenheit' = [math]::Round((32 + ($temps.'Temperature-Celsius' * 1.8)), 2)
$temps.'Temperature-Kelvin' = $degreeK

default {Write-Error -Message "Error!!! Should not be here" }



Each of the input parameters is in its own parameter set meaning that they are mutually exclusive – you can only use one of them!

The switch statement uses the active parameter set name to decide how to perform the relevant conversions – the output object contains the temperature in Celsius, Fahrenheit and Kelvin

May 22, 2018  8:52 AM

PowerShell version

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Discovering the PowerShell version you’re using can be an interesting task.

The automatic variable $PSVersionTable was introduced in PowerShell v2. On my Windows 10 version 1803 machine for Windows PowerShell I get

PS> $PSVersionTable

Name Value
—- —–
PSVersion 5.1.17134.48
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
BuildVersion 10.0.17134.48
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3

$PSVersionTable is a hashtable so the order in which items are displayed may vary. The contents of the hashtable have changed over time as well.

The important parts of $PsversionTable include:

The version of PowerShell itself

PS> $PSVersionTable.PSVersion

Major Minor Build Revision
—– —– —– ——–
5 1 17134 48

PS> $PSVersionTable.PSVersion.Major

This a simple test for version.

The edition is also important

PS> $PSVersionTable.PSEdition

Desktop means its full CLR – in other words Windows PowerShell

The WSMAN version is also important

PS> $PSVersionTable.WSManStackVersion

Major Minor Build Revision
—– —– —– ——–
3 0 -1 -1

You need v3 to use CIM sessions.

With PowerShell v6 you get a few more options

PS> $PSVersionTable

Name Value
—- —–
PSVersion 6.0.1
PSEdition Core
GitCommitId v6.0.1
OS Microsoft Windows 10.0.17134
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
WSManStackVersion 3.0

Notice PSEdition is set to Core.

The OS and Platform data are v6 only

The PSRemotingProtocolVersion, SerializationVersion and WSManStackVersion are the same for Windows PowerShell v5.1 and PowerShell v6.

PowerShell v6 runs on Windows, Linux and macOS. You can test which OS you’re on from $PSVersionTable or more simply using the following automatic variables:

PS> ls variable:\is*

Name Value
—- —–
IsLinux False
IsMacOS False
IsWindows True
IsCoreCLR True

Using these you could create branching logic to perform a task by calling the appropriate command based on the underlying operating system.

Forgot Password

No problem! Submit your e-mail address below. We'll send you an e-mail containing your password.

Your password has been sent to: