Monthly Archive

PowerShell Basics

Counting members

If you have a collection of objects

$proc = get-process


you can get the number of members using the Length property

PS>  $proc.Length

$proc is of type System.Array


PS>  $proc.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


MSDN documenattion is at


PowerShell also adds a Count property that is an alias of Length


if you are after the number of members in a collection via the pipeline you need to use the Measure-Object cmdlet

PS>  $proc | Measure-Object

Count    : 71
Average  :
Sum      :
Maximum  :
Minimum  :
Property :


or measure directly

PS>  Get-Process | Measure-Object

Count    : 71
Average  :
Sum      :
Maximum  :
Minimum  :
Property :


Notice that Measure-Object can also be used to discover other statistics – the Average, Sum, Maximum and Minimum. These only work for numeric properties.

Loading assemblies

PowerShell is .NET based but doesn’t load all available .NET assemblies when it starts.


Many people still use something  like



to load additional assemblies.  This is a hang over from PowerShell v1 when there wasn’t another way to perform the load.

The LoadWithPartialName method has been deprecated - – and shouldn’t be used.


Your alternatives are:

Add-Type -AssemblyName System.Windows.Forms


or in PowerShell v5

using assembly System.Windows.Forms

using namespace System.Windows.Forms


can be used

New variables with the variable cmdlets


So you have some data in csv format:

column1 column2 column3 column4
------- ------- ------- -------
a1      b1      c1      d1
a1      b2      c1      d1
a1      b3      c1      d1
a2      b3      c1      d1
a2      b4      c1      d1
a2      b3      c1      d1
a3      b5      c1      d1
a3      b6      c1      d1
a3      b7      c1      d1
a4      b5      c1      d1


In a variable $cd


You want colum1 and column2 in a new variable


Simplest way is probably

$new1 = $cd | select Column1, column2


The *-Variable cmdlets don’t get out much so I thought examples using them would be useful

You could also use the New-Variable cmdlet

New-Variable -Name new2 -Value ($cd | select Column1, column2)


Set-Variable also works

Set-Variable -Name new3 -Value ($cd | select Column1, column2)

Folder creation dates from WMI

A question on the about finding the creation date of folders raises some interesting points


To find a folder’s creation date use:

Get-WmiObject -Class Win32_Directory -Filter "Drive='C:' AND Path = '\\users\\$user\\'" | select Name, @{N='Creation date'; E={$_.ConvertToDateTime($_.CreationDate)}}




Get-CimInstance -ClassName Win32_Directory -Filter "Drive='C:' AND Path = '\\users\\$user\\'" | select Name, CreationDate


If you use Get-WmiObject the date is returned in the form



Which is why you need to perform the conversion using the ConvetToDateTime method that PowerShell adds to every WMI object.


Get-CimInstance automatically performs the conversion for you.


The other interesting part is the filter

"Drive='C:' AND Path = '\\users\\$user\\'"


Note that it’s wrapped in double quotes. Each of the values is a string so HAS to be in single quotes. Also note that you need to double the \ characters as WMI treats a single \ as an escape character so you have to escape the escape character.

Monitor Info

A question on the forum about combining information from 2 CIM classes produced this:


