Monthly Archive

Categories

PowerShell Basics

using help proactively

I was reading a thread on the forum about using a particular cmdlet and it occurred to me that the person posing the question hadn’t actually looked at the help file for the cmdlet.

 

One of the first things I do when coming across a new cmdlet – either in some code or when I need to perform a new task - is to read the FULL help file for the cmdlet.

Either 

Get-Help Get-Process –Full

 

or

Get-Help Get-Process –Online

 

The online version tends to be updated quicker than the downloadable version.

 

Two areas to concentrate on.

The parameter list shows you what parameters are available, the input and relevant information

 

The examples section is possibly the most useful as you’ll find how you can actually use the cmdlet.

 

The detailed description and notes a re worth a read – especially for a cmdlet you’ve not used before

 

Think about the PowerShell experts you’ve come across. How did they get to be experts? A lot of it was reading the help files.

Update-Help errors

Updatable help brings the benefit of up to date help with typos fixed and new edge cases described. The down side is that it sometimes fails:

 

PS> Update-Help -Force
Update-Help : Failed to update Help for the module(s) 'Microsoft.PowerShell.Operation.Validation'
with UI culture(s) {en-GB} : Unable to retrieve the HelpInfo XML file for UI culture en-GB. Make sure the HelpInfoUri property in the module manifest is valid or check your network connection and then try the command again.
At line:1 char:1
+ Update-Help -Force
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (:) [Update-Help], Exception
    + FullyQualifiedErrorId : UnableToRetrieveHelpInfoXml,Microsoft.PowerShell.Commands.UpdateHelpCo
   mmand

 

Update-Help : Failed to update Help for the module(s) 'PSScriptAnalyzer' with UI culture(s) {en-US} : The Help content at the specified location is not valid. Specify a location that contains valid Help Content.
At line:1 char:1
+ Update-Help -Force
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Update-Help], Exception
    + FullyQualifiedErrorId : HelpContentXmlValidationFailure,Microsoft.PowerShell.Commands.UpdateHe
   lpCommand

 

The failure messages are relatively self-explanatory if you read the details. The problem usually boils down to the needed help files not being available at the URI provided in the module manifest. This often occurs with new modules – the creation of the help files lags well behind the creation of the module usually.

 

Unfortunately there isn’t much you can do apart from try to find examples of the modules use online

Boolean in Where-Object filter

I was testing some code yesterday and realised there was a quirk in the way the original where syntax (with {}) worked and the way the newer syntax worked.

 

To demonstrate this I created a set of objects

$i = 0

$tests = while ($i -lt 25){
  New-Object -TypeName PSObject -Property @{
    Index = $i
    Current = if (-not($i % 2)){$true} else {$false}
  }
  $i++
}

 

Object properties are a numeric index and a boolean value

 

If you want just the $true values many people write this

$tests | where {$_.Current -eq $true}

 

or if using the newer syntax use this

$tests | where Current -eq $true

 

This is unnecessary typing as you can do this

$tests | where {$_.Current}

$tests | where Current

 

The reason is that the filter you are creating tests a property of the current object against your criteria and passes is if the result is true. A boolean property will by definition either be true of false so just need to test directly

 

if you want to double negative type test i.e. – not $true (which I don’t recommend as its very easy to get into  logic mess) then you have to do this

$tests | where {-not $_.Current}

 

as this fails
$tests | where –not Current

 

You could do this

$tests | where Current -ne $true

 

but it negates the whole code simplification objective

Months

It would be nice to be able to do this:

PS>  Get-Date -Day 25 -Month December -Year 2016
Get-Date : Cannot bind parameter 'Month'. Cannot convert value "December" to type "System.Int32".
Error: "Input string was not in a correct format."
At line:1 char:25
+ Get-Date -Day 25 -Month December -Year 2016
+                         ~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-Date], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.GetDateCo
   mmand

 

You have to do this:

PS>  Get-Date -Day 25 -Month 12 -Year 2016

25 December 2016 11:00:56

 

The time is set to current time automatically.

 

