## Monday, January 7, 2019

### Artificial Neural Networks in PowerShell - part 1

Neural Networks had their beginnings in 1943 when Warren McCulloch, a neurophysiologist, and a young mathematician, Walter Pitts, wrote a paper on how human neurons might work.
They focused on understanding how our brain can produce highly complex patterns by using many basic cells that are bound together. These basic brain cells are known as neurons, and McCulloch and Pitts imagined a highly simplified model of a neuron in their paper. The McCulloch and Pitts model of a neuron, also known as MCP neuron for short, has made an important contribution to the development of Artificial Neural Networks (ANN), which model key features of biological brain neurons.
A few years later, in 1958, Frank Rosenblatt, a neuro-biologist, proposed the Perceptron. The Perceptron is an algorithm for supervised learning of binary classifiers. In other words it is a function that, given a vector of inputs, is able to classify them into a class.
Today I am going to start a series of posts that will explain how to rewrite a Perceptron in PowerShell and that is meant to show how a single node of a neural network works and can be trained following the model defined by Rosenblatt. All of this in PowerShell.

But before we proceed, we’ll need to have a look at how a Perceptron does work. And before we do so, let’s see how we can mathematically emulate a neuron whose functioning can be represented with the following diagram:

As you can see here, and to simplify things, know that a neuron takes a vector of inputs, \$m1 ,\$m2 ,\$m... and produces an output by multiplying each input to different weights, which are real numbers expressing the importance of each input to the output.
Multiplying those weights by the input allows to decide whether a neuron fires or not.
In PowerShell the first thing to do is to define an Invoke-Neuron function which does exactly that, multiplying each input with a pseudo-random weight (notice the SetSeed parameter used during the generation of the first random weight):

The values of the weight parameters are initialized randomly (this stops them all converging to a single value). We will see later that when training starts and the Perceptron is presented with a dataset, those weights are adjusted towards values that give correct output.
As you can notice, in this function we have to call some kind of activation function, meaning a function that decides whether the neuron fires (in case the result approaches 1 or is 1) or not (in case the output approaches 0 or is 0).
Different activation functions exist. McCullogh and Pitts adopted the Heaviside function, which is used for something called Logistic Regression, meaning that the function will produce a binary output: the function produces 1 (or true) when input passes threshold limit whereas it produces 0 (or false) when input does not pass threshold. In context of pattern classification, such an algorithm can be useful to determine if a sample belongs to one class or the other, hence the name binary classifier.
This Heaviside step function can be represented as

and translates to PowerShell as a simple Switch-based function:

The alternative is to use a Non Linear Sigmoid function

which works perfectly to reduce the output to a number between 0 and 1, whatever the input numbers:

In this function [math]::Exp returns the number e raised to the power –t, where e is the unique number whose natural logarithm is equal to one and whose value is approximately equal to 2.71828.
Let’s focus again on the Invoke-Neuron function.
Let’s add another parameter which takes the name of activation function to use:

The produced output for the same input dataset changes from binary to a value between 0 and 1:

You have now probably noticed that a \$bias variable is added to the computation. His role is similar to the one of the constant b of a linear function:

which graphs to a straight line. It allows us to slide this line up and down to fit the prediction with the data better. Without that bias the line will always goes through the axis origin (0, 0) and you may get a poorer fit.
The \$bias is one of the learnable parameters of this model, the others being of course the weights, as we will see when we will develop a Perceptron in the next blog post.
Here’s our final code:
```\$w1 = Get-Random -SetSeed 1 -Minimum 0.01 -Maximum 0.99
\$w2 = Get-Random -Minimum 0.01 -Maximum 0.99
\$bias = Get-Random -Minimum 0.01 -Maximum 0.99

function Invoke-Neuron (\$m1, \$m2, \$w1 ,\$w2, \$bias, \$activation) {
\$t = \$m1*\$w1+\$m2*\$w2+\$bias
switch (\$activation) {
heaviside {Heaviside \$t}
sigmoid {Sigmoid \$t}
Default {Sigmoid \$t}
}
}
Invoke-Neuron 4 7 \$w1 \$w2 \$bias 'Heaviside'
Invoke-Neuron 4 7 \$w1 \$w2 \$bias 'Sigmoid'```
This piece of code acts like a human neuron, with dendrites (the inputs \$m1, \$m2), synapsis (the weights \$w1, \$w2) and a single output which is similar to the neuron output going through the axon.
Simple isn’t it?
In the next post we will see how we can code a Perceptron in PowerShell, and we’ll try to understand its role in the guess work to determine the best values to give to weights and bias.
Stay tuned.

## Tuesday, July 3, 2018

### MVP renewal for 2018-2019

I am very honored to be renewed as a Microsoft MVP for 2018-2019 in the category of Cloud and Datacenter Management. The MVP program recognizes content creators, code providers, technnical leaders and conference speakers who want to share their knowledge and broaden the outreach of the platforms they work with.
It is always truly gratifying to receive such an award by Microsoft, a company that is always moving, and that is surrounded by a great community I am proud to be part of.
To all my readers, thanks.

## Wednesday, June 20, 2018

### Introducing the Invoke-Wsus PowerShell function

A couple of weeks ago I wrote a blog post on WSUS management with PowerShell. The post came with a script that could be used to manage automatic approvals of WSUS patches and updates.

This time I have decided to transform that script in an advanced PowerShell function named Invoke-WSUS (you can find it on my Github).

This function is kind of jack-of-all-trade: depending on the used parameter set, it can do a series of actions, as shown in the description and in the help file.

```NAME
Invoke-Wsus

SYNOPSIS
Invoke-Wsus is a function used to manage WSUS.

SYNTAX
Invoke-Wsus -ShowApprovalGroups -SettingFile string [commonparameters]

Invoke-Wsus -SettingFile string -WsusName string -WsusPort int32 -WsusSSL -SyncDelay int32 -CleanupDay int32 -SendMail -SMTPServer string -From string -To string> [commonparameters]

Invoke-Wsus -SettingFile string -WsusName string -WsusPort int32 -WsusSSL -SyncDelay int32 -CleanupDay int32 -ShowAll [commonparameters]

Invoke-Wsus -SettingFile string -WsusName string -WsusPort int32 -WsusSSL -SyncDelay int32 -ShowApprovalSchedules [-RunApprovalSchedules] [commonparameters]

Invoke-Wsus -ShowWsusTargetGroups -WsusName string -WsusPort int32 -WsusSSL [commonparameters]

Invoke-Wsus -WsusName string -WsusPort int32 -WsusSSL -ShowLastWsusSync [commonparameters]

Invoke-Wsus -WsusName string -WsusPort int32 -WsusSSL -SyncWsusNow [commonparameters]

Invoke-Wsus -ShowNextPatchTuesday [commonparameters]

Invoke-Wsus -ShowNextSyncTuesday -SyncDelay int32 [commonparameters]

Invoke-Wsus -ShowNextClenaupDay -CleanupDay int32 [commonparameters]

DESCRIPTION
Invoke-Wsus is a function that is used to determine the next Patch Tuesday, to sync a
WSUS server with Microsoft,     to approve patches based on target WSUS groups, and to
show WSUS target groups configuration.

RELATED LINKS

REMARKS
To see the examples, type: "get-help Invoke-Wsus -examples".
For more information, type: "get-help Invoke-Wsus -detailed".
For technical information, type: "get-help Invoke-Wsus -full```

Before you can use this function you have to use the Set-ApprovalDelay script to generate a JSON setting file. This file will contain the list of the approval schedules and, for each schedule, you will have associated WSUS target computer groups and the delay in days between the synchronization of your WSUS server and the actual approval of patches.

Let's take as an example the following JSON settings:

```\$DelaySettings += [pscustomobject]@{

Name          = 'Immediate'

WsusGroup   = 'Standard'

ApprovalDelay = 1

}

\$DelaySettings += [pscustomobject]@{

Name          = 'OneWeek'

WsusGroup   = 'Touchy','Critical'

ApprovalDelay = 7

}```
This settings will basically say to the Invoke-Wsus function that
• computers belonging to a WSUS target computer group whose name matches the 'Standard' word will be patched 1 day after the synchroniezation of your WSUS server with Microsoft.
• computers belonging to a WSUS target computer group whose name matches the 'Touchy' or 'Critical' word will be patched 7 days after the synchroniezation of your WSUS server with Microsoft.

You can simply adapt these settings to your environment and re-run the Set-ApprovalDelay script to generate a new setting file for your organization.

Now that you have understood what the configuratrion file is used for, here's a couple of useful examples that show what the Invoke-Wsus function is capable of.

Example 1: Showing the approval delay per target computer group from the setting file in a readable way

```Invoke-Wsus -ShowApprovalGroups -SettingFile approvaldelaysettings.json'

Today is 06/20/2018 11:28:05

Name      WsusGroup          ApprovalDelay
----      ---------          -------------
Immediate Standard                       1
OneWeek   {Touchy, Critical}             7```
Example 2: Showing the list of target computer groups defined on the WSUS server

```Invoke-Wsus -ShowWsusTargetGroups -WsusName 'wsusserver' -WsusPort 8530 -WsusSSL:\$false

Today is 06/20/2018 13:26:45

Name                                     Total computers
----                                     ---------------
All Computers                                        381
Mission-Critical-M1                                   37
Mission-Critical-M2                                   35
Standard                                             210
User-Touchy-M1                                        50
User-Touchy-M2                                        49
Unassigned Computers                                   0```
Having this information will simplify the work of chosing the words to use in the setting file to match target computer groups.

