Skip to content

Instantly share code, notes, and snippets.

@sba923
Last active February 17, 2026 05:54
Show Gist options
  • Select an option

  • Save sba923/a71dd721e78ae08842616db244f19437 to your computer and use it in GitHub Desktop.

Select an option

Save sba923/a71dd721e78ae08842616db244f19437 to your computer and use it in GitHub Desktop.
Remove the unwanted keyboard layouts that Windows keeps adding based on input languages
# this is one of Stéphane BARIZIEN's public domain scripts
# the most recent version can be found at:
# https://gist.github.com/sba923/a71dd721e78ae08842616db244f19437#file-remove-uselesskeyboardLayouts-ps1
[CmdletBinding()]
param([string] $PhysicalKeyboardLayout)
# cSpell: ignore Stéphane BARIZIEN's
$isWinPS = ($null, 'Desktop') -contains $PSVersionTable.PSEdition
if (!$isWinPS)
{
Write-Host("This script requires Windows PowerShell, it doesn't run (yet) under PowerShell Core")
Exit(1)
}
# cSpell: ignore BARIZIEN's hexdigit LCID updateneeded
$languageList = Get-WinUserLanguageList
# remember what the first entry is
$firstLanguage = $languageList[0].languageTag
# guess the physical keyboard layout: it should be the layout with the highest
# number of occurrences for languages other than English
Write-Debug "Guess physical keyboard layout"
$keyboardLayoutUsed = @{}
for ($languageIndex = 0; $languageIndex -lt $languageList.Count; $languageIndex++)
{
if ($languageList[$languageIndex].languageTag -ne 'en-US')
{
for ($keyboardLayoutIndex = 0; $keyboardLayoutIndex -lt $languageList[$languageIndex].InputMethodTips.Count; $keyboardLayoutIndex++)
{
$keyboardLayout = $languageList[$languageIndex].InputMethodTips[$keyboardLayoutIndex] -replace '.*:', ''
if ($null -ne $keyboardLayoutUsed[$keyboardLayout])
{
$keyboardLayoutUsed[$keyboardLayout]++
}
else
{
$keyboardLayoutUsed[$keyboardLayout] = 1
}
}
}
}
# if at least one keyboard layout is configured, assume the actual physical keyboard layout
# is the one configured for the highest number of input methods,
# otherwise use the value of the -PhysicalKeyboardLayout parameter
if ($keyboardLayoutUsed.Keys.Count -ne 0)
{
$PhysicalKeyboardLayout = (@(
foreach ($k in $keyboardLayoutUsed.Keys)
{
[PSCustomObject] @{
keyboardLayout = $k
Count = $keyboardLayoutUsed[$k]
}
}) | Sort-Object -desc count | Select-Object -first 1).keyboardLayout
Write-Verbose ("Assuming physical keyboard layout is {0}" -f $PhysicalKeyboardLayout)
}
elseif ($PSBoundParameters.ContainsKey('PhysicalKeyboardLayout'))
{
Write-Verbose ("Using specified physical keyboard layout {0}" -f $PhysicalKeyboardLayout)
}
else
{
throw "Cannot guess the physical keyboard layout, must specify it with -PhysicalKeyboardLayout as 4-hexdigit value."
}
Write-Debug ("Physical keyboard layout: {0}" -f $PhysicalKeyboardLayout)
# make sure there's an entry for the physical keyboard layout for all the configured languages
$updateneeded = $false
Write-Debug ("Adding physical keyboard layout: {0} to all languages" -f $PhysicalKeyboardLayout)
for ($languageIndex = 0; $languageIndex -lt $languageList.Count; $languageIndex++)
{
$PhysicalKeyboardLayoutConfigured = $false
for ($keyboardLayoutIndex = 0; $keyboardLayoutIndex -lt $languageList[$languageIndex].InputMethodTips.Count; $keyboardLayoutIndex++)
{
if ($languageList[$languageIndex].InputMethodTips[$keyboardLayoutIndex] -match (':' + $PhysicalKeyboardLayout))
{
$PhysicalKeyboardLayoutConfigured = $true
}
}
if (!$PhysicalKeyboardLayoutConfigured)
{
Write-Verbose ("Adding keyboard layout {0} for language '{1}'" -f $PhysicalKeyboardLayout, $languageList[$languageIndex].languageTag)
if ($languageList[$languageIndex].InputMethodTips.Count -ge 1)
{
$languageList[$languageIndex].InputMethodTips.Add((($languageList[$languageIndex].InputMethodTips[0] -replace ':.*', '') + ':' + $PhysicalKeyboardLayout))
}
else
{
$languageList[$languageIndex].InputMethodTips.Add(("{0:x4}" -f [System.Globalization.CultureInfo]::GetCultureInfo($languageList[$languageIndex].languageTag).LCID).ToUpper() + ':' + $PhysicalKeyboardLayout)
}
$updateneeded = $true
}
}
if ($updateneeded)
{
# make sure the first entry in the list hasn't changed
# as it seems Set-WinUserLanguageList sets the Windows UI display language to the first entry in the list
if ($languageList[0].languageTag -ne $firstLanguage)
{
$newList = @()
$languageList | Where-Object { $_.languageTag -eq $firstLanguage } | ForEach-Object { $newList += $_ }
$languageList | Where-Object { $_.languageTag -ne $firstLanguage } | ForEach-Object { $newList += $_ }
$languageList = $newList
}
Set-WinUserLanguageList -languageList $languageList -Force
}
# first add the "language = XXX, keyboard layout = default layout for language XXX" if it's not on the list, to make sure removal does remove it 😜
$updateneeded = $false
Write-Debug("Add default layout for each language")
$languageList.languageTag | Foreach-Object {
$languageTag = $_
$languageCode = ($languageList | Where-Object { $_.languageTag -eq $languageTag }).InputMethodTips[0].SubString(0, 4)
$defaultInputMethod = $languageCode + ':0000' + $languageCode
$defaultKeyboardLayout = $defaultInputMethod -replace '^....', ''
for ($languageIndex = 0; $languageIndex -lt $languageList.Count; $languageIndex++)
{
if ($languageList[$languageIndex].languageTag -eq $languageTag)
{
$defaultLayoutPresent = $false
for ($keyboardLayoutIndex = 0; $keyboardLayoutIndex -lt $languageList[$languageIndex].InputMethodTips.Count; $keyboardLayoutIndex++)
{
if ($languageList[$languageIndex].InputMethodTips[$keyboardLayoutIndex] -match $defaultKeyboardLayout)
{
$defaultLayoutPresent = $true
}
}
if (!$defaultLayoutPresent)
{
Write-Verbose ("Adding input method {0} to ensure proper removal of keyboard layout {1} for language '{2}'" -f $defaultInputMethod, ($defaultInputMethod -replace '.*:', ''), $languageList[$languageIndex].languageTag)
$languageList[$languageIndex].InputMethodTips.Add($defaultInputMethod)
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'updateneeded', Justification = 'variable is used in various scopes throughout the script')]
$updateneeded = $true
}
}
}
}
if ($updateneeded)
{
# make sure the first entry in the list hasn't changed
# as it seems Set-WinUserLanguageList sets the Windows UI display language to the first entry in the list
if ($languageList[0].languageTag -ne $firstLanguage)
{
$newList = @()
$languageList | Where-Object { $_.languageTag -eq $firstLanguage } | ForEach-Object { $newList += $_ }
$languageList | Where-Object { $_.languageTag -ne $firstLanguage } | ForEach-Object { $newList += $_ }
$languageList = $newList
}
Set-WinUserLanguageList -languageList $languageList -Force
}
# now remove the unwanted keyboard layouts
$updateneeded = $false
Write-Debug("Remove unwanted keyboard layouts")
for ($languageIndex = 0; $languageIndex -lt $languageList.Count; $languageIndex++)
{
for ($keyboardLayoutIndex = 0; $keyboardLayoutIndex -lt $languageList[$languageIndex].InputMethodTips.Count; $keyboardLayoutIndex++)
{
if ($languageList[$languageIndex].InputMethodTips[$keyboardLayoutIndex] -notmatch (':' + $PhysicalKeyboardLayout))
{
Write-Verbose ("Removing keyboard layout {0} for language '{1}'" -f ($languageList[$languageIndex].InputMethodTips[$keyboardLayoutIndex] -replace '.*:', ''), $languageList[$languageIndex].languageTag)
$languageList[$languageIndex].InputMethodTips.RemoveAt($keyboardLayoutIndex)
$updateneeded = $true
}
}
}
if ($updateneeded)
{
# make sure the first entry in the list hasn't changed
# as it seems Set-WinUserLanguageList sets the Windows UI display language to the first entry in the list
if ($languageList[0].languageTag -ne $firstLanguage)
{
$newList = @()
$languageList | Where-Object { $_.languageTag -eq $firstLanguage } | ForEach-Object { $newList += $_ }
$languageList | Where-Object { $_.languageTag -ne $firstLanguage } | ForEach-Object { $newList += $_ }
$languageList = $newList
}
Set-WinUserLanguageList -languageList $languageList -Force
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment