About keyboard layouts available at logon

  • Context

During a Windows 10 migration, as long as you use the following wizard, you can set the logon keyboards’ layouts available before a user logs on.

Let’s say now that you want to do it remotely and that you’ve a huge number of target computers to modify.
You’d typically need automation and a tool like PowerShell to do this peacefully.

  • Problem

For the last 20 years, all you needed to do was to modify the keyboard layouts under the following registry HKEY_USERS\.DEFAULT\Keyboard Layout\Preload.
(40c is French, 409 is English US,…)
To be more accurate, the input language equals the keyboard layout when there’s only a value in the Preload key and nothing under the Substitutes key.
If there’s a Substitute, the value in the Preload key is the input language and the keyboard layout is under the Substitutes key.

You could for most simple cases (having the same pair input language/keyboard layout) just populate the Preload key with the following function provided by Jakub Jareš on this page that uses the *-ItemProperty cmdlets:
https://www.powershellmagazine.com/2014/03/24/set-keyboard-layouts-available-on-logon-screen-in-windows-8-1/

Unfortunately, you cannot just only modify these registry keys on recent Windows 10 versions. The above is no longer valid 😦
If you do, the LogonUI.exe process (being run by the SYSTEM account) uses other registry keys and will actually overwrite what you did.
It appears that HKEY_USERS\S-1-5-18\ and HKEY_USERS\.DEFAULT are the same registry hives.
The LogonUI.exe process uses what’s stored in this registry key ‘HKEY_USERS\S-1-5-18\Control Panel\International\User Profile’.
Do not delete this content as suggested on some websites.

I just want a simple fully supported native way to set the keyboards layouts available at logon.
I don’t want to hijack anything, alter the security or write more that 2 values in the registry.

  • Solution

My solution uses the native cmdlets from the International module to validate what was entered as input but that’s not the only reason.

There’s a huge issue with the International cmdlets. They can only be used in the Current User context.
To work around this caveat, my solution creates a scheduled task that runs as SYSTEM.
In other words, Set-WinUserLanguageList is launched by the SYSTEM account in a scheduled task.

I also wanted to be able to interact remotely with computers over WinRM and find the shortest way to create the scheduled tasks without using the XML schema or binaries.

Here’s my solution:

Let’s see it in action:


# Import the module
Import-Module KeyboardLayoutsAtLogon.psm1 -Verbose

# Have a look at the help
Get-Help Install-DefaultKeyboardLayout


# Yes, it can be invoked w/o parameters
Install-DefaultKeyboardLayout

# Use the verbose stream to see what it does by default
# when you don't specify any parameter
Install-DefaultKeyboardLayout -Verbose

# Let's do the opposite
# set en-GB as 1rst keyboard layout
# fr-FR as the 2nd keyboard layout
# and target 2 remote computers over PSRemoting
$HT = @{ FirstLanguage = 'en-GB' ; SecondLanguage = 'fr-FR'}
Install-DefaultKeyboardLayout -ComputerName 'target1','localhost' @HT -Verbose

# Use the -AsJob parameter
$HT = @{ FirstLanguage = 'en-GB' ; SecondLanguage = 'fr-FR'}
'localhost','target2' |
Install-DefaultKeyboardLayout @HT -Verbose -AsJob
Get-Job | Receive-Job

About Appx packages not working in the Start menu

  • Context

We have upgraded a Windows 10 1803 to 1809 back in June.
The end-user complained that he couldn’t launch calc anymore and his start menu looked like this:

He should have had this instead:

Microsoft just released some fixes in this October 15, 2019—KB4520062 (OS Build 17763.832) where it says:

Prevents blank tiles from appearing in the Start menu when you upgrade to Windows 10, version 1809 from any previous version of Windows 10. However, if you have already upgraded to Windows 10, version 1809, installing this update will not remove existing blank tiles.

I’m not sure if this KB would really help because it doesn’t match what we see in the “broken” Tiles in his Start menu.

  • Problem

Here’s the result of the

Get-AppxProvisionedPackage -Online


You can notice that there’s something wrong with the InstallLocation property.
That %systemdrive% is an environment variable.

I ran a procmon trace to find out what registry keys and values were being read when I run the Get-AppxProvisionedPackage cmdlet.

Then I queried what appears in these keys on his computer:

