Monthly Archive

Categories

Monthly Archives: September 2018

Copy-Item Container parameter

The Copy-Item Container parameter controls whether the folder structure is also copied.

Lets start with the source material – a folder C:\test with a bunch of files. A destination folder C:\D1 also exists

PS> Copy-Item -Path c:\test\* -Destination C:\D1

copies the files from test to D1

 

PS> Copy-Item -Path c:\test -Destination C:\D1

copies the FOLDER test to D1 BUT NOT the files in the folder

 

PS> Copy-Item -Path c:\test -Destination C:\D1 –Recurse

copies the FOLDER and its FILES to D1. test becomes a subfolder of D1

 

PS> Copy-Item -Path c:\test -Destination C:\D1 -Recurse –Container

copies the FOLDER and its FILES to D1. test becomes a subfolder of D1

 

PS> Copy-Item -Path c:\test -Destination C:\D1 -Recurse -Container:$false

Copies the files from test to D1

If the destination folder DOESN’T exist

PS> Copy-Item -Path c:\test -Destination C:\D2

creates just the folder

PS> Copy-Item -Path c:\test -Destination C:\D2 –Recurse

PS> Copy-Item -Path c:\test -Destination C:\D2 -Recurse –Container

Copy-Item -Path c:\test -Destination C:\D2 -Recurse -Container:$false

All create the folder and copies the files into it

 

These examples should enable you to utilise the –Container parameter as appropriate.

-like oddity

Recently saw a question asking why this code worked

PS> $a = 'aa'
PS> $b = 'a'
PS> $a -like "*$b*"
True
PS> Remove-Variable -Name b
PS> $a -like "*$b*"
True

 

Set 2 variables and compare using wildcards. The comparison comes back as True as you’d expect.

Remove $b and try the comparison again. Still comes back as true.

The reason is based on the definition of the wildcard *

 

By removing $b you’re really testing

PS> $a -like "**"
True

 

The definition of the * wildcard is that it:

Matches zero or more characters

 

So, just having a wildcard will always match

PS> $a -like '*'
True

 

Just a little something to be aware of when using wildcards and variables. If the variable is null – effective result if not present – you’ll always get a match.

Comparing strings and integers

Saw an interesting problem involving the comparison of 2 numbers. In reality its was comparing strings and integers.

The starting point was a CSV file but as I showed last time you can create a CSV in memory

PS> $file = @'
>> "Count";"Value"
>> "3";"Year1"
>> "1";"Year1"
>> "5";"Year3"
>> "8";"Year4"
>> "10";"Year15"
>> "12";"Year6"
>> "88";"Year7"
>> "154";"Year2"
>> '@ | ConvertFrom-Csv -Delimiter ';'
PS> $file

Count Value
----- -----
3     Year1
1     Year1
5     Year3
8     Year4
10    Year15
12    Year6
88    Year7
154   Year2

The goal is to get all lines where Count is greater than or equal to 8.

 

The obvious starting point is

PS> $file | where Count -ge 8

Count Value
----- -----
8     Year4
88    Year7

Which doesn’t supply the right answer.

 

Checking the object created from the CSV file

PS> $file | Get-Member


TypeName: System.Management.Automation.PSCustomObject

Name       MemberType    Definition
----       ----------    ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Count       NoteProperty string Count=3
Value       NoteProperty string Value=Year1

Count is a string

 

With comparison operators the RIGHT hand side is converted to be the same as the LEFT hand side if the types don’t match. So in effect you’re getting

PS> "154" -ge "8"
False

 

You need to force the LEFT hand side to be an integer

PS> [int]"154" -ge "8"
True
PS> ("154" -as [int]) -ge "8"
True

That makes the code to work with the CSV

PS> $file | where {[int]$_.Count -ge 8}

Count Value
----- -----
8     Year4
10    Year15
12    Year6
88    Year7
154   Year2

or

PS> $file | where {($_.Count -as [int]) -ge 8}

Count Value
----- -----
8     Year4
10    Year15
12    Year6
88    Year7
154   Year2

CSV files are great but you have to remember about type conversion when you’re comparing properties

CSV cmdlets

I was reading something and when ConvertFrom-CSV was mentioned it made me pause and think about what that cmdlet actually did. This is a quick explanation of the CSV cmdlets.

Export-CSV was available in PowerShell v1. You use it to create a CSV file from PowerShell objects. The code in PowerShell v6.1 is

PS> Get-Process -Name p* | select Name, id, StartTime | Export-Csv -Path C:\test\test1.csv

 

In Windows PowerShell v5.1 it would be

PS> Get-Process -Name p* | select Name, id, StartTime | Export-Csv -Path C:\test\test1.csv -NoTypeInformation

The –NoTypeInformation parameter is needed to prevent the type information being written to the first line of the CSV. In v6.1 the default is NOT to write type information to the csv file

 

Import-CSV again has been a staple of PowerShell since v1. You use this cmdlet to read a CSV file that you have saved on disk.

PS> Import-Csv -Path C:\test\test1.csv

