Running a script when you launch a certain Program - powershell

Is it possible to run a Script when launching a particular file? (Access in this case) If so, is it possible using the windows task scheduler? Also, keep in mind I used powershell to develop the script.

Maybe you can use a WMI event watcher. This snippet waits for calc.exe to be launched, then runs a script to adjust the priority to low. Afterwords it loops in waiting for the next instance of calc.exe to be spawned.
PowerShell script that runs in background changing specific process priority to low whenever it is ran
$prog = Get-Process -Name calc | ?{$_.PriorityClass = [System.Diagnostics.ProcessPriorityClass]::Idle}
while($true)
{
$Query = "select * from __instanceCreationEvent within 1 where targetInstance isa 'win32_Process' AND TargetInstance.Name = 'calc.exe'"
$Eventwatcher = New-Object management.managementEventWatcher $Query
$Event = $Eventwatcher.waitForNextEvent()
$prog = Get-Process -Name calc | ?{$_.PriorityClass = [System.Diagnostics.ProcessPriorityClass]::Idle}
}
Additional info.
http://blogs.technet.com/b/heyscriptingguy/archive/2012/06/08/an-insider-s-guide-to-using-wmi-events-and-powershell.aspx
http://blogs.technet.com/b/heyscriptingguy/archive/2009/01/19/how-can-i-be-notified-when-a-process-begins.aspx
another example.
https://superuser.com/questions/693596/how-to-know-which-service-is-responsible-for-any-exe-running/693601#693601

Related

PowerShell Remoting, Eventing, Runspace - sticky Event Subscriber

