What’s the last Sunday?

In October 2012, I published a function to find the last Sunday or another day of a month and based on some comments, I decided to revisit the code and improve it.
I started to improve the discovery of expected values by some parameters like the day, the month and year.

To achieve this I started to use a Dynamic parameter block and had the following code:

I wasn’t satisfied although it worked as expected. It’s hard to read, there are too many variables…

I remember I’ve already seen someone who wrote helper functions to avoid writing so many redundant code in the dynamic parameter block. (I cannot remember who. If you do, please post a link in the comments).

I wrote a quick helper function. I didn’t want to go further in order to keep the complexity quite low.

It’s around 50 lines.
Here’s an example to create a ValidateRange parameter attribute for the “Month” parameter.

Let’s see how this helper function is used in practice:

I first create the Dictionary object in the dynamic parameter block.
I load the helper function.
I then create the 3 dynamic parameters for the day, month and year.
Notice that I’ve also used some hash-table and splatting separated by a semicolon inside the same script block for a better readability.
There are only two variables left my dynamic parameter block ($Dictionary and $HT).

Now, let’s see how this function performs.
I’ve added a new feature where you can actually tell what day position in the month you want.
You can get the First, Second, Third, Fourth or Last.
You might be scratching your head why such options.
Well, let’s take a concrete example to illustrate why.
Let’s say Sunday.
In February 2016, you’ve got 4 Sundays but in April 2017, you’ve got 5.
One of the purpose of this function is to find out the last Sunday in a month to run a scheduled task.
Or you may want to run your task every fourth Sunday in a month.
Sometimes, the fourth Sunday is the last Sunday like it’s in February 2016.

But that’s not true in April 2017

I’ve got another code snippet to demo how this function can be used:


I can display the first, last, fourth Sunday in every month in 2016 and the number of days between the first and last ๐Ÿ˜Ž

Quick tip: disable WSUS approval rules

I stumbled upon the following article about WSUS and PowerShell a few weeks ago.
After seeing the second point, I realized, it’s nice to verify how “auto-approval” rules are configured but it would be better to know how to enable or disable these rules.

Here’s how to disable rules:


(Get-WsusServer).GetInstallApprovalRules() | 
Out-GridView -Passthru| 
ForEach-Object {
    $r = $_
    try {
        $_.Enabled = $false
        $_.Save()
        Write-Verbose "Successfully disabled $($_.Name)" -Verbose

    } catch {
        Write-Warning "Failed on rule $($r.Name) because $($_.Exception.Message)"
    }
}

With the above code snippet, you first get the out-grid view where you can select rules and then click ok:

I selected the only rule I had and got the following result:

If you want to seem more articles about WSUS from my blog, you can use WSUS tag to list them all ๐Ÿ™‚

(crazy) Tips: editing scripts

I’ve made a typo when invoking a script and was surprised by the result.
I decided to investigate and tried to understand what just happened.
Without further suspense, I did actually append a dot at the end of my script.

Whoaaa, notepad opened the script.
I didn’t get an error saying this file path doesn’t exist.
That was such a fast way to open my brave old notepad that this typo might even become my new way of editing scripts ๐Ÿ˜›
I mean, instead of typing notepad c:\test.ps1, I’ll from now on type c:\test.ps1.

To understand what happened, I first traced side by side my surprising command and the one I was supposed to run:

Trace-Command -Name * -Expression { C:\test.ps1 } -PSHost
Trace-Command -Name * -Expression { C:\test.ps1. } -PSHost

At the beginning, everything looks similar

But at some point, it starts doing something else (as you can see below).
It starts examining my PATHEXT environment variable.

To better highlight what I happened, I did:

Trace-Command -Name CommandDiscovery -Expression { C:\test.ps1 } -PSHost
Trace-Command -Name CommandDiscovery -Expression { C:\test.ps1. } -PSHost

In the first case, the CommandDiscovery correctly found that c:\test.ps1 is a script and invoked it.

In the second case, the CommandDiscovery found that c:\test.ps1. is an application and started looking for an application that can handle this extension.

If it finds a MRU list under HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\OpenWithList (if you’ve ever double-clicked on .ps1 file), it will use it.

If it’s not the case, it starts looking at this registry key HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\OpenWithProgids

and it should point to:

But when it doesn’t find the MRU list or the OpenWithProgids

It writes the OpenWithProgids value in the user hive and set it to Microsoft.PowerShellScript.1

(I’ll over simplify it) It then starts looking for any application listed under this key HKEY_LOCAL_MACHINE\SOFTWARE\RegisteredApplications to find out if .ps1 is listed in their “capabilities”

Then under the same key but under the user hive: HKEY_CURRENT_USER\Software\RegisteredApplications

It then reaches back again what’s in the machine hive to “open” what it detected as a “Microsoft.PowerShellScript.1”

and uses notepad

Tips: How to avoid redundant code

While working on a module that has a huge memory workload, I wanted to avoid redundant code and used the following nice tips Mike F. Robbins demonstrated in this blog post
First try the following in a console:

$test = 'Heavy task'
($test = 'Heavy task')

Do you see the difference?

Having parentheses around the variable statement also outputs the result in the console.

Isn’t it very nice?

Why there is such a difference?

Having parentheses will make the interpreter parse the simple pipeline/expression (what’s inside the parentheses) in expression mode and output the result of this expression. It’s notation is:

(<expression>)