Name       Id    StartTime
----       --    ---------
powershell 14632 24/09/2018 12:27:17
pwsh        9804 24/09/2018 12:40:38

You can then send the objects down the pipeline for further processing.

The important thing to remember is that Import/Export-CSV deal with files.

 

ConvertTo-Csv is used to create CSV strings from objects. In PowerShell v6.1

PS> Get-Process -Name p* | select Name, id, StartTime | ConvertTo-Csv
"Name","Id","StartTime"
"powershell","14632","24/09/2018 12:27:17"
"powershell_ise","10968","24/09/2018 12:48:56"
"pwsh","9804","24/09/2018 12:40:38"

 

If you do the same in Windows PowerShell v5.1 you get

PS> Get-Process -Name p* | select Name, id, StartTime | ConvertTo-Csv
#TYPE Selected.System.Diagnostics.Process
"Name","Id","StartTime"
"powershell","14632","24/09/2018 12:27:17"
"powershell_ise","10968","24/09/2018 12:48:56"
"pwsh","9804","24/09/2018 12:40:38"

 

So you need to use the –NoTypeInformation parameter again

PS> Get-Process -Name p* | select Name, id, StartTime | ConvertTo-Csv -NoTypeInformation
"Name","Id","StartTime"
"powershell","14632","24/09/2018 12:27:17"
"powershell_ise","10968","24/09/2018 12:48:56"
"pwsh","9804","24/09/2018 12:40:38"

ConvertTo-Csv is analogous to Export-CSV except that the data is in memory rather than being on disk.

 

ConvertFrom-CSV reads CSV strings and converts them to objects

$p = @'
Name,Id,StartTime
powershell,14632,24/09/2018 12:27:17
pwsh,9804,24/09/2018 12:40:38
'@

ConvertFrom-Csv -InputObject $p

ConvertFrom-Csv is analogous to Import-CSV except that the data is in memory rather than being on disk.

 

The Import/Export cmdlets deal with data on disk while the ConvertTo/ConvertFrom cmdlets are for data that remains in memory.

PowerShell copy file

An article I wrote for searchwindowsserver on PowerShell file copy was published recently.

The article is available at

https://searchwindowsserver.techtarget.com/tip/PowerShell-commands-to-copy-files-Basic-to-advanced-methods

Determining the PowerShell host

The PowerShell ecosystem is more diverse than it used to be which makes determining the PowerShell host a bit more difficult.

The variable $host, and its Name property, is a good starting point.

PS> $host.Name
ConsoleHost

 

You get ConSoleHost if you’re running in the console.

For ISE you get - Windows PowerShell ISE Host

And for VScode you get - Visual Studio Code Host

 

If you want to know the PowerShell version as well it’s a bit more awkward.

$host.Version works for the console and ISE as it returns the PowerShell version. In VScode however it returns the version of VScode. The best route is to use

$PSVersionTable.PSVersion

 

While you’re at it you may want to check the edition of PowerShell – Core or Desktop

PS> $PSVersionTable.PSEdition
Core

To make that a bit easier

function get-powershellhost {

New-Object -TypeName PSobject -Property @{
Hostname = $host.Name
PSversion = $PSVersionTable.PSVersion
PSEdition = $PSVersionTable.PSEdition
}

}

 

The function runs in the console, ISE and VScode and was tested across Windows PowerShell v5.1 and PowerShell v6.1

Feel free to add other information as required

Splitting paths

PowerShell has the Split-Path cmdlet that provides the leaf and parent of a path. But what if you’re splitting paths and need one or paths at a higher level.

Consider the path

PS> $path = 'C:\Scripts\HyperV\Admin\Optimize-VMDisks.ps1'

 

Its just an arbitrary path from my test machine.

Using Split-Path you can get the parent (by default) or the leaf path

PS> Split-Path -Path $path
C:\Scripts\HyperV\Admin
PS> Split-Path -Path $path -Parent
C:\Scripts\HyperV\Admin
PS> Split-Path -Path $path -Leaf
Optimize-VMDisks.ps1

 

But what if you want the grandfather path. That starts to get ugly just using Split-Path.

PS> Split-Path -Path (Split-Path -Path $path -Parent)
C:\Scripts\HyperV

 

The code gets uglier and uglier as you progress up the path

I decided I needed an object whose properties gave me a consistent view of the path hierarchy – the parent path was always level 1; the grandfather was always level 2 etc. Something like this

Level00 : C:\Scripts\HyperV\Admin\Optimize-VMDisks.ps1
Level01 : C:\Scripts\HyperV\Admin
Level02 : C:\Scripts\HyperV
Level03 : C:\Scripts
Level04 : C:\

 

My solution was to create this function

function split-multipath {
[CmdletBinding()]
param (
[string]$path
)

if (-not (Test-Path -Path $path -IsValid)) {
throw "Invalid path: $path" 
}

$outpaths = [ordered]@{
Level00 = $path
}

$l = 1

$path = Split-Path -Path $path -Parent

while ($path -ne ''){
$level = "Level{0:00}" -f $l
$outpaths += @{
$level = $path
}

$l++
$path = Split-Path -Path $path -Parent

}

New-Object -TypeName PSobject -Property $outpaths

}

 