I am currently stuck at a problem involving a WMI Event subscriber on a remote session running in a background runspace.
Below is the representative code that reproduces the issue (the real code is too long to share here) but, essentially, through a PS script I remotely install advertised WSUS updates with reboots when necessary. It takes about 90 minutes end to end.
The issue I am trying to solve at the moment is that during the course of patching, the support staff inadvertently log in to the the server being remotely patched and do their admin activities. To remind them, I want to display a pop up message as soon as the user logs in when my remote script is running. I am trying to do it using a background runspace plugged into the main patching script. It makes use of WMI eventing on the target server (which is being patched) to monitor user logons and display message as soon as it detects one. The below code is working as I expect. It even survives target server reboots.
$RemoteServerName = 'Server1.contoso.com'
$UserLogonAlertScriptBlock = {
param ($SyncedHashTable)
$RemoteServerName = $SyncedHashTable.target
try {
$Session = New-PSSession -ComputerName $RemoteServerName -ErrorAction Stop
} catch {}
while($true){
if($Session.State -eq 'Opened') {
$RemoteMonitoringJob = Invoke-Command -Session $Session -AsJob -ScriptBlock {
$null = Register-WMIEvent -Query "SELECT * FROM __InstanceCreationEvent WITHIN 3 WHERE TargetInstance ISA 'Win32_LogonSession'" -SourceIdentifier 'User.Logon'
Wait-Event -SourceIdentifier "User.Logon" -Timeout 7200 | ForEach-Object {
msg * /TIME:7200 /V "User logon detected" | Out-Null
$_ | Remove-Event
}
}
while($RemoteMonitoringJob.State -in #('NotStarted','Running')) {
Start-Sleep -Seconds 1
}
} else {
while($true) {
try {
$Session = New-PSSession -ComputerName $RemoteServerName -ErrorAction Stop
} catch {}
if($Session.State -eq 'Opened') {
break
}
Start-Sleep -Seconds 1
}
}
}
}
$Runspace = [runspacefactory]::CreateRunspace()
$PowerShell = [powershell]::Create()
$PowerShell.runspace = $Runspace
$SyncedHashTable = [hashtable]::Synchronized(#{})
$SyncedHashTable.host = $host
$SyncedHashTable.target = $RemoteServerName
$Runspace.Open()
$handle = $PowerShell.AddScript($UserLogonAlertScriptBlock).AddArgument($SyncedHashTable).BeginInvoke()
Write-Host '$(Get-Date): Long running script execution targeting $RemoteServerName has started'
Start-Sleep -Seconds 120 # it usually runs for upto 90 minutes, with remote reboots of $RemoteServerName
Write-Host "$(Get-Date): The script execution has completed"
### The code that cleans up the sticky event subscriber on the target server needs to be added here
The part I am stuck at is after the script completes its execution. The wsmanprovhost.exe running on the target server continues to stick around and shows alert messages when new users log on. I think it's because of the WMI event listener still being active on the box, not releasing the remote PS session.
In the above code, I need help close that remote listener so wsmanprovhost.exe disappears.
Could you please help?
PS. I have referred to #mklement0 's response in the following post but still no joy: The RunSpace and its closure
Update:
I have managed to address the challenge by adding a Boolean flag into the SyncedHashtable which is passed to the background runspace. When I want to stop the remote logon monitoring, in the main script I flip the flag. Since it's inside a synced hashtable, I can monitor that inside the runspace and terminate the remote invoke command job in the run space. But I still had to forcibly kill the remote wsmprovhost.exe as it refuses to go. I could do it by getting the pid of the remote PS session in advance. Not the most elegant way to close a remote PS session but it does the job for me. It's just that since the remote session is continuously monitoring for user logon event, there does not appear to be a way to run a piece of code in that session to unsubscribe the WMI event source. Will do more testing to see if there is any side effect.

PowerShell script lacks consistency when run through task scheduler, am I missing something or is this Windows being Windows?

I am a beginner in PowerShell.
I have created myself a PowerShell program to act as my alarm clock in the morning. I have task scheduler executing it on a time trigger. The problem i am having is a lack of consistency. Sometimes it will run properly without any interference, other times it will open PowerShell, error out and close immediately without executing (no error code). When i execute it myself with a double click, it seems to work just fine.
Execution Policy = All-Signed
Task Scheduler
Trigger Tab:
Trigger: Daily
Details: At 8:00 AM every Day
Status: Enabled
Action Tab:
Action: Start a Program
Program/Script: PowerShell.exe
Add arguments: -NoExit D:\Programs\AlarmClock\AlarmClockScript.ps1
Script:
#define loop start state
$Snoozevar = 'Yes'
#Import form module (for menu)
[reflection.assembly]::LoadWithPartialName("System.Windows.forms") | Out-Null
#Menu
$snoozeTxtBox = New-Object System.Windows.Forms.Button
$snoozeTxtBox.Text = 'Snooze'
$snoozeTxtBox.Location = '50,15'
$snoozeTxtBox.Size = '60,23'
$snoozeTxtBox.DialogResult = [System.Windows.Forms.DialogResult]::Yes # 'Snooze' = Yes
$quitTxtBox = New-Object System.Windows.Forms.Button
$quitTxtBox.Text = 'I`m Up'
$quitTxtBox.Location = '125,15'
$quitTxtBox.Size = '50,23'
$quitTxtBox.DialogResult = [System.Windows.Forms.DialogResult]::No # 'I`m Up' = No
$basicForm = New-Object System.Windows.Forms.Form
$basicForm.StartPosition = 'CenterScreen'
$basicForm.Size = '250,100'
$basicForm.Controls.Add($snoozeTxtBox)
$basicForm.Controls.Add($quitTxtBox)
$basicForm.TopMost = $true
while ($Snoozevar -eq 'Yes'){
Start-Process "D:\Programs\Winamp\winamp.exe" /PLAY # Start Winamp /autoplay
Start-Process D:\Programs\nircmd\nircmd.exe -ArgumentList " setsysvolume 65535" #Max Volume
$Snoozevar = $basicForm.ShowDialog() # Call Menu, assign output to $Snoozevar
$pro = Get-Process -Name "winamp" # Kill winamp post menu selection
Stop-Process -Id $pro.Id
$pro = ""
if ($Snoozevar -eq 'No'){ # Clean up powershell
$pro = Get-Process -Name powershell
Stop-Process $pro
} #end if
$rngvar = Get-Random -Minimum 540 -Maximum 720 # Time to Snooze (9-12 minutes)
Start-Sleep -Seconds $rngvar
} #end while
# SIG # Begin signature block
...
# SIG # End signature block
This is my first time asking a question here, please forgive and point out mistakes in forum standards.
Thank You in advance!
Here's a summary of the things that can be done to diagnose an inconsistend scheduled task execution.
Since your task is interactive (have a form), Run whether user is logged on or not should be left unchecked. While you'd normally want it checked most of the time, tasks that interact with the user (popup / forms / etc...) won't work properly if thus option is checked.
Add Start-Transcript -Path "Some\Path\AlarmLog_$(get-date -f 'yyyyMMdd').txt at the beginning of your file and Stop-Transcript at the end to gain more insight on when it fail
Make sure to check the Conditions tab as there are additional constraint that could affect task execution (eg: By default, task will not execute if on battery power)
If the task is running under a different user or in a different context (eg: with Highest priviledges), try to execute your script in that context to see if it fail (for instance, start Vscode / ISE using that context and run the task)
If you have multiple set of operations, you can wrap them in Try / Catch block so if one set fail, you can perform additional logging and also decide whether or not the task should be cancelled altogether or continue through. (Note: When using try/catch, you'll want to set -ErrorAction Stop on the functions that have that parameter to make sure they get caught properly in the catch block.
References
Msdocs - Start-Transcript
Msdocs - Task scheduler -Security context for running task

PowerShell ProccesorAffinity CPU Designation

I am trying to set a script that, when a service is restarted, the script resets the processor affinity to the settings I want.
The code I have used for other projects has worked in the past, but is now failing.
$Process = Get-Process -Name 'SpaceEngineersDedicated'
$Process.ProcessorAffinity = 254
$Process = Get-Process -Name 'SpaceEngineersDedicated'
$Process.ProcessorAffinity = 255
If I had to guess, this is because this is the first time I have tried to set up such a script on a server with two CPUs. (254,255 was for a computer with only one CPU) The server has 16 cores/threads total.
The goal of this script is to force the service to use all cores, as it only uses one core/thread (Core 0, Node 0) originally. I can do this manually from Task Manager, so I am not sure why it fails.
The error the code spits out says that the property ProcessorAffinity cannot be found on this object.
Your Get-Process call is returning multiple processes. In the below syntax, we force these to come back as an array of processes and loop over them to set the property:
#(Get-Process -Name 'SpaceEngineersDedicated') |
ForEach-Object { $_.ProcessorAffinity = 255 }
You cannot utilize Member Enumeration to set properties if more than one is returned:
## This doesn't work unless .Count = 1
#(Get-Process -Name 'SpaceEngineersDedicated').ProcessorAffinity = 255

scheduled tasks don't show up in Get-ScheduledTask result

I have defined some scheduled task using Windows Task Scheduler GUI under "" [default] path but when i run Get-ScheduledTask in powershell, it does not return them. why?
I have tried with Get-ScheduledTask -TaskName "MyTaskName" with one of my task name but it comes up with "No MSFT_ScheduledTask objects found with property 'TaskName' equal to 'MyTaskName'"
Actually I have tried https://library.octopusdeploy.com/step-template/actiontemplate-windows-scheduled-task-disable but it doesn't work so I have tried running script directly.
UPDATE
I have found the following script to get task list on http://www.fixitscripts.com/problems/getting-a-list-of-scheduled-tasks:
# PowerShell script to get scheduled tasks from local computer
$schedule = new-object -com("Schedule.Service")
$schedule.connect()
$tasks = $schedule.getfolder("\").gettasks(0)
$tasks | Format-Table Name , LastRunTime # -AutoSize
IF($tasks.count -eq 0) {Write-Host “Schedule is Empty”}
Read-Host
UAC
The result is likely affected by UAC. To see everything try right clicking the PowerShell icon, select Run as Administrator and then run your Get-ScheduledTask command again and see if that changes anything.
Further reading: http://david-homer.blogspot.co.uk/2017/10/not-all-scheduled-tasks-show-up-when.html
Have you tried using a com object? This code works for me:
# FOR A REMOTE MACHINE
$s = 'SERVER_NAME' # update this with server name
($TaskScheduler = New-Object -ComObject Schedule.Service).Connect($s)
# FOR LOCAL MACHINE
($TaskScheduler = New-Object -ComObject Schedule.Service).Connect()
#now we can query the schedules...
cls;$TaskScheduler.GetFolder('\').GetTasks(0) | Select Name, State, Enabled, LastRunTime, LastTaskResult | Out-GridView
This code will retrieve a particular task and enable it:
$task = $TaskScheduler.GetFolder('\').GetTask("TASKNAME")
$task.Enabled = $true
When running Get-ScheduledTask from a user-level prompt, even if the user is an administrator, they will see only the tasks that they can read with user-level permissions. Seeing them in the TaskSchd.Msc window indicates that program is running with different permissions.
Therefore, running a PowerShell prompt as administrator solves the problem.
The same issue occurs when using SchTasks.exe from a command prompt.

Web service call, if application is running

I'm looking for a way to execute a web form submittal if an application is running. I'm not sure the best approach, but I did create a PowerShell script that accomplishes what I want.
while($true) {
(Invoke-WebRequest -Method post 'Http://website.com').Content;
Start-Sleep -Seconds 600;
}
Now what I'd like to do is run this only when an application is running and then quit if the application is no longer running. I suspect maybe a Windows service would be the answer? If so, any idea how I could accomplish this?
I had also thought about running this as a Google Chrome extension, but then my googlefu was exhausted. For Chrome, I would just need the script and no need to check on the .exe.
Any thoughts or help would be appreciated. Again, I'm way out of my depth here but have found a need to create something so dummy steps would be much desired.
If you know the name of the process that runs for the application, you can do the following:
$processname = "thing"
# Wait until the process is detected
Do {
Sleep 60
} Until (Get-Process $processName)
# Once it is detected, run the script
# < SCRIPT RUN CODE HERE >
While (1) {
# Monitor the process to make sure it is still running
If (Get-Process $processName) {
Continue
}
Else {
# Stop the script, because the process isn't running.
# < SCRIPT STOP CODE HERE >
# Wait until the process is detected again
Do {
Sleep 60
} Until (Get-Process $processName)
# Once it is detected again, run the script
# < SCRIPT RUN CODE HERE >
}
# You can add in a delay here to slow down the loop
# Sleep 60
}
I think what you're looking for might be WMI eventing. You can register for (and respond to) events that occur within WMI, such as:
When a process starts or stops
When a service starts or stops
When a process exceeds a certain amount of memory usage
When a new version device driver is installed
When a computer is assigned to a new organizational unit
When a user logs on or off
When an environment variables changes
When a laptop battery drops below a certain threshold
Thousands of other cases
To register for WMI events, use the Register-WmiEvent cmdlet. You can use the -Action parameter to declare what PowerShell statements to execute when a matching event is detected. Here is a simple example:
# 1. Start notepad.exe
notepad;
# 2. Register for events when Notepad disappears
# 2a. Declare the WMI event query
$WmiEventQuery = "select * from __InstanceDeletionEvent within 5 where TargetInstance ISA 'Win32_Process' and TargetInstance.Name = 'notepad.exe'";
# 2b. Declare the PowerShell ScriptBlock that will execute when event is matched
$Action = { Write-Host -ForegroundColor Green -Object ('Process stopped! {0}' -f $event.SourceEventArgs.NewEvent.TargetInstance.Name) };
# 2c. Register for WMI events
Register-WmiEvent -Namespace root\cimv2 -Query $WmiEventQuery -Action $Action -SourceIdentifier NotepadStopped;
# 3. Stop notepad.exe
# Note: For some reason, if you terminate the process as part of the same thread, the event
# doesn't seem to fire correctly. So, wrap the Stop-Process command in Start-Job.
Start-Job -ScriptBlock { Stop-Process -Name notepad; };
# 4. Wait for event consumer (action) to fire and clean up the event registration
Start-Sleep -Seconds 6;
Unregister-Event -SourceIdentifier NotepadStopped;
FYI: I developed a PowerShell module called PowerEvents, which is hosted on CodePlex. The module includes the ability to register permanent WMI event subscriptions, and includes a 30+ page PDF document that helps you to understand WMI eventing. You can find this open-source project at: http://powerevents.codeplex.com.
If I were to adapt your code to something that is more practical for you, it might look something like the example below. You could invoke the code on a periodic basis using the Windows Task Scheduler.
# 1. If process is not running, then exit immediately
if (-not (Get-Process -Name notepad)) { throw 'Process is not running!'; return; }
# 2. Register for events when Notepad disappears
# 2a. Declare the WMI event query
$WmiEventQuery = "select * from __InstanceDeletionEvent within 5 where TargetInstance ISA 'Win32_Process' and TargetInstance.Name = 'notepad.exe'";
# 2b. Declare the PowerShell ScriptBlock that will execute when event is matched
# In this case, it simply appends the value of the $event automatic variable to a
# new, global variable named NotepadEvent.
$Action = { $global:NotepadEvent += $event; };
# 2c. Register for WMI events
Register-WmiEvent -Namespace root\cimv2 -Query $WmiEventQuery -Action $Action -SourceIdentifier NotepadStopped;
# 3. Wait indefinitely, or until $global:NotepadEvent variable is NOT $null
while ($true -and -not $global:NotepadEvent) {
Start-Sleep -Seconds 600;
(Invoke-WebRequest -Method post 'Http://website.com').Content;
}