Monthly Archive

PowerShell Basics

Awkward file and folder names

Spent some time today dealing with a situation where there were special characters – namely [ ] in folder a file names

£> Get-ChildItem -Path C:\Test

    Directory: C:\Test

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        21/01/2015     17:58            Awkward [One]
d----        21/01/2015     17:59            Simple One

 

Each folder has 3 files:

File 1.txt
File 2.txt
File 3.txt

 

Get-ChildItem -Path 'C:\Test\Simple One'

will work and show you the contents. When I’m typing paths like this I let Tab completion do the work. Type c:\test\ and use the Tab key to cycle round the available folders.

 

This gives

£> Get-ChildItem -Path 'C:\Test\Awkward `[One`]'
Get-ChildItem : Cannot find path 'C:\Test\Awkward `[One`]' because it does not exist.
At line:1 char:1
+ Get-ChildItem -Path 'C:\Test\Awkward `[One`]'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\Test\Awkward `[One`]:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

 

Unfortunately, the construct produced by Tab completion doesn’t work.  You need to double up on the back ticks so that it functions as an escape character.

Get-ChildItem -Path 'C:\Test\Awkward ``[One``]'

 

But that only shows you the folder not the contents.

Get-ChildItem -Path 'C:\Test\Awkward ``[One``]\*'

OR

Get-ChildItem -Path 'C:\Test\Awkward ``[One``]' -Include *

 

Will show you the contents of the folder.

 

But bizarrely

Get-Content -Path 'C:\Test\Awkward `[One`]\File 1.txt'

Works. As does

Copy-Item -Path 'C:\Test\Awkward `[One`]\File 1.txt' -Destination c:\test2

 

By using back ticks and quotes you can get round most problems like this. Other characters that cause similar problems are commas and quote marks.

Best advice of all – don’t use those awkward characters in your file names if you can possibly avoid it.

Event log dates

You can use Get-EventLog to query the event logs on you system

Get-EventLog -LogName System

 

One frequent task is to check if events occurred during a specific timespan. You may feel that you need to use a where-object filter to do this but there is a simple method.

Get-EventLog -LogName System -After (Get-Date -Date '1/1/2015')

 

Will return all events after the given date. if you don’t give a time your results start at midnight.

Get-EventLog -LogName System  -Before (Get-Date -Date '10/1/2015')

 

Will return all events before 10 January 2015.

You ususally use –Before in conjunction with –After to specify a data range

Get-EventLog -LogName System -After (Get-Date -Date '1/1/2015') -Before (Get-Date -Date '10/1/2015')

 

You can make these ranges quite specific

Get-EventLog -LogName System -After (Get-Date -Date '10/1/2015 14:31:00') -Before (Get-Date -Date '10/1/2015 15:00:00')

foreach, pipelines and $_

I’ve recently seen a few questions where people have been using a pipeline inside a foreach loop and experienced problems when they’ve tried to access properties on the looping objects. To illustrate we’ll start with a CSV file containing emailaddresses and job titles.

£> Import-Csv -Path C:\Test\userdata.txt

emailaddress             title   
------------             -----   
gdreen@Manticore.org     Boss    
dbrown@Manticore.org     Underling
dwhite@Manticore.org     Underling
jdaven@Manticore.org     Minion  
fgreen@Manticore.org     Minion  
dgreensmth@Manticore.org Minion  
dgreenly@Manticore.org   Minion

This is definitely an employee friendly organisation Smile

At the moment AD doesn’t contain any job title information

£> $users = Import-Csv -Path C:\Test\userdata.txt

foreach ($user in $users){
$mail = $user.EmailAddress

Get-ADUser -Filter {EmailAddress -eq $mail} -Properties Title |
select Name, Title

}

Name                                      Title                                  
----                                      -----                                  
Dave Green                                                                       
Dave Brown                                                                       
Dave White                                                                       
Jo Daven                                                                         
Fred Green                                                                       
Dale Greensmith                                                                  
Dave Greenly

 