function Get-MonitorInfo {
        $computername = $env:COMPUTERNAME

    $cs = New-CimSession -ComputerName $computername
    $monitors =  Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorId -Filter "Active = '$true'" -CimSession $cs
    foreach ($monitor in $monitors) {
        $in = ($monitor.InstanceName).Replace('\', '\\')
        Write-Verbose -Message $in
        $dp = Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorBasicDisplayParams -Filter "InstanceName = '$in'" -CimSession $cs
        $name = ''

        foreach ($c in $monitor.UserFriendlyName){
            if ($c -ne '00'){$name += [char]$c}

        $type = 'Unknown'
        switch ($dp.VideoInputType){
            0 {$type = 'Analog'}
            1 {$type = 'Digital'}
        New-Object -TypeName PSObject -Property @{
            Name = $name
            Type = $type
    Remove-CimSession -CimSession $cs


Create a CIM session to the computer. Get the instances of the WmiMonitorId class. Iterate through them and find the matching WmiMonitorBasicDisplayParams class instance.


The InstanceName of the monitor will look like this:


you need to replace \ by \\ to use the value in a CIM query because \ is treated as the escape character and you have to escape it to use it


Translate the UserFriendly name by converting the byte array to a string and determine the VideoInputType using the switch.


Create an object and output

Comparing lists

An interesting question on the forum regarding how you compare the contents of 2 collections.  The question revolved around comparing the contents of the Machine property of




$Machine = @(
'System\CurrentControlSet\Control\Server Applications',
'Software\Microsoft\Windows NT\CurrentVersion'


Start by getting the data from the registry

$p = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedExactpaths' -Name Machine


System\CurrentControlSet\Control\Server Applications
Software\Microsoft\Windows NT\CurrentVersion


So the 2 look identical but how do we do that programmatically.


You can’t do

$machine -eq $p.machine


you get nothing back.

You have to use Compare-Object:


Compare-Object $machine $p.machine


You’ll get nothing back so check  everything is equal:

PS> Compare-Object $machine $p.machine -IncludeEqual

InputObject                                                                     SideIndicator
-----------                                                                         -------------
System\CurrentControlSet\Control\ProductOptions        ==
System\CurrentControlSet\Control\Server Applications ==
Software\Microsoft\Windows NT\CurrentVersion            ==


Compare-Object returns nothing if the 2 objects match so your comparison ends up as

PS> if ( -not (Compare-Object $machine $p.machine)){'yay'}else{'nay'}


You can test it works

$m2 = 'item1', 'item2', 'item3'

PS> Compare-Object $machine $m2

InputObject                                          SideIndicator
-----------                                          -------------
item1                                                =>
item2                                                =>
item3                                                =>
System\CurrentControlSet\Control\ProductOptions      <=
System\CurrentControlSet\Control\Server Applications <=
Software\Microsoft\Windows NT\CurrentVersion         <=


PS> if ( -not (Compare-Object $machine $m2)){'yay'}else{'nay'}

CSV file with [] in headers

With the PowerShell Summit registration deadline rapidly approaching I wanted to see how registrations were going. I can down load a CSV file from the event website that lists attendees.

Great. Just need to do some sorting and grouping and I’m golden.


I’m running PowerShell 5.0 on Windows 10 Insider build 14271


First off I discovered that the headers of the CSV file contain [] i.e. they look like this for example:



Sorting by attendee type


Import-Csv -Path C:\test1\AttendeeReport.csv |
Sort-Object -Property [AttendeeType]

works but if I add a check for complete registrations


Import-Csv -Path C:\test1\AttendeeReport.csv |
Where-Object [REGISTRATIONSTATUS] -eq 'Complete'|
Sort-Object -Property [AttendeeType]


I get nothing back.


Import-Csv -Path C:\test1\AttendeeReport.csv |
Where-Object '[REGISTRATIONSTATUS]' -eq 'Complete'|
Sort-Object -Property [AttendeeType]


Is no better. You need to revert to old style where-object syntax

Import-Csv -Path C:\test1\AttendeeReport.csv |
Where-Object {$_.'[REGISTRATIONSTATUS]' -eq 'Complete'}|
Sort-Object -Property [AttendeeType]


Now I want to group on attendee type

PS> Import-Csv -Path C:\test1\AttendeeReport.csv |
>> Where-Object {$_.'[REGISTRATIONSTATUS]' -eq 'Complete'}|
>> Sort-Object -Property [AttendeeType] |
>> Group-Object -Property [AttendeeType] -NoElement
Group-Object : Wildcard characters are not allowed in "[AttendeeType]".
At line:4 char:1
+ Group-Object -Property [AttendeeType] -NoElement
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Group-Object], NotSupportedException
    + FullyQualifiedErrorId : ExpressionGlobbing2,Microsoft.PowerShell.Commands.GroupObjectCommand


This partially works

Import-Csv -Path C:\test1\AttendeeReport.csv |
Where-Object {$_.'[REGISTRATIONSTATUS]' -eq 'Complete'}|
Sort-Object -Property [AttendeeType] |
Group-Object -Property '`[AttendeeType`]' -NoElement


But just gives me a count of the total number of attendees.


I tried a number of ways of dealing with the [] in the headers and therefore in the property names. In the end I decided it was going to be easier to completely reset the headers in the csv file:

Import-Csv -Path C:\test1\AttendeeReport.csv -Header  'UserName', 'REGISTRATIONSTATUS', 'AttendeeType', 'ConfirmationCode', 'PaymentAuthorization', 'RegisteredByEmail', 'PromotionCode', 'BioName', 'BioTitle', 'BioEmail', 'Created', 'Updated', 'LastName', 'Dietary', 'Alumni', 'FirstName', 'Twitter', 'BestEmail'


This means I have to skip the first record because it looks like this:

UserName             : [UserName]
AttendeeType         : [AttendeeType]
ConfirmationCode     : [ConfirmationCode]
PaymentAuthorization : [PaymentAuthorization]
RegisteredByEmail    : [RegisteredByEmail]
PromotionCode        : [PromotionCode]
BioName              : [BioName]
BioTitle             : [BioTitle]
BioEmail             : [BioEmail]
Created              : [Created]
Updated              : [Updated]
LastName             : [LastName]
Dietary              : [Dietary]
Alumni               : [Alumni]
FirstName            : [FirstName]
Twitter              : [Twitter]
BestEmail            : [BestEmail]


My grouping script is now much simpler

Import-Csv -Path C:\test1\AttendeeReport.csv -Header  'UserName', 'REGISTRATIONSTATUS', 'AttendeeType', 'ConfirmationCode', 'PaymentAuthorization', 'RegisteredByEmail', 'PromotionCode', 'BioName', 'BioTitle', 'BioEmail', 'Created', 'Updated', 'LastName', 'Dietary', 'Alumni', 'FirstName', 'Twitter', 'BestEmail' |
Select-Object -Skip 1 |
Where-Object REGISTRATIONSTATUS -eq 'Complete'|
Sort-Object -Property AttendeeType |
Group-Object -Property AttendeeType –NoElement


If I come across other CSV files with [] in the headers I’m going to go for immediate replacement of the headers as the way to get the job done.

Scripting Game puzzle – – January 2016

Here’s how I’d solve the puzzle

function get-starttime {
        [Alias('CN', 'Computer')]
        [string[]]$computername = $env:COMPUTERNAME
        foreach ($computer in $computername){
            $props = [ordered]@{
                ComputerName = $computer
                StartTime = ''
                'UpTime (Days)' = 0.0
                Status = 'OFFLINE'
            if (Test-WSMan -ComputerName $computer -ErrorAction SilentlyContinue) {
                $lbt = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $computer -ErrorAction SilentlyContinue
                if ($lbt) {
                    $props['StartTime'] = $lbt.LastBootUpTime
                    $upt = [math]::round(((Get-Date) - $lbt.LastBootUpTime).TotalDays, 1)
                    $props['UpTime (Days)'] = $upt
                    $props['Status'] = 'OK'
                else {
                    $props['Status'] = 'ERROR'
            } ## endif
            New-Object -TypeName PSObject -Property $props
        } ## end foreach
    } ## end PROCESS

Create an advanced function. Yes I know I’ve used lower case for the function name. I always do to visually separate my code from cmdlets and other functions.


Use the [parameter] decorator to enable pipeline input. Only a single parameter so don’t need to bother woth positional parameters. Function is supposed to default to local machien so can’t make parameter mandatory.


Requirement to process multiple computers at once presumably means the computername parameter has to take an array – sumultaneous processing implies a work flow which negates the initial requirement to create a function


Use the PROCESS block to run a foreach loop that iterates over the collection of computernames.


Create a hash table for the results – I’ve used an ordered hash table to preserve the property order. Set the values to a failed connection.


use Test-Wsman to see if can reach the computer. If can’t the output object is created. If you can reach the machine then run Get-CimInstance - preferred over Get-WmiObject because it returns the date ready formatted


Assuming that works set the start time and status properties. Calculate the uptime in days. I’d prefer to see  just an integer here – tenths of days doesn’t mean anything to most people


If the call to Get-CimInstance  fails then set the status to ERROR

Output the object.


The requirement to add a proeprty for patching is not clear but I’m assuming it means if the machine has been up for more than 30 days with the 1/10 month as a typo

if you want to add that then


Add a property

MightNeedPatching = $false

to the hash table when you create it


and add this line

if ($upt -ge 30){$props['MightNeedPatching'] = $true}


$upt = [math]::round(((Get-Date) - $lbt.LastBootUpTime).TotalDays, 1)
$props['UpTime (Days)'] = $upt


How do you find the FQDN of the machine you’re using. 

The simplest way is to combine a couple of environmental variables:



If you like using CIM (and who doesn’t) you can try this

PS> Get-CimInstance -ClassName Win32_ComputerSystem |
>> select @{N='FQDN'; E={"$($_.DNSHostName).$($_.Domain)"}}



This could easily be used for remote machines as well by adding the –ComputerName parameter to Get-CimInstance


If you want to go down the .NET route you have:

PS> [System.Net.Dns]::GetHostByName('').HostName

Testing against an arrays contents

You may need to test if a value is a member of an array. PowerShell provides 2 operators for testing array membership  - -   -in and –contains.

Simple usage is like this


PS> $colours = 'red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'


PS> 'blue' -in $colours

PS> $colours -contains 'blue'


Note the order of value and array changes between the operators.


You  can also use these operators in Where-Object


PS> $testcolours = 'blue', 'pink', 'yellow'


PS> $testcolours | where {$_ -in $colours}

PS> $testcolours | where {$colours -contains $_}


Often the value we want is a property of an object


PS> $to = New-Object -TypeName PSObject -Property @{Colour = 'green'}
PS> $to1 = New-Object -TypeName PSObject -Property @{Colour = 'pink'}


PS> $to, $to1 | where Colour -in $colours


PS> $to, $to1 | where {$colours -contains $_.Colour}



The –in operator can be used in the simplified Where-Object syntax (it was introduced for that purpose) but –contains has to use the full, original syntax


For testing non-membership you also get –notin and –notcontains


PS> $to, $to1 | where Colour -notin $colours


PS> $to, $to1 | where {$colours -notcontains $_.Colour}