Start services in parallel - powershell

I have a script which checks if certain service on different servers is up, if it is not, the script should start the service.
The problem is, it doesn't start the services in parallel, instead it waits until each service is started.
Code:
$server_list = Get-Content -path D:\Path\list_of_servers.txt
$server_list | foreach {
(Get-Service -Name '*Service Name*' -computername $_) | Where-Object {$_.status -eq "Stopped"} | Set-Service -Status Running
}
I know it's due to the way the script is written, but maybe some of you have any suggestions how to make it better?
Cheers!

Here is an example of parallel processing using Powershell and Workflows:
$server_list = Get-Content -path D:\Path\list_of_servers.txt
workflow MyWorkflow
{
foreach -parallel($s in $server_list) {
inlinescript { (Get-Service -Name '*Service Name*' -PSComputerName $s) | Where-Object {$_.status -eq "Stopped"} | Set-Service -Status Running
}
}
}
Using Powershell V2 and jobs
Untested code, but should be close:
$server_list = Get-Content -path D:\Path\list_of_servers.txt
$maxJobs = 5
$scriptblock = {
(Get-Service -Name $args[1] -ComputerName $args[0]) | Where-Object {$_.status -eq "Stopped"} | Set-Service -Status Running
}
foreach ($s in $server_list)
{
$arguments = #($s, $service)
$running = #(Get-Job | Where-Object { $_.State -eq 'Running' })
while ($running.Count -gt ($maxJobs -1)) {
$done = Get-Job | Wait-Job -Any
$running = #(Get-Job | ? {$_.State -eq 'Running'})
}
start-job -scriptblock $scriptblock -ArgumentList $arguments
}
Get-Job | Wait-Job
Get-Job | Receive-Job

Related

Counting returned objects from jobs in powershell?

quick question, I've got a short little script that creates 2 background jobs and pings some websites, now since jobs cannot return variables apparently I figured I'd try it as follows (which is probably a stupid way of doing it but okay).
Now the weird thing is that it seems to work somewhat? But the count it returns is nowhere near what it should be, it's very random, sometimes it stops at 2, sometimes at 3, can anyone maybe tell me what I'm doing wrong? Shouldn't $i end up equalling 7?
Initially I wanted parallel runspaces but I've been trying to wrap my head around that for weeks now and I've come to the conclusion that I'm too dumb for that apparently.
Is what I'm trying to do here even possible?
$1 = #('google.com', 'bing.com', 'yahoo.com', 'startpage.com')
$2 = #('reddit.com', 'facebook.com', 'nonexistent.com')
function run {
$pwd = Get-Location
$i = 0
$list = {
function list1 ($1, $pwd) {
foreach ($ping in $1) {
"&"
if (Test-Connection -ComputerName $ping -Count 1 -Quiet) {
"$pwd\" + "$ping"
}
}
}
function list2 ($2, $pwd) {
foreach ($ping in $2) {
"&"
if (Test-Connection -ComputerName $ping -Count 1 -Quiet) {
"$pwd\" + "$ping"
}
}
}
}
echo "ping?`nyes or no`n"; $y = Read-Host
if ($y -eq 'y') {
Start-Job -ScriptBlock { list1 $using:1 $using:pwd } -InitializationScript $list
Start-Job -ScriptBlock { list2 $using:2 $using:pwd } -InitializationScript $list
While (Get-Job | Where-Object HasMoreData) {
Get-Job | Where-Object HasMoreData | Receive-Job | Where-Object { $_ -notlike "&" } | Tee-Object -FilePath ping.txt -Append
Get-Job | Where-Object HasMoreData | Receive-Job | Where-Object { $_ -like "&" } | Foreach-Object { $i += 1 }
}
$i | Add-Content totalcount.txt
}
}
Fix to the script is below.
Everytime you use Receive-Job, it takes the data from stream and displays it. After displaying it, that data is no longer accessible via Receive-job. To fix this, either Use -Keep switch or store the data you get from Receive-Object in a variable to use it multiple times.
if ($y -eq 'y') {
Start-Job -ScriptBlock { list1 $using:1 $using:pwd } -InitializationScript $list
Start-Job -ScriptBlock { list2 $using:2 $using:pwd } -InitializationScript $list
While (Get-Job | Where-Object HasMoreData) {
Get-Job | Where-Object HasMoreData | Receive-Job -Keep | Where-Object { $_ -notlike "&" } | Tee-Object -FilePath ping.txt -Append
Get-Job | Where-Object HasMoreData | Receive-Job | Where-Object { $_ -like "&" } | Foreach-Object { $i += 1 }
}
$i | Add-Content totalcount.txt
}
I would however recommend a little different route than what you have. Instead of getting the data while you are checking to see if the job completed, run the loop and output this way,
if ($y -eq 'y') {
Start-Job -ScriptBlock { list1 $using:1 $using:pwd } -InitializationScript $list
Start-Job -ScriptBlock { list2 $using:2 $using:pwd } -InitializationScript $list
While ((Get-Job | ? {$_.State -eq "Running" }).Count -gt 0) {
# wait for all jobs to complete...
Start-Sleep 1
}
#Once all the jobs are done, you can check the results.
$data = Get-Job | Receive-Job | Where-Object { $_ -notlike "&" }
$data | Tee-Object -FilePath ping.txt -Append
$data.Count | Add-Content totalcount.txt
Get-Job | Remove-Job # Dont forget to clean up.
}

