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:

#Requires -Version 3.0
#Requires -RunAsAdministrator
Function Install-DefaultKeyboardLayout {
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
[string[]]$ComputerName='localhost',
[Parameter()]
[string]$FirstLanguage='fr-FR',
[Parameter()]
[string]$SecondLanguage='en-GB',
[Parameter()]
[switch]$AsJob
)
Begin {
# Validate entered languages once
"$($FirstLanguage)","$($SecondLanguage)" |
ForEach-Object {
$l = "$($_)"
Write-Verbose -Message "Dealing with language $($l)"
try {
$test = New-WinUserLanguageList -Language "$($_)" -Verbose -ErrorAction Stop
if($null -eq (($test).InputMethodTips)) {
Throw "Input language $($l) isn't recognized as a valid language"
} else {
Write-Verbose -Message "Valid input language detected: $($_)"
}
} catch {
Throw "Testing Input language $($l) went wrong because $($_.Exception.Message)"
}
}
# Intialize a scriptblock
$s = {
Param($Verbose)
if ($null -eq $Verbose) { $Verbose = $false }
$lar = (
(New-WinUserLanguageList -Language $using:FirstLanguage)+
(New-WinUserLanguageList -Language $using:SecondLanguage)
)
$mHT = @{
Message = 'About to set {0} and {1} on computer {2}' -f "$($using:FirstLanguage)",
"$($using:SecondLanguage)",$($using:c)
}
Write-Verbose @mHT -Verbose:$Verbose
# Write-Verbose -Message "Verbose param is to: -$($Verbose)-" -Verbose
try {
$errHT = @{ ErrorAction = 'Stop' }
$aHT = @{
Execute = 'C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe'
Argument = '-Command "Set-WinUserLanguageList -LanguageList {0} -Force"' -f $lar
}
$HT = @{
TaskName = 'Set-Logon-KeyboardLayout'
User = 'S-1-5-18' #'nt authority\system'
Force = [switch]::Present
Action = (New-ScheduledTaskAction @aHT @errHT)
}
Register-ScheduledTask @HT @errHT |
Start-ScheduledTask @errHT
Write-Verbose -Message "Successfully set default keyboard layout on computer $($using:c)" -Verbose:$Verbose
} catch {
Write-Warning -Message "Failed because $($_.Exception.Message)"
}
}
}
Process {
$v = ($PSBoundParameters['Verbose'])
$ComputerName |
ForEach-Object {
$c = $_
Write-Verbose -Message "Dealing with computer $($c)"
try {
if ($v) {
Invoke-Command -ComputerName "$($c)" -ScriptBlock $s -ErrorAction Stop -AsJob:$AsJob -ArgumentList @($v)
} else {
Invoke-Command -ComputerName "$($c)" -ScriptBlock $s -ErrorAction Stop -AsJob:$AsJob
}
} catch {
Write-Warning -Message "Failed to invoke command on computer: $($c) because $($_.Exception.Message)"
}
}
}
End {}
<#
.SYNOPSIS
Set the keyobard layout at logon
.DESCRIPTION
Can set two keyboard layouts at logon (available before a user logs on).
.PARAMETER ComputerName
The list of remote computers to target and modify
.PARAMETER FirstLanguage
The first keyboard layout to set
.PARAMETER SecondLanguage
The second keyboard layout to set
.EXAMPLE
Install-DefaultKeyboardLayout
Set the keyboard layouts on the local computer to the default values: 1rst: fr-FR, 2nd en-GB
.EXAMPLE
Install-DefaultKeyboardLayout -Verbose
Set the keyboard layouts on the local computer to the default values: 1rst: fr-FR, 2nd en-GB
and shows a verbose stream of what happens
.EXAMPLE
Install-DefaultKeyboardLayout -FirstLanguage fr-FR -Verbose
Set the keyboard layouts on the local computer explicitly to french for the first keyboard layout,
uses the default value for the 2nd one (en-GB) and shows a verbose stream of what happens
.EXAMPLE
Install-DefaultKeyboardLayout -SecondLanguage en-GB -Verbose
Set the keyboard layouts on the local computer explicitly to english for the second keyboard layout,
uses the default value for the 1rst one (fr-FR) and shows a verbose stream of what happens
.EXAMPLE
Install-DefaultKeyboardLayout -ComputerName 'localhost' -Verbose
Set the keyboard layouts targeting the explicitely specified target computer
using the the default values: 1rst: fr-FR, 2nd en-GB
and shows a verbose stream of what happens
.EXAMPLE
$HT = @{ FirstLanguage = 'en-GB' ; SecondLanguage = 'fr-FR'}
Install-DefaultKeyboardLayout -ComputerName 'target1','target2' @HT -Verbose
Create a hashtable to define the first and second keyboard layouts to set
Set the keyboard layouts on the 2 remote computers and shows a verbose stream
.EXAMPLE
$HT = @{ FirstLanguage = 'en-GB' ; SecondLanguage = 'fr-FR'}
'target1','target2' |
Install-DefaultKeyboardLayout @HT -Verbose -AsJob
Get-Job | Receive-Job
Create a hashtable to define the first and second keyboard layouts to set
Set the keyboard layouts on the 2 remote computers as a job and shows a verbose stream
#>
}
Export-ModuleMember -Function 'Install-DefaultKeyboardLayout'

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

4 thoughts on “About keyboard layouts available at logon

  1. Hello,
    First of all. thank you for taking the time to post this info.
    Question, after checking MS Docs I can see that “Set-WinSystemLocale” is trying to set System wide the language, any chance you’ve try it ?
    I did but still having some issues 😦

    Thanks,
    Sok.

    • As far as I remember, yes, I tried all the cmdlets in the International module.
      These work great for the current user as I mentioned.
      But none is able to achieve the goal in this blog post: set the keyboard layouts at logon.

  2. I got the script to run successfully – it indeed needs the WinRM remote capabilities (winrm quickconfig = yes). The sad thing is – is does nothing, my logon screen is still using the wrong keyboard layout. :-((

  3. Pingback: Keyboard Layout Windows 10 Login - LoginCrunch

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.