The approach that causes problems is this:

£> $users = Import-Csv -Path C:\Test\userdata.txt

foreach ($user in $users){
$mail = $user.EmailAddress

Get-ADUser -Filter {EmailAddress -eq $mail} -Properties Title |
foreach {Set-ADUser -Identity $_ -Title $_.Title} |

Get-ADUser -Filter {EmailAddress -eq $mail} -Properties Title |
select Name, Title
}

Name                                      Title                                  
----                                      -----                                  
Dave Green                                                                       
Dave Brown                                                                       
Dave White                                                                       
Jo Daven                                                                         
Fred Green                                                                       
Dale Greensmith                                                                  
Dave Greenly     

 

When you use foreach as a keyword the $_ and $psitem variables aren’t available. These variables represent the current object on the pipeline.  The foreach keyword loop doesn’t have a pipeline as such.

 

Inside the foreach a pipeline is created

Get-ADUser -Filter {EmailAddress -eq $mail} -Properties Title |
foreach {Set-ADUser -Identity $_ -Title $_.Title -PassThru} |
select Name, Title

 

$_ is used correctly to identify the object on which Set-ADUser is to work – its the current object on the pipeline.

 

The use of  $_.Title  to set the user’s job title is where the problem really bites.  $_.Title  refers to the Title property of the current object on the pipeline so you are setting the Title to its existing value.

 

You need to reach back to the $user object that represents the current object from the set you are looping through with foreach to get the correct value

£> $users = Import-Csv -Path C:\Test\userdata.txt

foreach ($user in $users){
$mail = $user.EmailAddress

Get-ADUser -Filter {EmailAddress -eq $mail} -Properties Title |
foreach {Set-ADUser -Identity $_ -Title $user.Title} |

Get-ADUser -Filter {EmailAddress -eq $mail} -Properties Title |
select Name, Title
}

Name                                      Title                                  
----                                      -----                                  
Dave Green                                Boss                                   
Dave Brown                                Underling                              
Dave White                                Underling                              
Jo Daven                                  Minion                                 
Fred Green                                Minion                                 
Dale Greensmith                           Minion                                 
Dave Greenly                              Minion  

 

You’ll see similar problems if you have nested foreach-object loops or a switch statement inside a foreach-object loop.  $_ always refers to the current context and you have to either reach back to the looping variable in the csae of a foreach or set variables on the data in the outer foreach before entering the nested foreach.

More on dates

My recent post on date conversions seems to have generated a fair bit of confusion and discussion. The original task was to read in a string from a CSV file that contained a date in UK format – DD/MM/YYYY and reformat it to a string is US format – MM/DD/YYYY

 

As I showed you can’t use [datetime] type accelerator for this as it expects the date string to be in US format.

 

A number of people pointed out that you can use the –Date parameter on Get-Date

£> $strUK = '25/12/2014'
£> Get-Date -Date $strUK

25 December 2014 00:00:00

 

Now if you want that output as a string in US format

£> Get-Date -Date $strUK -Format "MM/dd/yyyy"
12/25/2014

 

Use a variable to contain that data and you can work with it as you wish.

 

You need to be aware that using –Date on Get-Date is culture aware:

£> Get-Date -Date '25/12/2014'

25 December 2014 00:00:00

£> Get-Date -Date '12/25/2014'
Get-Date : Cannot bind parameter 'Date'. Cannot convert value "12/25/2014" to type "System.DateTime". Error: "String was not recognized as a valid DateTime."
At line:1 char:16
+ Get-Date -Date '12/25/2014'
+                ~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-Date], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.GetDateCommand

Date conversions

Saw a question regarding the conversion of dates in UK format DD/MM/YYYY to US format MM/DD/YYYY

 

Using 25 December 2014 as an example

If you have a dat in US format you can do this

£> $sd = '12/25/2014'
£> $d = [datetime]$sd
£> $d

25 December 2014 00:00:00

 

$d = [datetime]$sd   creates a datetime object

 

