PowerShell for Windows Admins


October 3, 2018  8:56 AM

Windows Server 2019 GA

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Windows Server

Windows Server 2019 GA has happened.

https://cloudblogs.microsoft.com/windowsserver/2018/10/02/windows-server-2019-now-generally-available/

You can download evaluation edition or if you have a MSDN subscription get your hands on the full install media.

Time to rebuild my lab – AFTER PowerShell day next week.  I’ll stick with what I’ve got until the demos are done.

Rule 1 of presentations – DON’T change the demo

October 3, 2018  8:53 AM

Birthday present from Microsoft

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Windows 10

I’ve had an early birthday present from Microsoft. They named the latest Windows version after my birthday – Windows 10 October 2018

https://blogs.windows.com/windowsexperience/2018/10/02/find-out-whats-new-in-windows-and-office-in-october/

Thank you.

The install media is available on MSDN and the update should start to be available next week.


September 28, 2018  8:55 AM

Copy-Item Container parameter

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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.


September 28, 2018  5:42 AM

-like oddity

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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.


September 25, 2018  7:39 AM

Comparing strings and integers

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

Saw an interesting problem involving the comparison of 2 numbers. In reality it’s 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


September 24, 2018  6:23 AM

CSV cmdlets

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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.


September 23, 2018  5:16 AM

PowerShell copy file

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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


September 22, 2018  5:24 AM

Determining the PowerShell host

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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


September 21, 2018  5:32 AM

Splitting paths

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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


September 19, 2018  5:07 AM

PowerShell v6.1

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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


Forgot Password

No problem! Submit your e-mail address below. We'll send you an e-mail containing your password.

Your password has been sent to: