Monthly Archive

PowerShell Basics

Splatting and Default parameters

One thing you don’t hear much about is default parameters.

Consider this

Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceId = 'C:'"


A pretty standard use of CIM.


Now think if you have to do this across a number of machines on a regular basis.  Typing could get a bit tedious.

You could use splatting:

$params = @{
  ClassName = 'Win32_LogicalDisk'
  Filter = "DeviceId = 'C:'"

Get-CimInstance @params


Create a hash table of parameter names and values and use that to reduce your typing. Because its a hash table you can modify as required to use other classes or Filters


An alternative is to use default parameters

$PSDefaultParameterValues = @{
'Get-CimInstance:ClassName' = 'Win32_LogicalDisk'
'Get-CimInstance:Filter' = "DeviceId = 'C:'"



Use the $PSDefaultParameterValues variable to hold your default values. Note how the cmdlet and parameter are defined. You can then call the cmdlet and the default parameters and their values are applied.


If you want to override the default values you may have to do it for all of the default values for a cmdlet – in the above case the Filter is nonsensical if applied to Win32_OperatingSystem so you’d have to do this

Get-CimInstance -ClassName Win32_OperatingSystem -Filter "Manufacturer LIKE '%'"


Used with a bit of care splatting and default parameters are a good way to save typing

Out of Process

One thing I’ve been seeing come up a lot recently is the problem of modules and cmdlets cot being available when jobs and workflows are executed even though the module has been specifically loaded into PowerShell.


This is because workflows and Jobs run in a separate process when you execute them – NOT your current PowerShell process.  The worflow or job process doesn’t run your profile and doesn’t auto load modules.  


You need to specifically perform the module import. Remember you can’t use Import-Module in a workflow so you have to wrap that part in  an InlineScript block.

WMI wildcards and filtering

A question on the forum asking about filtering WMI results raises a number of interesting points.


The user wanted to pass a computername and a filter term to pull product information from remote machines. I ended up with this

$computername = $env:COMPUTERNAME
$filter = 'Live'

$scriptblock = {
    Get-WmiObject -Class Win32_product -Filter "Name LIKE '%$filter%'" |
    Select  IdentifyingNumber, Name, LocalPackage }

Invoke-Command -ComputerName $computername -ScriptBlock $scriptblock -ArgumentList $filter


You can pass an argument into the scriptblock you use with invoke-command by using the –Argumentlist parameter.

More interesting is the –Filter parameter on Get-Wmi-Object

-Filter "Name LIKE '%$filter%'"


Notice that % is the wildcard not * as you’d use for a string.  Its always better to filter the results from Get-WmiObject using –Filter rather than a where-object after the call.


Of course you can just use the wmi or cim cmdlets directly for this problem which is even better

Get-WmiObject -Class Win32_Product -ComputerName $computername -Filter "Name LIKE '%$filter%'" | Select  IdentifyingNumber, Name, LocalPackage


Get-CimInstance -ClassName Win32_Product -ComputerName $computername -Filter "Name LIKE '%$filter%'" | Select  IdentifyingNumber, Name, LocalPackage

WMI cmdlets and credentials

If you’re working with the WMI cmdlets and need to pass credentials you’ll end up with a statement something like this

Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computer -Credential $cred


If the computer name defaults to the local host or you use . or ‘localhost’ as the computer name you’ll get an error

PS> Get-WmiObject -Class Win32_ComputerSystem -ComputerName $env:COMPUTERNAME  -Credential $cred
Get-WmiObject : User credentials cannot be used for local connections
At line:1 char:1
+ Get-WmiObject -Class Win32_ComputerSystem -ComputerName $env:COMPUTER ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Get-WmiObject], ManagementException
    + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand


You need to build in some logic to prevent credentials being used ig you’re working against the local machine.  One way to this is to create a bunch of Get-WmiObject statements and use if statements to decide which to use.


I think there’s a neater way if you use splatting

#$computer = $env:COMPUTERNAME
#$computer = 'localhost'
#$computer = '.'
$computer = 'server02'

$params = @{
    'Class' =  'Win32_ComputerSystem '
    'ComputerName' = $computer

switch ($computer){
    "$env:COMPUTERNAME" {break}
    'localhost' {break}
    '.'   {break}
    default {$params += @{'Credential' = $cred}}

Get-WmiObject @params


Splatting involves creating a hash table of the parameters and their values. You can then use a switch statement to decide if computer matches any of the local name variants. If it doesn’t then add the credential


You could extend this slightly to cope with not having a computer name  in the initial set of params and only add it if required

$params = @{
    'Class' =  'Win32_ComputerSystem'

if ($computer) {
    $params += @{'ComputerName' = $computer}
    switch ($computer){
        "$env:COMPUTERNAME" {break}
        'localhost' {break}
        '.'   {break}
        default {$params += @{'Credential' = $cred}}

Get-WmiObject @params


Then you only test the computer name if you need to.

-in operator

As an alternative to the –contains operator you can use the –in operator


Repeating the tests from the previous post on –contains


PS> $primes = 1,3,5,7,11,13,17,19,23,29,31,37,41,43,47
PS> $candidate = 7
PS> $candidate -in $primes

PS> $candidate = 4
PS> $candidate -in $primes


The –in operator can be used in the simplified where syntax but –contains can’t

-contains operator

Sometimes you may want to test if a value is in a collection of values


For instance

$primes = 1,3,5,7,11,13,17,19,23,29,31,37,41,43,47


if you want to test if 7 is a member of the collection

$candidate = 7
$primes -contains $candidate


Likewise testing 4

$candidate = 4
$primes -contains $candidate

Testing connectivity before Invoke-Command

A question on the forum asked about testing if a remote machine could be reached before using Invoke-Command against it.


The usual way to test if you can reach a remote machine is to ping it

PS> Test-Connection -ComputerName $env:COMPUTERNAME -Quiet


That shows you can reach the machine but it doesn’t mean that you can use Invoke-Command to send a request.

I think a better test is to use Test-WSMan


It will test if the WinRm service is running (won’t test if remoting is enabled)


$computers = "$env:COMPUTERNAME", 'NotFound'

foreach ($computer in $computers){
    $target = $computer
    if (Test-WSMan -ComputerName $computer -ErrorAction Ignore) {
        Invoke-Command -ComputerName $computer -ScriptBlock {Get-Service}
    else {
        Write-Warning -Message "Couldn't connect to $computer"



You can push the output to file or put the unreachable machine names into a file if you need to record them.

Modifying MAC address


Another question on the forum brought up an interesting point. User want adapter name and mac address

PS> Get-NetAdapter | select Name, MacAddress

Name                         MacAddress
----                         ----------
WiFi                         28-18-78-D0-05-11
Bluetooth Network Connection 28-18-78-D0-05-12


but wanted to remove the hyphens in the mac address so tried

PS> Get-NetAdapter | select Name, MacAddress | foreach {$_.MacAddress -replace '-',''}


This doesn’t work because the foreach cmldet only knows to put put the new string when the hyphens have been replaced in the mac address. The name is effectively filtered out.


The answer is to use a calculated field in select-object like this

PS> Get-NetAdapter | select Name, @{N='MacAddress'; E={$_.MacAddress -replace '-',''}}

Name                         MacAddress
----                         ----------
WiFi                         281878D00511
Bluetooth Network Connection 281878D00512


N = Name and E= Expression though they are usually shorten for brevity as shown

Strings and collections

A question on the forum brought up a point that often confuses PowerShell novices.


QUESTION: What’s the difference between

$computers = 'W12R2SUS, W12R2DSC'


$computers = 'W12R2SUS', 'W12R2DSC'



The first is a string

PS> $computers.GetType() | ft -a

IsPublic IsSerial Name   BaseType
-------- -------- ----   --------
True     True     String System.Object


The second is an array – a collection of strings

PS> $computers.GetType() | ft -a

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


The difference is important when you try to use $computers as the value for a parameter where you want to supply a list of computer names:


if you use the string – you’ll get an error

PS> $computers = 'W12R2SUS, W12R2DSC'
PS> $ps = New-PSSession -ComputerName $computers
New-PSSession : One or more computer names are not valid. If you are trying to pass a URI, use the -ConnectionUri
parameter, or pass URI objects instead of strings.
At line:1 char:7
+ $ps = New-PSSession -ComputerName $computers
+       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (System.String[]:String[]) [New-PSSession], ArgumentException
    + FullyQualifiedErrorId : PSSessionInvalidComputerName,Microsoft.PowerShell.Commands.NewPSSessionCommand


You have to use an array

PS> $computers = 'W12R2SUS', 'W12R2DSC'
PS> $ps = New-PSSession -ComputerName $computers
PS> $ps

Id Name            ComputerName    State         ConfigurationName     Availability
-- ----            ------------    -----         -----------------     ------------
  2 Session2        W12R2DSC        Opened        Microsoft.PowerShell     Available
  1 Session1        W12R2SUS        Opened        Microsoft.PowerShell     Available


A trap that everyone falls into at some time or other but one that can be avoided

Controlling copies

I was recently asked about ‘forcing’ a copy so that only the files you want are copied


Consider a folder with lots of files. I want to copy those files that start with the letter t and have a txt extension

PS> Get-ChildItem -Path C:\test2\t*.txt

    Directory: C:\test2

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       08/08/2015     14:14              0 test.txt
-a----       08/08/2015     14:16              0 test1.txt
-a----       08/08/2015     14:17              0 test2.txt
-a----       08/08/2015     14:17              0 test3.txt
-a----       08/08/2015     14:17              0 test4.txt


I want to copy to c:\test1 which is empty


PS> Get-ChildItem -Path C:\test1\


Defining the copy is simply a matter of setting the path to files you want

PS> Copy-Item -Path C:\Test2\t*.txt -Destination C:\test1\ -Verbose
VERBOSE: Performing the operation "Copy File" on target "Item: C:\Test2\test.txt Destination: C:\test1\test.txt".
VERBOSE: Performing the operation "Copy File" on target "Item: C:\Test2\test1.txt Destination: C:\test1\test1.txt".
VERBOSE: Performing the operation "Copy File" on target "Item: C:\Test2\test2.txt Destination: C:\test1\test2.txt".
VERBOSE: Performing the operation "Copy File" on target "Item: C:\Test2\test3.txt Destination: C:\test1\test3.txt".
VERBOSE: Performing the operation "Copy File" on target "Item: C:\Test2\test4.txt Destination: C:\test1\test4.txt".

PS> Get-ChildItem -Path C:\test1\

    Directory: C:\test1

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       08/08/2015     14:14              0 test.txt
-a----       08/08/2015     14:16              0 test1.txt
-a----       08/08/2015     14:17              0 test2.txt
-a----       08/08/2015     14:17              0 test3.txt
-a----       08/08/2015     14:17              0 test4.txt