If you try that in UK format

£> $sduk = '25/12/2014'
£> $d = [datetime]$sduk
Cannot convert value "25/12/2014" to type "System.DateTime". Error: "String was not recognized as a valid DateTime."
At line:1 char:1
+ $d = [datetime]$sduk
+ ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvalidCastParseTargetInvocationWithFormatProvider

 

It fails.

.NET expects strings representing dates to be in US format.

£> [datetime]'25 December 2014'

25 December 2014 00:00:00

works but the data isn’t in that format.

 

The easiest way I know to create a date object when you have UK format strings would be:

£> $sd = '25/12/2014' -split '/'
£> $sd
25
12
2014
£> $d = Get-Date -Day $sd[0] -Month $sd[1] -Year $sd[2]
£> $d

25 December 2014 18:36:05

What Formatting cmdlets do to your data

I have seen an increasing number of questions recently where the answer has been to remove Format-Table from the pipeline. As an example consider the names of the processes running on your machine

Get-Process -Name calc | Stop-Process

 

works because you are piping the selected object into the Stop-Process cmdlet.

Now think about this

Get-Process -Name calc | select Name | Stop-Process

 

a bit more long winded (the select isn’t necessary) but the resultant object hitting Stop-Process identifies a process by name which is all Stop-Process needs to work.

 

As a way to approach the reason for the first sentence in the post is there any difference between these two statements?

Get-Process -Name calc

Get-Process -Name calc | Format-Table

 

The output to screen looks identical. So  I should be able to do this:

Get-Process -Name calc | Format-Table | Stop-Process

 

What I get is a bunch of errors of the form:

Stop-Process : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
At line:1 char:41
+ Get-Process -Name calc | Format-Table | Stop-Process
+                                         ~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (Microsoft.Power...FormatStartData:PSObject) [Stop-Process], ParameterB
   indingException
    + FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.StopProcessCommand

 

The reason for this is simple.  The Format cmdlets destroy your pipeline and objects.  They take what they are given and output formatting directives. The only thing you can do with them is display them on screen (you can pipe to a text file but effectively the same thing)

 

If in doubt about what you’re dealing with at any time use Get-member

£> Get-Process | Get-Member

   TypeName: System.Diagnostics.Process

 

£> Get-Process | select name | Get-Member

   TypeName: Selected.System.Diagnostics.Process

 

Notice the slight change in that its now Selected.System.Diagnostics.Process instead of System.Diagnostics.Process

This only applies if you’re selecting a subset of properties. If you want the first N objects

 

£> Get-Process | select -First 5 | Get-Member

   TypeName: System.Diagnostics.Process

 

You still have the original type.

 

However, lets look at formatting

£> Get-Process | Format-Table | Get-Member

TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatStartData

TypeName: Microsoft.PowerShell.Commands.Internal.Format.GroupStartData

TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData

TypeName: Microsoft.PowerShell.Commands.Internal.Format.GroupEndData

TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatEndData

 

You get 5 different objects out – none of which have anything to do with your processes – apart from the values.

The Format cmdlets are designed to format your data for display. Thats all they do. Once your data hits the Format cmdlets that data can only be used fro display – nothing else. Put your Format cmdlets at the end of your pipeline and don’t attempt to do anything else with the data.

The little changes that make a difference

Each version of PowerShell introduces a new headline feature – remoting, workflows, DSC, OneGet in version 2,3,4 and 5 respectively. While this can change the way we work there are also a host of little changes that come along that are often overlooked.

 

One example is a change to Get-ChildItem introduced in PowerShell 3.0.

 

Consider getting a directory listing:

Get-ChildItem -Path C:\Windows

 

This will give all subfolders and file in the given folder.

 

If you just wanted the files you had to do this:

Get-ChildItem -Path C:\Windows | where {$_.PSIsContainer}

 

If you want just the files you use:
Get-ChildItem -Path C:\Windows | where {-not $_.PSIsContainer}

 