scriptblock returning object for compare-object

I'm writing a script to compare 2 samba locations via compare object.
To speed things up i would like to give each location via a thread to a scriptblock where i let the object get made.
After that i'd like the output from the scriptblock as an object to use it in the Compare-Object cmdlet.
What i have sofar:
$nas_smb_share = "\\nas\loc\"
$cs_dest ="\\dest2\loc"
$check_hash = {
Param($loc)
$fso = (dir $loc -Recurse | Where-object{(!$_.psiscontainer) -AND ($_.LastWriteTime -gt (Get-Date).AddHours(-20))} | get-hash -Algorithm MD5)
return $fso
}
$compare_loc =#($nas_smb_share, $cs_dest)
foreach ($check in $compare_loc)
{
$running = #(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le 3)
{
$j = Start-Job -ScriptBlock $check_hash -ArgumentList $check -Name $check
} else
{
$running | Wait-Job
}
Get-Job | Receive-Job
$test = Receive-Job -Name $nas_smb_share -Keep
$test2 = Receive-Job -Name $cs_dest -Keep
}
Get-Job | Wait-Job | Receive-Job
so would still need to add this in somewhere:
(Compare-Object -ReferenceObject $fso -DifferenceObject $fsoBU -Property hash -PassThru).Path | %{if ($_.SideIndicator -eq "=>" ){$result = ("$($_.InputObject)")}}
(dir $cs_dest -Recurse | Where-Object {(!$_.psiscontainer)} | get-hash -Algorithm MD5 | ? {$_.hashstring -match $result})
But the result from test and test2 are hashtable (i think?) and not an object.
Any input would be appreciated on where i went wrong, or how i could do it differently
If you want to return the names of the files in the second location whose checksums don't match, the following editions would help.
$nas_smb_share = "\\nas\loc\"
$cs_dest = "\\dest2\loc"
$compare_loc = #($nas_smb_share, $cs_dest)
$check_hash = {
Param($loc)
return Get-ChildItem $loc -Recurse | Where-object {(!$_.psiscontainer) -AND ($_.LastWriteTime -gt (Get-Date).AddHours(-20))} | Get-FileHash -Algorithm MD5
}
$Jobs = #()
foreach ($check in $compare_loc) {
$Jobs += Start-Job -ScriptBlock $check_hash -ArgumentList $check -Name $check
}
$Jobs | Wait-Job | Out-Null
$test = Receive-Job -Name $nas_smb_share -Keep
$test2 = Receive-Job -Name $cs_dest -Keep
(Compare-Object -ReferenceObject $test -DifferenceObject $test2 -Property Hash -PassThru | Where-Object { $_.SideIndicator -eq "=>" }).Path

Why PS didn't saved informations in these txt files?

I didn't got any errors and powershell made these files, but they are empty. What did i wrong?
$Services = Get-Service
Foreach ($Proces in $Services) {
If($Proces.status -eq "running") { Out-File $Proces >> "C:\proces.txt"}
If($Proces.status -eq "stopped") { Out-File $Proces >> "C:\proces2.txt"}
}
>> is the append redirect operator, which is basically the same as Out-File -Append. So it is like calling Out-File twice.
With the command Out-File $Proces >> "C:\proces.txt" you pass in no input object to Out-File. So you write a blank file to $Proces. Then take the output of that command (nothing) and write that to C:\proces.txt, which creates the second blank file.
So you will want to decide on using Out-File -Append or >>
Here is your code using just Out-File:
$Services = Get-Service
Foreach ($Service in $Services) {
If ($Service.Status -eq "Running") { Out-File -InputObject $Service -Path "C:\proces.txt" -Append}
If ($Service.Status -eq "Stopped") { Out-File -InputObject $Service -Path "C:\proces2.txt" -Append }
}
Here is your code using just >>:
$Services = Get-Service
Foreach ($Service in $Services) {
If ($Service.Status -eq "Running") { $Service >> "C:\proces.txt" }
If ($Service.Status -eq "Stopped") { $Service >> "C:\proces2.txt" }
}
There are many other ways to do what you are attempting
Here is a way using the Where-Object cmdlet rather than a loop/conditional
$Services = Get-Service
$Services | Where-Object {$_.Status -eq "Running"} | Out-File "C:\proces.txt" -Append
$Services | Where-Object {$_.Status -eq "Stopped"} | Out-File "C:\proces2.txt" -Append
Here is a way using the .where() method using split
$Running,$Stopped = (Get-Service).Where({$_.Status -eq 'Running'},'Split')
$Running | Out-File "C:\proces.txt" -Append
$Stopped | Out-File "C:\proces2.txt" -Append

Powershell Multijob script. Make file open when job completes instead of waiting until all jobs complete

