Erratum on news and interests on the Windows taskbar

I’ve published a wrong information about the registry key in the following blog post. The path is incorrect and missing a ‘Windows’. Sorry, my bad 😦

Here’s how to remove the wrong path

# pick the appropriate GPO
$gpo = Get-GPO -All | Out-GridView -OutputMode Single

if ($gpo) { 
Remove-GPRegistryValue -Guid $gpo.Id -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Feeds' -ValueName 'EnableFeeds' 
}

Here’s how to add the correct path 🙂

# pick the appropriate GPO
$gpo = Get-GPO -All | Out-GridView -OutputMode Single

if ($gpo) { 
Set-GPRegistryValue -Guid $gpo.Id -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Windows Feeds' -ValueName 'EnableFeeds' -Type DWord -Value 0
}

Find the Parent path of an AD object

I recently needed to find the parent location of a computer, it’s parent distinguishedName. I wanted a function that would:
– work with a broader scope of AD objects, like users, OUs,…
– support piping objects from regular ActiveDirectory module or from itself
Here’s what I wanted to achieve visually

# Pipe an ADUser object
Get-ADUser 'Skywalker' | Get-ADObjectParentPath

# Pipe a DN string (you'd get the 'Users' OU)
'CN=Luc Skywalker,OU=Users,OU=Prod,DC=Star,DC=Wars,DC=com' | Get-ADObjectParentPath

# Do it twice, (you'd get the 'Prod' OU)
'CN=Luc Skywalker,OU=Users,OU=Prod,DC=Star,DC=Wars,DC=com' |
Get-ADObjectParentPath | Get-ADObjectParentPath


Here’s what I quickly wrote to meet my needs.