or the slightly shorter but not as easy to read:
Get-ChildItem -Path C:\Windows | where {!$_.PSIsContainer}

 

The PSIsContainer property name is not intuitive and I rarely remember the name exactly and try ISPSContainer first or some other variant.

 

Two additional filtering parameters were added to Get-ChildItem

Get-ChildItem -Path C:\Windows –Directory

 

and
Get-ChildItem -Path C:\Windows -File

 

produce listings of folders and files respectively.

 

A small simple change that makes life easier.

 

There are a lot of small changes like this scattered through the later PowerShell versions – I’d recommend going through the release notes to track down the ones that will be useful to you.

Output from jobs

I tripped over a little problem the other day that’s worth reporting.  I was running something like this:

 

$sb = {
$procs = get-service
$procs | Export-Csv test.csv -NoTypeInformation
}

Start-Job -ScriptBlock $sb -Name test

 

I was collecting some data and outputting a CSV.  My problem was more complex but this stands as a good example

 

I didn’t get the data I wanted

 

Thinking about it I put the full path to where I wanted the CSV

 

$sb = {
$procs = get-service
$procs | Export-Csv C:\MyData\scripts\Test\test.csv -NoTypeInformation
}

Start-Job -ScriptBlock $sb -Name test

 

And it works.

 

So where did my data go in the original version?

 

I ran this

 

$sb = {
Get-Location

$procs = get-service
$procs | Export-Csv test.csv -NoTypeInformation
}

Start-Job -ScriptBlock $sb -Name test

 

And then pulled the data from the job

 

£> Receive-Job -Id 10

Path
----
C:\Users\Richard\Documents

 

Obvious really – a job runs in a new powershell process that doesn’t run your profile so it starts in the default location  - which is your home directory. And sure enough the CSV file is there

 

£> ls C:\Users\Richard\Documents\*.csv

    Directory: C:\Users\Richard\Documents

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        14/09/2014     11:50      46042 test.csv

 

I can’t remember how many times I’ve told people that PowerShell jobs run in a separate process so I should have realised.  Excellent example of the more you know the more you need to learn

Grains of rice on a chess board

There is a story about the inventor of chess being rewarded by putting 1 grain of rice on the first square of the board; 2 on the second and so on.  How much rice does that come to?

 

The total number of grains is 1.84467440737096E+19

 

At 25mg per grain thats 461168601842739 kilogrammes of rice

 

Which in more understandable terms is:

 

461,168,601,842.739 metric tonnes

 

or

453,886,749,619.642 tons

 

That’s a lot of rice

 

If you want to play around with the numbers the calculations are:

 

[double]$result = 0

1..64 |
foreach {
 
$square = [math]::Pow(2, ($psitem -1))
$result += $square

}

$wt = 0.025

$totalweight = ($wt * $result)/1000
$totalweight

$mtwt = $totalweight /1000
$mtwt

$tons = $totalweight * 0.00098421
$tons

Event Log Providers

An event log provider is writes to an event log.  I’ve used WMI in the past to get these but while looking for somethign else discovered that Get-WinEvent can also find this information

 

Get-WinEvent -ListProvider * | ft Name, LogLinks -AutoSize –Wrap

 

Provides a nice long list of all of the providers and the event logs they write to.

 

Usually I’m only interested in what’s writing to a particular event log. And that’s where things get a bit more messy.

 

The loglinks are supplied as a System.Collections.Generic.IList[System.Diagnostics.Eventing.Reader.EventLogLink] LogLinks  object that doesn’t play nicely with –in or –contains

 

So we need a bit of PowerShell manipulation to get what we want

 

$log = 'System'

Get-WinEvent -ListProvider * |
foreach {
 
if ($log -in ($psitem | select -ExpandProperty Loglinks | select -ExpandProperty Logname)){
    New-Object -TypeName psobject -Property @{
      Name = $psitem.Name
      Log = $log
    }
}
}

 

The trick here is that the loglinks are a collection of objects so you need to expand them twice to get to the name.  Not pretty but it works