Example 3: Showing the next Patch Tuesday (optimistically renamed by Microsoft to Update Tuesday)

```Invoke-Wsus -ShowNextPatchTuesday

Today is 06/20/2018 13:30:27
Next Patch Tuesday will be in 20 days on Tuesday, July 10, 2018```
I let you take the time to find out what other actions this advanced function can perform. The only thing that is really important to understand is that this Invoke-Wsus function is meant to be scheduled daily on your WSUS Server just like in the example below:

`Invoke-Wsus -ShowApprovalSchedules -SettingFile 'approvaldelaysettings.json' -SyncDelay 13 -RunApprovalSchedules -WsusName 'wsusserver' -WsusPort 8530 -WsusSSL:\$false`
This basically tell the function to execute the automatic approval of needed patches and updates for target computer groups based on a delay between Patch Tuesday, Synchronization day and the delay specified in the setting file.

You could also add a second scheduled task that will inform you by mail of the actions of the day by using the 'Send Mail' parameter set.

Let me know how it goes for you and if you have any question or improvement to suggest, feel free to get in touch with me.

## Tuesday, June 12, 2018

### Gathering WSUS Security Monthly Quality Rollups with PowerShell

If you follow me, you know that I've been playing a lot with WSUS in recent days and have discovered there's an extremely simple way to extract the list of all the Microsoft Security Monthly Quality Rollups (which include cumulated security updates and non-security updates) residing on a WSUS server with the help of PowerShell.
The key cmdlet is Get-WsusUpdate. The Get-WsusUpdate cmdlet gets the Windows Server Update Services (WSUS) update object with details about existing updates.

`\$MSupdates  = Get-WsusUpdate`
Using Get-Member we can see the returned object type:
```\$MSupdates  | Get-Member

TypeName: Microsoft.UpdateServices.Commands.WsusUpdate

Name                               MemberType Definition
----                               ---------- ----------
Equals                             Method     bool Equals(System.Object obj)
GetHashCode                        Method     int GetHashCode()
GetType                            Method     type GetType()
ToString                           Method     string ToString()
Approved                           Property   string Approved {get;}
Classification                     Property   string Classification {get;}
ComputersInstalledOrNotApplicable  Property   int ComputersInstalledOrNotApp
ComputersNeedingThisUpdate         Property   int ComputersNeedingThisUpdate
ComputersWithErrors                Property   int ComputersWithErrors {get;}
ComputersWithNoStatus              Property   int ComputersWithNoStatus {get
InstalledOrNotApplicablePercentage Property   int InstalledOrNotApplicablePe
LanguagesSupported                 Property   System.Collections.Specialized
LicenseAgreement                   Property   string LicenseAgreement {get;}
MayRequestUserInput                Property   bool MayRequestUserInput {get;
MsrcNumbers                        Property   System.Collections.Specialized
MustBeInstalledExclusively         Property   bool MustBeInstalledExclusivel
Products                           Property   System.Collections.Specialized
Removable                          Property   bool Removable {get;}
RestartBehavior                    Property   string RestartBehavior {get;}
Update                             Property   Microsoft.UpdateServices.Admin
UpdateId                           Property   string UpdateId {get;}
UpdatesSupersededByThisUpdate      Property   System.Collections.Specialized
UpdatesSupersedingThisUpdate       Property   System.Collections.Specialized```
The key point here is that Get-WindowsUpdate used with no parameters reports patch information of the computer where it runs, which is just a subset of all the possible patches.

If you want to retrieve the list of all the patches hosted by your WSUS server here's the syntaxt to use:

`\$MSupdates = Get-WsusUpdate -Verbose -Approval AnyExceptDeclined`
Once you got that (it can take a while, and you'll se the WSUS database pretty busy), you can easily select the patches that are Security Monthly Quality Rollups:

```\$MSupdates.Update |

? Title -match 'Security Monthly Quality Rollup' |

Format-Table title

Title
-----
2018-03 Security Monthly Quality Rollup for Windows Server 2012 R2...
2018-03 Security Monthly Quality Rollup for Windows Server 2012 fo...
2018-03 Security Monthly Quality Rollup for Windows 7 for x64-base...
2018-03 Security Monthly Quality Rollup for Windows Server 2008 R2...
2018-03 Security Monthly Quality Rollup for Windows 7 for x86-base...```
As you can see the monthly rollups I see here is the one from last March Patch Tuesday (KB4088876).

I could think of counting all these Monthly Rollups by OS:

```\$MSupdates.Update |

? Title -match 'Security Monthly Quality Rollup' |

Group ProductTitles | Select Name,Count | Sort -Descending```
All you need to know is simply what kind of patches you want to extract from this huge list. A few examples now.

Patches whose title starts with 2018:

```\$MSupdates.Update |

? Title -match '^2018' |

Formaat-table title, creationdate, knowledgebasearticles, producttitles, state```
Patches whose title starts with 2017-12 (in this regex the caret ^ matches the position before the first character in the string):

```\$MSupdates.Update |

? Title -match '^2017-12' |

Format-Table title, creationdate, knowledgebasearticles, producttitles, state```
Patches with a certain KB number:

```\$MSupdates.Update |

? KnowledgebaseArticles -match '4088876' |

Format-Table title, creationdate, knowledgebasearticles, producttitles, state```
Stay tuned for more PowerShell and get ready for today's Patch Tuesday!

## Wednesday, June 6, 2018

### WSUS management with PowerShell

I have recently discovered the work of fellow MVP Adam Marshall who wrote a fantastic script aimed at cleaning your WSUS servers and decided to adopt it. A few days later I started thinking to how I could complete his work by adding a script that could automate the management of patches throughout the year for all of my servers, so I shouldn't have to manually sync and approve patches.

In this post I will explain how I manage the whole patch process and introduce my PowerShell script, which I named wsus-operation.ps1 (which you can find on Github). Sure, this is an on-going work, so I'll try to keep this post updated with feedbacks from the Community and with all the things I discover over time.

The first step is to understand how to manage the patching process through the year. Hence the reason that pushed me to spend a good amount of time studying the calendar to finally draw the circular timeline you see below: it allows easy visualization of the patching process from Patch Tuesday to the day Adam's scripts does the monthly cleanup after all the patches have been deployed.

Supposing that today it's June, 5th, next Patch Tuesday will happen in 7 days on Tuesday, June 12, 2018: Patch Tuesday comes the second Tuesday of the month so for sure we will have to include in our script a few lines of code to calculate it correctly.

After Patch Tuesday, it's a best practice to wait roughly a couple of weeks before synching your WSUS server, then existing Domain Group Policy (GPO) should intervene and patch servers following their criticality.

A typical way of doing is to safely sync your WSUS with Microsoft on the fourth Monday of the month (aliased to FM in the circular timeline).

The day after the sync (FM + 1 day) you could auto-approve needed patches for standard non-critical servers, then a GPO configured in 'Auto download and schedule the install' mode could fire the installation every Wednesday (which is always FM + 2 days) so that those servers actually get the patches and are eventually rebooted.

For touchy and critical servers, you could approve one week after the sync (FM + 7 days) and set the corresponding GPO configured in 'Notify for download and notify for install' mode, so that nothing actually happen on the servers until you are 100% sure those patches don't impact negatively your environment (i.e. in case of a bug).

In the end you would configure AdamJ script to perform the monthly cleanup of the WSUS database on the 7h of the month following Patch Tuesday so you have plenty of time to manually patch your most critical servers.

That could be resumed to:

• Patch Tuesday (PT)
• Sync WSUS avec Windows Update on fourth Monday (FM) (PT + 13 days)
• Approve for non-critical servers (FM + 1 day)
• GPO schedule the install on non-critical servers on Wednesday (FM + 2 days)
• Approve for touchy and critical servers (FM + 7 days)
• GPO notify new patches to touchy and critical servers (FM + 8 days)
• Adam's scripts clean up old computers and superseded patches (7th of following month)

Actually, you have to know that Adam's script runs different actions whether it's a standard daily run, a monthly run or a quarterly run (on January, April, July and October).

Here's the actions performed by AdamJ script which daily:

• Declines Multiple Types of Updates Stream
• Cleans Up WSUS Synchronization Logs
• Cleans up Computer Object
• Performs WSUS DB Maintenance
• Performs WSUS Server Cleanup Wizard

The same script is also in charge of monthly:
• Cleaning All daily tasks
• Removing Obsolete Updates
• Compressing Update Revisions

Furthermore, on a quarterly basis, the same script:
• Cleans all daily and monthly tasks
• Removes WSUS Drivers
• Removes Declined WSUS Updates

So back to my script now. Here's an explanation of the most relevant parts.

Everything starts with retrieving the current day and putting it in a variable which I will reuse:

`\$now = Get-Date`

Here's how I decided to calculate the next Patch Tuesday:

```\$d0 = Get-Date -Day 1 -Month \$(\$now.Month) -Year \$now.Year

switch (\$d0.DayOfWeek){

"Sunday"    {\$patchTuesday0 = \$d0.AddDays(9); break}

"Monday"    {\$patchTuesday0 = \$d0.AddDays(8); break}

"Tuesday"   {\$patchTuesday0 = \$d0.AddDays(7); break}

"Wednesday" {\$patchTuesday0 = \$d0.AddDays(13); break}

"Thursday"  {\$patchTuesday0 = \$d0.AddDays(12); break}

"Friday"    {\$patchTuesday0 = \$d0.AddDays(11); break}

"Saturday"  {\$patchTuesday0 = \$d0.AddDays(10); break}

}

\$d1 = Get-Date -Day 1 -Month \$(\$now.Month + 1) -Year \$now.Year

switch (\$d1.DayOfWeek){

"Sunday"    {\$patchTuesday1 = \$d1.AddDays(9); break}

"Monday"    {\$patchTuesday1 = \$d1.AddDays(8); break}

"Tuesday"   {\$patchTuesday1 = \$d1.AddDays(7); break}

"Wednesday" {\$patchTuesday1 = \$d1.AddDays(13); break}

"Thursday"  {\$patchTuesday1 = \$d1.AddDays(12); break}

"Friday"    {\$patchTuesday1 = \$d1.AddDays(11); break}

"Saturday"  {\$patchTuesday1 = \$d1.AddDays(10); break}

}

if(\$now.date -le \$patchTuesday0.date){

\$patchTuesday = \$patchTuesday0}else{\$patchTuesday = \$patchTuesday1

}```

The very same code is used to calculate the next Fourth Monday, which is the days I suggest you sync your WSUS with Microsoft:

```\$d0 = Get-Date -Day 1 -Month \$(\$now.Month) -Year \$now.Year

switch (\$d0.DayOfWeek){

"Sunday"    {\$FourthMonday0 = \$d0.AddDays(22); break}

"Monday"    {\$FourthMonday0 = \$d0.AddDays(21); break}

"Tuesday"   {\$FourthMonday0 = \$d0.AddDays(20); break}

"Wednesday" {\$FourthMonday0 = \$d0.AddDays(26); break}

"Thursday"  {\$FourthMonday0 = \$d0.AddDays(25); break}

"Friday"    {\$FourthMonday0 = \$d0.AddDays(24); break}

"Saturday"  {\$FourthMonday0 = \$d0.AddDays(23); break}

}

\$d1 = Get-Date -Day 1 -Month \$(\$now.Month + 1) -Year \$now.Year

switch (\$d1.DayOfWeek){

"Sunday"    {\$FourthMonday1 = \$d1.AddDays(22); break}

"Monday"    {\$FourthMonday1 = \$d1.AddDays(21); break}

"Tuesday"   {\$FourthMonday1 = \$d1.AddDays(20); break}

"Wednesday" {\$FourthMonday1 = \$d1.AddDays(26); break}

"Thursday"  {\$FourthMonday1 = \$d1.AddDays(25); break}

"Friday"    {\$FourthMonday1 = \$d1.AddDays(24); break}

"Saturday"  {\$FourthMonday1 = \$d1.AddDays(23); break}

}

if(\$now.date -le \$FourthMonday0.date){

\$FourthMonday = \$FourthMonday0}else{\$FourthMonday= \$FourthMonday1

}```

On top of this last portion of code I built a calculation for the days when I approve patches both for non-critical and critical servers. Here's the code:

```if(\$now.date -le \$FourthMonday0.adddays(1).date){

\$StandardApprovalDay = \$FourthMonday0.AddDays(1)}else{\$StandardApprovalDay= \$FourthMonday1.AddDays(1)

}

if(\$now.date -le \$FourthMonday0.adddays(1).date){

\$CriticalApprovalDay = \$FourthMonday0.AddDays(7)}else{\$CriticalApprovalDay= \$FourthMonday1.AddDays(7)

}```

Then all the possible conditions have to be evaluated so that proper actions are executed. If no actions are to be executed, then we just display a message.

```if(\$now.date -eq \$PatchTuesday.date){

"==> It's patch Tuesday!`n"

}

else {

"Next Patch Tuesday is in \$((New-TimeSpan -Start \$now.date -End \$patchTuesday.date).days) days on \$(\$patchTuesday.ToLongDateString())`n"

}```

```if(\$now.date -eq \$FourthMonday.date){

(Get-WsusServer).GetSubscription().StartSynchronization()

}

else {

"Next Sync will happen in \$((New-TimeSpan -Start \$now.date -End \$FourthMonday.date).days) days on \$(\$FourthMonday.ToLongDateString())`n"

}```

Before we continue, a quick note about WSUS group assignment: you really should enable Client Side Targeting in your WSUS GPO so that you are assured that your servers will automatically fall in the right WSUS groups.

In my case I have configured targeting of non-critical servers in a group named 'standard servers' and targeting of touchy and critical servers in groups named 'touchy servers' and 'critical servers'.

In the following part I am using the Approve-WsusUpdate cmdlet to approve unapproved patches that are needed by servers residing in WSUS groups that match the word 'standard' in their names:

```if(\$now.date -eq \$StandardApprovalDay.date){

"==> It's the day after fourth monday of the month - approving for Standard servers`n"

\$wsus = Get-WsusServer

\$allupdates = \$wsus.GetUpdates()

\$alltargetgroups = \$wsus.GetComputerTargetGroups()

\$computergroups = (\$alltargetgroups | ? name -match 'Standard').name

\$computergroups | % {

Get-WsusUpdate -Approval Unapproved -Status FailedOrNeeded | Approve-WsusUpdate -Action Install -TargetGroupName \$_ –Verbose

}

}

else {

"Next approval for Standard servers will happen in \$((New-TimeSpan -Start \$now.date -End \$StandardApprovalDay.Date).days) days on \$(\$StandardApprovalDay.ToLongDateString())`n"

}```

In the following part I am approving needed unapproved patches for servers residing in WSUS groups that have the words 'touchy' or 'critical' in their names:

```if(\$now.date -eq \$CriticalApprovalDay.date){

"==> It's the 7th day after fourth monday of the month - approving for User-Touchy and Mission-Critical servers`n"

\$wsus = Get-WsusServer

\$allupdates = \$wsus.GetUpdates()

\$alltargetgroups = \$wsus.GetComputerTargetGroups()

\$computergroups = (\$alltargetgroups | ? name -match 'touchy|critical').name

\$computergroups | % {

Get-WsusUpdate -Approval Unapproved -Status FailedOrNeeded | Approve-WsusUpdate -Action Install -TargetGroupName \$_ –Verbose

}

}

else {

"Next approval for User-Touchy and Mission-Critical servers will happen in \$((New-TimeSpan -Start \$now.date -End \$CriticalApprovalDay.date).days) days on \$(\$CriticalApprovalDay.ToLongDateString())`n"

}```

```if(\$now.day -eq 7){

"==> Today is WSUS monthly clean up day`n"

}

else{

"Next WSUS monthly clean up will happen in \$((New-TimeSpan -Start \$now.date -End \$(Get-Date -Day 7 -Month \$(\$now.Month + 1) -Year \$now.Year -OutVariable datenextcleanup).Date).Days) days on \$(\$datenextcleanup.ToLongDateString())`n"

}```

As you can see, coupling those actions with well configured group policies and with Adam's script will make your WSUS installation agile and pretty automated.

Of course this approach can be improved and if I find better ways of doing I won't hesitate to update the PowerShell script on GitHub as well as this post. I hope that the community will contribute to the improvement of this script based on its experience, so that this can benefit the Community and will make WSUS admins less prone to headache.

If you liked this post, feel free to share.

UPDATE June, 11th 2018: This post got a lot of feedbacks, and there is an optimization in particular that made it to the master branch on GiHub: I'd like to thank CleverTwain (reddit github) for making my script more modular. He made a few nice additions such as:

Moving parameters and general settings to the top:

```\$action = \$false

\$now = Get-Date

\$comments = "Today is \$(\$now.ToLongDateString())`n"

\$WSUSServerParams = @{

Name   = 'wsusserver'

Port   = 8530

UseSSL = \$false

}

# Moved these to the top as others may want to tweak as necessary

\$SyncDelay = 13 # How many days after Patch Tuesday should we wait before syncing WSUS

\$WSUSCleanUpDay = 7 # What numerical day of the month whould the WSUS cleanup script run?

# Changed delay settings to use objects, as that is the most flexible

\$DelaySettings = @()

\$DelaySettings += [pscustomobject]@{

Name          = 'Immediate'

# Now multiple collections can share the same delay settings without adding multiple checks

Collections   = 'Standard', 'NonCritical'

ApprovalDelay = 1

}

\$DelaySettings += [pscustomobject]@{

Name          = 'OneWeek'

Collections   = 'Touchy', 'Critical'

ApprovalDelay = 7

}```
Making Fourth Monday calculation dependant from Patch Tuesday through a SyncDelay variable that WSUS admins can set according to their internal policy:

```\$firstOfThisMonth = (Get-Date -Day 1 )

switch ( \$firstOfThisMonth.DayOfWeek ) {

"Sunday" {\$thisPatchTuesday = \$firstOfThisMonth.AddDays(9)}

"Monday" {\$thisPatchTuesday = \$firstOfThisMonth.AddDays(8)}

"Tuesday" {\$thisPatchTuesday = \$firstOfThisMonth.AddDays(7)}

"Wednesday" {\$thisPatchTuesday = \$firstOfThisMonth.AddDays(13)}

"Thursday" {\$thisPatchTuesday = \$firstOfThisMonth.AddDays(12)}

"Friday" {\$thisPatchTuesday = \$firstOfThisMonth.AddDays(11)}

"Saturday" {\$thisPatchTuesday = \$firstOfThisMonth.AddDays(10)}

}

