Unexpected behavior with Get-Job when only one job running - powershell

I'm new to Powershell and I'm having an issue with the Get-Job command. In my script, I'm testing out multi-threading and am doing something like:
$Program = {
"Thread " + $args[0];
Start-Sleep 5;
}
Start-Job $Program -ArgumentList #($i) | Out-Null
The Start-Job call is actually in a loop in which I'm creating multiple jobs. Below this, I have:
Get-Job
"Jobs Running: " + $(Get-Job -State Running).count
If there are multiple jobs running, I will get output like:
Id Name State HasMoreData Location Command
-- ---- ----- ----------- -------- -------
2201 Job2201 Running True localhost ...
2199 Job2199 Running True localhost ...
2197 Job2197 Running True localhost ...
2195 Job2195 Running True localhost ...
2193 Job2193 Completed True localhost ...
2191 Job2191 Completed True localhost ...
2189 Job2189 Completed True localhost ...
2187 Job2187 Completed True localhost ...
Jobs Running: 4
But if there is only one job running, it seems that $(Get-Job -State Running).count isn't returning anything:
Id Name State HasMoreData Location Command
-- ---- ----- ----------- -------- -------
2207 Job2207 Running True localhost ...
Jobs Running:
As you can see, there is one job running, but $(Get-Job -State Running).count doesn't return anything. Any idea what's happening here? To me, it looks like that if there are multiple jobs, $(Get-Job -State Running) returns a collection of jobs which has the .count property, whereas if there is only one job, it returns just that job, and doesn't have the .count property. If this is the case (or if I'm doing something wrong), what command should I be using to get my expected result of $(Get-Job -State Running).count == 1?

Try using Measure-Object
$(Get-Job -State Running | Measure-Object).count

In PS 2.0 count only works on arrays. When Get-Job only returns one job, it returns it as an OBJECT, not an array. To make it work, you could e.g. force Get-Job to always return an array by using #(code) . Try this:
$Program = {
"Thread " + $args[0];
Start-Sleep 5;
}
Start-Job $Program -ArgumentList #($i) | Out-Null
Get-Job
"Jobs Running: " + $(#(Get-Job -State Running).count)

Related

Using Get-Job to test-connection, but quite different [duplicate]

This question already has answers here:
If using Test-Connection on multiple computers with -Quiet how do I know which result is for which computer?
(2 answers)
Closed 2 years ago.
It's my first post here, I'm tring to write scripts on PS on my own, now my target is to write script that checks if computer is online at network, for example: test-Connection 192.168.0.1, 2, 3 etc. Doing this one by one on loop for takes some time if some computers are offline, I've found some tutorials on this site to use -AsJob param, but I'm not really Sure how could it work. I mean I'd like to output every checked PC to excel, so i need if operator. eg:
if (Job1 completed successfull (computer pings)){
do smth}...
I need to get output from Job boolean (true/false), but one by one. I'm taking my first steps in PS, I've made program that checks it one by one in for loop, but as i said it take some time till my excel file fill...
I can see, that AsJob makes working more effective and I think it's important to understand it
Thanks and sorry for bad text formatting, by the time I'll go on with this!
In your example, in the Start-Job scriptblock you are trying to access $_ which is not available in the codeblock scope. If you replace $_ with $args[0] it should work since you are passing in the $ip value as an argument
Your Example
$ipki = Get-Content 'C:\Users\pchor\Desktop\ipki.txt'
foreach ($ip in $ipki) {
Start-Job -Name "$ip" -ScriptBlock {
Test-Connection $_ -Count 1 # <---- replace $_ with $args[0]
} -ArgumentList $_ # <----- change $_ to $ip
}
You'll probably also want to wait for all the jobs to finish. I recommend something like this
$computers = #(
'www.google.com'
'www.yahoo.com'
)
$jobs = $computers |
ForEach-Object {
Start-Job -ScriptBlock {
[pscustomobject]#{
Computer = $using:_
Alive = Test-Connection $using:_ -Count 1 -Quiet
}
}
}
# Loop until all jobs have stopped running
While ($jobs |
Where-Object { $_.state -eq 'Running' }) {
"# of jobs still running $( ($jobs | Where-Object {$_.state -eq 'Running'}).Count )";
Start-Sleep -Seconds 2
}
$results = $jobs | Receive-Job | Select-Object Computer, Alive
$results | Format-Table
Output
Computer Alive
-------- -----
www.google.com True
www.yahoo.com True
To modify the properties to what you want there are different ways of doing this. Easiest in this case is probably to use a calculated property
$newResults = $results |
Select-Object Computer,
#{Label = 'State'; Expression = { if ($_.Alive) { 'Online' } else { 'Offline' } } }
Objects will now look like this (I added another fake address to illustrate offline state)
Computer State
-------- -----
www.google.com Online
www.yahoo.com Online
xxx.NotAValidAddress.xxx Offline
You can then export the objects to csv using Export-csv
$newResults | Export-Csv -Path c:\temp\output.csv

Powershell check if service is running and log to file

So i have 2 variables
one include 4 services
service 1
service 2
service 3
service 4
second variable include all services which are running on computer
$servicesall = (Get-service | Group-Object -Property Name).Name
$servicestocheck = Get-Content
i want to log to file info like Service 1 is running, Service 2 is not running
I'm not sure if this is exactly what you're looking for but if you store the services you need in a text file you can do the following:
$services = Get-Content "C:\ServiceList.txt"
$statusReport = Get-Service -Name $services | Select-Object Name, Status
$statusReport | Out-File -path "c:\logfile.log"
Result:
Name Status
---- ------
spooler Running
winrm Stopped
wsearch Running
The name parameter in Get-Service -Name $services accepts an array of strings, which is why you don't need to do any sort of 'foreach' loop.
This may not be the best way to do this, but here you have something to work with:
$services = (
"vds",
"VSS",
"W32Time",
"abc",
"bcd"
)
foreach ($item in $services) {
$service = Get-Service -Name $item -ErrorAction SilentlyContinue
if ($service) {
$exist = $true
} else {
$exist = $false
$service = #{
status = "nope"
Name = $item
}
}
$service | select Status, Name, #{Name="Exist"; Expression={$exist}}
}
Result:
Status Name Exist
------ ---- -----
Stopped vds True
Stopped VSS True
Running W32Time True
nope abc False
nope bcd False

Powershell jobs & variables

How i can get vars from a jobs? Something like that:
$Var=72
$Job=start-job -Name Test {$b=8;$a=777;while($true){$a+=1;sleep 1}}
$sum=(Receive-Job $Job).a + $Var
write-host $sum
Write-Host (Receive-Job $Job).b
This would output $i every second:
start-job { for ($i = 0; $i -lt 10; $($i++;sleep 1)) {$i} } | receive-job -wait -auto
0
1
2
3
4
5
6
7
8
9
Start-ThreadJob has an undocumented -StreamingHost parameter:
Start-ThreadJob { write-host hi } -StreamingHost $host
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
11 Job11 ThreadJob NotStarted False PowerShell write-host hi
PS C:\Users\js> hi

PowerShell Start-Job Increments

I am curious as to why Start-Job increments in twos. My worry is that I am doing something wrong that makes the ID of a new job jump by 2.
Start-Job -ScriptBlock {Get-WinEvent -LogName system -MaxEvents 1000}
Results as shown by Get-Job
Id Name State HasMoreData Command
-- ---- ----- ----------- -------
2 Job2 Completed False Get-WinEvent -LogName system -MaxEvents 1000
4 Job4 Completed False Get-WinEvent -LogName system -MaxEvents 1000
6 Job6 Completed True Get-WinEvent -LogName system -MaxEvents 1000
Question: Can you control the Start-Job Id increments, or force them to be just 1?
Each time you start a job, it consists of a parent job and one or more child jobs. If you run get-job | fl you'll see the child jobs, and you'll see that their names are the "missing" odd numbered names.
#1.618 give the right answer, here are some more details :
Start-Job -ScriptBlock {Get-Process}
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
2 Job2 BackgroundJob Running True localhost Get-Process
Get-Job | fl *
State : Completed
HasMoreData : True
StatusMessage :
Location : localhost
Command : Get-Process
JobStateInfo : Completed
Finished : System.Threading.ManualResetEvent
InstanceId : 49a67ca4-840b-49ec-b293-efa9303e38bb
Id : 2
Name : Job2
ChildJobs : {Job3}
PSBeginTime : 03/03/2014 20:43:54
PSEndTime : 03/03/2014 20:44:00
PSJobTypeName : BackgroundJob
Output : {}
Error : {}
Progress : {}
Verbose : {}
Debug : {}
Warning : {}
get-job -IncludeChildJob
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
2 Job2 BackgroundJob Completed True localhost Get-Process
3 Job3 Completed True localhost Get-Process
Here is why, when you start a job, powershell create two jobs ?
Windows PowerShell jobs created through Start-Job always consist of a parent job and a child job. The child job does the actual work. If you were running the job against a number of remote machines by using Invoke-Command and its –AsJob parameter, you would get one child job per remote machine.
When you manage jobs, anything you do to the parent job is automatically applied to any child jobs. Removing or stopping the parent job performs the same action on the child jobs. Getting the results of the parent job means you get the results of all the child jobs.
You can access the child jobs directly to retrieve their data, n a simple job, as in the example, you can access the data through the parent or child jobs :
Receive-Job -Id 2 -Keep
Receive-Job -Id 3 -Keep
When you have multiple child jobs, its usually easier to access the child jobs in turn:
$jobs = Get-Job -Name Job2 | select -ExpandProperty ChildJobs
foreach ($job in $jobs){Receive-Job -Job $job -Keep}

Start-Job of Get-Counter paths from array

As a follow up to my last question I would like to compile a list of perfmon counters that are spawned and continuously sampled(-Continuous) at 1 second interval (default) with Start-Job, sleep for 60 seconds then run Receive-job to get the past 60-seconds of stats (-Average, -Sum, -Minimum, -Maximum).
The problem I'm having now is the job starts and stays in a "running" state when I use then absolute counter path. If I try to iterate through an array of performance counter strings the job state goes to "Completed".
Here are two code samples that show non-working and working results.
Doesn't work. Job state Completes even though -Continuous is set. No errors are raised.
$jobs=#{}
$counters=#("\Processor(*)\% Processor Time",
"\Network Interface(*)\Bytes Received/sec",
"\Network Interface(*)\Bytes Sent/sec")
foreach ($counterPath in $counters) {
$job=Start-Job {get-counter -Counter "$counterPath" -Continuous | foreach {$_.CounterSamples} }
$jobs[$job.id]=$counterPath
}
Works as expected, but doesn't allow multiple counters to start-job via a loop.
$jobs=#{}
$job=Start-Job {get-counter -Counter "\Processor(*)\% Processor Time" -Continuous | foreach {$_.CounterSamples} }
$jobs[$job.id]=$counter
Resulting Output
PS C:\Users\msnow> $jobs=#{}
[string]$counter="\Processor(*)\% Processor Time"
$job=Start-Job {get-counter -Counter "$counter" -Continuous | foreach {$_.CounterSamples} }
$jobs[$job.id]=$counter
__________________________________________________________________________________________________________________________
PS C:\Users\msnow> $jobs=#{}
$job=Start-Job {get-counter -Counter "\Processor(*)\% Processor Time" -Continuous | foreach {$_.CounterSamples} }
$jobs[$job.id]=$counter
__________________________________________________________________________________________________________________________
PS C:\Users\msnow> Get-Job
Id Name State HasMoreData Location Command
-- ---- ----- ----------- -------- -------
1 Job1 Completed True localhost get-counter -Counter "...
3 Job3 Running True localhost get-counter -Counter "...
__________________________________________________________________________________________________________________________
PS C:\Users\msnow> receive-job -id 3 | measure CookedValue -sum -Average
Count : 11466
Average : 5.20268509822716
Sum : 59653.9873362726
Maximum :
Minimum :
Property : CookedValue
I believe the problem is in the scope of the variable $counter. Start-job runs the scriptblock in other runspace and the variable it is not visible. You need to pass it as -argumentlist .try:
Start-Job {get-counter -Counter "$($args[0])" -Continuous | foreach {$_.CounterSamples} } -ArgumentList $counter
or
Start-Job {param($counter) ; get-counter -Counter "$($counter)" -Continuous | % {$_.CounterSamples} } -ArgumentList $counter