Using PowerShell to set the Num Lock state

During these work from home times, on the darkest timeline, I spent a lot of my day using my work PC over an RDP connection. For some reason, every time I connected, the Num Lock status would be set to off. Which is silly and annoying. 

When you are connected to a remote machine and you press the Num Lock key on your keyboard, it toggles the Num Lock state for both the client and guest machine. Which can get confusing if you are working with apps on both machines and the Num Lock state is different between the machines. Ideally, you want the Num Lock state to be the same on both machines.

For me, the preference was a command-line option for setting Num Lock. My shell of choice on Windows is PowerShell, so I decided to write a PowerShell script to set the Num Lock Status. The Num Lock key is a toggle switch, press once to turn on, press again to turn off. The script needed to be able to check the current state to be able to set a specified state correctly.

With a little trial and error, I came up with the following script. I put it on gist to make it easier to grab and make your own:

# One parameter, to set the Num Lock state to On or Off, with
# On as the default
Param(
[Parameter(Mandatory=$false)]
[ValidateSet("On", "Off")]
[String[]] $onoff='On'
)
# Get the current state of the Num Lock key
$CurrentState = [console]::NumberLock
# the RequestedState, based in the command line param.
# On is true, Off is false
if ($onoff -eq 'On') {
$RequestedState = $true
}
else {
$RequestedState = $false
}
# If the requested state is the current state, we declare
# victory and go home
if ($RequestedState -eq $CurrentState) {
if ($CurrentState -eq $false)
{
Write-Host 'Num Lock is already off'
}
else {
Write-Host 'Num Lock is already on'
}
}
else {
if ($CurrentState -eq $false)
{
Write-Host 'Num Lock is off, turning on'
}
else {
Write-Host 'Num lock is on, turning off'
}
# If the requested state is not the current state, then
# we need to do a Num Lock press
# Create a new instance of the WScript object and send
# the NumLock key press to it
(New-Object ComObject WScript.Shell).SendKeys('{NUMLOCK}')
}
view raw num-lock.ps1 hosted with ❤ by GitHub

 

I put the comments inline, it should be pretty self-explanatory with the comments. While PowerShell is supported on macOS and Linux now, this is a Windows only script. The [console] ::NumberLock expression returns the following error message:

OperationStopped: Operation is not supported on this platform.

Which is odd, [console] is a shortcut for [system.console], a class that is accessible on the macOS Powershell. If you run the following command in a PowerShell on either Windows or macOS, you’ll get a list of static properties that should be readable from a POSH script.

[system.console] | Get-Member -Static -MemberType property | Format-Table 

And the NumberLock property is listed, but just not implemented. And that’s not even the real sticking point. This script creates an instance of a WScript object and uses it’s SendKeys method to pass in a Num Lock key press. WScript is the Windows Script Host, a technology that lets scripting languages make Windows API calls. A minor sticking point, I only need this Windows. You can get a list of the special keys that SendKeys can send from here.

That’s the heavy lifting. Because I am lazy, I don’t want to type in the name of the script. So I created an alias for it.

Set-Alias nl d:\scripts\num-lock.ps1

Now I can just run “nl” or “nl On” or “nl Off” to change the Num Lock state. I added the alias to the profile so it’s always available. Now when I connect to my work PC, I run “nl” and all is well.

Add a POSH ADB to your Windows Terminal

Windows Terminal is so close to being out of beta. It’s been my default CLI on Windows for about a year. I still don’t think in PowerShell, but I try to use PowerShell as my default shell. I just love how you can configure Terminal.

The default shell for me is PowerShell Core, aka Powershell 7.0. Out of the box, it doesn’t have ADB on the path. When I’m doing Android stuff, I want the ADB. But I don’t want it to be on the path by default. Just like Visual Studio has the “Android ADB Command Prompt” menu option, I wanted to add shell option to Terminals so I can spin up a new PowerShell, but with ADB support. This is what I ended up with:

PowerShell with ADB goodness.

The first thing I did was to create a PowerShell script that just adds the ADB tooling to the path. My first attempt was:

$NeedsAdb = $true

foreach ($p in $env:Path.Split(";"))
{
    if ($p -match "android-sdk\\platform")
    {
        $NeedsAdb = $false
        break
    }
}

if ($NeedsAdb)
{
    write-host "Adding Android SDK Platform tools to path"
    $env:Path += ";C:\Program Files (x86)\Android\android-sdk\platform-tools"
}

It walks through the path and checks to see the folder with the ADB bits is already there.  If not, it gets added. That worked but seemed like a lot. I did a quick refactor and got it down to this

$NeedsAdb = $true

$env:Path.Split(";") | ForEach-Object {
    if ($_ -match "android-sdk\\platform") {
        $NeedsAdb = $false
        break
    }
}