if (\$now.date -le \$thisPatchTuesday.date) {
\$patchTuesday = \$thisPatchTuesday

}
else {
\$firstOfNextMonth = (Get-Date -Day 1 -Month ((Get-Date).AddMonths(1).Month) )

switch ( \$firstOfNextMonth.DayOfWeek ) {

"Sunday" {\$patchTuesday = \$firstOfNextMonth.AddDays(9); break}

"Monday" {\$patchTuesday = \$firstOfNextMonth.AddDays(8); break}

"Tuesday" {\$patchTuesday = \$firstOfNextMonth.AddDays(7); break}

"Wednesday" {\$patchTuesday = \$firstOfNextMonth.AddDays(13); break}

"Thursday" {\$patchTuesday = \$firstOfNextMonth.AddDays(12); break}

"Friday" {\$patchTuesday = \$firstOfNextMonth.AddDays(11); break}

"Saturday" {\$patchTuesday = \$firstOfNextMonth.AddDays(10); break}

}
}

\$SyncDay = (Get-Date -Date \$patchTuesday).AddDays(\$SyncDelay)```
Gathering all WSUS patch information once and once only:

```# Getting this once now, rather than for each iteration....

\$wsus = Get-WsusServer @WSUSServerParams

\$allupdates = \$wsus.GetUpdates()

\$alltargetgroups = \$wsus.GetComputerTargetGroups()

