Search code examples
vbscriptscheduled-taskswsh

SendKeys doesn't work from background task


Initial Problem:
I use an external keyboard at the office, so I want the NumLock ON. But when I'm at home I just use the laptop keyboard, so then I get numbers instead of letters and I have to turn NumLock OFF.

Initial Solution: The below script detects one or two keyboards and turns NumLock ON or OFF as appropriate.

New Problem:
This works perfectly from the command line, but I want it to trigger when I log in and happen automatically. When I run it from Task Scheduler in the background, this line doesn't work:

        Shell.SendKeys "{NUMLOCK}"             

It fires but doesn't toggle the lock. No errors reported.

UPDATE: If I schedule it to run under my account "only when user is logged on" then it works but shows the cmd window. If I run it under my account or under the SYSTEM account with "whether user is logged in or not" the window goes away nicely but it doesn't work.

Whether from cmd or run as a scheduled task, I get this output when it should toggle the lock:

    Microsoft (R) Windows Script Host Version 5.812
    Copyright (C) Microsoft Corporation. All rights reserved.

    Found HID Keyboard Device
    Found HID Keyboard Device
    numLock is OFF
    Toggling Numlock

So the script itself is working correctly.

UPDATE2: Looks like it might have something to do with not having a windows station when running as a background task. Turns out that DetectNumlockConsole.exe isn't working either. That is a simple c# app that returns the results of this line

numLock = Control.IsKeyLocked(Keys.NumLock);    

Again, this works when run "only when user is logged on" but not "whether user is logged in or not."
--------- vbs script -----------

    set OUT = WScript.StdOut        
    Set Shell=CreateObject("Wscript.Shell")
    Dim KeyCount
    KeyCount = 0
    Computer = "."
    'set NumLock = CheckState
    Set WMIService = GetObject("winmgmts:\\" & Computer & "\root\cimv2")
    Set Devices = WMIService.ExecQuery ("Select * From Win32_USBControllerDevice")

    For Each Device in Devices
        DeviceName = Device.Dependent
        Quotes = Chr(34)
        DeviceName = Replace(DeviceName, Quotes, "")
        DeviceNames = Split(DeviceName, "=")
        DeviceName = DeviceNames(1)
        Set USBDevices = WMIService.ExecQuery ("Select * From Win32_PnPEntity Where DeviceID = '" & DeviceName & "'")

        For Each USBDevice in USBDevices
            'OUT.WriteLine USBDevice.Description  ' Write description to command line to see what to look for
            If InStr( LCase( USBDevice.Description ), "keyboard" ) <> 0 Then
                KeyCount = KeyCount + 1
                OUT.WriteLine "Found " & USBDevice.Description
            End If
        Next
    Next        

    dim numLock
    numLock = Shell.Run("DetectNumlockConsole.exe",0,True)

    If (numLock = 0) Then
        OUT.WriteLine "numLock is OFF"
    Else
        OUT.WriteLine "numLock is ON"
    End If

                ' If we have a keyboard, and numlock is OFF
                ' Or we don't have a keyboard, and numlock is ON
                ' Then toggle it
    If (((KeyCount > 1) AND (numLock = 0)) OR ((KeyCount = 1) AND (numLock = 1))) Then 
        Shell.SendKeys "{NUMLOCK}"      ' *** Problem here, doesn't toggle **      
        OUT.WriteLine "Toggling Numlock"
    End If

Solution

  • That is how windows security works. It is nothing to do with sendkeys per se, but tasks under different security contexts cannot affect other tasks.

    As you can see it works when run under the same security context as only run when user logged in does.

    It's called process isolation and the principal is that no one can mess with the interactive user for both security and UI principals.