Persistent PowerShell job - powershell

Is it possible to launch a PowerShell Job that persists after the creating script terminates? So far as I can tell, the Job is tied to the initiating script.
What I have now is this
$name = 'iexplore'
$waitTimeinMinutes = 30
Start-Job -argumentList #($name, $waitTimeinMinutes) -scriptblock {
param (
[string]$name,
[int]$waitTimeinMinutes
)
$cutoff = [DateTime]::Now.AddMinutes($waitTimeinMinutes)
while ([DateTime]::Now -lt $cutoff) {
if (Get-Process -name:$name -errorAction:silentlyContinue) {
Stop-Process -name:$name -force -errorAction:silentlyContinue
}
if (Get-Service $name -errorAction:silentlyContinue | Where-Object {$_.status -eq "running"}) {
Stop-Service $name -force -errorAction:silentlyContinue
}
Start-Sleep -s:10
}
}
It works as intend. For half an hour it watches for a Process or Service called iexplore and kills it if it is found. However, I need to continue processing a calling script, and this job is killed when the calling script terminates. I presume there must be a way to initiate a job separate from the calling script?

Related

Multithreading a powershell script and challenges

I have developed a script which does a lot of processing for a front end tool, now I am attempting to have the script run with multiple threads. It interacts a lot with SQL databases, this should not be a problem for multithreading as the database transactions are very short lived, and the queries well optimised.
what is the issue ?
#.\tester.ps1 -servers (1,'Server1',3,1),(2,'Server2',3,1) -output_folder 'C:\temp'
param ([array[]]$servers),$output_folder
for ($i = 0; $i -lt $servers.Count; $i++)
{
$myserverid = $servers[$i][0]
$myservername = $servers[$i][1]
$mylocationid = $servers[$i][2]
$myappid = $servers[$i][3]
write-output " $myserverid and $myservername and $mylocationid and $myappid"
invoke-sqlcmd -ServerInstance "$myservername" -query "select top 10 name from sysobjects" -Database "master"
}
The script file above will gets passed an array of servers and currently it will loop through the array one by one. A way for me to make the process faster is to run the script in parallel /run the script with multiple threads.
Research
I have looked at a technet script on https://gallery.technet.microsoft.com/scriptcenter/Run-a-PowerShell-script-991c8a42
Its not quite the same as my array is not just a list of servers, there will be other parameters sent with it.
What am I after
A way or pointer to make the script be able to run in parallel or an example using the provided script above.
Thanks in advance.
Extending my comment. In PowerShell v5, use Jobs and Workflows for Parallel use cases.
# Example using parallel jobs
$start = Get-Date
# get all hotfixes
$task1 = { Get-Hotfix }
# get all scripts in your profile
$task2 = { Get-Service | Where-Object Status -eq Running }
# parse log file
$task3 = { Get-Content -Path $env:windir\windowsupdate.log | Where-Object { $_ -like '*successfully installed*' } }
# run 2 tasks in the background, and 1 in the foreground task
$job1 = Start-Job -ScriptBlock $task1
$job2 = Start-Job -ScriptBlock $task2
$result3 = Invoke-Command -ScriptBlock $task3
# wait for the remaining tasks to complete (if not done yet)
$null = Wait-Job -Job $job1, $job2
# now they are done, get the results
$result1 = Receive-Job -Job $job1
$result2 = Receive-Job -Job $job2
# discard the jobs
Remove-Job -Job $job1, $job2
$end = Get-Date
# Example, using WorkFlow
workflow Test-WFConnection
{
param
(
[string[]]$Computers
)
foreach -parallel ($computer in $computers)
{
Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue
}
}

How to tail a log file while a process is running?