One of the gaps in .NET is an enum for the months of the year. You get an enum for days of the week:

PS>  [System.DayOfWeek]::Friday
Friday

 

PS>  0..6 | foreach {[System.DayOfWeek]$psitem}
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

 

PowerShell v5 gives you the capability to make your own enums:

enum MonthsOfYear {
January = 1
February = 2
March = 3
April = 4
May = 5
June = 6
July = 7
August = 8
September = 9
October = 10
November = 11
December = 12
}

 

PS>  [MonthsOfYear]10
October

PS>  [MonthsOfYear]::April
April

 

PS>  1..12 | foreach {[MonthsoFYear]$PSItem}
January
February
March
April
May
June
July
August
September
October
November
December

 

PS>  [System.Enum]::GetNames([MonthsOfYear])
January
February
March
April
May
June
July
August
September
October
November
December

 

Now you can use the name of the month

PS>  Get-Date -Day 25 -Month ([MonthsOfYear]::December) -Year 2016

25 December 2016 11:19:38

 

Enums are one of those pieces of functionality that you don’t necessarily think about but are very useful. The ability to create your own in PowerShell is very useful and will become more widespread as people realise its available.

WMI Filters

A common mistake with WMI/CIM filters is:

PS>  Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceId=C:"
Get-WmiObject : Invalid query "select * from Win32_LogicalDisk where DeviceId=C:"
At line:1 char:1
+ Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceId=C:"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-WmiObject], ManagementException
    + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCo
   mmand

 

The clue is in the invalid query error message

When you use the –Filter parameter and are testing a property of type string the value you are testing against has to be in quotes

PS>  Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceId='C:'"

DeviceID     : C:
DriveType    : 3
ProviderName :
FreeSpace    : 108999528448
Size         : 248480002048
VolumeName   : Windows

 

The filter is defined as a string so you need to use single quotes inside the double quotes. You could mess around with all single quotes but then you have to escape the inner set of quotes – good luck with that – its an unnecessary exercise in frustration

 

How do you know the property is a string?

PS>  (Get-CimClass -ClassName Win32_LogicalDisk).CimClassProperties['DeviceId']

Name               : DeviceID
Value              :
CimType            : String
Flags              : Property, Key, ReadOnly, NullValue
Qualifiers         : {CIM_Key, read, key, MappingStrings...}
ReferenceClassName :

 

The same rules for –Filter apply to Get-CimInstance

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

32 or 64 and/or Administrator

When you run the PowerShell console (or ISE) the default icon runs a 32 or 64 bit version that matches your OS. On a 64 bit machine you have the option of running in 32bit (icons have a (x86) suffix on the title.

 

How can you tell whether you’re running in 32 or 64 bit mode?

 

One way is shown in this forum question - http://powershell.org/forums/topic/requires/#post-42226

 

I prefer the simpler:

if ([System.IntPtr]::Size -eq 8) {$size = '64 bit'}
else {$size = '32 bit'}

 

I don’t like automatically kicking into the required bit version if its wrong. I prefer to abort the processing with a message to run as 32 or 64 bit as appropriate

 

You can perform a similar test for administrator privileges (running elevated)

$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
$secprin = New-Object Security.Principal.WindowsPrincipal $currentUser
if ($secprin.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator))
{$admin = 'Administrator'}
else {$admin = 'non-Administrator'}

 

Though on later versions of Powershell its easier to use

#requires –RunAsAdministrator

Dates in file and folder names

If you want to incorporate the date in a file or folder name you can’t use Get-Date directly

PS>  Get-Date

01 June 2016 20:52:03

 

The simplest answer is to use the –Format or –Uformat parameters:

PS>  Get-Date -Format yyyyMMdd
20160601

PS>  Get-Date -UFormat %Y%m%d
20160601

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
71

$proc is of type System.Array

 

PS>  $proc.GetType()

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

 

MSDN documenattion is at https://msdn.microsoft.com/en-us/library/system.array(v=vs.110).aspx

 

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

[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')

 

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 - https://msdn.microsoft.com/en-us/library/12xc5368(v=vs.110).aspx – 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)