I'm writing a script to ping multiple sites and then open the file to show you the results. I would like the results to open as they finish. Instead it waits until all jobs are finished before opening the files. I've also had issues where it will only open some of the files. Any help would be appreciated
$count = 500
$sites = "www.google.com","8.8.8.8","127.0.0.1"
foreach ($site in $sites)
{
Remove-Item "C:\WFSupport\Self Service Tool\$site.txt"
start-job -Name $site -ScriptBlock { param ($count,$site) ping -n $count $site } -ArgumentList $count, $site
}
While ((Get-Job).State -match 'Running')
{
foreach ($Job in Get-Job | where {$_.HasMoreData})
{
$Jobname = $Job.Name
Receive-Job $Job | Out-File -Encoding ascii -Append "C:\WFSupport\Self Service Tool\$Jobname.txt"
}
Start-Sleep -Seconds 10
}
While ((Get-Job).State -match 'Completed')
{
foreach ($Job in Get-Job | where {$_.HasMoreData})
{
$Jobname = $Job.Name
Receive-Job $Job | Out-File -Encoding ascii -Append "C:\WFSupport\Self Service Tool\$Jobname.txt"
Invoke-Item "C:\WFSupport\Self Service Tool\$Jobname.txt"
}
Get-Job | Remove-Job
}
This is because the While loop checking for 'Running' doesn't stop until all the jobs are stopped running. None of the code below that will run until that while loop finishes.
while ((Get-Job).State -match 'Running') {
foreach ($job in Get-Job | where {$_.HasMoreData}) {
$jobname = $job.name
Receive-Job $Job | Out-File -Encoding ascii -Append "C:\WFSupport\Self Service Tool\$Jobname.txt"
if ($job.State -like 'Completed'){
Invoke-Item "C:\WFSupport\Self Service Tool\$Jobname.txt"
$job | remove-job
}
}
start-sleep -seconds 10
}

Multi-threading with PowerShell

I have done lots of reading about multi-threading in PowwerShell with Get-Job and Wait-Job but still cant seem to work it out.
Eventually, I will have this as a GUI based script to run and don't want my GUI to freeze up while its doing its task.
The script is looking for Event Logs of my Domain Controllers and then getting the details I want, then outputting them, it works like I need it to.
I can start a job using Invoke-Command {#script goes here} -ComputerName ($_) -AsJob -JobName $_ and the jobs run.
Script below:
Clear-Host
Get-Job | Remove-Job
(Get-ADDomainController -Filter *).Name | ForEach-Object {
Invoke-Command -ScriptBlock {
$StartTime = (Get-Date).AddDays(-4)
Try{
Get-WinEvent -FilterHashtable #{logname='Security'; id=4740;StartTime=$StartTime} -ErrorAction Stop `
| Select-Object * | ForEach-Object {
$Username = $_.Properties[0].Value
$lockedFrom = $_.Properties[1].Value
$DC = $_.Properties[4].Value
$Time = $_.TimeCreated
Write-Host "---------------------------------------------"
Write-Host $Username
Write-Host $lockedFrom
Write-Host $DC
Write-Host $Time
Write-Host "---------------------------------------------"
}#ForEach-Object
}catch [Exception] {
If ($_.Exception -match "No events were found that match the specified selection criteria") {
Write-Host "No events for locked out accounts." -BackgroundColor Red
}#If
}#Try Catch
} -ComputerName ($_) -AsJob -JobName $_ | Out-Null # Invoke-Command
}#ForEach-Object
Currently I have a While loop to tell me its waiting then to show me the result:
(Get-ADDomainController -Filter *).Name | ForEach-Object {
Write-Host "Waiting for: $_."
While ($(Get-Job -Name $_).State -ne 'Completed') {
#no doing anything here
}#While
Receive-Job -Name $_ -Keep
}#ForEach-Object
#clean up the jobs
Get-Job | Remove-Job
Thinking of my GUI (to be created), I will have a column for each Domain Controller and showing results under each heading, how do make it not freeze my GUI and show the results when they arrive?
I know its been asked a few times, but the examples I cant work out.
I would avoid Start-Job for threading - for efficiency try a runspace factory.
This is a basic setup which could be useful (I also have PS 4.0), and open to suggestions/improvements.
$MaxThreads = 2
$ScriptBlock = {
Param ($ComputerName)
Write-Output $ComputerName
#your processing here...
}
$runspacePool = [RunspaceFactory]::CreateRunspacePool(1, $MaxThreads)
$runspacePool.Open()
$jobs = #()
#queue up jobs:
$computers = (Get-ADDomainController -Filter *).Name
$computers | % {
$job = [Powershell]::Create().AddScript($ScriptBlock).AddParameter("ComputerName",$_)
$job.RunspacePool = $runspacePool
$jobs += New-Object PSObject -Property #{
Computer = $_
Pipe = $job
Result = $job.BeginInvoke()
}
}
# wait for jobs to finish:
While ((Get-Job -State Running).Count -gt 0) {
Get-Job | Wait-Job -Any | Out-Null
}
# get output of jobs
$jobs | % {
$_.Pipe.EndInvoke($_.Result)
}