I'm running an exe from a PowerShell script. This executable writes its logs to a log file. I would like to continuously read and forward the logs from this file to the console while the executable is running.
Currently, I'm starting the exe like this:
$UNITY_JOB = Start-Job
-ScriptBlock { & "C:\Program Files\Unity\Hub\Editor\2019.2.11f1\Editor\Unity.exe" $args | Out-Null }
-ArgumentList $UNITY_ARGS
If I just do Get-Content $LOG_PATH -Wait at this point, I cannot detect when the exe terminates and the script blocks indefinitely.
If I start a second job for the logs, the output is not sent to the console:
$LOG_JOB = Start-Job -ScriptBlock { Get-Content $LOG_PATH -Wait }
(I need "real time" output, so I don't think Receive-Job would work)
I'd use a loop which ends when the job's status is Completed:
# Just to mock the execution
$extProgram = Start-Job -ScriptBlock { Start-Sleep -Seconds 30}
$file = 'C:\path\to\file.txt'
do {
cls
Get-Content $file -Tail $host.ui.RawUI.WindowSize.Height
Start-Sleep -Seconds 5 # Set any interval you need
} until ((Get-Job -Id $extProgram.id).State -eq "Completed")

Calling other PowerShell scripts within a PowerShell script

I'm trying to get one master PowerShell script to run all of the others while waiting 30-60 seconds to ensure that the tasks are completed. Everything else I tried wouldn't stop/wait for the first script and its processes to complete before going through all the others at the same time and would cause a restart automatically.
Main script, run as admin:
$LogStart = 'Log '
$LogDate = Get-Date -Format "dd-MM-yyy-hh-mm-ss"
$FileName = $LogStart + $LogDate + '.txt.'
$scriptList = #(
'C:\Scripts\1-OneDriveUninstall.ps1'
'C:\Scripts\2-ComputerRename.ps1'
);
Start-Transcript -Path "C:\Scripts\$FileName"
foreach ($script in $scriptList) {
Start-Process -FilePath "$PSHOME\powershell.exe" -ArgumentList "-Command '& $script'"
Write-Output "The $script is running."
Start-Sleep -Seconds 30
}
Write-Output "Scripts have completed. Computer will restart in 10 seconds."
Start-Sleep -Seconds 10
Stop-Transcript
C:\Scripts\3-Restart.ps1
1-OneDriveUninstall.ps1:
Set-ItemProperty -Path REGISTRY::HKEY_LOCAL_MACHINE\Software\Microsoft\windows\CurrentVersion\Policies\System -Name ConsentPromptBehaviorAdmin -Value 0
taskkill /f /im OneDrive.exe
C:\Windows\SysWOW64\OneDriveSetup.exe /uninstall
2-ComputerRename.ps1:
$computername = Get-Content env:computername
$servicetag = Get-WmiObject Win32_Bios |
Select-Object -ExpandProperty SerialNumber
if ($computername -ne $servicetag) {
Write-Host "Renaming computer to $servicetag..."
Rename-Computer -NewName $servicetag
} else {
Write-Host "Computer name is already set to service tag."
}
The log file shows:
Transcript started, output file is C:\Scripts\Log 13-09-2019-04-28-47.txt.
The C:\Scripts\1-OneDriveUninstall.ps1 is running.
The C:\Scripts\2-ComputerRename.ps1 is running.
Scripts have completed. Computer will restart in 10 seconds.
Windows PowerShell transcript end
End time: 20190913162957
They aren't running correctly at all though. They run fine individually but not when put into one master script.
PowerShell can run PowerShell scripts from other PowerShell scripts directly. The only time you need Start-Process for that is when you want to run the called script with elevated privileges (which isn't necessary here, since your parent script is already running elevated).
This should suffice:
foreach ($script in $scriptList) {
& $script
}
The above code will run the scripts sequentially (i.e. start the next script only after the previous one terminated). If you want to run the scripts in parallel, the canonical way is to use background jobs:
$jobs = foreach ($script in $scriptList) {
Start-Job -ScriptBlock { & $using:script }
}
$jobs | Wait-Job | Receive-Job

Job doing Stop-Job on self

Just looking for verification here. Can a job Stop-Job itself? I have a script that creates a job that suppresses a service for as long as the main script is running (by way of passed $PID) and I am currently using this
Start-Job -name:'SuppressAdAppMgrSvc' -argumentList $id -scriptBlock {
param (
$id
)
do {
if ((Get-Service AdAppMgrSvc -errorAction:silentlyContinue).Status -eq 'Running') {
Stop-Service AdAppMgrSvc -force -warningAction:silentlyContinue -errorAction:silentlyContinue
}
Start-Sleep -s 5
$powershellProcess = Get-Process -id:$id -errorAction:silentlyContinue
} while ($powershellProcess)
Stop-Job 'SuppressAdAppMgrSvc' -warningAction:silentlyContinue -errorAction:silentlyContinue
Remove-Job 'SuppressAdAppMgrSvc' -warningAction:silentlyContinue -errorAction:silentlyContinue
My thinking is the job will run, and when $PowershellProcess is no longer, then the Stop-Job would run. But I suspect the Remove-Job would not, since this is the very job that just got stopped. In general it probably isn't a problem, as 99% of the time I do a reboot when my script completes, but I am curious if there is a pattern for dealing with this? Or is it something of an edge case?
How do you expect Remove-Job to run inside a stopped job? And why would you want to do that from inside the job in the first place?
The job will automatically enter the Stopped state when the code in the scriptblock terminates, so all you need to do is to wait for that to happen and then remove the job:
Start-Job -Name 'SuppressAdAppMgrSvc' -ArgumentList $id -ScriptBlock {
...
} | Wait-Job | Remove-Job

How to Correctly Check if a Process is running and Stop it

What is the correct way of determining if a process is running, for example FireFox, and stopping it?
I did some looking around and the best way I found was this:
if((get-process "firefox" -ea SilentlyContinue) -eq $Null){
echo "Not Running"
}
else{
echo "Running"
Stop-Process -processname "firefox"
}
Is this the ideal way of doing it? If not, what the correct way of doing so?
The way you're doing it you're querying for the process twice. Also Lynn raises a good point about being nice first. I'd probably try something like the following:
# get Firefox process
$firefox = Get-Process firefox -ErrorAction SilentlyContinue
if ($firefox) {
# try gracefully first
$firefox.CloseMainWindow()
# kill after five seconds
Sleep 5
if (!$firefox.HasExited) {
$firefox | Stop-Process -Force
}
}
Remove-Variable firefox
If you don't need to display exact result "running" / "not runnuning", you could simply:
ps notepad -ErrorAction SilentlyContinue | kill -PassThru
If the process was not running, you'll get no results. If it was running, you'll receive get-process output, and the process will be stopped.
#jmp242 - the generic System.Object type does not contain the CloseMainWindow method, but statically casting the System.Diagnostics.Process type when collecting the ProcessList variable works for me. Updated code (from this answer) with this casting (and looping changed to use ForEach-Object) is below.
function Stop-Processes {
param(
[parameter(Mandatory=$true)] $processName,
$timeout = 5
)
[System.Diagnostics.Process[]]$processList = Get-Process $processName -ErrorAction SilentlyContinue
ForEach ($Process in $processList) {
# Try gracefully first
$Process.CloseMainWindow() | Out-Null
}
# Check the 'HasExited' property for each process
for ($i = 0 ; $i -le $timeout; $i++) {
$AllHaveExited = $True
$processList | ForEach-Object {
If (-NOT $_.HasExited) {
$AllHaveExited = $False
}
}
If ($AllHaveExited -eq $true){
Return
}
Start-Sleep 1
}
# If graceful close has failed, loop through 'Stop-Process'
$processList | ForEach-Object {
If (Get-Process -ID $_.ID -ErrorAction SilentlyContinue) {
Stop-Process -Id $_.ID -Force -Verbose
}
}
}
To start with process-killing, here python, my 2 cents:
Get-Process python3.9|Stop-Process
Thanks #Joey. It's what I am looking for.
I just bring some improvements:
to take into account multiple processes
to avoid reaching the timeout when all processes have terminated
to package the whole in a function
function Stop-Processes {
param(
[parameter(Mandatory=$true)] $processName,
$timeout = 5
)
$processList = Get-Process $processName -ErrorAction SilentlyContinue
if ($processList) {
# Try gracefully first
$processList.CloseMainWindow() | Out-Null
# Wait until all processes have terminated or until timeout
for ($i = 0 ; $i -le $timeout; $i ++){
$AllHaveExited = $True
$processList | % {
$process = $_
If (!$process.HasExited){
$AllHaveExited = $False
}
}
If ($AllHaveExited){
Return
}
sleep 1
}
# Else: kill
$processList | Stop-Process -Force
}
}