if ($NeedsAdb) {
    write-host "Adding Android SDK Platform tools to path"
    $env:Path += ";C:\Program Files (x86)\Android\android-sdk\platform-tools"
}

A little cleaner, but still too much.  I was taking the path, splitting it up into an array of strings, and then testing each string.  But wait, the path is a string.  That made it simpler.  Instead of doing a string match on each folder the path, I can make one string match against the whole thing.

if ($env:Path -NotMatch "Android\\android-sdk\\platform")
{
    write-host "Adding Android SDK Platform tools to path"
    $env:Path += ";${env:ProgramFiles(x86)}\Android\android-sdk\platform-tools"
}

So I called that one add-adb.ps1 and saved it to a common folder that I put scripts in.  Next, I wanted a cosmetic tweak so that I knew which shell has the power of ADB.  I went to materialdesignicons.com and did a search on “Android”.  I found an icon named “android-debug-bridge”, which was perfect.  I downloaded the .svg version of the icon and then made a .png file out of it with PhotoShop.  I named it android-debug-bridge.png.  Then I made a smaller version to be the icon. You can grab the images and the .ps1 file from the following Gist link: https://gist.github.com/anotherlab/364e3805d9ea56b574b394127acc9aa6

Now that I had the script and the images, it’s time to add a new shell profile.  From within Windows Terminal, select “Settings” from the drop-down menu.  You can also press the CTRL+, shortcut.  This will load the settings.json file in the text editor of your choice.  I have Visual Studio Code registered as the default app for JSON files.  You can use a lesser editor, but that’s on you.

There will be an array of objects named “list”.  These objects are the different shells that can be run from within Windows Terminals.  I added the following item to the list array

 

"list":
[
    {
        "guid": "{50caca3f-bff1-4891-b7f7-e3a05c040003}",
        "fontFace":  "Cascadia Code PL",
        "backgroundImage": "d:/grfx/android-debug-bridge.png",
        "backgroundImageStretchMode": "uniform",
        "backgroundImageOpacity": 0.15,
        "hidden": false,
        "name": "ADB PowerShell",
        "icon": "d:/grfx/adb-32.png",
        "commandline": "pwsh.exe -noe -c D:/scripts/add-adb.ps1"
    },
]

Let’s go over this line by line.

FieldValue
guidIt wants a GUID, so just get one.  I used guidgenerator.com, but any GUID generator will do.
fontFaceI’m using the Powerline version of Cascadia.  More on that in a bit
backgroundImageGrab it from my Gist or use your own.
backgroundImageStretchMode Size the image to fit the Shell window
backgroundImageOpacity I set it to be mostly transparent
hiddenIf you want to hide this from the list of shells, just set it to True
nameCall it what you want
iconThe tab icon is optional, but you can grab it from my Gist.
commandlineThis is what gets launched. The “-c” option says to run the next parameter and the “-noe” says not to exit after running the command

And that lets me spin up a new PowerShell with ADB on the path.  Mixing PowerShell with ADB makes it easier to do ADB commands that would normally be clunky.  For example, I want to test some Android code that would access the photo gallery.  When you new up a new Android emulator, there are no images.   ADB lets copy files to the emulator’s filesystem, but it doesn’t do wild cards.

You may have notice the “/” slash being used instead of “\” for file paths. Windows Terminal lets you use the “/” as the directory separator and this avoids having to use “\\” to get a single “\” in.

I have a folder with a bunch of images that I wanted to copy to an Android Emulator image.  I then run the following command from PowerShell:

Get-ChildItem .\*  -Include *.jpg,*.png | Foreach-Object {adb push $_.Name /sdcard/Pictures}

Get-ChildItem gets a directory listing and I use the -Include parameter to only include the files that have the .jpg and .png extension. If I didn’t need to filter the files list, I could the aliases for Get-ChildItem of gci or ls. I then pipe the results into Foreach-Object. This will execute everything in the {} block for each item. $_.Name is the name of the file and that gets passed to adb push to copy that file to /sdcard/Pictures in the emulator. There’s a bit of typing, but it does save time when trying to copy a set of files over to Android.

About that “Cascadia Code PL” font face.  I’m running a theming engine inside PowerShell called “Oh-my-posh”.  I’ll do a longer post on it in the future, but the short story is that it makes the PowerShell prompt contain the current git status for the current folder.  Read about it and get it here.  On the Mac, I use Oh-my-zsh to get a souped-up zsh shell.  Scott Hanselman did a good write up of Oh-my-posh here.

Oh-my-posh uses Powerline Glyphs (originally defined here) as part of the status display.  So you’ll need a font that includes the Powerline Glyphs.  Microsoft’s Cascadia Code font has a version with Powerline and you can get it here.  Here’s what that looked like when I was adding the files to the Gist

You can see the color and text information change as I used git to add the icon to the Gist. When working with git, it’s very handy to easily see which branch you are working with and the current status of that branch.