How to change task triggers using powershell? - 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.

Related

Powershell issues with Start-Process of same script elevated

I have a script that inventories the installed software on a machine, based on certain criteria provided in arguments. It also needs to support being buried in a very deep folder structure (Architects just think like that and I need to support it, not control it). And since it now also supports AppX, which requires running elevated, which in turn requires the full path to the PS1 in the -file argument, I am running into issues with the overall length of the Target value in the shortcut.
So, I was looking at the usual run with the -verb argument approach, like this
If (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
$psArguments = #('-nologo', '-noExit', '-executionpolicy', 'bypass', '-verb', 'Runas', '-file',$PSCommandPath)
$pxArguments = #($myInvocation.BoundParameters.GetEnumerator().ForEach({"-$($_.Key)", "$($_.Value)"}))
$arguments = $psArguments + $pxArguments
Start-Process 'powershell.exe' -ArgumentList:$arguments -Verb:Runas
Stop-Process $PID
} else {
Write-Host 'Elevated'
# actual code to process
}
But I am having two issues there, one is critical, one less so.
On the critical side, I see a second blue PowerShell console open for a split second, but then it closes with no error or anything. I would have expected the -noExit argument in $psArguments to at least leave that window open for me, so I could read any errors. But I can't see any red, so that makes me think there is no error, and this usually happens when the -file argument doesn't point to an actual script. However, if I validate the arguments like so
foreach ($item in $arguments) {
Write-Host "$item"
}
Write-Host "$PSCommandPath $(Test-Path $PSCommandPath)"
The arguments all look good and Test-Path returns true.
So, what in this am I getting wrong, such that I don't get a new PowerShell console that actually works?
The second issue is that I have defined fonts and colors and console size in the shortcut, and I wonder if there is a single variable, perhaps $host, that I could then send to the second session populated with the values of the first. I suspect not, because I think the only types that can be used as arguments are strings and numbers.
DOH: I was on the right track, with that comment about it looking like the script wasn't found. There are SPACES in that path, so I need
$psArguments = #('-nologo', '-noExit', '-executionpolicy', 'bypass', '-file',"`"$PSCommandPath`"")
Now I just need to deal with resizing the console. I can live without fonts and colors, but the width and text wrapping I need to address.

How to prevent input from displaying in console while script is running