You can see that the environment variable %systemdrive% isn’t expanded because the value type is wrong.
It’s currently a REG_SZ whereas it should be a REG_EXPAND_SZ to be able to work with the environment variable.

  • Solution

There are 2 ways to fix this. Either replace %systemdrive% by C: or change the type of the value from REG_SZ to REG_EXPAND_SZ.
Here’s a example using the REG_EXPAND_SZ way of fixing this:

#PSSATPARIS2019

Last Saturday, I’ve been to the PowerShell Saturday Paris 2019

It wouldn’t have been possible without:

    • the sponsors
    • the speakers:
    • speakers who delivered lightening session during the lunch break.
    • the PowerBeer at the end
    • the help and work of organizers (here are only 3 of them busy at work)

I enjoyed all the sessions and every minute of this event, this community is just awesome.
I ❤ the PowerShell community.

I’ve also won a Learn dbatools in a Month of Lunches book
written by Chrissy LeMaire and Rob Sewell and published by Manning Publications

Thanks again to anybody involved in this event who made it a great success.

Split strings

  • Context

I worked recently on some Active Directory properties and needed to split a path.

$h = (
Get-ADUser UserName -Properties HomeDirectory
).HomeDirectory
$h = '\\servername\homepath.username$'

The HomeDirectory is a System.String and my variable $h contains something like \\servername\homepath.username$ (there’s a dollar at the end to hide the share).

  • Problem

I’d like to split on the \ character and to do so, you need to escape it with another backslash character because it’s a special character for regular expressions.

The official documentation About Split says that the delimiter is evaluated as a regular expression if I use the highlighted 3rd syntax:
The Options parameter is optional in the syntax and defaults to the value RegexMatch
The doc says:

RegexMatch: Use regular expression matching to evaluate the delimiter.

If I use the split operator like this:

$h -split '\\'

$h -split '\\' | measure

[string]::Empty -eq ($h -split '\\')[0]
[string]::Empty -eq ($h -split '\\')[1]
[string]::Empty -eq ($h -split '\\')[2]
[string]::Empty -eq ($h -split '\\')[3]

I get 4 results. The first 2 results of the split operation are an empty system.string.
This is expected.

I want to capture the server name and the share name, not the backslashes and not the empty strings.

  • Solution(s)

My first solution followed the idea “just ignore and discard empty strings”:

$server=$share=$null
# capture
$null,$null,$server,$share=$h -split '\\'
# display result
$server
$share

My second solution is based on the idea that “there’s probably a better way to skip these empty strings”:

$server=$share=$null
$server,$share=$h.Split('\\',2,'RemoveEmptyEntries')
# display result
$server
$share

If you’ve another great idea and want to share another (better?) way to skin the cat, please post it in the comments 🙂 .

[Quick post] Exchange (Online) team’s changes to the default policy about blocked file types

The Exchange (Online) team is about to change the default policy about blocked file types, meaning that OWA (a.k.a Outlook on the web) users will be impacted by this change.

Their message is quite clear and was published on this page https://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Changes-to-File-Types-Blocked-in-Outlook-on-the-web/ba-p/874451

Almost all the most common extensions related to PowerShell are concerned: .ps1, .ps1xml, .ps2, .ps2xml, .psc1, .psc2, .psd1, .psdm1, .cdxml, .pssc. That said, neither .ps2, ps2xml nor .psc2 are used and recognized by PowerShell or pwsh. We just wonder where they got this list of extensions? 🙄 (from telemetry?)

If you’ve the habit to send these files by email, your recipient may not be able to download and open the sent files if he uses Exchange Online (EXO) (i.e. OWA / Outlook on the web).

If you are an EXO admin, you can override the default policy and set your own policy using the guidance in the above article mentioned

As a user, you can zip the files before sending them. Or, if you’ve a OneDrive (or a similar online storage) you can store these blocked files there.

The kind of change doesn’t add anything to the security of your endpoints. On any Windows, these file types have always been associated by default with notepad and not PowerShell .

If any user gets a PowerShell script (a .ps1) by email, when he downloads and opens it, the .ps1 script doesn’t get executed but it’s instead opened by notepad.exe and its content displayed in notepad.exe.

The attack surface of notepad is extremely tiny. As far as I can remember, there’s only 1 known vulnerability that was discovered by a Google Project Zero security researcher over the last few years. It isn’t notepad.exe that was actually vulnerable but a more subtle sub-component CTF (a.k.a. Windows Text Services Framework) used by notepad.