How do I know this?
Well, I just took the example Bruce Payette gave in the “Statement termination” paragraph in his awesome book “PowerShell in Action” and the error message revealed it ๐Ÿ™‚

Let me also quote what’s written in the “grouping /subexpressions and array expressions” paragraph:

Parantheses group expression operations and may contain either a simple expression or a simple pipeline

The notation can be either

(<expression>)

or

(<pipeline>)

Now, consider the if/else statement for a few seconds that has the following notation:

if (<pipeline>) { 
 <statementlist>
} elseif (<pipeline>) {
 <statementlist>
} else {
 <statementlist>
}

It also means that you can do both the variable assignment and test if anything assigned exists inside the if pipeline/expression. It will evaluate to $true if the output exists and to $false if it doesn’t. As far as I remember, some participants in the scripting games were also using this approach and Don Jones talked about it a few years ago (if you’ve a link please share it, I’ll add it).

When you process only once the heavy workload, capture its result into the variable and immediately test if it exists, it also allows to write less code and have a slightly faster execution in memory.
Here’s an example of what I mean:

if ($test = 'do my heavy task/function/cmdlet') {
 $test | Do-Something 
} else {
 Do-SomethingElse
}

Using the above syntax, you can actually avoid writing more code.
Please, let me repeat that there’s nothing wrong in doing the following:

# assign once but outside of the if/else statement
$test = 'do my heavy task/function/cmdlet'
if ($test) {
 $test | Do-Something 
} else {
 Do-SomethingElse
}

But, you definitely should avoid running twice the heavy memory workload if you do:

if ('do my heavy task/function/cmdlet') {
 $test =  'do my heavy task/function/cmdlet' 
 $test | Do-Something 
} else {
 Do-SomethingElse
}

Let’s measure how fast each of these pieces of code run.
I haven’t found better examples yet but you can try these examples.
The first and second examples run almost at the same speed.

# Example1:
# Both variable assignment and test 
# inside the if/else
Measure-Command {
 if ($test = 1..10000 | % { $_ * 2 }) {
  'ok'
 } else {
  'nok'
 }
}

# Example2:
# Variable assignment outside
# and test inside the if/else
Measure-Command {
 $test = 1..10000 | % { $_ * 2 }
 if ($test) {
  'ok'
 } else {
  'nok'
 }
}

# Example3:
# Test an expression
# Late variable assignment
# Runs twice, so avoid it
Measure-Command {
 if (1..10000 | % { $_ * 2 }) {
  $test = 1..10000 | % { $_ * 2 }
  'ok'
 } else {
  'nok'
 }
}

Windows 10 Update and Privacy Settings

I had the displeasure to start the control panel on my Windows 10 (1607) this Sunday morning and find an entry I didn’t know about and that I didn’t install myself otherwise I’d remember having done so.
Here’s what I saw:

Being a sysadmin dealing with security, I immediately thought “OMG! My children running as standard users have been able to install a software that was able to do a privilege escalation despite the harden configuration I set up”.

In less than 2 seconds, knowing exactly what registry keys have been queried to populate these entries in the control panel, I realized my kids who have been somehow trained about social engineering tactics,… wouldn’t have fallen in this basic trap and install something called “Windows 10 Update and Privacy Settings”.
Sure, my 10 years old son was able to identify the following malicious tactics described in this article “Breaking down a notably sophisticated tech support scam M.O.” a few weeks ago before this article was published.

Mystery solved. As you can see above, there’s URLInfoAbout that points to http://support.microsoft.com/kb/4013214 and it’s not a fake.

Is it legitimate?
Knowing also how to find other footprints of this installation in the registry, I was able to identify where the msi file is stored on the disk.

Using the local package path, I can check its digital signature like this:

What the irony! Something legitimate called “Windows 10 Update and Privacy Settings” was installed automatically via Windows Update without prompting me to do so. I could have been prompted like any license agreement with a message saying: “Hey! We are preparing 1703/creator update deployments and need you to pay attention to this update and install it before we can run the upgrade.”

First release of MVP PowerShell module

My friend and fellow CDM MVP Franรงois-Xavier Cat asked early February if anybody wanted to work on the newly announced MVP API.

We started working on this project and @LazyWinAdm released the first version on the PowerShell gallery on this page: https://www.powershellgallery.com/packages/MVP

I’m glad I could help and collaborate with FX on this project ๐Ÿ˜€

If you want to give it a try, you can start to read the awesome detailed blog post FX wrote about the module, the API and how to get started:
https://lazywinadmin.github.io/powershell/2017/05/MVP_Module.html

Of course, if you encounter any issue and want to provide some feedback, suggestions, and/or pull requests, you can using the following github repository:
https://github.com/lazywinadmin/MVP

#PowerShell and its community rock ๐Ÿ˜Ž

April 2017 Stop-Computer issue

If you’ve updated your Windows computer and use the Stop-Computer cmdlet, you may have noticed that it doesn’t work anymore as it used to.

Instead you get an error saying:
Stop-Computer : Privilege not held.

This was quickly reported to this reddit thread and a PowerShell team member, Steve Lee, quickly answered that the culprit is actually a .Net update introduced in April.

As you can see, he also immediately provided a workaround. You have to use the -AsJob switch that will create a WMIJob

I must add that WMI is also impacted by this regression.
Using the brave old Win32Shutdown WMI method has the problem as the Stop-Computer cmdlet.

Microsoft acknowledged the problem and provided another workaround on this page

I must also add that the problem isn’t only present only on Windows 10, any Windows that had this .Net update is currently affected.