Job doing Stop-Job on self - powershell

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

Related

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")

Powershell: How to stop start-job?

I have the following job in powershell:
$DoIt = '...'
....
If ([IntPtr]::size -eq 8) {
start-job {
param($a) IEX $a
} -RunAs32 -Argument $DoIt | wait-job | Receive-Job
Get-Job | Stop-Job
Exit
Write-Host "exit powershell" -NoNewLine -ForegroundColor Green
}
else {
IEX $DoIt
}
simply put, how to stop or close powershell after executing the start-job function. What should I do?Tried some ways to find that it didn't work.
Remove-Job. Here's an example. If the job completes, you don't have to run stop-job. Although invoke-expression is almost always the wrong answer, and you have to watch user input to it. I'm in osx.
PS /Users/js> $job = start-job { param($a) sleep $a } -Args 3600
PS /Users/js> get-job
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
11 Job11 BackgroundJob Running True localhost param($a) sleep $a
PS /Users/js> stop-job $job
PS /Users/js> remove-job $job # or just remove-job -force
PS /Users/js> get-job # none
how to stop or close powershell after executing the start-job function.
If you exit your session before the background job launched with Start-Job has completed, you'll stop (abort) it implicitly, because PowerShell cleans up pending background jobs automatically when the session that created them exits.
Therefore, if you want to launch a background operation that keeps running when the session that initiated it exits, you'll have to use a different mechanism, such as Start-Process -WindowStyle Hidden ... (Windows-only).
To wait for the background job to finish, use Wait-Job or - combined with retrieving its output - Receive-Job -Wait.
Note that this is only useful if you perform other actions in between starting the background job and waiting for its completion - otherwise, you could just run the operations directly, without the need for a background job.

Persistent PowerShell job

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?

Powershell Start-Jobs throttling

I am having trouble with throttling of jobs and "hung" or "failed" jobs. Here is basically what I am trying to do.
$allServers = Import-Csv "C:\temp\input.csv"
$job = $allServers | % {
while (#(Get-Job -State Running).Count -ge 6) {
Start-Sleep -Seconds 2
}
Start-Job -Name $_.computerName -ScriptBlock {
param ($cpn,$dom)
(DO QUERIES HERE)
(OUTPUT TO OBJECT HERE)
} -ArgumentList $_.computerName,$_.Domain
}
$jobsdone = $job | Wait-Job | Receive-Job
I would like to run 5 concurrent jobs, simple enough.
The issue is when I query a server that does not respond, the job hangs and the script never ends. I have tried adding...
Wait-Job -Name $_.computerName -Timeout 20
...above the last curly brace, but all that does is effectively limit the concurrence to one thread, until 20 seconds goes by, then abandoning the hung job to do other jobs. The whole script still doesn't finish in that instance.
This code works fine without the throttling and job waiting, so long as I don't get a non-responsive server.
Inside your while loop check the length of time the job has been running. If that is greater than some timeout you determine, then stop that job e.g.:
while (#(Get-Job -State Running).Count -ge 6) {
$now = Get-Date
foreach ($job in #(Get-Job -State Running)) {
if ($now - (Get-Job -Id $job.id).PSBeginTime -gt [TimeSpan]::FromMinutes(2)) {
Stop-Job $job
}
}
Start-Sleep -sec 2
}
You might want to check out this PowerShell team blog post on how to throttle jobs using a queue.

Powershell: Run multiple jobs in parralel and view streaming results from background jobs

Overview
Looking to call a Powershell script that takes in an argument, runs each job in the background, and shows me the verbose output.
Problem I am running into
The script appears to run, but I want to verify this for sure by streaming the results of the background jobs as they are running.
Code
###StartServerUpdates.ps1 Script###
#get list of servers to update from text file and store in array
$servers=get-content c:\serverstoupdate.txt
#run all jobs, using multi-threading, in background
ForEach($server in $servers){
Start-Job -FilePath c:\cefcu_it\psscripts\PSPatch.ps1 -ArgumentList $server
}
#Wait for all jobs
Get-Job | Wait-Job
#Get all job results
Get-Job | Receive-Job
What I am currently seeing:
Id Name State HasMoreData Location Command
-- ---- ----- ----------- -------- -------
23 Job23 Running True localhost #patch server ...
25 Job25 Running True localhost #patch server ...
What I want to see:
Searching for approved updates ...
Update Found: Security Update for Windows Server 2003 (KB2807986)
Update Found: Windows Malicious Software Removal Tool - March 2013 (KB890830)
Download complete. Installing updates ...
The system must be rebooted to complete installation.
cscript exited on "myServer" with error code 3.
Reboot required...
Waiting for server to reboot (35)
Searching for approved updates ...
There are no updates to install.
cscript exited on "myServer" with error code 2.
Servername "myServer" is fully patched after 2 loops
I want to be able to see the output or store that somewhere so I can refer back to be sure the script ran and see which servers rebooted, etc.
Conclusion:
In the past, I ran the script and it went through updating the servers one at a time and gave me the output I wanted, but when I started doing more servers - this task took too long, which is why I am trying to use background jobs with "Start-Job".
Can anyone help me figure this out, please?
You may take a look at the module SplitPipeline.
It it specifically designed for such tasks. The working demo code is:
# import the module (not necessary in PS V3)
Import-Module SplitPipeline
# some servers (from 1 to 10 for the test)
$servers = 1..10
# process servers by parallel pipelines and output results immediately
$servers | Split-Pipeline {process{"processing server $_"; sleep 1}} -Load 1, 1
For your task replace "processing server $_"; sleep 1 (simulates a slow job) with a call to your script and use the variable $_ as input, the current server.
If each job is not processor intensive then increase the parameter Count (the default is processor count) in order to improve performance.
Not a new question but I feel it is missing an answer including Powershell using workflows and its parallel possibilities, from powershell version 3. Which is less code and maybe more understandable than starting and waiting for jobs, which of course works good as well.
I have two files: TheScript.ps1 which coordinates the servers and BackgroundJob.ps1 which does some kind of check. They need to be in the same directory.
The Write-Output in the background job file writes to the same stream you see when starting TheScript.ps1.
TheScript.ps1:
workflow parallelCheckServer {
param ($Servers)
foreach -parallel($Server in $Servers)
{
Invoke-Expression -Command ".\BackgroundJob.ps1 -Server $Server"
}
}
parallelCheckServer -Servers #("host1.com", "host2.com", "host3.com")
Write-Output "Done with all servers."
BackgroundJob.ps1 (for example):
param (
[Parameter(Mandatory=$true)] [string] $server
)
Write-Host "[$server]`t Processing server $server"
Start-Sleep -Seconds 5
So when starting the TheScript.ps1 it will write "Processing server" 3 times but it will not wait for 15 seconds but instead 5 because they are run in parallel.
[host3.com] Processing server host3.com
[host2.com] Processing server host2.com
[host1.com] Processing server host1.com
Done with all servers.
In your ForEach loop you'll want to grab the output generated by the Jobs already running.
Example Not Tested
$sb = {
"Starting Job on $($args[0])"
#Do something
"$($args[0]) => Do something completed successfully"
"$($args[0]) => Now for something completely different"
"Ending Job on $($args[0])"
}
Foreach($computer in $computers){
Start-Job -ScriptBlock $sb -Args $computer | Out-Null
Get-Job | Receive-Job
}
Now if you do this all your results will be mixed. You might want to put a stamp on your verbose output to tell which output came from.
Or
Foreach($computer in $computers){
Start-Job -ScriptBlock $sb -Args $computer | Out-Null
Get-Job | ? {$_.State -eq 'Complete' -and $_.HasMoreData} | % {Receive-Job $_}
}
while((Get-Job -State Running).count){
Get-Job | ? {$_.State -eq 'Complete' -and $_.HasMoreData} | % {Receive-Job $_}
start-sleep -seconds 1
}
It will show all the output as soon as a job is finished. Without being mixed up.
If you're wanting to multiple jobs in-progress, you'll probably want to massage the output to help keep what output goes with which job straight on the console.
$BGList = 'Black','Green','DarkBlue','DarkCyan','Red','DarkGreen'
$JobHash = #{};$ColorHash = #{};$i=0
ForEach($server in $servers)
{
Start-Job -FilePath c:\cefcu_it\psscripts\PSPatch.ps1 -ArgumentList $server |
foreach {
$ColorHash[$_.ID] = $BGList[$i++]
$JobHash[$_.ID] = $Server
}
}
While ((Get-Job).State -match 'Running')
{
foreach ($Job in Get-Job | where {$_.HasMoreData})
{
[System.Console]::BackgroundColor = $ColorHash[$Job.ID]
Write-Host $JobHash[$Job.ID] -ForegroundColor Black -BackgroundColor White
Receive-Job $Job
}
Start-Sleep -Seconds 5
}
[System.Console]::BackgroundColor = 'Black'
You can get the results by doing something like this after all the jobs have been received:
$array=#()
Get-Job -Name * | where{$array+=$_.ChildJobs.output}
.ChildJobs.output will have anything that was returned in each job.
function OutputJoblogs {
[CmdletBinding(DefaultParameterSetName='Name')]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[System.Management.Automation.Job] $job,
[Parameter(Mandatory=$true, Position=1)]
[string] $logFolder,
[Parameter(Mandatory=$true, Position=2)]
[string] $logTimeStamp
)
#Output All logs
while ($job.sate -eq "Running" -or $job.HasMoreData){
start-sleep -Seconds 1
foreach($remotejob in $job.ChildJobs){
if($remotejob.HasMoreData){
$output=(Receive-Job $remotejob)
if($output -gt 0){
$remotejob.location +": "+ (($output) | Tee-Object -Append -file ("$logFolder\$logTimeStamp."+$remotejob.Location+".txt"))
}
}
}
}
#Output Errors
foreach($remotejob in $job.ChildJobs){
if($remotejob.Error.Count -gt0){$remotejob.location +": "}
foreach($myerr in $remotejob.Error){
$myerr 2>&1 | Tee-Object -Append -file ("$logFolder\$logTimeStamp."+$remotejob.Location+".ERROR.txt")
}
if($remotejob.JobStateInfo.Reason.ErrorRecord.Count -gt 0){$remotejob.location +": "}
foreach($myerr in $remotejob.JobStateInfo.Reason.ErrorRecord){
$myerr 2>&1 | Tee-Object -Append -file ("$logFolder\$logTimeStamp."+$remotejob.Location+".ERROR.txt")
}
}
}
#example of usage
$logfileDate="$((Get-Date).ToString('yyyy-MM-dd-HH.mm.ss'))"
$job = Invoke-Command -ComputerName "servername1","servername2" -ScriptBlock {
for ($i=1; $i -le 5; $i++) {
$i+"`n";
if($i -gt 2){
write-error "Bad thing happened"};
if($i -eq 4){
throw "Super Bad thing happened"
};
start-sleep -Seconds 1
}
} -asjob
OutputJoblogs -Job $job -logFolder "$PSScriptRoot\logs" -logTimeStamp $logfileDate