How to check if NumLock is enabled - powershell

Recently I ran into an issue with a laptop that had NumLock disabled automatically at certain times (such as when coming out of sleep mode). This prompted me to look for ways to programmatically check if NumLock was off, and if so, turn it on.
I'm looking for the best way to accomplish this. I want to run the script when certain events occur, such as when logging on to the laptop. I plan to do this with a scheduled task, and I'd prefer to use PowerShell over VBScript, but I'd be happy to use whatever works.

In PowerShell, [console]::NumberLock is a Read Only property that will evaluate to true if NumLock is enabled.
The script I ended up writing is as follows:
if(-not [console]::NumberLock){
$w = New-Object -ComObject WScript.Shell;
$w.SendKeys('{NUMLOCK}');
}

Building on Thriggle's answer above, you can ensure NumLock never gets turned off by adding a loop. Note that this is pretty hard on CPU:
While($true){
if(-not [console]::NumberLock){
$w = New-Object -ComObject WScript.Shell;
$w.SendKeys('{NUMLOCK}');
}
}

Related

Changing the caption permanently

In my script, I start a third-party non-GUI application. I'm sort of trying to run this embedded in my script itself, so I will be able to change the icon and the windows caption.
I have two restriction:
I have to use & 'application.exe' to start the application. I tested Start-Process -NoNewWindow, but that breaks the functionality of application.exe.
The application.exe needs to be running in my script. I can only change the icon when I compile my script with PS1 to Exe afterwards.
The challenge I'm now facing is related to the first restriction. I need to change the caption a-synchronously. The $host.ui.RawUI.WindowTitle = “New Title” is not working, because application.exe changes the caption right after execution. So I need to change it by using functions like SetWindowText(). This is working in VB.NET, but I'm looking for a way to start this function in parallel with the & 'application.exe'. When I use &, the application is executed and the script waits until it terminates. So I need to do the SetWindowText() in parallel.
Visual Basic/C has a BackgroundWorker functions for such cases. Is something like that also available in PowerShell?
Thanks for any help in advance!
Kind regards,
Eric
Everybody thank you very much for your help!
The solution proved to be a lot easier that I thought. You don't have to keep on renaming the window. You just have to start the cmd window, wait a bit (in the background it's doing something with conhost.exe) and then rename it once. Here's the code I used:
$titletext = "My New CMD Window Title"
# Start a thread job to change the window title to $titletext
$null = Start-ThreadJob { param( $rawUI, $windowTitle )
Start-Sleep -s 2 #Wait until cmd.exe is started
if ( $rawUI.WindowTitle -ne $windowTitle ) {
$rawUI.WindowTitle = $windowTitle
}
} -ArgumentList $host.ui.RawUI, $titletext
& 'c:\windows\system32\cmd.exe'
Kind regards,
Eric

Powershell SendKeys No Input

I'm working on a dual pc stream setup and would like to use my elgato stream deck on my gaming pc for some specific functions. The only problem is, no obs control. I was combing through reddit and came across the suggestion to use SendKeys in powershell scripts to perform hotkey functions. I am able to connect the two computers through PSSession and run my script, but nothing happens. I have the application focused. Even when I just open a text file and run the script, nothing comes up. I don't really think this should be that hard should it? Any help would be appreciated.
$wshell = New-Object -ComObject wscript.shell; $wshell.SendKeys(']')
Continuing from my comment...
Sendkeys is a thing but can be really finicky, focus, timing issues, etc. GUI automation is not really PowerShell's strong suit. Custom tools like AutoIT, Selenium, etc., are better options.
With Sendkeys, you often must set delays to ensure focus before calling keystrokes.
YOu can also avoid the use of ...
New-Object -ComObject wscript.shell
... and use this...
Add-Type -AssemblyName System.Windows.Forms
Here are a few examples you can try.
# pops the WinKey Start Menu
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('^{ESC}')
Or this way...
# Initialize a variable with the .Net namespace, then use it.
$SendKeys = [System.Windows.Forms.SendKeys]
$SendKeys::SendWait('^{ESC}')
# SendKeys to hold down keys
$SendKeys::SendWait('q(+%) + (+)q')
# Send commnad results to notepad - Note the sleep to wait for notepad to open, paste the content then select the file menu
Get-NetIPConfiguration | clip | notepad
Sleep -Seconds 1
$SendKeys::SendWait('^V')
Sleep -Seconds 2
$SendKeys::SendWait('%F')
Update as per our comments below:
SendKeys requires a GUI up and running. A GUI requires a logged-on, interactive user.

How to change task triggers using powershell?

The script below removes the task triggers for each task ONLY if the tasks are in a folder. However my tasks are in the MAIN WINDOW in task scheduler. When you click on Task Scheduler Library, they are right there. They are not in any folder. However the $folder = $service.GetFolder('\') does not work. I tried it without the backlash and without the quotes, still does not work. What is going on ?
$service = New-Object -ComObject Schedule.Service
$service.Connect($env:COMPUTERNAME)
$folder = $service.GetFolder('\')
$tasks = $folder.gettasks(0)
foreach ($t in $tasks)
{
$definition = $t.Definition
$triggersCount = $definition.Triggers.Count
for($id=$triggersCount; $id -gt 0; $id--){
$definition.Triggers.Remove($id)
}
$folder.RegisterTaskDefinition($t.Name, $definition, 4, $null, $null, $null)
}
Get-ScheduledTask -TaskPath "\" may show all the tasks but there are no obvious commands or methods in the module to remove the triggers. I couldn't see a way and this answer seems to confirm it.
It's unclear from your question if $folder = $service.GetFolder('\') itself is producing an error, or if its the subsequent call to $tasks = $folder.gettasks(0). In this answer I'm assuming it's the latter, only because that's where I seem to have run into problems in my own environment.
I think this might be a combination of permissions and/or the hidden status of a task. Firstly, can you try running as elevated?
Reason I think this is after $folder = $service.GetFolder('\') I was able to get a list of tasks in the root folder, however only 2 of 9 were listed. When I ran in elevated I got 6 of 9.
I even tried the old PowerShell pack "TaskScheduler" module, which internally uses pretty much the same COM code, and I got the same results.
Now I was finally able to get the COM approach to show all 9 in the elevated session by flipping the GetTasks argument to 1:
$tasks = $folder.gettasks(1)
I believe the argument means to show or not to show hidden tasks, however it only accepts an [Int]. So, 0 = false, 1 = true. I confirmed this by looking at the code in the old "TaskScheduler" module. Putting a Boolean like $true in the argument doesn't work, only an [Int] will do.
Note: There is a hidden check box in the lower left, on the first tab
of the Task properties dialog. In may case the correlation matched what I ultimately found in the console.
At any rate give these 2 things a try:
Run as elevated.
Flip the argument to 1
Let me know how it turns out. Thanks.

How to perform keystroke inside powershell?

I have ps1 script to grab some information from the vmware cluster environment.
In some place of ps1 script requires the ENTER button keystroke.
So, How to do that ?
-Thanks
If I understand correctly, you want PowerShell to send the ENTER keystroke to some interactive application?
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('title of the application window')
Sleep 1
$wshell.SendKeys('~')
If that interactive application is a PowerShell script, just use whatever is in the title bar of the PowerShell window as the argument to AppActivate (by default, the path to powershell.exe). To avoid ambiguity, you can have your script retitle its own window by using the title 'new window title' command.
A few notes:
The tilde (~) represents the ENTER keystroke. You can also use {ENTER}, though they're not identical - that's the keypad's ENTER key. A complete list is available here: http://msdn.microsoft.com/en-us/library/office/aa202943%28v=office.10%29.aspx.
The reason for the Sleep 1 statement is to wait 1 second because it takes a moment for the window to activate, and if you invoke SendKeys immediately, it'll send the keys to the PowerShell window, or to nowhere.
Be aware that this can be tripped up, if you type anything or click the mouse during the second that it's waiting, preventing to window you activate with AppActivate from being active. You can experiment with reducing the amount of time to find the minimum that's reliably sufficient on your system (Sleep accepts decimals, so you could try .5 for half a second). I find that on my 2.6 GHz Core i7 Win7 laptop, anything less than .8 seconds has a significant failure rate. I use 1 second to be safe.
IMPORTANT WARNING: Be extra careful if you're using this method to send a password, because activating a different window between invoking AppActivate and invoking SendKeys will cause the password to be sent to that different window in plain text!
Sometimes wscript.shell's SendKeys method can be a little quirky, so if you run into problems, replace the fourth line above with this:
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('~');
function Do-SendKeys {
param (
$SENDKEYS,
$WINDOWTITLE
)
$wshell = New-Object -ComObject wscript.shell;
IF ($WINDOWTITLE) {$wshell.AppActivate($WINDOWTITLE)}
Sleep 1
IF ($SENDKEYS) {$wshell.SendKeys($SENDKEYS)}
}
Do-SendKeys -WINDOWTITLE Print -SENDKEYS '{TAB}{TAB}'
Do-SendKeys -WINDOWTITLE Print
Do-SendKeys -SENDKEYS '%{f4}'
Send "Enter" key to an App, for example for pressing "OK". Works great:
Add-Type -AssemblyName microsoft.VisualBasic
Add-Type -AssemblyName System.Windows.Forms
# Get the desired process:
$ProcessName = Get-Process -Name Calculator
Start-Sleep -Seconds 1
# If the process is actually running, bring it to front:
If ($ProcessName)
{
(New-Object -ComObject.Wscript.Shell).AppActivate((Get-Process $ProcessName -ErrorAction SilentlyContinue).MainWindowTitle)
}
# Send "Enter" key to the app:
[Microsoft.VisualBasic.Interaction]::AppActivate($ProcessName.ProcessName)
[System.Windows.Forms.SendKeys]::SendWait({'~'})
Also the $wshell = New-Object -ComObject wscript.shell; helped a script that was running in the background, it worked fine with just but adding $wshell. fixed it from running as background! [Microsoft.VisualBasic.Interaction]::AppActivate("App Name")

PowerShell: Toggle "Num Lock" on and off.

I would like to be able toggle on and off the "Num Lock" key on the keyboard. I have tried multiple examples around the web and here, with no success. This is the closest thing I've got to a solution:
[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
[System.Windows.Forms.SendKeys]::SendWait("{NUMLOCK}")
The above code looks like it might work and I see the "Num Lock" indicator on my keyboard flash for a second but it doesn't "stick".
I experienced the same on/off flicker you did.
This works fine for me though, give it a try:
$wsh = New-Object -ComObject WScript.Shell
$wsh.SendKeys('{NUMLOCK}')
For what it is worth, from the perspective of the keyboard, the OS sends a set and then a reset of the toggle key (caps, scroll lock, numlock) when using [System.Windows.Forms.SendKeys]::SendWait("{CAPSLOCK}"), but only sends a single event using WScript.Shell.
Stumbled here looking for a way to keep Num Lock ON eternally (apparently the word "lock" doesn't mean much 🤷‍♂️). This script checks Num Lock status every 2 seconds, and switches it back on if it's been turned off:
$wsh = New-Object -ComObject WScript.Shell
while($true){
if ([console]::NumberLock -eq $false) {
$wsh.SendKeys('{NUMLOCK}')
}
Start-Sleep 2
}
I think it would be a good candidate for converting into a standalone tiny .exe using PS2EXE.
intTime=0
strInputVal=InputBox("Enter the time","Enter Hours in Int")
intTime=strInputVal * 60 * 60
set WshShell = WScript.CreateObject("WScript.Shell")
For i = 1 to intTime
WScript.Sleep 500
WshShell.SendKeys "{NUMLOCK}"
WScript.Sleep 500
Next
WScript.Quit
Set WshShell = Nothing