\$NeededUpdates = Get-WsusUpdate -Approval Unapproved -Status FailedOrNeeded```
So thanks to him for these contributions. The updated script can be found here. That is exactly what being part of a technical community should be like.

UPDATE June, 14th 2018: Added a few more date conditions so that when the script runs between approvals, it is able to handle correctly the action to do. Added also a few minor fixes. Find the updated code on GitHub.

UPDATE June, 20th 2018: Published the Invoke-Wsus advanced PowerShell function which improves and replaces the wsus-operations.ps1 script.

## Monday, February 12, 2018

### How to use PowerShell to solve WSUS error 0x80244022

Recently I have started seeing my WSUS clients returning error 0x80244022 during the classic Windows Update checks.

At first I tought there was some kind of bug on my clients, then, once I checked my WSUS server, I came across an error message on the patch management console which stated I had to perform a Node Reset for the WSUS service to get back online.

After a bit of digging, I discovered that the WSUS pool is configured with a hardcoded Private Memory Limit set to 1843200 (which is 1.8 GB). Further analysis made me realize that each time that memory limit is reached, the IIS pool simply stops, breaking my WSUS service.

Needless to say, I decicded to try and solve this issue using my favorite tool: PowerShell.

So I produced a quick and dirty PowerShell script that increases that private memory limit to 8 GB, which should be enough for this kind of service.

Here's the code:
```Import-Module WebAdministration
\$NewPrivateMemoryLimit = 8388608
\$ApplicationPoolsPath = "/system.applicationHost/applicationPools"
\$ApplicationPools = Get-WebConfiguration \$applicationPoolsPath
foreach (\$AppPool in \$ApplicationPools.Collection) {
if (\$AppPool.name -eq 'WsusPool') {
\$AppPoolPath = "\$ApplicationPoolsPath/add[@name='\$(\$AppPool.Name)']"
\$CurrentPrivateMemoryLimit = (Get-WebConfiguration "\$AppPoolPath/recycling/periodicRestart/@privateMemory").Value
"Private Memory Limit for \$(\$AppPool.name) is currently set to: \$(\$CurrentPrivateMemoryLimit/1000) MB"
Set-WebConfiguration "\$AppPoolPath/recycling/periodicRestart/@privateMemory" -Value \$NewPrivateMemoryLimit
"New Private Memory Limit for \$(\$AppPool.name) is: \$(\$NewPrivateMemoryLimit/1000) MB"
Restart-WebAppPool -Name \$(\$AppPool.name)
"Restarted the \$(\$AppPool.name) Application Pool to apply changes"
}
}```
Once you run this piece of code on your WSUS server, the application pool gets restarted and your WSUS will have happy access to an increased memory space:

If you look at the following Resource Monitor screenshot you will see that the actual used memory becomes in my case a bit more of 2 GB, which is larger than the default 1.8 GB value but smaller than the 8 GB I set, so I'm fine:

Stay tuned for more PowerShell.

## Friday, February 2, 2018

### PowerShell oneliner to list all the installed .NET versions on a remote computer

Not so long ago I described how to retrieve the installed .NET version in a single line of PowerShell code through a registry query. This time I want to show you how the same can be achieved on a remote system by modifying a bit our approach. The big difference is that for a remote instance you have to access the HKEY_LOCAL_MACHINE base key through the RegistryKey.OpenRemoteBaseKey method.

The key path is the same as the one we saw on the locally run oneliner:
`HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP`

The regular expression is moved at the beginning of the oneliner, so that I can cycle through every item and open one subkey at the time.
`"v1.1.4322","v2.0.50727","v3.0","v3.5","v4\Full"|%{([Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', \$Computer)).OpenSubKey("SOFTWARE\Microsoft\NET Framework Setup\NDP\\$_").GetValue('version')}`
As you can see with a bit of PowerShell gymnastics, you can get pretty concise lines of code that do powerful things.
Stay tuned for more PowerShell.

## Thursday, December 21, 2017

### Configuring Visual Studio Code for PowerShell Core 6 after executable name change

That's a quick one before the end of the year. Last october the PowerShell Team, after a long debate with community members decided to rename the executable of PowerShell Core 6 from powershell.exe to pwsh.exe.

This allows to easily distinguish PowerShell Core from the PowerShell 5.1 built into your Windows 10. And now you can execute pwsh on Windows or Linux and get PowerShell Core 6 in a consistent manner.

Assuming you have moved from the PowerShell ISE to Visual Studio Code and got the PowerShell Extensions installed, there's a couple setting you have to put in place so that you can work with pwsh.exe as the default terminal:

"powershell.powerShellExePath": "c:/Program Files/PowerShell/6.0.0-rc.2/pwsh.exe",
"terminal.integrated.shell.windows": "c:/Program Files/PowerShell/6.0.0-rc.2/pwsh.exe"

The year 2017 is almost over. If you haven't yet played with the beta of PowerShell Core, I heartedly suggest you to do so, so that you can start getting used with a cross-platform shell available for Linux, Mac or Windows. You can find it open sourced here.

If you still stick with the PowerShell ISE, you should give a try to Visual Studio Code, which you can download here. For those of you familiar with the ISE, there's a great video tutorial by fellow MVP Mike F Robbins which shows you how to make your Visual Studio Code ISE-like. Just add the two settings above and you'll get a familiar environment ready for PowerShell Core.

## Monday, December 18, 2017

### PowerShell oneliner to list all the installed .NET versions on a local computer

In my drawers of PowerShell tools, I have a pretty extensive list of oneliners I have written for specific tasks. Today I want to share the line of code I wrote to check the .NET version installed on a local system. Since there is no direct method to find that information, a lot of people have come up with advanced functions, which is a bit overkill to me. So, given the fact that I like playing with the pipeline, and that I needed a quick and dirty script for this, I decided to see if I could find a way to get this done in just one line of code.

Starting from .NET 1.1 the registry key where the list of installed frameworks is installed is found under:
`HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP`
Under this key, you'll see separate keys for each .NET Framework version installed in your system.

Possible paths are:
```1.1 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v1.1.4322
2.0 HKEY_LOCAL_MACHINE\Software\Microsoft\NET Framework Setup\NDP\v2.0.50727
3.0 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.0
3.5 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5
4.0 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full
4.5 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full
4.5.1 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full
4.5.2 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full
4.6 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full
4.6.1 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full
4.6.2 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full```
As you can see, there are five possible unique paths.

These can be matched with a regular expression:
`v1.1.4322\$|v2.0.50727\$|v3.0\$|v3.5\$|v4\\Full\$`
which excludes v4.0 because it is deprecated.

Having PowerShell native access to the registry, we can run the following line on our local computer:
`ls 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP'`
just like you'd be browsing your file system.

Now what you have to know is that each .NET framework has a version name and a version number:
• .NET version name is composed by digits, and comes with the product name i.e. .NET 2.0, .NET 3.5, .NET 4.7.1
• .NET Version number follows this convention: (Major version).(Minor version).(Revision number).(Build number)

Here's some version numbers I have in my environment:

• 4.7.02053
• 4.5.51209
• 3.5.30729.01
• 2.1.21022

As you can see version names and version numbers share the first two fields. So, for istance, .NET 4.7.1 has version number 4.7.02556. We can then say that we don't care about the revision or build numbers (third and fourth field). The first two fields are all that I need.

To make a long story short, here's the PowerShell oneliner that I need to run to locally get all the installed .NET releases:
`((ls 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -rec|? name -match 'v1.1.4322\$|v2.0.50727\$|v3.0\$|v3.5\$|v4\\Full\$').name -replace 'HKEY_LOCAL_MACHINE','HKLM:'|%{ Get-ItemProperty \$_}).Version`
Hope this helps guys. Let me know in the comments if somebody here is able to make my oneliner shorter.

UPDATE: On older PowerShell version use:
`((ls 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -rec|? {\$_.name -match 'v1.1.4322\$|v2.0.50727\$|v3.0\$|v3.5\$|v4\\Full\$'} | select -expand name) -replace 'HKEY_LOCAL_MACHINE','HKLM:') -replace 'HKEY_LOCAL_MACHINE','HKLM:'|%{ Get-ItemProperty \$_} | select -expand Version`

UPDATE: You can also specify this registry path by specifying the registry provider's name, followed by "::". The registry provider's full name is Microsoft.PowerShell.Core\Registry, but this can be shortened to just Registry. So the following syntax is also possible
`((ls 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -rec|? name -match 'v1.1.4322\$|v2.0.50727\$|v3.0\$|v3.5\$|v4\\Full\$').name |%{ Get-ItemProperty "Registry::\$(\$_)"}).Version`

## Tuesday, December 5, 2017

### PowerShell oneliners by guest blogger Brian

During the latest PowerShell Oneliner Contest, Brian came up with a solution to Task 3 which is completely fantastic: he makes a very smart use of Group-Object -AsHashTable -AsString as well as of Invoke-Expression to produce an impressive 187 chars long solution.

Brian as kindly accepted to be my guest blogger today.

First of all, let's have a look at his answers to my PowerShell contest:

TASK 1: MANIPULATING OUTPUT - 43 chars
`gwmi Win32_Share|% N*|%{"\\\$(hostname)\\$_"}`
TASK 2: MANDELBROT JOKE - 54 chars
`"The B in \$((\$b=gv q* -v|% s*g 27 20)) stands for \$b."`
TASK 3: TEXT MINING - 187 chars
`\$t1,\$t2|%{\$_-split'\W+'|% *wer|group -ash -ass}-ov h|% K*|sort|gu|%{(\$d+=(\$1=\$h[0].\$_.Count)*(\$2=\$h[1].\$_.Count)),(\$a+=\$1*\$1),(\$b+=\$2*\$2)}|%{\$m="[math]::Sqrt("}{}{"\$d/(\$m\$a)*\$m\$b))"|iex}`

1. Brian, tell us a bit about yourself and about the way you got to work with PowerShell

I'm a syadmin/SRE/Systems Engineer working mostly on Windows but going cross platform whenever I can. I got started with PowerShell relatively late; maybe about 5 years ago now. Before using PowerShell my scripting tasks were mostly done with Ruby. I really enjoyed it as a language. At one of my previous jobs PowerShell was in heavy use there (particularly by the Exchange admin) so I started picking it up and then ran with it.

2. Is there any PowerShell project of your you want to speak about?

I have a module called Idempotion that lets you easily use DSC resources directly in scripts. It's a templated wrapper around Invoke-DscResource that gives you more natural PowerShell function syntax and some additional features (-WhatIf support, etc.).

This is also on the GitHub page but for example, where you would use a line like this:
`Invoke-DscResource -Name File -ModuleName PSDesiredStateConfiguration -Method Set -Property @{ DestinationPath = 'C:\Folder\File.txt' ; Contents = 'Hello' }`
Idempotion lets you do this:
`Set-File -DestinationPath 'C:\Folder\File.txt' -Contents 'Hello'`

3. Can you show us the way you tackled Cosine Similarity Task?

This was a pretty challenging task. I had never heard of cosine similarity before, so I had to first learn what that was, learn how to apply to it a string (since it's really about numbers), then come up with an implementation that could be sufficiently golfed.

My first attempt, at 254 characters defined functions (as ScriptBlocks in variables) for dot product and vector magnitude, and then called them later once the full vectors were realized. To make a long story short, it's much better to calculate the dot product and magnitude as you go along; it just took me a while to figure out that I could do that with this algorithm.

So actually I want to talk about some of the other challenges.

Unlike traditional code golfing, this is specifically a one-liner contest; so no newlines and no semicolons. This really forces you think hard about how you can do discrete tasks (even variable assignment) without stopping for a new statement. The fact that we're starting with two discrete variables for the source string puts that problem right up front.

So I start by making an array of \$t1 and \$t2 with the comma operator and then pipe that into ForEach-Object.

Splitting the string with \W+ splits on contiguous non-word characters as needed so that we get an array of words. After that I really want to lowercase version of the words, and then I want to group them into a hashtable.

To do lowercase, you can call .ToLower() but calling methods directly is painful in code golf. You need the entire name, need to use parentheses, if the source is not a variable or literal you also have to wrap the source in parentheses.

Luckily there's a little-known parameter set to ForEach-Object. Instead of passing a script block, you pass a member name like a property or method and then it gets retrieved/invoked for each input object. It even takes arguments for methods. Best of all it accepts wildcards (it must be unambiguous). With properties, this is like using Select-Object -ExpandProperty, just much shorter than even select -exp.

So:
`("I can't read words."-split'\W+').ToLower()`
Can become:
`"I can't read words."-split'\W+'|% *wer`
I use this extensively in code golf, and I wrote about it in the Tips for Golfing PowerShell thread on Stack Exchange's Code Golf site.

So you'll see me use this A LOT in this task.

Back to the pipeline: after ToLower I'm using Group-Object with -AsHashTable and -AsString. You'll see soon why I want a hashtable. -AsString is needed to get real strings for the hashtbale keys (this is annoying). The purpose of grouping is to get the the counts of each unique word. Group-Object isn't case sensitive so we don't actually need ToLower for this; but we need it later.

So the result of this ForEeach-Object is two hashtables, one for each of the input strings. The keys of each hashtable are the unique words, the values are an array of each instance of the word. So if the string contained the word "really" twice, the hashtable would contain a key of "really" with a value of @('really', 'really').

I'm using the -OutVariable parameter to store the resulting array of hashtables in a variable named h, while also sending it down the pipeline.

The next part, |% K* uses the aforementioned method of using ForEach-Object to expand a property. K* resolves to "Keys". This gets passed to sort and gu (Get-Unique) to get a list of unique keys. Since Get-Unique is case sensitive, this is why I lowercased the words previously. At this point in the pipeline though, all we have are keys. The pipeline objects are just strings, and the original hashtables they came from are not in the pipeline. So that's why I put them in \$h.

The next ForEach-Object does a lot of the "work" here. For each key I'm sending in, I need to retrieve the count of that key from the first hashtable (\$h[0], which is the words in \$t1) and the second hashtable (\$h[1], the words in \$t2). So \$h[0].\$_.Count does that for \$t1, where \$_ is the current key. These are the "pairs" of each vector. Doing it this way, with hashtables, ensures I'll get 0 for words that are in one string but not in another. Originally I was just using groups and missing words because of that. I'm going to need each of these values 3 times so it makes sense to store them in variables. I chose \$1 and \$2.

Small aside: PowerShell has a neat little quirk whereby you can do an assignment inside of a substatement (parentheses), and it does the assignment while also returning the value that was assigned. This also works with += and -=. This is really critical here.

To calculate the dot product, I need to multiply \$1 and \$2, and keep adding those up as I go along. \$d holds my dot product. So:
`\$d+=(\$1=\$h[0].\$_.Count)*(\$2=\$h[1].\$_.Count)`
Keeps accumulating \$d with my dot product as I go along, while assigning \$1 and \$2 for what comes next in the current iteration.

Within this iteration I also have to accumulate the values for magnitude for each vector (or at least for the squares of the current vector value). \$a and \$b hold the accumulating pre-square root magnitude values for \$1 and \$t2 respectively.

So I need to do 3 assignments here all within this 1 iteration, without semi-colons and newlines, so what to do? Let's just make an array of all three while assigning them, with prodigious use of parentheses and 2 commas!

Now I'm accumulating all the right values at the right time. Problem is, I don't need this array! I could nullify it, but that's a problem too; if I don't return anything to the pipeline, the next element won't run its process block.

Instead, the next ForEach-Object uses all 3 blocks, and the Process block is empty, because at this point I don't care about what's in the pipeline, I just want to finish the work.

The begin Block was necessary to get to End block, so I kind of got it "for free". What a perfect place to do another assignment! I'm setting up \$m to contain a string that looks an awful lot like a piece of PowerShell code that calls [math]::Sqrt(.

In the End block, we bring it all together. What I need to do here is divide the dot product (\$d) by the product of the Sqrt of \$a and the Sqrt of \$b. I do this by generating a string which ends up containing something like "123/([math]::Sqrt(4)*[math]::Sqrt(5))", and then I pipe that into Invoke-Expression (iex).

An intermediate solution I had assigned [math]::Sqrt (the actual method itself) to the variable \$m, so that I could call \$m.Invoke(\$a) through the shorter \$m|% I* \$a, but the stringification with iex is actually way shorter. This kind of thing comes in handy a lot in golfing.

So that's it! You can find more of my golfing on StackOverflow's golfing site (I pretty much only use PowerShell there even though they are open to any language).

## Thursday, November 30, 2017

### PowerShell oneliners by guest blogger Simon

During the last PowerShell Oneliner Contest, Simon came up with the shortest working solution to Task 2, and I am happy to say that he promptly accepted to be my guest blogger today.

Let's see his three remarkable answers to my PowerShell contest:

TASK 1: MANIPULATING OUTPUT - 46 chars
`Gwmi Win32_Share|%{"\\\$(\$_|% P*e)\\$(\$_.Name)"}`
TASK 2: MANDELBROT JOKE - 50 chars
`gv q* -v|% Su* 26 21|%{"The B in\$_ stands for\$_."}`
TASK 3: TEXT MINING - 196 chars
`"\$t1 \$t2"-split'\W'|group|%{\$o=\$i=\$p=0}{if(\$q=\$_.Name){\$1,\$2=\$t1,\$t2|%{(\$_-split'\W'|group|? name -eq \$q).Count}}if(\$o+=\$1*\$1){\$i+=\$2*\$2}\$p+=\$1*\$2}{if(\$3,\$4=\$o,\$i|%{[math]::Sqrt(\$_)}){\$p/(\$3*\$4)}}`

1. Simon, tell us a bit about yourself and about the way you got to work with PowerShell

I'm working as consultant in Sweden helping customers planning and building all kinds of automation, mainly using PowerShell. With a background in IT support and IT operations and a strong passion for coding I'm trying to preach developer practices to the ITPro community such as sourceControl, continuous integration and testing. Being able to collaborate on PowerShell scripts with your team and have the scripts automatically tested, signed and delivered to where they can be run solves a lot of questions like for example: "how do I know this script hasn't been changed since I ran it last time?".
I started my PowerShell journey quite a few years back when I was in client management. We had tens of thousands of clients reporting to Altiris and McAfee ePO and it was my task to sort and delegate actions based on those reports. When I realized I could parse the reports with PowerShell and do some basic sorting and filtering, that job got a lot easier and I was sold.

2. Is there any PowerShell project of your you want to speak about?

I think Phosphor (https://github.com/PowerShell/Phosphor) is an amazing project that hasn't got the attention it deserves. Phosphor can basically do a Get-Command and generate a web-form for each cmdlet. This could be used as a cross platform implementation of Show-Command or for building simple self-service portals. Imaging having a webserver that uses Kerberos Constrained Delegation to log the user in to a bunch of JEA endpoints and then generate a form for each command available to that user. This way any PowerShell savvy person in the Operations team could deliver self-service business value to the organization without depending on web-developers.
Each time I have an hour of spare time I try to learn some TypeScript so I can fully understand how Phosphor works and hopefully I can get to contributing to the project in the future.

3. I was impressed by your solution to Mandelbrot's riddle. Can you explain your approach to it?

Thank you! I started by trying to find text that was similar in both the question and the answer and saw that the string " Benoit B. Mandelbrot" was the longest text I could find in the question that was repeated in the answer.
I started by just figuring out the shortest way to insert that into a string and got this:
```\$x = " Benoit B. Mandelbrot"
"The B in\$x stands for\$x."```
Then I tried to find the shortest way to break out my string from the question and got to this:
```\$x = \$Question.Substring(26,21)
"The B in\$x stands for\$x."```
This looked quite good to me, but I wanted to shorten the substring part. This took me to one of my favorite code-golf tricks, using Foreach-Object with the parameter MemberName. Foreach-Object has a not very well known parameter called MemberName that instead of running a piece of code for each object coming through the pipeline, it invokes the named member of each object that has such a member. In this case I want to invoke the member Substring and give it the arguments 26 and 21 using the parameter ArgumentsList. Like this:
```\$x = \$Question | Foreach-Object -MemberName Substring -ArgumentList 26, 21
"The B in\$x stands for\$x."```
Now this doesn't look shorter, but we can shorten it! Let's look at the parameters of Foreach-Object using Get-Help:
`Get-Help -Name Foreach-Object -Parameter *`
This tells us that the MemberName parameter belongs to a set called PropertyAndMethodSet, let's filter on that set:
`Get-Help -Name Foreach-Object -Parameter * | Where-Object -Property parameterSetName -like *PropertyAndMethodSet*`
Ok, so we have three parameters:
InputObject accepts pipeline input ByValue and will be bound since we are piping \$Question.
MemberName is a positional parameter with position 0 so the first positional value/argument will be bound to MemberName. I also happen to know that MemberName accepts wildcards!
ArgumentList is not positional, but it takes value "FromRemainingArguments" meaning that all values/arguments that remains after MemberName is bound will be bound to argument list.
Using Get-Alias we can also find that Foreach-Object has an alias '%'
`Get-Alias -Definition Foreach-Object`
With this knowledge we can shorten our code significantly:
```\$x = \$Question | % Su* 26 21
"The B in\$x stands for\$x."```
Now that looks good, but \$Question is also quite long, what if we could use a wildcard to get the variable? Let's try with Get-Variable which has an alias of gv. To get that value of the variable and not the variable itself we also use the parameter -ValueOnly, but we don't need to write ValueOnly, PowerShell is happy as long as it can figure out that is what we want. Since ValueOnly is the only parameter starting with a v, -v is enough.
```\$x = gv q* -v |  % Su* 26 21
"The B in\$x stands for\$x."```
Let's make this a one-liner using Foreach-Object and replacing \$x with \$_ and remove unnecessary spaces and we get this:
`gv q* -v|% Su* 26 21|%{"The B in\$_ stands for\$_."}`

4. Do you see any possible scenario where using Cosine Similarity in PowerShell could help?

Yes absolutely! I'm doing Active Directory migrations and identity projects where we need to match a user or person in one system with a user in another system. This is easy as long as we have a common and unique attribute like email to match on, but when we don't we often need to match on for example givenname and surname. Using cosine similarity I can find persons with similar names.

## Monday, November 27, 2017

### PowerShell oneliners by guest blogger Ka-Splam

As you know, the PowerShell Oneliner Contest 2017 has its winner: Ka-Splam, from UK. Today I am proud to announce that Ka-Splam has accepted to answer a few questions on this blog.

Take the time to read it all and, if the PowerShell monk in you is able to solve 'The 25 chars contest' hidden in his answers, leave a comment for greater glory.

1. Ka-Splam, it's an honor to have you here today to talk about the the impressive oneliners you provided. I know that you are very active on the PowerShell subreddit and regularly competing and winning in Shortest Script challenges. Let's get straight to the point: how one gets good at those challenges?

Oneliners take a long time to write, they're often frustrating, they run slowly, they're hard to read, you shouldn't use them in production scripts, and they don't handle edge cases or errors. There is no reason you should want to get involved in them.
Except that they're fun.
If you like that kind of thing - if you enjoy nitpicking over details .. hold up a moment! Here's a detail to nitpick: shortest code challenges ('codegolf') are what produces hard to read code. Oneliners are clean, clear, readable – what you get when you trim away the fluff and use the tools skillfully.
Happysysadm hinted that I should try and write something the community could learn from, and that's a bit scary; what I'm going to do is solve a simple problem and then shorten it. In detail. I don't know whether the work of writing short code has any real-world use, but I'm convinced that if you are engaged in something (anything), if you are thinking about it, experimenting with it, trying to shape it to your will - you will learn something.
Short-code challenges keep me engaged, keep me digging into edge cases in my understanding of the language in a way that classic problems like "write three pages of code to simulate an object oriented deck of cards" don't. Nobody talked me into this kind of thing by saying it would be good for me; I have always liked using built-in tools with no need for 3rd party dependencies, utilities that consist of a no-install .exe, and reduced "bloat".
Except writing. If you skim read this article and think it's too long - look how much effort it takes me to shorten this puzzle. Doing that to shorten this blog post would take weeks! ??
Problem: "things in the root of my C:\ drive, where the name is more than ten characters, just their names", and the long form code is:
```\$items = Get-ChildItem –Path 'C:\'
\$longNames = @()
\$items | ForEach-Object –Process {
If (\$_.Name.Length -gt 10)
{
\$longNames += \$_.Name
}
}
Foreach (\$name in \$longNames)
{
Write-Output \$name
}```
It is 12 lines and 241 characters, which I will keep track of as we reduce it, and the output is four strings:
```OpenSSL-Win32
Program Files
Program Files (x86)
Python27-32bit```
I have made this code a bit laborious, but people familiar with PowerShell should easily follow what it's doing – there is clear separation of the major steps: gathering data into a variable. Stop. Checking the name length. Stop. Collecting the results. Stop. Displaying the output. Stop.
(NB. Beginner programmers will still struggle - there's nothing intuitive from everyday life about `Get-ChildItem` or syntax like `+=` or `\$_.Name.Length` or piping into `ForEach-Object`, but they are common patterns in PowerShell. As we shorten the code, we move from common patterns towards rare, novelty patterns).
But I don't want to type so much code to satisfy a momentary curiosity about folder name length and this is not an engaging or interesting puzzle on its own - once you've got past the basics of PowerShell there is no challenge. Instead of looking for a harder puzzle, we can make this more challenging by writing it in less code. And it's an open ended challenge: there's no fixed place to get to, no pass or fail, and you're mostly competing against yourself. That's something I like about it.
How do we write it in less code, what is that process?
You know how putting five numbers in order is easy and people learn how to sort numbers into order some time during childhood, learning by example? Telling someone steps to sort five numbers in order is much harder - that's computer science academic work. Well, I know how to write shorter code but I don't know how to tell other people how to write shorter code. My examples here are step by step progress, and hopefully you can learn by example.
Many of you will look at the last four lines of the long solution and think they "do nothing". Great, just writing `\$longNames` is enough for PowerShell itself to pull the items out of the array and write them to the output, and they will end up on screen. It was going to do that anyway, there's no need for us to explicitly write that:
```\$items = Get-ChildItem –Path 'C:\'
\$longNames = @()
\$items | ForEach-Object –Process {
If (\$_.Name.Length -gt 10)
{
\$longNames += \$_.Name
}
}
\$longNames```
9 lines, 188 characters.
But it needs a bit more understanding of the language to know why writing a variable name on its own does anything, and what it does. But that is a common pattern for PowerShell users familiar with functions.
As well as offloading work onto the computer, we can offload work to the programmer's brain – this code stores the directory listing in a variable named `\$items` and pulls it straight out again, a needless double step going A to B to C. We can connect the listing output to the loop input with a pipe and skip right from A to C, but then understanding the code requires that the programmer is comfortable enough with the way the language works to follow A to C with nothing in between to hold on to:
```\$longNames = @()
Get-ChildItem –Path 'C:\' | ForEach-Object –Process {
If (\$_.Name.Length -gt 10)
{
\$longNames += \$_.Name
}
}
\$longNames```
8 lines, 171 characters.
That doesn't need more understanding - we already used the pipeline – but it's my observation from code and questions on the internet that this change is hard for people. This changes the nature of the program from "taking clear, distinct steps, one at a time" to "a flow from start to finish, however far it goes, all at once". Knowing that pipelining things is possible doesn't seem to be enough - it needs quite a lot of practice for this "let the data slide through" code-shape to become comfortable and familiar.
The same thing happens from here on down – more understanding means greater leaps. A to C becomes A to E, then A to J. The heart of all "code readability" arguments might be whether the reader has enough familiarity of the patterns used in the code, rather than whether the code itself is "readable", too long, or too short.
From here, part of me wants to squash the `if` into a single line, part of me wants to get rid of the `@() / +=` combination and have loop output to array in one go – that's an example of what I was just writing, sending loop output into a variable without any intermediate steps is a pattern that looks weird from other languages, but gets more familiar with use - and part of me wants to get rid of the separation of "storing the names, then displaying them" by merging that into one "find and display them" step:
```\$longNames = Get-ChildItem –Path 'C:\' | ForEach-Object –Process {
If (\$_.Name.Length -gt 10) { \$_.Name }
}
\$longNames```
4 lines, 129 characters.
This uses the same understanding from earlier ("why \$longNames does something when written on its own") to understand how writing `\$_.Name` does something on its own, even though the context is different. From directory listing to variable, no stops along the way. A to D.
Have you noticed these changes work on different layers of the code? Some of them are purely visual – removing line breaks from the `if` made no difference to the way the code works. Others change what's happening behind the scenes - connecting `Get-ChildItem` to `ForEach-Object` removed an array and a variable name, uses less memory, without affecting the output. That's something else I like – shortening the code involves understanding up and down the layers, from PS reading the code, to what it does behind the scenes, to how those parts interact with each other, to what exactly needs to happen to the data to solve the problem.
If we can remove newlines around the `if`, let's do that a bit more and put `\$longName` up on the previous line, with the rest of the code:
```\$longNames = Get-ChildItem –Path 'C:\' | ForEach-Object –Process {
If (\$_.Name.Length -gt 10) { \$_.Name }
} \$longNames```
Oh no!
`ForEach-Object : Cannot bind parameter 'RemainingScripts'`
Nothing happened when I took the newlines away from the `if` statement , but take this one away and it won't run. This specific problem plagued me for a while with short code challenges, and following up on that error and why it happens was interesting and useful. Something else I'm not showing in this article is how many times I try things which don't work. We'll undo that change, and let's get rid of the names array instead, and output directly:
```Get-ChildItem –Path 'C:\' | ForEach-Object –Process {
If (\$_.Name.Length -gt 10) { \$_.Name }
}```
3 lines, 98 characters.
Some of you have been hitting your heads on why I'm using a loop and a test, instead of merging them together and using `Where-Object` - why two steps instead of one?? OK let's merge those:
```Get-ChildItem –Path 'C:\' | Where-Object –FilterScript {
\$_.Name.Length -gt 10
} | ForEach-Object -Process {
\$_.Name
}```
5 lines, 136 characters. Longer.
I joke, you probably expected this shape:
`Get-ChildItem –Path 'C:\' |Where-Object {\$_.Name.Length -gt 10} |Select-Object -ExpandProperty Name`
1 line, 99 characters (yay! Dropping below a 100 char cutoff is pleasing)
This is another dimension of it – my early changes made it shorter, no question. But we're now at the point where some changes to make things shorter don't quite do the same thing, and so they need another change elsewhere to compensate, and the whole thing ends up longer. Squashing things into a small space is easy at first, but after a while every push in one place makes things pop up somewhere else, progress slows down, and that's a hint that you're getting past the easy gains. Maybe it's a good place to stop?
I had to do something, the `if` test wasn't just checking the length it was also extracting the `.Name` property. `Where-Object` can only do the length test, so the Name property needs to be handled in new code. That's another dimension, going from "script" to "oneliner" towards "codegolf" means testing the rules of what's allowed as output. If the full directory listing output is OK, then we can cut the entire last chunk from this code, if it's mandatory to just output the names then we have to get the Name out. (Tip: argue with whoever set the puzzle that your rule-bending output should be valid ;-))
And the shape of the code changed by merging the loop with the `if` test – it now uses `Select-Object`. Writing short code requires knowing several different ways to do things, so practicing writing code in different ways means you can "choose the shortest way you can think of" compared to people who only know one way.
Back to my original nitpick right up at the top, I'm going to call that last example a oneliner: there's no variables, no loops, just three pipeline blocks neatly connected, one each for the three stages of the problem – get data, process it, output it. No stops. It's 8% of the lines and 40% of the typing, and does the same thing.
Let's not end here, let's start here – assuming I still didn't want to type all that at the console, let's go through the same tricks again:
1) Offload work to the computer
2) Offload work to the programmer
3) Use greater understanding of the language to do (1) and (2)
That means:
• `-ExpandProperty` - parameter can be shortened; if PowerShell can match what you type to a single parameter, it will. Tiny bit of language knowledge, quite common.
• Aliases: `where` and `select`, very common knowledge.
• Default parameters: with `Get-ChildItem` `-Path` is assumed for the first parameter, if you don't type it. So commonly used, I used it all the time before I even knew that's what was happening. Don't tell the computer to go that way, if it was going that way anyway.
Now:
`Get-ChildItem 'C:\' |Where {\$_.Name.Length -gt 10} |Select –Expand Name`
71 characters.
We just knocked a quarter of it off, and it's still almost the same. I'm happy this is the kind of code I'd write at the command line, off the top of my head, share with people, but not use in production scripts.
Repeat: same again – make the computer do work, use greater knowledge of the language, etc. etc.
• Aliases: `?` and `gci`, quite common.
• In parameter parsing mode strings without spaces don't need quotes, again common.
• `-Exp` is still unique, instead of `-Expand`, also common.
These changes make it:
`gci c:\ |? {\$_.Name.Length -gt 10} |select -exp Name`
52 characters.
Another 30% reduction. Looking like a traditional "oneliner" now, getting unreadable, approaching "codegolf" territory. There isn't a distinct cutoff that I know of, but you can see it's nothing like the earlier code, and yet you followed the blog post down this far step by step and you can also see it's exactly like the earlier code.
Code so far has enough whitespace to be clear and readable, but "shortest code" means delete all the spaces. The space between `c:\ |` can go and everything will work. Remove the space between `gci c:\` and it won't work. Removing the space from `} |select` is fine, removing the space from `-exp Name` isn't. Trying these will give you errors, and exploring why the errors are coming involves learning something about PowerShell.
Now we're at the point where there's a couple of spaces I could trim to drop just below 50 chars, but that must be almost as far as it goes, right? We list the directory contents, check the name length, expand the property. What else is there to get rid of?
How far can it go? This is where it gets fun and challenging.
```# remove the easy spaces, and gci has an alias 'ls' for –4 chars
ls c:\|?{\$_.Name.Length -gt 10}|select -Exp Name    #48 chars

# select can take a pattern for the name as long as it's unique -2
ls c:\|?{\$_.Name.Length -gt 10}|select -Exp N*    #46 chars

# comparisons and other operators don't always need spaces -2
ls c:\|?{\$_.Name.Length-gt10}|select -Exp N*    #44 chars

# If you know how PowerShell handles properties on Arrays, rewrite for –8
(ls c:\|?{\$_.Name.Length-gt10}).Name    # 36 chars

# Or, ForEach-Object can expand a property, a little known feature, -2
ls c:\|?{\$_.Name.Length-gt10}|% N*     #34 chars```
35% gone. And we dropped below three blocks, momentarily, with a big rewrite – after going towards wildcard patterns, changing to a parentheses wrapper and back to the full `.Name` still saved a lot. Come on brain, what else can we dig up? TYPES! I haven't mentioned casting yet, and that is a huge part of it. Look at this:
```gci c:\ | ForEach-Object { \$_ }     # directory listing output
gci c:\ | ForEach-Object { "\$_" }    # names only !```
If you force the output items to be a string, they become just the name, not the whole directory listing, or the full path. For this problem, that's convenient. For others, it isn't. In other code, casting between types is incredibly common and tricks to cast between arrays, strings, numbers, are very useful. Let's abuse the string cast and get rid of calling `.Name` entirely:
```# Now \$_ is a string name
ls c:\|%{"\$_"}|?{\$_.Length-gt10}    # 32 chars

# but wait, there's a trick with `Where-Object` to avoid the scriptblock
# we couldn't use it for a double lookup Name.Length but now
# we have unlocked it, because we're working with one property, -2
ls c:\|%{"\$_"}|? Length -gt 10    # 30 chars

# and that trick can take patterns for the property name, -3
ls c:\|%{"\$_"}|? Le* -gt 10    # 27 chars```
What else do I happen to know about strings, casting, types, pattern matching? Regular Expressions! I skip over another pile of background knowledge and edge case behavior, and show:
```# Completely different array filtering approach, based on using a regular expression to count
(ls c:\)-match'.{10,}'|%{"\$_"}    # 30 chars (boo)

# use a previous short version of `%` again, -3
# "going back to something I was using" happens a lot
(ls c:\|% n*)-match'.{10,}'    # 27 chars (Q: why are the parens needed?)```
Wait. This is getting silly. Almost halved the 52. And if you just saw one of these answers pasted into a web page, you wouldn't see the pages of "getting it a bit shorter each time" happening earlier. It would look like "What, who can just write that, one-liners are awful".
Anyway, I try a lot of things. I spend a lot of time on it. I drag in as much knowledge as I can find, all that matters is getting the right output. It's a form of minimalism, if you can scrape by with a skeleton crew of code and pieces falling off everywhere, as long as it gets past the finish line once, everything else can go. At the same time, it's not minimalism – if you can spend 1GB of memory and 5 seconds of timewasting to save 1 character, do it and be grateful for it.
Two completely different approaches, both hitting 27 characters.
This fascinates me; the early code and the tiny code are so different to humans, but do the same thing.
This
```\$longNames = @()
Get-ChildItem –Path 'C:\' |
ForEach-Object –Process {
If (\$_.Name.Length -gt 10)
{
\$longNames += \$_.Name
}
}
\$longNames```
Is the same as this
`ls c:\|%{"\$_"}|? Le* -gt 10`
and this
`(ls c:\|% n*)-match'.{10,}'`
People find one more readable, more writable, but the computer is never confused. This makes me feel there's something really interesting underneath this about "expressing computation". How can they be so different and "do the same"? How much of the code is important, how much is fluff? What features could this language be missing that could make that computation shorter or clearer?
Is that is? Can it go below 27 characters? (hint: yes, I have a 25 .. so far)
One last tip for those willing to compete in codegolf competitions: spend a lot of time on them, and then present them as a finished script that looks effortless. And practice rewriting and rewriting in different ways. And shamelessly steal every code-shortening idea from other people that you can possibly find, put them ALL in.

2. Tell us a bit about yourself and about the way you got to PowerShell.

I have a long dislike of writing code as an amateur and sharing it with people, only for them to say "I haven't got Python, or a Java runtime" or "how do I install that" or "what's the Visual Basic runtime?". I envied the Linux distributions with their built-in C compilers and Perl and Python, almost as much as I didn't enjoy VBScript.
When PowerShell came to Windows I jumped on it. Windows Vista, PowerShell 1 or 2. At last a powerful scripting language everyone would have by default!
I didn't understand it, and I dropped it.
A few years later, 2012 ish, working in IT with Exchange requiring it, newer versions getting better and better, it grew on me. Then it took over from Python as my everyday playing around language, now I'm a J. Snover's Witness.

3. Can you tell us your approach to Task 1?

Task 1 mandated the use of `Win32_Share` and the shortest way I know is with `gwmi`. After a few trials and errors, I thought of the forced-cast-to-string approach mentioned above and checked to see what happens - one output object becomes:
`\\Computer01\root\cimv2:Win32_Share.Name="ADMIN\$"`
That's so close to the required output format, string cutting to get rid of the middle bit has got to be one of the shortest possible routes to an answer. And trim the annoying trailing quote. Luckily I like Regular Expressions, so a bit of `-replace` experiments later and I have something that seems roughly as good as I could ever get it.

4. How did you cope with Task 2?

I stared at it, and I saw it needed `\$question` to be used and print this output:
`The B in Benoit B. Mandelbrot stands for Benoit B. Mandelbrot.`
The words "in, stands, for" are not in \$question so they must be in my code. "Benoit B. Mandelbvrot" is in the answer twice, remove that duplication, fin.
In my head was the shape "output string, get the first `Benoit B. Mandelbrot` from `\$question` with string manipulation (== probably regex), use it and store and re-use for the second place it appears, probably in a sub-expression in the string".
And then trial and error until I had it quite short. As it happened, SubString came out shorter than regex. This rarely happens. The most pleasing adjustment in my answer was taking the space in front of `Benoit` from the input as well.

5. Your oneliner for Cosine Similarity is impressive. Can you explain how you got to such a short solution?

Nope ?? The whole of this article is trying to answer this question. All that – trial and error, codegolf experience, weird language behavior edge cases, looking at the language specification, spending lots of time, knowing a pile of short-code tricks – that's how.
This one was scary. I hadn't heard of it before, I'm not a skilled mathematician, and the Wikipedia page math-terminology-explanation was no help. It took me quite a while of Googling before I decided to look at the Reddit discussion, expecting other people to have finished it already. Other people were puzzled and that was a bit of a relief.
After finding C# examples, explanations, discussions, I started to get a clue. It's word counting, adding, dividing, then I could start to make sense of the Wikipedia equation – A1*B1 + A2*B2 … An*Bn on the top. A1 squared + A2 squared … An squared on the bottom left. Same for B on the bottom right. I can make those work.
Something which isn't covered in the previous few pages, is the way I split it into smaller parts – lots of testing how to split the input strings into words, lots of testing ways of counting words in sentence 1 vs sentence 2, before I started combining the code together and trying for a full answer. Then a lot of struggling to get past the "no semi-colons" restriction.
Particular techniques in this answer:
```\$a=1   # assign a variable
(\$a=1)  # assign variable, and use value 1 right here in the code as well```
```foreach () { } \$t    # because, as noted earlier
... | Foreach-object { } \$t    # this structure doesn't work```
Instead of writing these:
```\$lowerLeft += \$a * \$a
\$lowerRight += \$b * \$b```
with a semicolon:
`\$lowerLeft += \$a * \$a; \$lowerRight += \$b * \$b`
It was a moment of insight to use:
`\$throwawayVar = (\$lowerLeft += \$a * \$a), (\$lowerRight += \$b * \$b)`
Programming by side effect, making an array of the results and ignoring it. It still looks like a very redundant answer to me, three big repeating patterns, I was expecting someone else to get rid of them and be 1/3rd shorter.
It was only by coding it that I came to understand what it did, and spent a while pacing up and down explaining to myself what an N-dimensional cosine means, why it makes any sense at all relating to documents and words, how it measures document similarity, and imagining dogs pulling in the 'bacon' direction vs the 'dog park' direction. So that was fun.

TL:DR:

One dimension:

0 --- 1 --- 2 --- 3 --- 4 --- > Bacon.

A dog which pulls strength 3 in the bacon direction is similar to another dog which pulls strength 3 in the bacon direction. They are different from a dog which pulls strength 1 in the bacon direction.
A document which says "bacon bacon bacon" is similar to another "bacon bacon bacon", but they are different from "bacon".

Two dimensions:

/\ Dog Park (up)
|
0 --- -> Bacon (right)

Dogs which pull strongly towards bacon are similar. Dogs which pull in both directions equally, can't make up their minds, and go off at an angle – are similar. Dogs pulling in the Dog park dimension are similar.
Dogs pulling towards bacon are a bit different from dogs pulling in both directions and going at an angle.
Dogs pulling towards bacon only are very different from dogs which pull towards dog park only.

Exactly how strongly they pull in each direction, determines which angle they go off at, mostly bacon, mostly dog park, or split the difference.
A document which says "bacon bacon park park bacon park" is similar to "park park park bacon bacon bacon". It pulls in both dimensions and goes off at an angle. It's a bit different from "bacon park park park" which pulls more towards the park. It's very different from "park park park park park" which pulls only towards the park.
Each word is a direction, a dimension. The word count is how strongly the document "pulls" in that direction. After all the pulls combine, the document goes off at an angle. Different word counts make different angles. This equation works out "how different". I can't visualize more than 3 dimensions, but I can up to 3 and it now makes sense that it works. More words, more dimensions, same idea.

6. What's your take on Powershell for one-liners if compared to other languages you might know?

For shell use oneliners, there isn't any other language quite in the same category so it's great. For general purpose programming one-liners I like it, plenty of convenience syntaxes. Some things that annoy me. I tend to forget it's an admin scripting language, and instead wish it pulled in every convenience feature from every language I'm dimly aware of.
http://codegolf.stackexchange.com is a multi-language site, and in my experience the answers (other people's answers, I don't know most of the languages!) separate themselves into tiers:
• Really short: single-purpose languages designed for golfing – GolfScript, CJam, many others.
• Short: the major scripting languages - Perl, Python, JavaScript, PowerShell, Ruby, etc.
• Medium: mainstream languages, older languages with fewer built-in conveniences (C#, Java, Lisps)
• Long: novelty answers - SQL, etc.
PowerShell has a reputation for being long and wordy, but the language designers did a fantastic job with the "elastic syntax" to make those things optional, and with things like automatic module imports and type accelerators, being a shell and having direct access to the filesystem it is strong. Other languages save not needing `\$` on variable names, but then can't put variables in strings easily. Or they have better string slicing but worse regex subroutines. PowerShell tends to be slightly behind the other popular languages, often because you need quotes and parens so much or don't have quite as easy int->char->string conversion.
What really seems to make the difference is whether language X happens to have a convenience feature that fits the question. Often the languages trade places for different problems, so JavaScript might have a short way of doing one thing, but C# has a lambda expression and Linq combo which surpasses it for other things, and Mathematica has an inbuilt list of country names so it takes a winning place in some specific question, etc. PowerShell is fun, competitive enough.