I have a script that runs several loops of code and relies on specific input at various phases in order to advance. That functionality is working. My current issue revolves around extraneous input being supplied by the user displaying on screen in the console window wherever I have the cursor position currently aligned.
I have considered ignoring this issue since the functionality of the script is intact, however, I am striving for high standards with the console display of this script, and I would like to know a way to disable all user input period, unless prompted for. I imagine the answer has something to do with being able to command the Input Buffer to store 0 entries, or somehow disabling and then re-enabling the keyboard as needed.
I have tried using $HOST.UI.RawUI.Flushinputbuffer() at strategic locations in order to prevent characters from displaying, but I don't think there's anywhere I could put that in my loop that will perfectly block all input from displaying during code execution (it works great for making sure nothing gets passed when input is required, though). I've tried looking up the solution, but the only command I could find for manipulating the Input Buffer is the one above. I've also tried strategic implementation of the $host.UI.RawUI.KeyAvailable variable to detect keystrokes during execution, then $host.UI.RawUI.ReadKey() to determine if these keystrokes are unwanted and do nothing if they are, but the keystrokes still display in the console no matter what.
I am aware that this code is fairly broken as far as reading the key to escape the loop goes, but bear with me. I hashed up this example just so that you could see the issue I need help eliminating. If you hold down any letter key during this code's execution, you'll see unwanted input displaying.
$blinkPhase = 1
# Set Coordinates for cursor
$x = 106
$y = 16
$blinkTime = New-Object System.Diagnostics.Stopwatch
$blinkTime.Start()
$HOST.UI.RawUI.Flushinputbuffer()
do {
# A fancy blinking ellipses I use to indicate when Enter should be pressed to advance.
$HOST.UI.RawUI.Flushinputbuffer()
while ($host.UI.RawUI.KeyAvailable -eq $false) {
if ($blinkTime.Elapsed.Milliseconds -gt 400) {
if ($blinkPhase -eq 1) {
[console]::SetCursorPosition($x,$y)
write-host ". . ." -ForegroundColor gray
$blinkPhase = 2
$blinkTime.Restart()
} elseif ($blinkPhase -eq 2) {
[console]::SetCursorPosition($x,$y)
write-host " "
$blinkPhase = 1
$blinkTime.Restart()
}
}
start-sleep -m 10
}
# Reading for actual key to break the loop and advance the script.
$key = $host.UI.RawUI.ReadKey()
} while ($key.key -ne "Enter")
The expected result is that holding down any character key will NOT display the input in the console window while the ellipses is blinking. The actual result, sans error message, is that a limited amount of unwanted/unnecessary input IS displaying in the console window, making the script look messy and also interfering with the blinking process.
What you're looking for is to not echo (print) the keys being pressed, and that can be done with:
$key = $host.UI.RawUI.ReadKey('IncludeKeyDown, NoEcho')
Also, your test for when Enter was pressed is flawed[1]; use the following instead:
# ...
} while ($key.Character -ne "`r")
Caveat: As of at least PSReadLine version 2.0.0-beta4, a bug causes $host.UI.RawUI.KeyAvailable to report false positives, so your code may not work as intended - see this GitHub issue.
Workaround: Use [console]::KeyAvailable instead, which is arguably the better choice anyway, given that you're explicitly targeting a console (terminal) environment with your cursor-positioning command.
As an aside: You can simplify and improve the efficiency of your solution by using a thread job to perform the UI updates in a background thread, while only polling for keystrokes in the foreground:
Note: Requires the ThreadJob module, which comes standard with PowerShell Core, and on Windows PowerShell can be installed with Install-Module ThreadJob -Scope CurrentUser, for instance.
Write-Host 'Press Enter to stop waiting...'
# Start the background thread job that updates the UI every 400 msecs.
# NOTE: for simplicity, I'm using a simple "spinner" here.
$jb = Start-ThreadJob {
$i=0
while ($true) {
[Console]::Write("`r{0}" -f '/-\|'[($i++ % 4)])
Start-Sleep -ms 400
}
}
# Start another thread job to do work in the background.
# ...
# In the foreground, poll for keystrokes in shorter intervals, so as
# to be more responsive.
While (-not [console]::KeyAvailable -or ([Console]::ReadKey($true)).KeyChar -ne "`r" ) {
Start-Sleep -Milliseconds 50
}
$jb | Remove-Job -Force # Stop and remove the background UI thread.
Note the use of [Console]::Write() in the thread job, because Write-Host output wouldn't actually be passed straight through to the console.
[1] You tried to access a .Key property, which only the [SystemConsoleKeyInfo] type returned by [console]::ReadKey() has; the approximate equivalent in the $host.UI.rawUI.ReadKey() return type, [System.Management.Automation.Host.KeyInfo], is .VirtualKeyCode, but its specific type differs, so you can't (directly) compare it to "Enter"; The latter type's .Character returns the actual [char] instance pressed, which is the CR character ("`r") in the case of Enter.

How to check if NumLock is enabled

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}');
}
}

Check if a Powershell script has frozen using a separate script