Function Get-ADObjectParentPath {
<#
.SYNOPSIS
Get the parent DN location of an AD object.
.DESCRIPTION
Get the parent DN location of an AD object.
.PARAMETER DistinguishedName
DistinghedNames objects passed as input (from AD cmdlets).
.PARAMETER DN
DistinghedNames strings passed as input
.EXAMPLE
Get-ADUser Skywalker | Get-ADObjectParentPath
.EXAMPLE
'CN=Luc Skywalker,OU=Users,OU=Prod,DC=Star,DC=Wars,DC=com' | Get-ADObjectParentPath
#>
[CmdletBinding(DefaultParameterSetName = 'String')]
Param(
[Parameter(ParameterSetName = 'Obj', Mandatory,ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
$DistinguishedName,
[Parameter(ParameterSetName = 'String', Mandatory,ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[string[]]$DN
)
Begin {
if (-not(Test-Path Path 'AD:\' PathType Container)) {
Write-Warning Message 'Failed to find the AD drive, aborting'
break
}
}
Process {
if ($DistinguishedName) {
$DistinguishedName |
ForEach-Object Process {
$parent = ((Get-Item "AD:\$($_)" ErrorAction SilentlyContinue).PSParentPath | Split-Path NoQualifier ) -replace '//RootDSE/',''
if ($parent) {
$parent
}
}
}
if ($DN ) {
$DN |
ForEach-Object Process {
if (Test-Path Path "AD:\$($_)" ErrorAction SilentlyContinue) {
$parent = ((Get-Item "AD:\$($_)" ErrorAction SilentlyContinue).PSParentPath | Split-Path NoQualifier ) -replace '//RootDSE/',''
if ($parent) {
$parent
}
} else {
Write-Warning Message "Path AD:\$($_) not found"
}
}
}
}
End {}
}

Quick post: Remove a permission on a GPO

There’s no cmdlet named Remove-GPPermission. There’s only Get- GPPermission and Set- GPPermission. How do I remove a GPO permission?

# Choose a single GPO
$gpo = Get-GPO -All | Out-GridView -OutputMode Single
# Choose a single target
$target = Get-GPPermission -Guid $gpo.Id -All | Out-GridView -OutputMode Single
# Remove target from that GPO
Set-GPPermission -Guid $gpo.Id -PermissionLevel None -Replace -TargetName "$($target.Trustee.Name)" -TargetType "$($target.Trustee.SidType)"

The answer is to replace the existing permission with a level of “None”. This is equivalent of a removal.

About news and interests on the Windows taskbar

Context

Microsoft is about to add a new feature “News and interests on the taskbar” to Windows 10.

“Devices running Windows 10, version 1909 (and later) who have installed the May 2021 Windows monthly update (or later) will be included in this phased rollout.”

While it may be welcome in the consumer space, it could be the opposite in a corporate environment.

Problem

They did not publish up-to-date ADMX templates and don’t tell you in their blog post what’s required in the registry to achieve the same as in the Endpoint Manager agent (a.k.a Intune), especially if you just rely on good old robust Group Policies for your fully managed devices.

Solution

Fortunately Michael Niehaus published the following two posts on this subject and addresses the above shortcoming.

  1. https://oofhours.com/2021/02/07/turn-off-the-insider-news-interests-page/
  2. https://oofhours.com/2021/04/22/turn-off-news-interests-page-via-policy/

That said, you don’t really need to grab the ADMX files from an Insider build or wait for Microsoft to publish the required ADMX templates in any relevant Windows build.

Knowing the registry key and values is what matters.

If you’ve the GroupPolicy module and an existing GPO, you can already populate the registry with these 2 lines of code


# pick the appropriate GPO
$gpo = Get-GPO -All | Out-GridView -OutputMode Single

if ($gpo) { 

Set-GPRegistryValue -Guid $gpo.Id -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Feeds' -ValueName 'EnableFeeds' -Type DWord -Value 0

}

Ready for the next brilliant move 😎

Last but not least, if you want to grab the policy templates, Glenn Turner indicated that you can get Feeds.admx and Feeds.adml files by installing the LCU preview KB5001396.

Quick post: Remove an alternate data stream (ADS)

  • Context

I’ve been downloading updates (.msu) files from the Windows Update catalog
As an example, for KB5001330 I go to https://www.catalog.update.microsoft.com/Search.aspx?q=5001330 and click Download

dir ~/downloads/other/*.msu |
gi -Stream * |
Select Stream,Length

  • Problem

I can remove the Zone.Identifier alternate data stream (ADS) using the built-in Unblock-File cmdlet.

dir ~/downloads/other/*.msu |
Unblock-File -Verbose

dir ~/downloads/other/*.msu |
gi -Stream *|
Select Stream,Length

But, I doesn’t remove the SmartScreen ADS.

  • Solution

Here’s how to remove the SmartScreen alternate data stream (ADS):

dir ~/downloads/other/*.msu |
gi -Stream 'SmartScreen' -EA 0|
Foreach-Object {
Remove-Item -Path $_.FileName -Stream 'SmartScreen'
}

Windows Defender Attack Surface Reduction (ASR) Rules module

I’m pleased to share with you a new #PowerShell module about Windows Defender Attack Surface Reduction (ASR) Rules 🚀

After seeing what Palantir did about their ASR telemetry and the content of the following repository, I thought that we need a more “PowerShell-friendly” way to view and set Windows Defender Attack Surface Reduction (ASR) rules.

It seems that I’m not the first person to have this idea and you can find an another implementation of this idea on the PowerShell gallery here.

If you don’t know anything about Windows Defender Attack Surface Reduction, I’d recommend that you watch this 6 minutes long video Susan Bradley made

My approach is slightly different than the 2 other PowerShell code implementations/repositories I mentioned above. I don’t provide any graphical interface (GUI), although you can for sure use the built-in Out-GridView cmdlet to send the output and inspect it in a GUI. I propose to have 3 functions that you can use to bind properly using the pipeline and made an effort to have all the parameters data being discoverable using the TAB key.

Let’s see some practical examples in a video:

I’d like to point out another useful resource to test the defense measures and configuration.

What else?
If you encounter an issue with this module, you’re welcome to open an issue in the github repo with this link.

Last but not least. Let’s say you’ve configured some ASR rules using GPO but not all of them. The Get-ASRRuleConfig function is able to display the effective rules that apply (GPO or local and if GPO, GPO wins over local). But the Set-ASRRuleConfig is only able to set the local rules. It cannot touch GPO rules. If you use it to set a rule that is already managed by GPO, it will work on the local value only. If you then use back Get-ASRRuleConfig, you get the results of what’s effective. Remember GPO wins.

How do I get started?

Find-Module -Name ASRRules -Repository PSGallery
Save-Module -Name ASRRules -Repository PSGallery -Path ~/Downloads

Import-Module ~/Downloads/ASRRules/1.0.0/ASRRules.psd1 -Force -Verbose

# if in PS 7.x and there's a complaint about the required module,
Import-Module -Name ConfigDefender -Force
Import-Module ~/Downloads/ASRRules/1.0.0/ASRRules.psd1 -Force -Verbose

Enjoy 😎

Quick post: Delete a WSUS update

  • Context

I started working on the KB4577586, the update responsible for the removal of Adobe Flash Player.

I’ve encountered two issues.

  • Issue

First issue, I imported the update for Windows 10 2OH2 x64 using the Id of Server 2019, D’oh!

Second issue, when I first imported my files and their Id, it failed with this message:
Exception calling “ImportUpdateFromCatalogSite” with “2” argument(s): “The underlying connection was closed: An unexpected error occurred on a send.”

Solution

Let’s start with the 2nd issue: the fact that I cannot import an update.
It appears that although I’ve set the correct Protocol to use

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

The ImportUpdateFromCatalogSite method is a .Net method and .NET requires to StrongCrypto value to be set in the registry.

My 2nd issue was solved by

$HT = @{
 Name = 'SchUseStrongCrypto'
 Value = '1'
 Type = 'DWord'
}
$null,'Wow6432Node' | 
Foreach-Object {
 Set-ItemProperty @HT -Path "HKLM:\SOFTWARE$($_)\Microsoft\.NetFramework\v4.0.30319" 
}
Restart-Computer

Let’s get back to my first issue. I got the correct file but not the Id.

# Identify the offending update (no Out-GridView available)
(Get-WsusServer).SearchUpdates('4577586') | Select Title
# Find its Id
(Get-WsusServer).SearchUpdates('4577586') | Select-Object -First 3 | 
Select -Last 1 -ExpandProperty Id
# Detete (no output if ok)
(Get-WsusServer).DeleteUpdate('20bd2d6b-26a9-4ddd-8a3f-04a79b683c1f')

I could reimport the same file using the correct Id, happy days 😎

MSRC API and MsrcSecurityUpdates module improvements

Great news, the PowerShell MsrcSecurityUpdates has been updated to version 1.9.0 (available on the PSGallery) and doesn’t have any API key requirement anymore 😎

Here’s the latest blog post from the MSRC about this API key requirement removal and more

While the API key removal is a very nice move, it’s also somehow the tip of the iceberg 🙂

I’d like to highlight the recent changes made in the code that appear in its GitHub repository.

Change desscription: Related commit:
Adding the info about who issues the CVE (CNA info) 5c8dd29a86fce3146986acdff4f234ca6bfed531
Adding known issues column and info b5876b0aaa22c9f40ea904320ee533a2aed612bd
Forcing any https call to use TLS 1.2 47557e9a2f34b7938d346a932ef37cb7c78895a1
Fixing some CSS issues about a timeout and changing some colors 555acbe8a437ab76dbc91fdb26254aa616091467
Adding more info in error messages (it was discussed here in issue 73) 598755821c8d4473ef54a13c41bd4cc523ebe19a
Restoring Summary (makes sense in Advisories for example) and sort revisions 2114e895900f2d9de0f1f6ea59279cb615e69a0d
Adding a tag in the html commented lines about the version of the MsrcSecurityUpdates module used to generate the html pages 87ba10278b0cbf03921c21e25b881c4de5128acf
Adding a switch to avoid displaying a progress bar 34b528dd954f907c6c7d29217270df72bd041b4d
Yes, we try to fix PSScriptAnalyzer issues reported fdb79f03733dccd4112111516bc92ec0910af26a


If you encounter issues about the module, they can be reported here

Because of the CSS timeout issue, I’ve downloaded all the bulletin data I store in this separate repository.

How to notify logged-on users

You may have already seen or know how to display a nice pop-up toast notification to your Windows 10 users.
If not, I’d like to point out some material related to these notifications:

Now, that you’re familiar with the notifications part, let’s see how to display them.
Being a big fan of what’s robust and sustainable, I’ll show you how to leverage the built-in Windows 10 scheduled tasks to notify your users 😀

Let’s say you have a PowerShell script that triggers a notification in the end-user context. It’s named toast.ps1.
Let’s say you’ve a Group Policy (GPO) that runs a scheduled task under the NT AUTHORITY\System account.

How can you escape the SYSTEM account and run the Toast.ps1 in every interactively logged on standard account?

Well, the solution is to identify these interactively logged on users and run a new scheduled task that targets their identity.

# Identify interactive users
try {
 $Users = @(Get-WmiObject -Query 'Select *  from Win32_Process' -ErrorAction Stop) |
 Where-Object { $_.ProcessName -eq 'explorer.exe'} |
 ForEach-Object {
  [PSCustomObject]@{
   User = '{0}\{1}' -f $($_.GetOwner().Domain),$($_.GetOwner().User)
  }
 }
} catch {
 Write-Warning -Message "Failed to get logged on users because $($_.Exception.Message)"
}
# If any and hour is 10AM for example, notify
if ($Users -and ((Get-Date).ToString('HH',[Globalization.CultureInfo]::InvariantCulture) -in '10')) {
 $Users | ForEach-Object {
 Start-UserNotification -UserName $_ -Prefix 'WhatEver' -FilePath 'C:\Toast.ps1'
 }
}

Here’s the code sample of the Start-UserNotification function used above:

#Requires -RunAsAdministrator
Function Start-UserNotification {
<#
.SYNOPSIS
Start a script in the user context
.DESCRIPTION
Start a script in the user context using scheduled tasks
.PARAMETER UserName
String that represents the targeted user name
.PARAMETER FilePath
String that represents the path to the script to be executed
.PARAMETER Prefix
String that is being used to prefix the task name
.EXAMPLE
Start-UserNotification -UserName LocalUserName -FilePath C:\notif.ps1
#>
[CmdletBinding(SupportsShouldProcess)]
[OutputType('System.Boolean')]
Param(
[Parameter(Mandatory)]
[string]$UserName,
[Parameter(Mandatory)]
[ValidateScript({Test-Path -Path $_ -PathType Leaf})]
[string]$FilePath,
[Parameter()]
[string]$Prefix='__'
)
Begin {
$errHT = @{ ErrorAction = 'Stop' }
$cmd = '& {0}' -f [Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($FilePath)
$aHT = @{
Execute = 'C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe'
Argument = '-NoProfile -WindowStyle Hidden -Exec Bypass -Command "{0}"' -f "$($cmd)"
}
}
Process {
}
End {
# Notify user using a Scheduled task
try {
$taskName = '{0}-Notify' -f [regex]::Escape($Prefix)
Get-ScheduledTask -TaskPath '\' @errHT | Where-Object { $_.TaskName -match "^$($taskName)_" } | ForEach-Object {
if ($pscmdlet.ShouldProcess("$($_.TaskName)", 'Remove previous task')) {
$null = $_ | Stop-ScheduledTask -Verbose @errHT
$null = $_ | Unregister-ScheduledTask -Verbose -Confirm:$false @errHT
Write-Verbose -Message "Successfully remove task $($_.TaskName)"
}
}
} catch {
Write-Warning -Message "Failed to stop and unregister because $($_.Exception.Message)"
}
try {
$HT = @{
TaskName = '{0}_{1}' -f "$($taskName)","$($UserName)"
User = '{0}' -f "$($UserName)"
Force = [switch]::Present
Action = (New-ScheduledTaskAction @aHT @errHT)
Settings = (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Compatibility 'Win8' @errHT)
}
if ($pscmdlet.ShouldProcess("$($HT['TaskName'])", 'Create and run task')) {
$null = Register-ScheduledTask @HT @errHT | Start-ScheduledTask @errHT
Write-Verbose -Message "Successfully created and ran task $($HT['TaskName'])"
$true
}
} catch {
Write-Warning -Message "Failed to register and run notification task because $($_.Exception.Message)"
$false
}
}
}

Here’s how to use the function:

End of Life of Flash?

  • Context

Since Adobe will no longer be supporting Flash Player after December 31, 2020 and Adobe will block Flash content from running in Flash Player beginning January 12, 2021, Adobe strongly recommends all users immediately uninstall Flash Player to help protect their systems.

Source: https://www.adobe.com/products/flashplayer/end-of-life.html

This statement is worrying because it suggests that Adobe has some sort of kill switch.

It appears that they have hardcoded the EoL (End-Of-Life) date in the code. If you change the date of your computer before January 12, 2021, you’ll notice a different behavior and won’t get the above blue EoL logo.

Instead you can get for example, just a reminder (IE11 in my case):

  • Issue

You may still have a critical Line of Business (LoB) application that requires Flash to run in a browser.

What can you do after the EoL date?

If you don’t rely on Flash at all, instead of waiting for it to be removed by Microsoft, you can for example implement what I described in this post to avoid Flash from being instantiated at all in Windows and Office. (This post doesn’t include what you’d need to do for Google Chrome or Edge Chromium).

  • Solution

Let’s you have to run Flash for a critical LoB application after January 12, 2021. What can be done?

Well, it depends on the browser.

Let’s say you want it to run in Google Chrome or Edge Chromium.

Both Chromium based browsers (Edge and Google) implement the Chromium based Flash component named PepperFlash.

First, you’ll need to get rid of what’s installed under:

$($env:LOCALAPPDATA)\Google\Chrome\User Data\PepperFlash
$($env:LOCALAPPDATA)\Microsoft\Edge\User Data\PepperFlash

Yes, it’s a per-user non-roaming location 😦
Once, you did the above step, you don’t have any Flash at all in these Chromium based browsers.

To be able to restore Flash, you need to get an older vulnerable version of the PPAPI provided by Adobe and install it.
Once installed, you’ll have for example a pepflashplayer64_32_0_0_371.dll file in C:\Windows\system32\Macromed\Flash.
Don’t ask me what I think about having 2 vulnerable dll files planted in System32 🙄 .

NB: The following registry keys store the version and location of the Adobe PPAPI component: HKLM\SOFTWARE\Macromedia\FlashPlayerPepper and HKLM\SOFTWARE\WOW6432Node\Macromedia\FlashPlayerPepper

To make Google Chrome or Edge Chromium load it, you’ll have to create a folder in these per-user locations

mkdir "$($env:LOCALAPPDATA)\Google\Chrome\User Data\Default\Pepper Data\Shockwave Flash\System"
mkdir "$($env:LOCALAPPDATA)\Microsoft\Edge\User Data\Default\Pepper Data\Shockwave Flash\System"

You’ll also need to create files there, named mms.cfg, that contain some instructions:

'System32','SysWOW64' |
Foreach-Object {
 $p = $_
 try {
  @'
AutoUpdateDisable=1
SilentAutoUpdateEnable=0
EOLUninstallDisable=1
EnableAllowList=1
AllowListUrlPattern=http://my-LOB-server
'@ | 
  Out-File -FilePath "C:\Windows\$($p)\Macromed\Flash\mms.cfg" -Encoding UTF8 -Force -ErrorAction Stop
 } catch {
  Write-Warning -Message "Failed to write C:\Windows\$($p)\Macromed\Flash\mms.cfg because $($_.Exception.Message)"
 }
}

NB 1: Yes, I’ve created the mms.cfg in C:\Windows\System32\Macromed\Flash.
It just needs to be copied to the “LOCALAPPDATA..\Shockwave Flash\System” folders created previously.

NB 2:
The Adobe® Flash® Player 32.0 Administration Guide (uploaded here as well, if it disappears any time soon) says the following:

EnableAllowList. Disabled by default.
Allows system administrators to allow Flash Player to only load content from a set of allowed URLs.
After Flash Player EOL, EnableAllowList will default to true and the MMS.CFG setting will be ignored.

Since you rolled out an older version, you need this setting to be enabled 🙂 .

You may also want to have a tighter control on Flash in these Chromium based browsers using Group Policy that block or allow an list of URL if you don’t trust that much text files (mms.cfg, especially in the user profile as it can be modified).
Here are 2 examples:

Let’s say you want Flash to run in Internet Explorer 11.
You’ve already created the 2 mms.cfg files in C:\Windows\System32\Macromed\Flash and C:\Windows\SysWOW64\Macromed\Flash.

There are two security updates that would need to be removed:
4580325, Security Update for Adobe Flash Player: October 13, 2020
4561600, Security Update for Adobe Flash Player: June 9, 2020
😥
Yes, that’s worse! Now, all binaries under C:\Windows\System32\Macromed\Flash are vulnerable and can potentially be loaded in a browser 😥