The split-multipath function takes a string as a parameter. That string is tested to determine if its a valid path – that could be moved to a ValidateScript test on the parameter.

The output is created from an ordered hash table. The Level00 property is set to the full path as input.

Set the counter and split the path to get the parent.

 

Use a while loop to add the current path into the hash table – create the property name with the string format operator –f.

Increment the counter and split the path - again taking the parent.

The while loop runs while the path has data. If you try to split when the path just contains the drive you get an empty string

PS> (Split-Path -Path c:\ -Parent) -eq ''
True

 

The data is output as an object so you can access a particular level. For instance if you want the great-grandfather level – you use Level03

PS> $test.Level03
C:\Scripts

 

You can use the output object to create a new path based on the level of your choice

PS> Join-Path -Path $test.Level03 -ChildPath Test47
C:\Scripts\Test47

PowerShell v6.1

PowerShell v6.1 was released last week – there’s no big ticket items like v6.0 but a lot of bug fixes and minor improvements.

You can download from https://github.com/PowerShell/PowerShell/releases

 

and find the release notes at

https://docs.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-core-61?view=powershell-6

Get-Date –DisplayHint

On the surface Get-Date is a simple cmdlet that’s been around since PowerShell v1. However, it has some interesting quirks. In this post I’ll show how Get-Date –DisplayHint works.

By default Get-Date returns the data and time

PS> Get-Date

13 September 2018 16:09:34

The DisplayHint parameter ( available in PowerShell v5.1, 6.0 and 6.1) modifies the data shown

PS> Get-Date -DisplayHint Date

13 September 2018

PS> Get-Date -DisplayHint DateTime

13 September 2018 16:11:18

PS> Get-Date -DisplayHint Time

16:11:25

You might think that all –DisplayHint does is just force the display to be date, time or datetime and you’ve lost the rest of the data but in reality all that’s happening is that the default display of your object is constrained by the value of the DisplayHint property:

PS> Get-Date | Get-Member -Name DisplayHint

TypeName: System.DateTime

Name MemberType Definition
---- ---------- ----------
DisplayHint NoteProperty DisplayHintType DisplayHint=DateTime

PS> Get-Date -DisplayHint Date | Get-Member -Name DisplayHint

TypeName: System.DateTime

Name MemberType Definition
---- ---------- ----------
DisplayHint NoteProperty DisplayHintType DisplayHint=Date

PS> Get-Date -DisplayHint Time | Get-Member -Name DisplayHint

TypeName: System.DateTime

Name MemberType Definition
---- ---------- ----------
DisplayHint NoteProperty DisplayHintType DisplayHint=Time

Note that DateTime is the default setting if –DisplayHint isn’t used.

PS> $t = Get-Date -DisplayHint Time
PS> $t

16:16:59

But you can still access the other properties of the datetime object returned by get-date

PS> $t.Date

13 September 2018 00:00:00

PS> $t.Year
2018
PS> $t.Hour
16

If you just need to see the date or time –DisplayHint is a good place to start

PowerShell new line

A PowerShell new line can be generated using `n. It’s one of a series of special characters recognised by PowerShell.

The full list of Windows PowerShell v5.1 special characters is:

`0 Null
`a Alert
`b Backspace
`f Form feed
`n New line
`r Carriage return
`t Horizontal tab
`v Vertical tab
--% Stop parsing

 

Here’s some examples:

`0 is a null character – empty space

PS> "abcd`0efg"
abcd efg

 

`a causes the machine to issue an alert – beep

PS> "`a Beep "; "`a Beep"
Beep
Beep

Two alerts are sent but you might only hear one.

 

The backspace character moves the cursor back one space overwriting the character that was there:

PS> "abcd`befg"
abcefg

 

Form feed is for printers only

 

A new line is added by `n

PS> "abc`nde`nfg"
abc
de
fg

 

The carriage return returns the cursor to the beginning of the line so any text before it will be overwrittem

PS> "This original text`rIs overwritten by this brand new text"
Is overwritten by this brand new text

 

A horizontal tab is added by `t

PS> "This`twill`ttab`tacross`tthe`tscreen"
This will tab across the screen

 

And `v for vertical tabs but only when printing documents – it doesn’t work on screen output

 

You can use –% to stop interpreting input as PowerShell commands or expressions. In this case PowerShell attempts to evaluate $x

PS> Write-Host $x = a variable
= a variable

but what you really want is

PS> Write-Host --% $x = a variable
--% $x = a variable

Unfortunately, you also get the –% come through.

The about_parsing help file has an example using icacls.

 

You’ll have noticed that all of the examples were in double quotes – single quotes stop the special character being recognised:

PS> "This`twill`ttab`tacross`tthe`tscreen"
This will tab across the screen
PS> 'This`twill`ttab`tacross`tthe`tscreen'
This`twill`ttab`tacross`tthe`tscreen

 

PowerShell v6 introduced `u for working with unicode characters

PS> "`u{2195}"

you can have 1 – 6 hex chacraters with a maximim value of 10FFFF