WindowsKeyboardManagement.psm1
function Get-WindowsKeyboardMap { <# .SYNOPSIS Shows all effective Windows keyboard mappings, or returns a list of available key mappings. .DESCRIPTION Windows allows you to redefine keys on your keyboard. This is done by changing the virtual key code associated with a keyboard key. Features: (1) map one key to another (e.g. caps lock -> shift) (2) map multiple keys to one key (e.g. caps lock OR F12 -> F1) (3) swap 2 keys (e.g. a -> b && b -> a) Limitations: (1) you cannot map a keystroke sequence (e.g. alt-tab) to a single key (2) some proprietary keyboard buttons may not work (e.g. Logitech) (3) you cannot map mouse buttons to the keyboard Administrative privileges are required to modify keyboard mappings. Changes affect all users. .PARAMETER Online Read configuration from the Windows registry path 'HKEY_LOCAL_MACHINE\\System\CurrentControlSet\Control\Keyboard Layout'. The property 'Scancode Map' will be parsed. .PARAMETER Available Lists the keys that are supported. No escalated privileges are required to execute with this flag. Windows will not be modified. .PARAMETER SystemHiveMountPath Registry mount point to "HKEY_LOCAL_MACHINE\System". You should mount the registry hive file "%SystemRoot%\System32\Config\System" to your online registry. #> [CmdletBinding(DefaultParameterSetName = 'OnlineSet')] Param( [Parameter(Mandatory, ParameterSetName = 'OnlineSet')] [switch]$Online, [Parameter(Mandatory, ParameterSetName = 'OfflineServiceSet')] [string]$SystemHiveMountPath, [Parameter(Mandatory, ParameterSetName = 'GetAvailableSet')] [switch]$Available ) # # Inspired by <randy/sharpkeys> # - https://github.com/randyrants/sharpkeys # $keyDataJson = @' [{"code":"00_01","desc":"Escape","category":"Special"},{"code":"00_02","desc":"1 !","category":"Key"},{"code":"00_03","desc":"2 @","category":"Key"},{"code":"00_04","desc":"3 #","category":"Key"},{"code":"00_05","desc":"4 $","category":"Key"},{"code":"00_06","desc":"5 %","category":"Key"},{"code":"00_07","desc":"6 ^","category":"Key"},{"code":"00_08","desc":"7 \u0026","category":"Key"},{"code":"00_09","desc":"8 *","category":"Key"},{"code":"00_0A","desc":"9 (","category":"Key"},{"code":"00_0B","desc":"0 )","category":"Key"},{"code":"00_0C","desc":"- _","category":"Key"},{"code":"00_0D","desc":"= +","category":"Key"},{"code":"00_0E","desc":"Backspace","category":"Special"},{"code":"00_0F","desc":"Tab","category":"Special"},{"code":"00_10","desc":"Q","category":"Key"},{"code":"00_11","desc":"W","category":"Key"},{"code":"00_12","desc":"E","category":"Key"},{"code":"00_13","desc":"R","category":"Key"},{"code":"00_14","desc":"T","category":"Key"},{"code":"00_15","desc":"Y","category":"Key"},{"code":"00_16","desc":"U","category":"Key"},{"code":"00_17","desc":"I","category":"Key"},{"code":"00_18","desc":"O","category":"Key"},{"code":"00_19","desc":"P","category":"Key"},{"code":"00_1A","desc":"[ {","category":"Key"},{"code":"00_1B","desc":"] }","category":"Key"},{"code":"00_1C","desc":"Enter","category":"Special"},{"code":"00_1D","desc":"Left Ctrl","category":"Special"},{"code":"00_1E","desc":"A","category":"Key"},{"code":"00_1F","desc":"S","category":"Key"},{"code":"00_20","desc":"D","category":"Key"},{"code":"00_21","desc":"F","category":"Key"},{"code":"00_22","desc":"G","category":"Key"},{"code":"00_23","desc":"H","category":"Key"},{"code":"00_24","desc":"J","category":"Key"},{"code":"00_25","desc":"K","category":"Key"},{"code":"00_26","desc":"L","category":"Key"},{"code":"00_27","desc":"; :","category":"Key"},{"code":"00_28","desc":"\u0027 \\","category":"Key"},{"code":"00_29","desc":"` ~","category":"Key"},{"code":"00_2A","desc":"Left Shift","category":"Special"},{"code":"00_2B","desc":"\\\\ |","category":"Key"},{"code":"00_2C","desc":"Z","category":"Key"},{"code":"00_2D","desc":"X","category":"Key"},{"code":"00_2E","desc":"C","category":"Key"},{"code":"00_2F","desc":"V","category":"Key"},{"code":"00_30","desc":"B","category":"Key"},{"code":"00_31","desc":"N","category":"Key"},{"code":"00_32","desc":"M","category":"Key"},{"code":"00_33","desc":", \u003c","category":"Key"},{"code":"00_34","desc":". \u003e","category":"Key"},{"code":"00_35","desc":"/ ?","category":"Key"},{"code":"00_36","desc":"Right Shift","category":"Special"},{"code":"00_37","desc":"*","category":"Num"},{"code":"00_38","desc":"Left Alt","category":"Special"},{"code":"00_39","desc":"Space","category":"Special"},{"code":"00_3A","desc":"Caps Lock","category":"Special"},{"code":"00_3B","desc":"F1","category":"Function"},{"code":"00_3C","desc":"F2","category":"Function"},{"code":"00_3D","desc":"F3","category":"Function"},{"code":"00_3E","desc":"F4","category":"Function"},{"code":"00_3F","desc":"F5","category":"Function"},{"code":"00_40","desc":"F6","category":"Function"},{"code":"00_41","desc":"F7","category":"Function"},{"code":"00_42","desc":"F8","category":"Function"},{"code":"00_43","desc":"F9","category":"Function"},{"code":"00_44","desc":"F10","category":"Function"},{"code":"00_45","desc":"Num Lock","category":"Special"},{"code":"00_46","desc":"Scroll Lock","category":"Special"},{"code":"00_47","desc":"7","category":"Num"},{"code":"00_48","desc":"8","category":"Num"},{"code":"00_49","desc":"9","category":"Num"},{"code":"00_4A","desc":"-","category":"Num"},{"code":"00_4B","desc":"4","category":"Num"},{"code":"00_4C","desc":"5","category":"Num"},{"code":"00_4D","desc":"6","category":"Num"},{"code":"00_4E","desc":"+","category":"Num"},{"code":"00_4F","desc":"1","category":"Num"},{"code":"00_50","desc":"2","category":"Num"},{"code":"00_51","desc":"3","category":"Num"},{"code":"00_52","desc":"0","category":"Num"},{"code":"00_53","desc":".","category":"Num"},{"code":"00_54","desc":"0x0054","category":"Unknown"},{"code":"00_55","desc":"0x0055","category":"Unknown"},{"code":"00_56","desc":"ISO extra key","category":"Special"},{"code":"00_57","desc":"F11","category":"Function"},{"code":"00_58","desc":"F12","category":"Function"},{"code":"00_59","desc":"0x0059","category":"Unknown"},{"code":"00_5A","desc":"0x005A","category":"Unknown"},{"code":"00_5B","desc":"0x005B","category":"Unknown"},{"code":"00_5C","desc":"0x005C","category":"Unknown"},{"code":"00_5D","desc":"0x005D","category":"Unknown"},{"code":"00_5E","desc":"0x005E","category":"Unknown"},{"code":"00_5F","desc":"0x005F","category":"Unknown"},{"code":"00_60","desc":"0x0060","category":"Unknown"},{"code":"00_61","desc":"0x0061","category":"Unknown"},{"code":"00_62","desc":"0x0062","category":"Unknown"},{"code":"00_63","desc":"0x0063","category":"Unknown"},{"code":"00_64","desc":"F13","category":"Function"},{"code":"00_65","desc":"F14","category":"Function"},{"code":"00_66","desc":"F15","category":"Function"},{"code":"00_67","desc":"F16","category":"Function"},{"code":"00_68","desc":"F17","category":"Function"},{"code":"00_69","desc":"F18","category":"Function"},{"code":"00_6A","desc":"F19","category":"Function"},{"code":"00_6B","desc":"F20","category":"Function"},{"code":"00_6C","desc":"F21","category":"Function"},{"code":"00_6D","desc":"F22","category":"Function"},{"code":"00_6E","desc":"F23","category":"Function"},{"code":"00_6F","desc":"F24","category":"Function"},{"code":"00_70","desc":"0x0070","category":"Unknown"},{"code":"00_71","desc":"0x0071","category":"Unknown"},{"code":"00_72","desc":"0x0072","category":"Unknown"},{"code":"00_73","desc":"0x0073","category":"Unknown"},{"code":"00_74","desc":"0x0074","category":"Unknown"},{"code":"00_75","desc":"0x0075","category":"Unknown"},{"code":"00_76","desc":"0x0076","category":"Unknown"},{"code":"00_77","desc":"0x0077","category":"Unknown"},{"code":"00_78","desc":"0x0078","category":"Unknown"},{"code":"00_79","desc":"0x0079","category":"Unknown"},{"code":"00_7A","desc":"0x007A","category":"Unknown"},{"code":"00_7B","desc":"0x007B","category":"Unknown"},{"code":"00_7C","desc":"0x007C","category":"Unknown"},{"code":"00_7D","desc":"¥ -","category":"Special"},{"code":"00_7E","desc":"0x007E","category":"Unknown"},{"code":"00_7F","desc":"0x007F","category":"Unknown"},{"code":"E0_01","desc":"0xE001","category":"Unknown"},{"code":"E0_02","desc":"0xE002","category":"Unknown"},{"code":"E0_03","desc":"0xE003","category":"Unknown"},{"code":"E0_04","desc":"0xE004","category":"Unknown"},{"code":"E0_05","desc":"0xE005","category":"Unknown"},{"code":"E0_06","desc":"0xE006","category":"Unknown"},{"code":"E0_07","desc":"Redo","category":"F-Lock"},{"code":"E0_08","desc":"Undo","category":"F-Lock"},{"code":"E0_09","desc":"0xE009","category":"Unknown"},{"code":"E0_0A","desc":"0xE00A","category":"Unknown"},{"code":"E0_0B","desc":"0xE00B","category":"Unknown"},{"code":"E0_0C","desc":"0xE00C","category":"Unknown"},{"code":"E0_0D","desc":"0xE00D","category":"Unknown"},{"code":"E0_0E","desc":"0xE00E","category":"Unknown"},{"code":"E0_0F","desc":"0xE00F","category":"Unknown"},{"code":"E0_10","desc":"Prev Track","category":"Media"},{"code":"E0_11","desc":"Messenger","category":"App"},{"code":"E0_12","desc":"Webcam","category":"Logitech"},{"code":"E0_13","desc":"iTouch","category":"Logitech"},{"code":"E0_14","desc":"Shopping","category":"Logitech"},{"code":"E0_15","desc":"0xE015","category":"Unknown"},{"code":"E0_16","desc":"0xE016","category":"Unknown"},{"code":"E0_17","desc":"0xE017","category":"Unknown"},{"code":"E0_18","desc":"0xE018","category":"Unknown"},{"code":"E0_19","desc":"Next Track","category":"Media"},{"code":"E0_1A","desc":"0xE01A","category":"Unknown"},{"code":"E0_1B","desc":"0xE01B","category":"Unknown"},{"code":"E0_1C","desc":"Enter","category":"Num"},{"code":"E0_1D","desc":"Right Ctrl","category":"Special"},{"code":"E0_1E","desc":"0xE01E","category":"Unknown"},{"code":"E0_1F","desc":"0xE01F","category":"Unknown"},{"code":"E0_20","desc":"Mute","category":"Media"},{"code":"E0_21","desc":"Calculator","category":"App"},{"code":"E0_22","desc":"Play/Pause","category":"Media"},{"code":"E0_23","desc":"Spell","category":"F-Lock"},{"code":"E0_24","desc":"Stop","category":"Media"},{"code":"E0_25","desc":"0xE025","category":"Unknown"},{"code":"E0_26","desc":"0xE026","category":"Unknown"},{"code":"E0_27","desc":"0xE027","category":"Unknown"},{"code":"E0_28","desc":"0xE028","category":"Unknown"},{"code":"E0_29","desc":"0xE029","category":"Unknown"},{"code":"E0_2B","desc":"0xE02B","category":"Unknown"},{"code":"E0_2C","desc":"0xE02C","category":"Unknown"},{"code":"E0_2D","desc":"0xE02D","category":"Unknown"},{"code":"E0_2E","desc":"Volume Down","category":"Media"},{"code":"E0_2F","desc":"0xE02F","category":"Unknown"},{"code":"E0_30","desc":"Volume Up","category":"Media"},{"code":"E0_31","desc":"0xE031","category":"Unknown"},{"code":"E0_32","desc":"Home","category":"Web"},{"code":"E0_33","desc":"0xE033","category":"Unknown"},{"code":"E0_34","desc":"0xE034","category":"Unknown"},{"code":"E0_35","desc":"/","category":"Num"},{"code":"E0_36","desc":"0xE036","category":"Unknown"},{"code":"E0_37","desc":"PrtSc","category":"Special"},{"code":"E0_38","desc":"Right Alt","category":"Special"},{"code":"E0_2038","desc":"Alt Gr","category":"Special"},{"code":"E0_39","desc":"0xE039","category":"Unknown"},{"code":"E0_3A","desc":"0xE03A","category":"Unknown"},{"code":"E0_3B","desc":"Help","category":"F-Lock"},{"code":"E0_3C","desc":"Office Home","category":"F-Lock"},{"code":"E0_3D","desc":"Task Pane","category":"F-Lock"},{"code":"E0_3E","desc":"New","category":"F-Lock"},{"code":"E0_3F","desc":"Open","category":"F-Lock"},{"code":"E0_40","desc":"Close","category":"F-Lock"},{"code":"E0_41","desc":"Reply","category":"F-Lock"},{"code":"E0_42","desc":"Fwd","category":"F-Lock"},{"code":"E0_43","desc":"Send","category":"F-Lock"},{"code":"E0_44","desc":"0xE044","category":"Unknown"},{"code":"E0_45","desc":"€","category":"Special"},{"code":"E0_46","desc":"Break","category":"Special"},{"code":"E0_47","desc":"Home","category":"Special"},{"code":"E0_48","desc":"Up","category":"Arrow"},{"code":"E0_49","desc":"Page Up","category":"Special"},{"code":"E0_4A","desc":"0xE04A","category":"Unknown"},{"code":"E0_4B","desc":"Left","category":"Arrow"},{"code":"E0_4C","desc":"0xE04C","category":"Unknown"},{"code":"E0_4D","desc":"Right","category":"Arrow"},{"code":"E0_4E","desc":"0xE04E","category":"Unknown"},{"code":"E0_4F","desc":"End","category":"Special"},{"code":"E0_50","desc":"Down","category":"Arrow"},{"code":"E0_51","desc":"Page Down","category":"Special"},{"code":"E0_52","desc":"Insert","category":"Special"},{"code":"E0_53","desc":"Delete","category":"Special"},{"code":"E0_54","desc":"0xE054","category":"Unknown"},{"code":"E0_55","desc":"0xE055","category":"Unknown"},{"code":"E0_56","desc":"\u003c \u003e |","category":"Special"},{"code":"E0_57","desc":"Save","category":"F-Lock"},{"code":"E0_58","desc":"Print","category":"F-Lock"},{"code":"E0_59","desc":"0xE059","category":"Unknown"},{"code":"E0_5A","desc":"0xE05A","category":"Unknown"},{"code":"E0_5B","desc":"Left Windows","category":"Special"},{"code":"E0_5C","desc":"Right Windows","category":"Special"},{"code":"E0_5D","desc":"Application","category":"Special"},{"code":"E0_5E","desc":"Power","category":"Special"},{"code":"E0_5F","desc":"Sleep","category":"Special"},{"code":"E0_60","desc":"0xE060","category":"Unknown"},{"code":"E0_61","desc":"0xE061","category":"Unknown"},{"code":"E0_62","desc":"0xE062","category":"Unknown"},{"code":"E0_63","desc":"Wake (or Fn)","category":"Special"},{"code":"E0_64","desc":"0xE064","category":"Unknown"},{"code":"E0_65","desc":"Search","category":"Web"},{"code":"E0_66","desc":"Favorites","category":"Web"},{"code":"E0_67","desc":"Refresh","category":"Web"},{"code":"E0_68","desc":"Stop","category":"Web"},{"code":"E0_69","desc":"Forward","category":"Web"},{"code":"E0_6A","desc":"Back","category":"Web"},{"code":"E0_6B","desc":"My Computer","category":"App"},{"code":"E0_6C","desc":"E-Mail","category":"App"},{"code":"E0_6D","desc":"Media Select","category":"App"},{"code":"E0_6E","desc":"0xE06E","category":"Unknown"},{"code":"E0_6F","desc":"0xE06F","category":"Unknown"},{"code":"E0_70","desc":"0xE070","category":"Unknown"},{"code":"E0_71","desc":"0xE071","category":"Unknown"},{"code":"E0_72","desc":"0xE072","category":"Unknown"},{"code":"E0_73","desc":"0xE073","category":"Unknown"},{"code":"E0_74","desc":"0xE074","category":"Unknown"},{"code":"E0_75","desc":"0xE075","category":"Unknown"},{"code":"E0_76","desc":"0xE076","category":"Unknown"},{"code":"E0_77","desc":"0xE077","category":"Unknown"},{"code":"E0_78","desc":"0xE078","category":"Unknown"},{"code":"E0_79","desc":"0xE079","category":"Unknown"},{"code":"E0_7A","desc":"0xE07A","category":"Unknown"},{"code":"E0_7B","desc":"0xE07B","category":"Unknown"},{"code":"E0_7C","desc":"0xE07C","category":"Unknown"},{"code":"E0_7D","desc":"0xE07D","category":"Unknown"},{"code":"E0_7E","desc":"0xE07E","category":"Unknown"},{"code":"E0_7F","desc":"0xE07F","category":"Unknown"},{"code":"E0_F1","desc":"Hanja Key","category":"Special"},{"code":"E0_F2","desc":"Hangul Key","category":"Special"}] '@ $keyData = ConvertFrom-Json $keyDataJson if ($PSCmdlet.ParameterSetName -eq 'GetAvailableSet') { foreach ($keyItem in $keyData) { [pscustomobject]@{ Code = $keyItem.code Description = $keyItem.desc Category = $keyItem.category } } } elseif (($PSCmdlet.ParameterSetName -eq 'OnlineSet') -or ($PSCmdlet.ParameterSetName -eq 'OfflineServiceSet')) { if ($PSCmdlet.ParameterSetName -eq 'OfflineServiceSet') { $regKeyPath = Join-Path $SystemHiveMountPath -ChildPath 'CurrentControlSet\Control\Keyboard Layout' } else { $regKeyPath = 'HKLM:\System\CurrentControlSet\Control\Keyboard Layout' } $regValueName = 'Scancode Map' if (-not (Test-Path $regKeyPath)) { return $null } $regKey = Get-Item $regKeyPath if ($regKey.GetValueNames() -notcontains $regValueName) { return $null } if ($regKey.GetValueKind($regValueName) -ne 'Binary') { throw ('The Windows registry key "{0}" is expected to have a Binary type property named "{1}", but found the existing type to be {2}.' -f $regKeyPath, $regValueName, $regKey.GetValueKind($regValueName)) } $keyMapData = [byte[]]($regKey.GetValue($regValueName)) $regKey.Close() $regKey.Dispose() if ($keyMapData.Length -le 8) { return $null } for ($i = 0; $i -lt [int]::Parse($keyMapData[8].ToString()) - 1; $i++) { $strFromCode = [string]::Format("{0,2:X}_{1,2:X}", $keyMapData[($i * 4) + 12 + 3], $keyMapData[($i * 4) + 12 + 2]) $strFromCode = $strFromCode.Replace(" ", "0") $strFromDesc = $keyData | where { $_.code -eq $strFromCode } | select -expand desc $strToCode = [string]::Format("{0,2:X}_{1,2:X}", $keyMapData[($i * 4) + 12 + 1], $keyMapData[($i * 4) + 12 + 0]) $strToCode = $strToCode.Replace(" ", "0") $strToDesc = $keyData | where { $_.code -eq $strToCode } | select -expand desc [pscustomobject]@{ 'KeyName' = $strFromDesc 'MapTo' = $strToDesc } } } else { throw 'Internal error: uncaught parameter set.' } } function Set-WindowsKeyboardMap { <# .SYNOPSIS Modifies the virtual key code generated when a key is pressed on the keyboard. This feature only works on Windows. .DESCRIPTION You can use this command to define all key mappings, add a key mapping, or modify an existing one. Type "Get-Help Get-WindowsKeyboardMap" for more details on Windows key mapping and available virtual key codes. Use "Remove-WindowsKeyboardMap" or "Clear-WindowsKeyboardMap" to remove key mappings. This command requires administrative privileges and affects all users on the Windows installation. .PARAMETER KeyName The key to modify. Use "Get-WindowsKeyboardMap -Available" for a list of available key codes. .PARAMETER MapTo The key that should be mapped to. Use "Get-WindowsKeyboardMap -Available" for a list of available key codes. .PARAMETER KeyMap A hashtable of key name to mapped names. Use this parameter to define all key mappings. .PARAMETER Online Writes changes to 'HKEY_LOCAL_MACHINE\\System\CurrentControlSet\Control\Keyboard Layout'. The property 'Scancode Map' will be created, modified or removed. .PARAMETER SystemHiveMountPath Registry mount point to "HKEY_LOCAL_MACHINE\System". You should mount the registry hive file "%SystemRoot%\System32\Config\System" to your online registry. #> [CmdletBinding(DefaultParameterSetName = 'KvpOnlineSet')] Param( [Parameter(Mandatory, ParameterSetName = 'KvpOnlineSet')] [Parameter(Mandatory, ParameterSetName = 'KvpOfflineServiceSet')] [string]$KeyName, [Parameter(Mandatory, ParameterSetName = 'KvpOnlineSet')] [Parameter(Mandatory, ParameterSetName = 'KvpOfflineServiceSet')] [string]$MapTo, [Parameter(Mandatory, ParameterSetName = 'MapOnlineSet')] [Parameter(Mandatory, ParameterSetName = 'MapOfflineServiceSet')] [hashtable]$KeyMap, [Parameter(Mandatory, ParameterSetName = 'KvpOnlineSet')] [Parameter(Mandatory, ParameterSetName = 'MapOnlineSet')] [switch]$Online, [Parameter(Mandatory, ParameterSetName = 'KvpOfflineServiceSet')] [Parameter(Mandatory, ParameterSetName = 'MapOfflineServiceSet')] [string]$SystemHiveMountPath ) if (-not (New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) { throw 'This command requires administrative privileges.' } $regKeyPath = 'HKLM:\System\CurrentControlSet\Control\Keyboard Layout' if ($PSCmdlet.ParameterSetName -like '*OfflineServiceSet') { $regKeyPath = Join-Path $SystemHiveMountPath -ChildPath 'CurrentControlSet\Control\Keyboard Layout' } $regValueName = 'Scancode Map' $keyData = Get-WindowsKeyboardMap -Available $effectiveMap = @{} if ($PSCmdlet.ParameterSetName -like 'Map*') { foreach ($mapFromDesc in $KeyMap.Keys) { $mapToDesc = $KeyMap[$mapFromDesc] if (($mapFromDesc -isnot [string]) -or ($mapToDesc -isnot [string])) { throw ('The parameter KeyMap must be a hashtable of strings.') } $mapFromInfo = $keyData | where { $_.Description -eq $mapFromDesc } if (-not $mapFromInfo) { throw ('Unsupported source key "{0}". Use "Get-WindowsKeyboardMap -Available" to get a list of supported keys.' -f $mapFromDesc) } elseif ($mapFromInfo.Count -gt 1) { throw ('Internal error: ambiguous key description "{0}"' -f $mapFromDesc) } $mapToInfo = $keyData | where { $_.Description -eq $mapToDesc } if (-not $mapToInfo) { throw ('Unsupported target key "{0}". Use "Get-WindowsKeyboardMap -Available" to get a list of supported keys.' -f $mapToDesc) } elseif ($mapToInfo.Count -gt 1) { throw ('Internal error: ambiguous key description "{0}"' -f $mapToDesc) } $effectiveMap[$mapFromInfo.Code] = $mapToInfo.Code } } elseif ($PSCmdlet.ParameterSetName -like 'Kvp*') { if ($PSCmdlet.ParameterSetName -like '*OnlineSet') { $currentMap = Get-WindowsKeyboardMap -Online } elseif ($PSCmdlet.ParameterSetName -like '*OfflineServiceSet') { $currentMap = Get-WindowsKeyboardMap -SystemHiveMountPath $SystemHiveMountPath } else { throw 'Internal error: uncaught parameter set.' } if ($currentMap) { foreach ($mapItem in $currentMap) { if (-not $mapItem.KeyName) { continue } $mapFromInfo = $keyData | where { $_.Description -eq $mapItem.KeyName } if (-not $mapFromInfo) { throw ('Registry data corruption. Clear the current key mapping using "Clear-WindowsKeyboardMap" and try again.' -f $mapItem.KeyName) } elseif ($mapFromInfo.Count -gt 1) { throw ('Internal error: ambiguous key description "{0}"' -f $mapItem.KeyName) } $mapToInfo = $keyData | where { $_.Description -eq $mapItem.MapTo } if (-not $mapToInfo) { throw ('Registry data corruption. Clear the current key mapping using "Clear-WindowsKeyboardMap" and try again.' -f $mapItem.MapTo) } elseif ($mapToInfo.Count -gt 1) { throw ('Internal error: ambiguous key description "{0}"' -f $mapItem.MapTo) } $effectiveMap[$mapFromInfo.Code] = $mapToInfo.Code } } # add or modify $mapFromInfo = $keyData | where { $_.Description -eq $KeyName } if (-not $mapFromInfo) { throw ('Unsupported source key "{0}". Use "Get-WindowsKeyboardMap -Available" to get a list of supported keys.' -f $KeyName) } elseif ($mapFromInfo.Count -gt 1) { throw ('Internal error: ambiguous key description "{0}"' -f $KeyName) } $mapToInfo = $keyData | where { $_.Description -eq $MapTo } if (-not $mapToInfo) { throw ('Unsupported target key "{0}". Use "Get-WindowsKeyboardMap -Available" to get a list of supported keys.' -f $MapTo) } elseif ($mapToInfo.Count -gt 1) { throw ('Internal error: ambiguous key description "{0}"' -f $MapTo) } $effectiveMap[$mapFromInfo.Code] = $mapToInfo.Code [pscustomobject]@{ KeyName = $mapFromInfo.Description MapTo = $mapToInfo.Description } } else { throw 'Internal error: uncaught parameter set.' } if ($effectiveMap.Keys.Count -eq 0) { # delete the key if (($PSCmdlet.ParameterSetName -like '*OnlineSet') -or ($PSCmdlet.ParameterSetName -like '*OfflineServiceSet')) { if (-not (Test-Path $regKeyPath)) { # do nothing } else { $regKey = Get-Item $regKeyPath if ($regValueName -in $regKey.GetValueNames()) { Write-Verbose ('Remove "{0}" property "{1}".' -f $regKeyPath, $regValueName) Remove-ItemProperty -Path $regKeyPath -Name $regValueName #$regKey.DeleteValue($regValueName) } $regKey.Close() $regKey.Dispose() if ($PSCmdlet.ParameterSetName -like '*OnlineSet') { Write-Warning ('Restart the computer or logout for changes to take effect.') } } } else { throw 'Internal error: uncaught parameter set.' } } else { $keymapCount = $effectiveMap.Keys.Count $regData = [byte[]]::CreateInstance([byte], (8 + 4 + (4 * $keymapCount) + 4)) # skip #0-7 # set #8 to count $regData[8] = [Convert]::ToByte($keymapCount + 1) # skip #9-11 # add the list $cursor = 0 foreach ($mapFromCode in $effectiveMap.Keys) { # Example: E0_0020 $mapFromReg = $mapFromCode.Split('_')[0] $mapFromBin = $mapFromCode.Split('_')[1] # trim the prefix 0s (0020 -> 20) if ($mapFromBin.Length -gt 2) { $mapFromBin = $mapFromBin.Substring(2) } $mapToCode = $effectiveMap[$mapFromCode] $mapToReg = $mapToCode.Split('_')[0] $mapToBin = $mapToCode.Split('_')[1] if ($mapToBin.Length -gt 2) { $mapToBin = $mapToBin.Substring(2) } # scan codes are stored in ToHi ToLo FromHi FromLo $regData[($cursor * 4) + 12 + 0] = [Convert]::ToByte($mapToBin, 16) $regData[($cursor * 4) + 12 + 1] = [Convert]::ToByte($mapToReg, 16) $regData[($cursor * 4) + 12 + 2] = [Convert]::ToByte($mapFromBin, 16) $regData[($cursor * 4) + 12 + 3] = [Convert]::ToByte($mapFromReg, 16) $cursor += 1 } # skip last 4 if (($PSCmdlet.ParameterSetName -like '*OnlineSet') -or ($PSCmdlet.ParameterSetName -like '*OfflineServiceSet')) { if (-not (Test-Path $regKeyPath)) { $regKey = md $regKeyPath -Force $regKey.Close() $regKey.Dispose() } if ($PSCmdlet.ParameterSetName -like '*OfflineServiceSet') { $regKey = Get-Item $regKeyPath $regKey.SetValue($regValueName, $regData) $regKey.Close() $regKey.Dispose() } else { Set-ItemProperty -Path $regKeyPath -Name $regValueName -Value $regData Write-Warning ('Restart the computer or logout for changes to take effect.') } } else { throw 'Internal error: uncaught parameter set.' } } } function Clear-WindowsKeyboardMap { <# .SYNOPSIS Removes all Windows key mappings. .DESCRIPTION Type "Get-Help Get-WindowsKeyboardMap" for more details on Windows key mapping and available virtual key codes. Use "Remove-WindowsKeyboardMap" to remove a single key mapping. This command requires administrative privileges and affects all users on the Windows installation. .PARAMETER Online Writes changes to 'HKEY_LOCAL_MACHINE\\System\CurrentControlSet\Control\Keyboard Layout'. The property 'Scancode Map' will be removed. .PARAMETER SystemHiveMountPath Registry mount point to "HKEY_LOCAL_MACHINE\System". You should mount the registry hive file "%SystemRoot%\System32\Config\System" to your online registry. #> [CmdletBinding(DefaultParameterSetName = 'OnlineSet')] Param( [Parameter(Mandatory, ParameterSetName = 'OnlineSet')] [switch]$Online, [Parameter(Mandatory, ParameterSetName = 'OfflineServiceSet')] [string]$SystemHiveMountPath ) $effectiveMap = @{} if ($PSCmdlet.ParameterSetName -eq 'OnlineSet') { Set-WindowsKeyboardMap -KeyMap $effectiveMap -Online } elseif ($PSCmdlet.ParameterSetName -eq 'OfflineServiceSet') { Set-WindowsKeyboardMap -KeyMap $effectiveMap -SystemHiveMountPath $SystemHiveMountPath } else { throw 'Internal error: uncaught parameter set.' } } function Remove-WindowsKeyboardMap { <# .SYNOPSIS Removes a keyboard mapping on Windows. .DESCRIPTION Use this command to remove an existing Windows key mapping. Type "Get-Help Get-WindowsKeyboardMap" for more details on Windows key mapping and available virtual key codes. Use "Clear-WindowsKeyboardMap" to remove all key mappings. This command requires administrative privileges and affects all users on the Windows installation. .PARAMETER KeyName The key to remove. Use "Get-WindowsKeyboardMap -Available" for a list of available key codes. .PARAMETER Online Writes changes to 'HKEY_LOCAL_MACHINE\\System\CurrentControlSet\Control\Keyboard Layout'. The property 'Scancode Map' will be modified or removed. .PARAMETER SystemHiveMountPath Registry mount point to "HKEY_LOCAL_MACHINE\System". You should mount the registry hive file "%SystemRoot%\System32\Config\System" to your online registry. #> [CmdletBinding(DefaultParameterSetName = 'OnlineSet')] Param( [Parameter(Mandatory)] [string]$KeyName, [Parameter(Mandatory, ParameterSetName = 'OnlineSet')] [switch]$Online, [Parameter(Mandatory, ParameterSetName = 'OfflineServiceSet')] [string]$SystemHiveMountPath ) $effectiveMap = @{} if ($PSCmdlet.ParameterSetName -eq 'OnlineSet') { $currentMap = Get-WindowsKeyboardMap -Online } elseif ($PSCmdlet.ParameterSetName -eq 'OfflineServiceSet') { $currentMap = Get-WindowsKeyboardMap -SystemHiveMountPath $SystemHiveMountPath } else { throw 'Internal error: uncaught parameter set.' } if (-not $currentMap) { # do nothing return } foreach ($mapItem in $currentMap) { $effectiveMap[$mapItem.KeyName] = $mapItem.MapTo } if ($KeyName -notin $effectiveMap.Keys) { # do nothing return } # remove the item $effectiveMap.Remove($KeyName) if ($PSCmdlet.ParameterSetName -eq 'OnlineSet') { Set-WindowsKeyboardMap -KeyMap $effectiveMap -Online } elseif ($PSCmdlet.ParameterSetName -eq 'OfflineServiceSet') { Set-WindowsKeyboardMap -KeyMap $effectiveMap -SystemHiveMountPath $SystemHiveMountPath } } function Get-WindowsMediaKey { <# .SYNOPSIS Shows all effective Windows multimedia keyboard configuration, or returns a list of supported multimedia keys. .DESCRIPTION Windows Explorer allows you to customize the applications that are launched using keys on multimedia keyboards. Administrative privileges are required to modify keyboard mappings. Changes affect all users. .PARAMETER Available Lists the media keys that are supported. No escalated privileges are required to execute with this flag. Windows will not be modified. .PARAMETER Online Reads from the Windows registry path 'HKEY_LOCAL_MACHINE\\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AppKey'. Sub-keys that can be parsed as integers will be analyzed. .PARAMETER SoftwareHiveMountPath Registry mount point to "HKEY_LOCAL_MACHINE\Software". You should mount the registry hive file "%SystemRoot%\System32\Config\Software" to your online registry. #> [CmdletBinding(DefaultParameterSetName = 'OnlineSet')] Param( [Parameter(Mandatory, ParameterSetName = 'OnlineSet')] [switch]$Online, [Parameter(Mandatory, ParameterSetName = 'OfflineServiceSet')] [string]$SoftwareHiveMountPath, [Parameter(Mandatory, ParameterSetName = 'GetAvailableSet')] [switch]$Available ) $regKeyPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AppKey' if ($PSCmdlet.ParameterSetName -eq 'OfflineServiceSet') { $regKeyPath = Join-Path $SoftwareHiveMountPath -ChildPath 'Microsoft\Windows\CurrentVersion\Explorer\AppKey' } # # Enum values from Microsoft # - https://docs.microsoft.com/en-us/windows/win32/api/winuser/ # $mediaKeyboardMapping = @( 'Backward' 'Forward' 'Refresh' 'Stop' 'Search' 'Favorites' 'Home' 'Mute' 'Down' 'Up' 'Next Track' 'Previous Track' 'Stop' 'Play/Pause' 'Mail' 'Media Select' 'App1/Computer' 'App2/Calculator' 'Bass Down' 'Bass Boost' 'Bass Up' 'Treble Down' 'Treble Up' 'Microphone Volume Mute' 'Microphone Volume Down' 'Microphone Volume Up' 'Help' 'Find' 'New' 'Open' 'Close' 'Save' 'Print' 'Undo' 'Redo' 'Copy' 'Cut' 'Paste' 'Reply To Mail' 'Forward Mail' 'Send Mail' 'Spell Check' 'Dictate or Command Control Toggle' 'Microphone On/Off Toggle' 'Correction List' ) if ($PSCmdlet.ParameterSetName -eq 'GetAvailableSet') { for ($i = 0; $i -lt $mediaKeyboardMapping.Count; $i++) { [pscustomobject]@{ KeyCode = $i + 1 Description = $mediaKeyboardMapping[$i] } } return } if (-not (Test-Path $regKeyPath)) { return } $mediaKeyBindings = dir $regKeyPath | select -expand PSChildName $mediaKeyBindings | ForEach-Object { $subkey = Get-Item (Join-Path $regKeyPath -ChildPath $_) $keyIndex = 0 if (-not [int]::TryParse($_, [ref]$keyIndex)) { continue } else { if (($keyIndex -gt 0) -and ($keyIndex -le $mediaKeyboardMapping.Count)) { $keyDesc = $mediaKeyboardMapping[$keyIndex - 1] } else { $keyDesc = 'Unknown' } } if ('ShellExecute' -in $subkey.GetValueNames()) { $shellExec = Get-ItemProperty -Path (Join-Path $regKeyPath -ChildPath $_) -Name 'ShellExecute' | select -expand ShellExecute } else { $shellExec = '' } if ('RegisteredApp' -in $subkey.GetValueNames()) { $regApp = Get-ItemProperty -Path (Join-Path $regKeyPath -ChildPath $_) -Name 'RegisteredApp' | select -expand RegisteredApp } else { $regApp = '' } if ('Association' -in $subkey.GetValueNames()) { $assoc = Get-ItemProperty -Path (Join-Path $regKeyPath -ChildPath $_) -Name 'Association' | select -expand Association } else { $assoc = '' } $outObject = [pscustomobject]@{ KeyCode = $_ Description = $keyDesc } if ($shellExec -ne '') { $outObject | Add-Member -MemberType NoteProperty -Name 'ShellExecute' -Value $shellExec } if ($regApp -ne '') { $outObject | Add-Member -MemberType NoteProperty -Name 'RegisteredApp' -Value $regApp } if ($assoc -ne '') { $outObject | Add-Member -MemberType NoteProperty -Name 'Association' -Value $assoc } $outObject } } function Set-WindowsMediaKey { <# .SYNOPSIS Modifies the program associated with a Windows multimedia key. This feature only works on Windows. .DESCRIPTION You can use this command to define the program to launch when a multimedia key is press on your keyboard. Changes are immediate. Type "Get-Help Get-WindowsMediaKey" for more details on Windows multimedia keys. Use "Remove-WindowsMediaKey" or "Reset-WindowsMediaKey" to remove customized multimedia key configurations. This command requires administrative privileges and affects all users on the Windows installation. .PARAMETER KeyCode The key to modify. Use "Get-WindowsMedia -Available" for a list of available key codes. .PARAMETER ShellExecute Executes a program. May be an executable or shell object. Some examples are: - calc.exe - ::{20D04FE0-3AEA-1069-A2D8-08002B30309D} Any valid command that works in the Windows "Run" dialog box will work here. .PARAMETER RegisteredApp Execute the default application for a category of applications. You can configure default applications under Windows Settings. Some examples are: .PARAMETER Association Execute the default application associated with a protocol. Some examples are: - http .PARAMETER NoClobber Remove all previous customization on the multimedia key first, if any. Defaults to True. .PARAMETER Online Writes changes to the Windows registry path 'HKEY_LOCAL_MACHINE\\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AppKey\<KeyCode>' .PARAMETER SoftwareHiveMountPath Registry mount point to "HKEY_LOCAL_MACHINE\Software". You should mount the registry hive file "%SystemRoot%\System32\Config\Software" to your online registry. #> [CmdletBinding(DefaultParameterSetName = 'ShellExecuteOnlineSet')] Param( [Parameter(Mandatory)] [int]$KeyCode, [Parameter(Mandatory, ParameterSetName = 'ShellExecuteOnlineSet')] [Parameter(Mandatory, ParameterSetName = 'ShellExecuteOfflineServiceSet')] [string]$ShellExecute, [Parameter(Mandatory, ParameterSetName = 'RegisteredAppOnlineSet')] [Parameter(Mandatory, ParameterSetName = 'RegisteredAppOfflineServiceSet')] [string]$RegisteredApp, [Parameter(Mandatory, ParameterSetName = 'AssociationOnlineSet')] [Parameter(Mandatory, ParameterSetName = 'AssociationOfflineServiceSet')] [string]$Association, [Parameter()] [switch]$NoClobber = $true, [Parameter(Mandatory, ParameterSetName = 'ShellExecuteOnlineSet')] [Parameter(Mandatory, ParameterSetName = 'RegisteredAppOnlineSet')] [Parameter(Mandatory, ParameterSetName = 'AssociationOnlineSet')] [switch]$Online, [Parameter(Mandatory, ParameterSetName = 'ShellExecuteOfflineServiceSet')] [Parameter(Mandatory, ParameterSetName = 'RegisteredAppOfflineServiceSet')] [Parameter(Mandatory, ParameterSetName = 'AssociationOfflineServiceSet')] [string]$SoftwareHiveMountPath ) if (-not (New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) { throw 'This command requires administrative privileges.' } $regKeyPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AppKey' if ($PSCmdlet.ParameterSetName -like '*OfflineServiceSet') { $regKeyPath = Join-Path $SoftwareHiveMountPath -ChildPath 'Microsoft\Windows\CurrentVersion\Explorer\AppKey' } $targetPath = Join-Path $regKeyPath -ChildPath $KeyCode if (-not (Test-Path $targetPath)) { Write-Verbose ('Create profile for media key "{0}".' -f $KeyCode) $regkey = md $targetPath -Force } else { Write-Verbose ('Modify profile for media key "{0}".' -f $KeyCode) $regkey = Get-Item $targetPath if ($NoClobber) { @('Association', 'RegisteredApp', 'ShellExecute') | ForEach-Object { if ($_ -in $regkey.GetValueNames()) { Write-Verbose ('Removing existing property "{0}".' -f $_) Remove-ItemProperty -Path $targetPath -Name $_ } } } } if ($PSCmdlet.ParameterSetName -like 'ShellExecute*') { $propName = 'ShellExecute' $propValue = $ShellExecute } elseif ($PSCmdlet.ParameterSetName -like 'RegisteredApp*') { $propName = 'RegisteredApp' $propValue = $RegisteredApp } elseif ($PSCmdlet.ParameterSetName -like 'Association*') { $propName = 'Association' $propValue = $Association } else { throw 'Internal error: uncaught parameter set.' } Write-Verbose ('Set property "{0}".' -f $propName) Set-ItemProperty -Path $targetPath -Name $propName -Value $propValue $regkey.Close() $regkey.Dispose() } function Remove-WindowsMediaKey { <# .SYNOPSIS Removes all settings on a Windows multimedia key. This feature only works on Windows. .DESCRIPTION You can use this command to remove all settings on a Windows multimedia key. Changes are immediate. Type "Get-Help Get-WindowsMediaKey" for more details on Windows multimedia keys. This command will remove the entire Windows registry key associated. To remove customization properties only, use "Reset-WindowsMediaKey". This command requires administrative privileges and affects all users on the Windows installation. .PARAMETER KeyCode The key to modify. Use "Get-WindowsMedia -Available" for a list of available key codes. .PARAMETER Online Removes the Windows registry path 'HKEY_LOCAL_MACHINE\\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AppKey\<KeyCode>' if required. .PARAMETER SoftwareHiveMountPath Registry mount point to "HKEY_LOCAL_MACHINE\Software". You should mount the registry hive file "%SystemRoot%\System32\Config\Software" to your online registry. #> [CmdletBinding(DefaultParameterSetName = 'OnlineSet')] Param( [Parameter(Mandatory)] [int]$KeyCode, [Parameter(Mandatory, ParameterSetName = 'OnlineSet')] [switch]$Online, [Parameter(Mandatory, ParameterSetName = 'OfflineServiceSet')] [string]$SoftwareHiveMountPath ) if (-not (New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) { throw 'This command requires administrative privileges.' } $regKeyPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AppKey' if ($PSCmdlet.ParameterSetName -eq 'OfflineServiceSet') { $regKeyPath = Join-Path $SoftwareHiveMountPath -ChildPath 'Microsoft\Windows\CurrentVersion\Explorer\AppKey' } $targetPath = Join-Path $regKeyPath -ChildPath $KeyCode if (-not (Test-Path $targetPath)) { Write-Verbose ('Unable to find an existing profile for media key "{0}".' -f $KeyCode) return } Write-Verbose ('Removing profile for media key "{0}".' -f $KeyCode) Remove-Item $targetPath -Recurse -Force } function Reset-WindowsMediaKey { <# .SYNOPSIS Reset all settings on a Windows multimedia key to its defaults. This feature only works on Windows. .DESCRIPTION You can use this command to remove customization properties on a Windows multimedia key. Changes are immediate. Type "Get-Help Get-WindowsMediaKey" for more details on Windows multimedia keys. This command will remove customization properties only, To remove the entire Windows registry key associated, use "Remove-WindowsMediaKey". This command requires administrative privileges and affects all users on the Windows installation. .PARAMETER KeyCode The key to modify. Use "Get-WindowsMedia -Available" for a list of available key codes. .PARAMETER Online Removes the following properties under the Windows registry path 'HKEY_LOCAL_MACHINE\\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AppKey\<KeyCode>' if required: - Association - RegisteredApp - ShellExecute .PARAMETER SoftwareHiveMountPath Registry mount point to "HKEY_LOCAL_MACHINE\Software". You should mount the registry hive file "%SystemRoot%\System32\Config\Software" to your online registry. #> [CmdletBinding(DefaultParameterSetName = 'OnlineSet')] Param( [Parameter(Mandatory)] [int]$KeyCode, [Parameter(Mandatory, ParameterSetName = 'OnlineSet')] [switch]$Online, [Parameter(Mandatory, ParameterSetName = 'OfflineServiceSet')] [string]$SoftwareHiveMountPath ) if (-not (New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) { throw 'This command requires administrative privileges.' } $regKeyPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AppKey' if ($PSCmdlet.ParameterSetName -eq 'OfflineServiceSet') { $regKeyPath = Join-Path $SoftwareHiveMountPath -ChildPath 'Microsoft\Windows\CurrentVersion\Explorer\AppKey' } $targetPath = Join-Path $regKeyPath -ChildPath $KeyCode if (-not (Test-Path $targetPath)) { Write-Verbose ('Created profile for media key "{0}" using default values.' -f $KeyCode) $regkey = md $targetPath -Force } else { $regkey = Get-Item $targetPath @('Association', 'RegisteredApp', 'ShellExecute') | ForEach-Object { if ($_ -in $regkey.GetValueNames()) { Write-Verbose ('Removed property "{0}" of media key "{1}".' -f $_, $KeyCode) Remove-ItemProperty -Path $targetPath -Name $_ } } } $regkey.Close() $regkey.Dispose() } |