So I'm preforming some automated testing using powershell in jenkins. I'm testing a web application where I must fill out forms, retrieve values, etc.
It is all fine but the web app contains some pop up messages that appear every now and then, which causes the main script to freeze until they are manually closed in the application. Below is a link to a stack overflow thread with a similar problem.
Powershell Website Automation: Javascript Popup Box Freeze
I followed the advice of the first answer. I created a separate powershell script that is executing constantly and can tell if there is a pop up window present (as they have their own process ids, so if there is more then one iexplore process id present it must be a popup) and then uses sendkeys to close it.
Main Script example:
#start application
start C:\Users\Webapp
#start the monitor script
Start-Process Powershell.exe -Argumentlist "-file C:\Users\Monitor.ps1"
#Get app as object
$app = New-Object -ComObject Shell.Application
$ClientSelectPage = $app.Windows() | where {$_.LocationURL -like "http:webapp.aspx"}
#Input value to cause popup message
$MemNumberInput = $ClientSelectPage.Document.getElementByID("MemNum")
$MemNumberInput.Select()
$MemNumberInput.value = "22"
$FindBtn.click()
It is at this point my script will freeze (as pop up window appears to tell me info abotu the client I've inserted) If this popup can be seen as a process, the monitor code will close it.
Example of monitor
$i = 0
while($i -eq 0)
{
#Check what process are currently running under the webapps name
$Mainprocid = Get-Process | where {$_.mainWindowTitle -like "*webapp*" } | select -expand id
$Mainprocid.COUNT
$integer = [int]$Mainprocid
#If there is only one process, no action
if( $Mainprocid.count -eq 1)
{
echo "no popup"
}
else
{
if($integer -eq '0')
{
#If there are no processes close the script
$i = 1
echo "close process"
}
else
#If there are two processes one must be a pop, send 'enter' to the app
{
echo "POP UP!"
$title = Get-Process |where {$_.mainWindowTItle -like "*webapp*"}
#Code to sendkeys 'ENTER' to the application to close the popup follows here
}
}
}
However, for whatever reason, some pop ups cannot be found as processes and the monitor script is useless with them. These are few and far between, so I figured the best way was for the monitor script to check and see if the main script has frozen for a certain amount of time. If so, it can use the sendkeys method it does for the other popups.
Is there a way for me to check and see if the main script has frozen, from the monitor script? I understand I could pass a parameter from the main script every now and then, to let the monitor script know it is still active, but this seems like a messy way of doing it, and an alternative method would be preferable.
Both scripts are saved as .ps1 files.

Powershell: Passing command options forward slash changing to backward

I am trying to use the invoke (ii) command to open an access database that has command line options. What I would like to have executed is below (yes there is a space in the name of the access database). The database is in the same folder as the Powershell script.
What I want: program name.accdb /cmd Rester
What I get: program name.accdb \cmd Rester
The exact commands I am using are:
$Path_To_EXE = "program name.accdb /cmd Rester"
&ii $Path_To_EXE
I am new to Powershell and have done some searching but can't seem to find an answer. I can create a work around by creating a separate .bat file but that seems like going backwards.
Thoughts?
You should also give a shot to the start-process cmdlet :
$Path_To_EXE = "c:\program.exe"
#Notice the simple quotes ...
$Arguments = #( "name.accdb", '/cmd' , "Rester" )
start-process -FilePath $Path_To_EXE -ArgumentList $Arguments -Wait
I'm not quite sure of the format of the answer you'll get tough ...
for database interaction, I'll rather use JGreenwell's Approach, since the answer that you'll get will be much easier to read/debug ...
Let me know if it works.
If you want to run a VBA script while passing it a parameter with powershell:
$aApp = New-Object -ComObject access.application
$aApp.Application.OpenCurrentDatabase("some program.accdb")
$aApp.Application.Run("VBAScriptName", [ref] "Raster")
First according to Microsoft Support you can use ;; for /cmd from the command line. Second because of the way call quotes and dequotes variables you have to include the /cmd flag separate from the variable (well, its the easiest way). Third, you might consider creating a new com-object to handle running Access with Powershell as it allows for a lot more options (just ask and I can add some examples of this). This being said try:
$Path_To_EXE = "program name.accdb"
&ii $Path_To_EXE ;;Rester #Try ;;"Rester" if it doesn't work.
#if that works then its a problem in Rester
#fyi another way is:
$Path_To_EXE = #("program name.accdb", ";;Rester")
&ii $Path_To_EXE
If you want to use an ActiveX Object Controller to open and perform operations on Access look at this blog from technet <- Read the link there are pitfalls to avoid.
$adOpenStatic = 3
$adLockOptimistic = 3
$objConnection = New-Object -com "ADODB.Connection"
$objRecordSet = New-Object -com "ADODB.Recordset"
$objConnection.Open("Provider = Microsoft.Jet.OLEDB.4.0; Data Source = C:\Scripts\Test.mdb")
$objRecordset.Open("Select * From Computers", $objConnection,$adOpenStatic,$adLockOptimistic)
$objRecordSet.AddNew()
$objRecordSet.Fields.Item("ComputerName").Value = "atl-ws-001"
$objRecordSet.Fields.Item("SerialNumber").Value = "192ATG43R"
$objRecordSet.Update()
$objRecordSet.Close()
$objConnection.Close()