PowerShell for Windows Admins

March 17, 2018  7:19 AM

PowerShell v6.0.2

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

PowerShell v6.0.2 is available from https://github.com/PowerShell/PowerShell/releases

The only real change is that .NET core 2.0.6 is used in this release to counter the Hash Collision vulnerability – https://github.com/PowerShell/Announcements/issues/4

A number of changes that affect the way the project builds and releases code have also been implemented – these SHOULD be transparent to the user  🙂

Win32-OpenSSH also has a relatively new release – v7.6.0.0p1-Beta – https://github.com/PowerShell/Win32-OpenSSH/releases. The jump in version number is to bring it inline with the base OpenSSH code.

Still no simple way to install and configure OpenSSH but it is getting a bit better. Most won’t  need to run a instance of powershell in bypassexecutionpolicy mode as part of the install.  If you’re not allowing scripts to run then why bother with OpenSSH remoting?  If you’re using Allsigned then it probably makes sense. In any case it’s still too complicated.

The ability to release new versions like this is one of the big plus points for having PowerShell as an open source project.

March 15, 2018  6:37 AM

Set active hours

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Last time time you saw how to get the current active hours. This is how you set the active hours.

$sb = {
Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings -Name ActiveHoursStart -Value 10 -PassThru
Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings -Name ActiveHoursEnd -Value 22 -PassThru

Get-ADComputer -Filter * -Properties OperatingSystem |
where OperatingSystem -like “*Server*” |
select -ExpandProperty Name |
foreach {

Invoke-Command -ComputerName $psitem -ScriptBlock $sb -ArgumentList $psitem -HideComputerName |
select -Property * -ExcludeProperty RunSpaceId

The script block uses Set-ItemProperty to set the start and end of active hours. On Windows server 2016 you’re restricted to a 12 hour span for your active hours. Later Windows 10 builds allow up to 18 hours. I’ve used 10 for the start and 22 for the end to give me the best time spread that matches my activity – you can choose your own hours of course.

Getting the list of systems from AD and running the command remotely is as previously.

March 14, 2018  1:42 PM

Get Active Hours

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Windows 10 and Server 2016 (and later) have the concept of active hours. These are the hours you define as working hours in effect. This is how you get active hours for a system

$sb = {

$ahs = Get-Item -Path HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings

$props = [ordered]@{
ComputerName = $computerName
ActiveHoursStart = $ahs.GetValue(‘ActiveHoursStart’)
ActiveHoursEnd = $ahs.GetValue(‘ActiveHoursEnd’)

New-Object -TypeName PSobject -Property $props


Get-ADComputer -Filter * -Properties OperatingSystem |
where OperatingSystem -like “*Server*” |
select -ExpandProperty Name |
foreach {

Invoke-Command -ComputerName $psitem -ScriptBlock $sb -ArgumentList $psitem -HideComputerName |
select -Property * -ExcludeProperty RunSpaceId

The script block reads the registry key that contains the active hours information and outputs and object that contains the computer name, and start and end (in 24 hour clock) of the active hours.

I’m getting the information for all servers in the domain – use the OperatingSystem property on the computer to deselect non-servers. use Invoke-Command to run the command against the remote computer – hide the automatic computer name and runspaceid properties.

March 5, 2018  7:00 AM

Cmdlet parameters

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

I discovered another way to investigate cmdlet parameters.

If you dig into the output of Get-Command you’ll see it has a parameters property

PS> Get-Command Clear-RecycleBin | select parameters

{[DriveLetter, System.Management.Automation.ParameterMetadata], [Force, System.Management.Automation.ParameterMetada...

If you expand the parameters property:

PS> Get-Command Clear-RecycleBin | select -ExpandProperty parameters

Key Value
--- -----
DriveLetter System.Management.Automation.ParameterMetadata
Force System.Management.Automation.ParameterMetadata
Verbose System.Management.Automation.ParameterMetadata
Debug System.Management.Automation.ParameterMetadata
ErrorAction System.Management.Automation.ParameterMetadata
WarningAction System.Management.Automation.ParameterMetadata
InformationAction System.Management.Automation.ParameterMetadata
ErrorVariable System.Management.Automation.ParameterMetadata
WarningVariable System.Management.Automation.ParameterMetadata
InformationVariable System.Management.Automation.ParameterMetadata
OutVariable System.Management.Automation.ParameterMetadata
OutBuffer System.Management.Automation.ParameterMetadata
PipelineVariable System.Management.Automation.ParameterMetadata
WhatIf System.Management.Automation.ParameterMetadata
Confirm System.Management.Automation.ParameterMetadata

The really nice thing is that you get the common parameters listed as well.

If you want to dig into the individual parameters

PS> $params = Get-Command Clear-RecycleBin | select -ExpandProperty parameters

PS> $params.DriveLetter

Name : DriveLetter
ParameterType : System.String[]
ParameterSets : {[__AllParameterSets, System.Management.Automation.ParameterSetMetadata]}
IsDynamic : False
Aliases : {}
Attributes : {__AllParameterSets, System.Management.Automation.ValidateNotNullOrEmptyAttribute}
SwitchParameter : False


PS> $params.Verbose

Name : Verbose
ParameterType : System.Management.Automation.SwitchParameter
ParameterSets : {[__AllParameterSets, System.Management.Automation.ParameterSetMetadata]}
IsDynamic : False
Aliases : {vb}
Attributes : {System.Management.Automation.AliasAttribute, __AllParameterSets}
SwitchParameter : True

You can see the parameter sets and aliases which is useful. The parameter type indicates the input data.

This is very useful where you haven’t installed the help files on a system

March 4, 2018  10:41 AM

Finding special folders

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Windows has a number of special folders. These can be accessed either directly through the file system – for example the Documents special folder is C:\<user>\Richard\Documents or through code. But how do you go about finding special folders.

The easiest way is to use this script

1..1000 |
ForEach-Object {
$shell = New-Object -ComObject Shell.Application
$sf = $shell.NameSpace($psitem)

if ($sf) {

$props = [ordered]@{
Value = $psitem
Name = $sf.Title
Path = $sf.Self.Path

New-Object -TypeName PSobject -Property $props

Looping through the values 1-1000 the Shell.Application COM object is used to find the namespace corresponding to that number. If it exists, the value, name and path are output.

In the Path property you’ll either get a normal looking path e.g. C:\Windows\System32 or something like this – ::{645FF040-5081-101B-9F08-00AA002F954E} – which is the CLSID or Windows Class Identifier

February 28, 2018  10:58 AM

PowerShell while

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

PowerShell has a number of looping structures – do; while; for; foreach. This is how the PowerShell while loop works

The while statement has the form:

while (<condition>){<statement list>}

The while loop is probably the simplest of the PowerShell loops. For example:

$x = 0
while ($x -lt 5){
Write-Host “`$x is $x”


$x is 0
$x is 1
$x is 2
$x is 3
$x is 4

As long as the condition is true the statement list is executed

The condition is evaluated BEFORE the loop is executed meaning that it may never run

PS> $x = 10
while ($x -lt 5){
Write-Host “`$x is $x”


The value of $x is greater than 4 so the loop never executes.

February 27, 2018  10:27 AM

Get-Date format

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

A common question revolves around Get-Date format. In other words how can you format the output of Get-Date.

Standard output is

PS> Get-Date

27 February 2018 16:02:13

You can use –DisplayHint to control what’s displayed

PS> Get-Date -DisplayHint Date

27 February 2018

PS> Get-Date -DisplayHint Time


PS> Get-Date -DisplayHint DateTime

27 February 2018 16:03:17

Which is fine if you just want the date or the time

You’ve also got some methods on the DateTime object that can help

PS> (Get-Date).ToShortDateString()
PS> (Get-Date).ToShortTimeString()
PS> (Get-Date).ToLongDateString()
27 February 2018
PS> (Get-Date).ToLongTimeString()
PS> (Get-Date).ToFileTime()
PS> (Get-Date).ToFileTimeUtc()
PS> (Get-Date).ToUniversalTime()

27 February 2018 16:07:01

Universal Time is more properly known as Greenwich Mean Time

If you want a bit more control you can use the –Format parameter. A description of the format specifiers is available at https://msdn.microsoft.com/en-GB/Library/system.globalization.datetimeformatinfo(VS.85).aspx


will give a very long list of the possible formats. Unfortunately it just displays the results not the format specifier.

There are some preset formats – for example:

PS> Get-Date -Format g
27/02/2018 16:14
PS> Get-Date -Format r
Tue, 27 Feb 2018 16:14:58 GMT
PS> Get-Date -Format s

Or you can customise the format

PS> Get-Date -Format “ddMMyyyyhhmmss”
PS> Get-Date -Format “dd/MM/yyyy hh:mm:ss”
27/02/2018 04:18:03
PS> Get-Date -Format “dd/MM/yyyy HH:mm:ss”
27/02/2018 16:18:08

Read the article at the link for the full list of options. NOTE – the options ARE case sensitive

Alternatively, you could use the –Uformat parameter to use Unix formatting. This is explained in the NOTES section of the Get-Date help file.

Some examples

PS> Get-Date -UFormat “%d/%m/%Y %r”
27/02/2018 04:23:25 PM
PS> Get-Date -UFormat “%d/%m/%Y %R”
27/02/2018 16:23

Between the display hints, the methods, –Format and –UFormat you should be able to get the date into the format you need.

February 27, 2018  4:16 AM

Iron Scripter puzzles

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

We keep innovating around the content of the PowerShell Summit to ensure it remains fresh and relevant to our attendees. This year we’re introducing the Iron Scripter competition. As a run up to the main competition I’ve written a number of challenges. The first half of the Iron Scripter puzzles are available.

The puzzle is published on a Sunday with a commentary the following Sunday. I’ve listed the first six commentaries:

Puzzle 1: fix code to get monitor information and create objects – https://powershell.org/2018/01/21/iron-scripter-prequel-puzzle-1-a-solution/

Puzzle 2: change text output to objects and create format file – https://powershell.org/2018/01/28/iron-scripter-prequel-puzzle-2-a-commentary/

Puzzle 3: web feeds – https://powershell.org/2018/02/04/iron-scripter-2018-prequel-puzzle-3-a-commentary/

Puzzle 4: legacy utilities – https://powershell.org/2018/02/11/iron-scripter-2018-prequel-puzzle-4-a-commentary/

Puzzle 5: working with performance counters – https://powershell.org/2018/02/18/iron-scripter-2018-prequel-puzzle-5-a-commentary/

Puzzle 6: determining uptime – https://powershell.org/2018/02/25/iron-scripter-prequel-puzzle-6-commentary/

The commentary documents supply the puzzle and then the commentary. If you’ve not looked at them they are designed to have some serious learning points.

You don’t have to be attending the Summit to work through the puzzles.

February 26, 2018  8:10 AM

PowerShell Scope

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

PowerShell Scope has an important impact on the way your code runs. When you run a script or a function in PowerShell it runs it in its own scope. This means that all variables, functions, classes, aliases etc are removed from memory at the end of the script.

Here’s an example

create a class

class test {
[int]$P1 = 1
[int]$p2 = 2


Now save the code as test.ps1.
Run the code in the console

PS> .\test.ps1
PS> [test]::new()
Unable to find type [test].
At line:1 char:1
+ [test]::new()
+ ~~~~~~
+ CategoryInfo : InvalidOperation: (test:TypeName) [], RuntimeException
+ FullyQualifiedErrorId : TypeNotFound

The reason you’re not seeing [test] is that a script runs in its own scope and all variables, functions classes etc are removed at the end of the script’s execution.

You need to dot source the script so that the class remains in memory. You do that by putting a . in front of the script name like this

PS> . .\test.ps1
PS> [test]::new()

P1 p2
— —
1 2

Anything defined in the console is visible to scripts you run but not vice versa.

We spent a lot of time discussing Scope in PowerShell in Action – https://www.manning.com/books/windows-powershell-in-action-third-edition

February 23, 2018  2:30 PM

Controlled zip

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Powershell v5 introduced the Compress- and Expand-Archive cmdlets which enabled you to manage compressed archives. I had a question about how you could control adding files to archives using a CSV file. This is how you do a controlled zip.

Start by creating a set of test data.

1..100 |
foreach {
$file = “File$psitem.txt”
Get-Process | Out-File -FilePath $file

$i = Get-Random -Minimum 1 -Maximum 4
$zip = “Archive$i.zip”

$props = @{
FileName = $file
Archive = $zip

New-Object -TypeName PSObject -Property $props
} | Export-Csv -Path FilesToArchive.CSV –NoTypeInformation

I created a 100 files – name of the form FileN.txt and into each piped the output of Get-Process just so they weren’t empty.

I wanted 3 zip files – named ArchiveN.zip

I used Get-Random to assign the zip file.

Create an object to with the file and archive and output to CSV

The CSV looks like this:

FileName Archive
——– ——-
File1.txt Archive1.zip
File2.txt Archive3.zip
File3.txt Archive1.zip
File4.txt Archive2.zip
File5.txt Archive1.zip
File6.txt Archive3.zip

To perform the zip

Import-Csv .\FilesToArchive.CSV |
foreach {
Compress-Archive -Path $_.FileName -DestinationPath $_.Archive -Update

Read the CSV file and for each file add it to the appropriate archive. The –Update parameter on Compress-Archive is what allows you to add files to an existing archive.

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: