Get-Job Format-Table with Runtimes of Jobs - powershell

I am trying to write a PowerShell 5.1 script for monitoring jobs.
I am having a problem writing a proper Get-Job table, the following is what I have.
Get-Job | Format-Table -AutoSize -Property name, state, #{name = 'Runtime'; Expression = {$((get-date)-($_.psbegintime)).ToString('HH:mm')}}
What this code returns is a table of named jobs and states, but does populate the runtime column. It works if I remove the .ToString('HH:mm') from the code but that populates the runtime column with Hour:Minute:Second:Millisecond in this format HH:mm:ss:fffffff. How do I remove the Seconds and Milliseconds part?

The reason why .ToString(HH:mm) doesn't work is because the result of:
Get-Date - $_.PSBeginTime
Is not a datetime object, it's a timespan object. Calling .ToString(HH:mm) on a timespan object throws the following exception:
MethodInvocationException: Exception calling "ToString" with "1" argument(s): "Input string was not in a correct format."
According to TimeSpan.ToString MS Docs formatting is possible however the format is .ToString('hh\:mm\:ss').
Here you have an example of how to achieve what you're looking for:
$testJobs = 5
$jobs = 1..$testJobs | ForEach-Object {
Start-Job {
'Hello from Job {0}' -f $using:_
Start-Sleep ([random]::new().Next(5,10))
}
}
while($jobs.State -contains 'Running')
{
Clear-Host
Get-Job | Format-Table -AutoSize -Property Name, State, #{
Name = 'Runtime'
Expression = {
([datetime]::Now - $_.PSBeginTime).ToString('hh\:mm\:ss')
# .ToString('hh\:mm') for Hours and Minutes only.
}
}
Start-Sleep 1
}
$jobs | Receive-Job -Wait -AutoRemoveJob
This answer shows a similar, more developed alternative, using a function to wait for Jobs with progress and a optional TimeOut parameter.

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

Retrieve average CPU-workload with Get-Counter

I am looking for a way to retrieve the average CPU-workload with PowerShell.
At the beginning of a script, I want to start the tracking of the CPU-workload and when it is finished I want to get the average CPU-workload between. (like 71.5%)
After a research on the web I started using a (PowerShell-)"job" for this purpose, but I was not able to get it working. This is the reduced code I have till now:
$JobObject = Start-Job -Name "MyJob" -ScriptBlock {
Get-Counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -Continuous
}
Start-Sleep -Seconds 5
$Result = Receive-Job -Job $JobObject
I was not able to simple get the average of captured values.
How to get this working?
Thank you
Edit: It is not a requirement by me to make use of jobs.
You could calculate the average separately after receiving the job:
$JobObject = Start-Job -Name "MyJob" -ScriptBlock {
Get-Counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -Continuous
}
Start-Sleep -Seconds 5
$Result = Receive-Job -Job $JobObject
$Result.Readings | Foreach-Object { ($_ -split ':')[-1].Trim() } |
Measure-Object -Average | Select-Object #{n='Average';e={"{0:f2}" -f $_.Average}}
Explanation:
$Result.Readings contains all of your sample readings in string format. You will have to parse the CPU percentage from that.
-split ':' creates an array by separating the input string by :. The resulting array contains as many elements as there are : characters. Since we only want the number after the final :, index [-1] is used.
The format operator -f requires a specific syntax. The {0} syntax represents a placeholder for the first object after the -f. {1} would represent the second object. A trivial example would be "{0}=={1}" -f $var1,$var2, which will output string versions of $var1 and $var2 separated by a ==. The {0:f2} tells PowerShell to format the first object (indicated by 0) using a fixed decimal (indicated by f) to two places (indicated by 2).

Apply conditional to output of a commandlet

I want to output the result of the commandlet (Invoke-Command) on success and add a custom message if the result is null. The code as shown below produces the desired results except in the event of a null response, it simply outputs nothing on that line.
I can not pipe directly to an if statement, nor can I output on 2 opposing conditions (True & False). Is it possible to get a custom response on $null while not suppressing the normal output on success?
Invoke-Command -ComputerName PC1, PC2, PC3 -Scriptblock {get-eventlog system | where-object {$_.eventid -eq 129} | select MachineName, EventID, TimeGenerated, Message -last 1}
If you run the example code block assuming that PC1 and PC3 have the event ID but PC2 does not, the output will simply skip PC2.
I want to output something like "Event Not found" in that case.
Placing the entire thing in a loop and then running the results through another conditional loops destroys performance so that is not an ideal solution.
I would create a new object for returning from Invoke-Command. So you are sure you will receive from every host something even the event log is not present. And might you can change get-eventlog to Get-WinEvent. Get-WinEvent was for my tasks the most time faster than get-eventlog.
[System.Management.Automation.ScriptBlock]$Scriptblock = {
[System.Collections.Hashtable]$Hashtable = #{
WinEvent = Get-WinEvent -FilterHashtable #{ LogName = 'System'; Id = 129 } -MaxEvents 1 -ErrorAction SilentlyContinue #-ErrorAction SilentlyContinue --> otherwise there is an error if no event is available
}
return (New-Object -TypeName PSCustomObject -Property $Hashtable)
}
Invoke-Command -ComputerName 'PC1', 'PC2', 'PC3' -Scriptblock $Scriptblock

Format the results of a PowerShell script

I'm trying to retrieve Azure service bus queues who has exceeded a specific message count, and the script is working well so far, but when it retrieves the values, it just does it in the following format:
The limit has been exceeded for the following queues:q1-monitoring-endpoint q1-monitoring-endpoint q2-monitoring-endpoint q3-monitoring-endpoint
I need to retrieve them in a better format.
Here is the script, I'm using:
$NS = Get-AzureRmServiceBusNamespace
foreach ($NS in $NS)
{
$Queue = Get-AzureRmServiceBusQueue -ResourceGroupName $NS.ResourceGroup -Namespace $NS.Name
if ($Queue.MessageCount -eq 0 )
{
"The limit has been exceeded for the following queues:" + $Queue.Name
}
}
I don't have access to an Azure subscription at the moment to test this, but consider doing something along these lines:
Get-AzureRmServiceBusNamespace |
ForEach-Object {
Get-AzureRmServiceBusQueue -ResourceGroupName $_.ResourceGroup -Namespace $_.Name |
ForEach-Object {
[PsCustomObject]#{
Queue = $_.Name
LimitExceeded = ($_.MessageCount -eq 0)
}
}
}
This will (hopefully) produce output similar to the following:
Queue LimitExceeded
------ -------------
Queue1 False
Queue2 True
Queue3 False
Not only is this a neater format than the original string output, it means you get some objects as output, which can be further manipulated.
You should explain "in a better format". However, your main issue is within your foreach loop where you try to iterate over $NS but assign the current object to $NS. You need to choose a different variable:
$NS = Get-AzureRmServiceBusNamespace
foreach ($N in $NS)
{
$Queue = Get-AzureRmServiceBusQueue -ResourceGroupName $N.ResourceGroup -Namespace $N.Name
if ($Queue.MessageCount -eq 0 )
{
"The limit has been exceeded for the following queues:" + $Queue.Name
}
}
Note: I would recommend to retrieve all empty queues and save it in a variable (which enables you to use it later):
$emptyQueue = Get-AzureRmServiceBusNamespace |
ForEach-Object {
Get-AzureRmServiceBusQueue -ResourceGroupName $_.ResourceGroup -Namespace $_.Name
} |
Where-Object MessageCount -eq 0
Finally, output it in your desired format:
Write-Host "The limit has been exceeded for the following queues: $($Queue.Name -join ',')"
You can try the Format commands available in powershell to get the output in a better format.
You can define your custom format as well with Format-Custom command.
Reference for Format commands in Powershell : https://learn.microsoft.com/en-us/powershell/scripting/getting-started/cookbooks/using-format-commands-to-change-output-view?view=powershell-6

PowerShell: Start-Job -scriptblock Multi line scriptblocks?

Hoping someone can help me to figure out if this is possible. I have a 20 ish lines that scan a log file in a while loop. I need this to happen in parallel with the rest of the script. The log scanning loop is passing the entries into SQLite and other scripts need to act on this information - hence wanting to run them in parallel.
If i use the Job-Start command then it seems the -SciptBlock function will only accept one piped line of commands. I have too many commands to want to pipe so i need to run multiple lines in the scriptblock.
I tried several ways of doing it but the following examples give the least errors. I also tried it in an Invoke-Command -ScriptBlock -as job like in the second example - both ways will not accept a multiline scriptblock.
what am i doing wrong, please?
Start-Job -Name LogScan -ScriptBlock
{
$EventOld = ConvertFrom-Json (Get-content ( Get-ChildItem | Sort-Object -Property LastWriteTime | Select-Object -last 1 ) | Select-Object -last 1)
$RunLoop = 1
while (RunLoop -ge 1)
{
Start-Sleep -Milliseconds 333
$RunLoop = $RunLoop +1
$EventNew = ConvertFrom-Json (Get-content ( Get-ChildItem | Sort-Object -Property LastWriteTime | Select-Object -last 1 ) | Select-Object -last 1)
if ($EventOld.timestamp -ne $EventNew.timestamp)
{
# lots of commands and here passing the array to SQLite
}
$EventOld = $EventNew
}
}
Error is as follows:
Start-Job : Missing an argument for parameter 'ScriptBlock'.
Specify a parameter of type 'System.Management.Automation.ScriptBlock'
and try again. [..]
Kudos to briantist for helping me to get there :)
When using Start-Job and Invoke-Command, be careful to put the opening { for the -ScriptBlock parameter on the same line as the command. Do not put it on the line below as you would with an if or while command.
This leads to a the further issue of not spotting that you have not matched opening { and closing } brackets properly, as you are used to the convention of matching their indentation level to pair them up.
Please note that the references to $Event.timestamp in the code. These come from the fact that one of the JSON fields is called timestamp - it is not a method or property of a standard string or array.