Simultaneous PowerShell script execution - powershell-2.0

I have written the following script for SQL patching:
cls
$computers = Get-Content D:\Abhi\Server.txt
foreach ($line in $computers)
{
psexec \\$line -s -u Adminuser -p AdminPassword msiexec /i D:\SQL_PATCH\rsSharePoint.msi SKIPCA=1 /qb
}
My doubt here is to parallelize this script execution on all the servers mentioned in the text file. Meaning, as soon I start the execution of the script, this should initiate the patching activity on the servers simultaneously and also to track the progess on all the servers, as this script is doing now only for one server.
Kindly help me on this.

Thanks Ansgar Wiechers.
This piece of code did it. It helps in executing the .exe simultaneously on all the servers as well as track their status:
cls
$servers = Get-Content 'D:\Abhi\Server.txt'
$servers | ForEach-Object {$comp = $_
Start-Job -ScriptBlock {psexec \\$input -s -u Adminuser -p AdminPassword C:\SQL_PATCH\SQLServer2008R2SP3-KB2979597-x64-ENU.exe /quiet /action=patch /allinstances /IAcceptSQLServerLicenseTerms} -InputObject $comp}
While (Get-Job -State Running)
{
Get-Job | Receive-Job
#Start-Sleep 2
#Write-Host "Waiting for update removal to finish ..."
}
Remove-Job *

Related

Powershell, wait for first process to end

Powershell script should exit if one of the main Processes stopped.
This script is the main Docker process. Docker container should stop if one of those Apps (app1, app2)stopped.
Current approach is to use Exit Events for one of the Apps and Wait-Process for the other. Is there a better approach?
$pApp1 = Start-Process -PassThru app1
$pApp2 = Start-Process -PassThru app2
Register-ObjectEvent -InputObject $pApp1 -EventName exited -Action {
Get-EventSubscriber | Unregister-Event
exit 1
}
Wait-Process -Id $pApp2.id
exit 1
Wait for the HasExited property on either of them to change:
$apps = 'app1','app2' |ForEach-Object { Start-Process $_ -PassThru }
while(#($apps |Where HasExited -eq $true).Count -lt 1){
Write-Host "Waiting for one of them to exit..."
Start-Sleep -Seconds 1
}
As of PowerShell 7.2.1, Wait-Process, when given multiple processes, invariably waits for all of them to terminate before returning; potentially introducing an -Any switch so as to only wait for any one among them is the subject of GitHub proposal #16972, which would simplify the solution to Wait-Process -Any -Id $pApp1.id, $pApp2.id
Delegating waiting for the processes to exit to thread / background jobs avoids the need for an event-based or periodic-polling solution.
# Start all processes asynchronously and get process-information
# objects for them.
$allPs = 'app1', 'app2' | ForEach-Object { Start-Process -PassThru $_ }
# Start a thread job for each process that waits for that process to exit
# and then pass the process-info object for the terminated process through.
# Exit the overall pipeline once the first output object from one of the
# jobs is received.
$terminatedPs = $allPs |
ForEach-Object { Start-ThreadJob { $ps = $using:_; Wait-Process -Id $ps.Id; $ps } } |
Receive-Job -Wait |
Select-Object -First 1
Write-Verbose -Verbose "Process with ID $($terminatedPs.Id) exited."
exit 1
Note:
I'm using he Start-ThreadJob cmdlet, which offers a lightweight, much faster thread-based alternative to the child-process-based regular background jobs created with Start-Job.
It comes with PowerShell (Core) 7+ and in Windows PowerShell can be installed on demand with, e.g., Install-Module ThreadJob -Scope CurrentUser.
In most cases, thread jobs are the better choice, both for performance and type fidelity - see the bottom section of this answer for why.
If Start-ThreadJob isn't available to you / cannot be installed, simply substitute Start-Job in the code above.
PowerShell (Core) 7+-only solution with ForEeach-Object -Parallel:
PowerShell 7.0 introduced the -Parallel parameter to the ForEach-Object cmdlet, which in essence brings thread-based parallelism to the pipeline; it is a way to create multiple, implicit thread jobs, one for each pipeline input object, that emit their output directly to the pipeline (albeit in no guaranteed order).
Therefore, the following simplified solution is possible:
# Start all processes asynchronously and get process-information
# objects for them.
$allPs = 'app1', 'app2' | ForEach-Object { Start-Process -PassThru $_ }
$terminatedPs = $allPs |
ForEach-Object -Parallel { $_ | Wait-Process; $_ } |
Select-Object -First 1
Write-Verbose -Verbose "Process with ID $($terminatedPs.Id) exited."
exit 1

Formatting nmap output

I have an nmap output looking like this
Nmap scan report for 10.90.108.82
Host is up (0.16s latency).
PORT STATE SERVICE
80/tcp open http
|_http-title: Did not follow redirect to https://10.90.108.82/view/login.html
I would like the output to be like
10.90.108.82 http-title: Did not follow redirect to https://10.90.108.82/view/login.html
How can it be done using grep or any other means?
You can use the following nmap.sh script like that:
<nmap_command> | ./nmap.sh
nmap.sh:
#!/usr/bin/env sh
var="$(cat /dev/stdin)"
file=$(mktemp)
echo "$var" > "$file"
ip_address=$(head -1 "$file" | rev | cut -d ' ' -f1 | rev)
last_line=$(tail -1 "$file" | sed -E "s,^\|_, ,")
printf "%s%s\n" "$ip_address" "$last_line"
rm "$file"
If you do not mind using a programming language, check out this code snippet with Python:
import nmapthon as nm
scanner = nm.NmapScanner('10.90.108.82', ports=[80], arguments='-sS -sV --script http-title')
scanner.run()
if '10.90.108.82' in scanner.scanned_hosts(): # Check if host responded
serv = scanner.service('10.90.108.82', 'tcp', 80)
if serv is not None: # Check if service was identified
print(serv['http-title'])
Do not forget to execute pip3 install nmapthon.
I am the author of the library, feel free to have a look here
Looks like you want an [nmap scan] output to be edited and displayed as you wish. Try bash scripting, code a bash script and run it.
Here's an link to a video where you might find an answer to your problem:
https://youtu.be/lZAoFs75_cs
Watch the video from the Time Stamp 1:27:17 where the creator briefly describes how to cut-short an output and display it as we wish.
If you require, I could code an bash script to execute an cut-shorted version of the output given by an nmap scan.

How to execute a CMD file in remote computer

I am looking to execute a command in remote machine using invoke but the .cmd file will call for additional .vbs script. So i guess i may have to mention CScript if so how do i mention both cmd/c and cscript in the below command
Invoke-Command -computername blrscrv01 -ScriptBlock { param($path, $command ) cmd /c $path $command } -args '"C:\windows\system32\cscript.exe"','"/?"'
Your example worked for me when I removed the extra level of quoting.
Invoke-Command -computername blrscrv01 -ScriptBlock { param($path, $command ) cmd /c $path $command } -args 'C:\windows\system32\cscript.exe','/?'
Troubleshooting
Enter a remote session and poke around.
Enter-PSSession -computername blrscrv01
Verify that the target script exists and is accessible.
dir \\lcsap027\deploy\c2.cmd
dir \\lcsap027\deploy
type \\lcsap027\deploy\c2.cmd
Attempt to run the script interactively.
\\lcsap027\deploy\c2.cmd
or
cmd /c \\lcsap027\deploy\c2.cmd
Alternative
Another thing you might try is not invoking a cmd script remotely, but issuing the commands remotely. New-PSSession will return a handle you can use to deal interactively with the remote machine. You can repeatedly issue commands with Invoke-Command and get the results (as primitive data types and generic objects, though, not the actual objects themselves).
Altered Script
Here's an altered version of the script you put in your comment. I've removed the nested Invoke-Command (I don't know why it was necessary, you're already running commands on the remote machine). Since the line breaks got lost in the comment, I don't know if there were any statement separator problems (I'll just assume there weren't, though in its "formatted" form as a one-liner, it would have died horribly because PoSH wouldn't have known where one statement ended and the next began).
param(
[string]$ComputerName,
[string]$User,
[string]$pass
)
Get-PSSEssion | Remove-PSSession
$session = New-PSSession -ComputerName $ComputerName
Invoke-Command -Session $session -ScriptBlock {
param(
[string]$ComputerName,
[string]$Username,
[string]$Password
)
$net = new-object -ComObject WScript.Network
$net.MapNetworkDrive("x:", "\\machinename\sharename", $false, $Username, $Password)
cmd.exe /c "x:\c2.cmd"
$net.RemoveNetworkDrive("x:")
} -args $ComputerName, $User, $pass
This at least got the remote script to run and produced the expected output. (I emitted the computer name, user name, and file location.)
Bear in mind that this method doesn't employ any transport-/application-layer encryption, so the password is sent cleartext over the network.

Get result from a remote call to msiexec using Powershell

I'm using Powershell on Win 2008r2 to make a remote call to msiexec as follows:
Invoke-Command -session $Session -ScriptBlock{param($arguments) start-process -FilePath "msiexec.exe" -Wait $arguments } -Argument $arguments
Currently I'm checking for success using if(!$?) but this is no good because I've just seen the msiexec process throw a 1638 error (because the app is already installed on the remote server) but the value of $? was True.
Can anyone please tell me how I can capture the 1638 code, or whatever else, is returned by msiexec on the remote server?
Thanks, Rob.
This was a very hackish way to do this, but I got around this by using a global-like variable in terms of $script:functionexitcode which I would assign with the value of the .ErrorCode from the msiexec.exe using Start-Process.
Then in the main part of the PowerShell script, I would test that value if ($functionexitcode -eq 0).
Here is the full snippet from a very similar install scenario with Start-Process:
# Start MSP upgrade to UR
$upgrade = (Start-Process -Filepath $msiexecpath -ArgumentList $argumentlist_BEGIN$argumentlist_MSP$argumentlist_END -PassThru -Wait -ErrorAction Stop)
if ($upgrade.ExitCode -eq 0) {
Write-Host "Upgrade successful. Error code:" $upgrade.ExitCode `
"`nUpgrade logfile location: " $workingdirectory\$msi_logfile_upgrade
$script:FunctionExitCode = $upgrade.ExitCode

Long Running Powershell Script Freezes

We are using a long running PowerShell script to perform a lot of small operations that can take an extremely long amount of time. After about 30 minutes the scripts froze. We were able to get the scripts to start running again by pressing Ctrl-C which caused the scripts to resume execution instead of killing the process.
Is there some sort of script timeout or mechanism that prevents long running scripts within PowerShell?
I had this problem due to a bad habit I have. If you select a little bit of text inside a console powershell, scripts logs freeze. Make sure nothing is selected after launching a big script :)
Like mentioned, when clicking/selecting text in powershell console, the script stops. You can disable this behaviour like this:
Right-click the title bar
Select Properties
Select Options
Under Edit Options, disable QuickEdit Mode
Note: You won't be able to select text from powershell window anymore.
Try my kill timer script. Just change the $ScriptLocation variable to the script you want to run. That script will then run as a background job while the current windows keeps track of the timer. After the time expires the current window will kill the background job and write it all to logs.
Start-Transcript C:\Transcriptlog-Cleanup.txt #write log to this location
$p = Get-Process -Id $pid | select -Expand id # -Expand selects the string from the object id out of the current process.
Write-Host $p
$BJName = "Clean-up-script" #Define Background job name
$startTime = (Get-Date) # set start time
$startTime
$expiration = (Get-Date).AddMinutes(2)#program expires at this time
# you could change the expiration time by changing (Get-Date).AddSeconds(20) to (Get-Date).AddMinutes(10)or to hours or whatever you like
#-----------------
#Timer update function setup
function UpdateTime
{
$LeftMinutes = ($expiration) - (Get-Date) | Select -Expand minutes # sets minutes left to left time
$LeftSeconds = ($expiration) - (Get-Date) | Select -Expand seconds # sets seconds left to left time
#Write time to console
Write-Host "------------------------------------------------------------------"
Write-Host "Timer started at : " $startTime
Write-Host "Current time : " (Get-Date)
Write-Host "Timer ends at : " $expiration
Write-Host "Time on expire timer : " $LeftMinutes "Minutes" $LeftSeconds "Seconds"
Write-Host "------------------------------------------------------------------"
}
#get background job info and remove the it afterwards + print info
function BJManager
{
Receive-Job -Name $BJName #recive background job results
Remove-Job -Name $BJName -Force #remove job
Write-Host "Retrieving Background-Job info and Removing Job..."
}
#-----------------
$ScriptLocation = "C:\\Local-scripts\Windows-Server-CleanUp-Script-V2.4(Beta).ps1" #change this Var for different kind of script locations
Start-Job -Name $BJName -FilePath $ScriptLocation #start this script as background job
# dont start job in the loop.
do{ #start loop
Write-Host "Working"#start doing other script stuff
Start-Sleep -Milliseconds 5000 #add delay to reduce spam and processing power
UpdateTime #call upadate function to print time
Get-Job -Name $BJName | select Id, State ,Location , Name
if((Get-Job).State -eq "Failed")
{
BJManager
}
elseif((Get-Job).State -eq "Completed")
{
BJManager
}
}
until ($p.HasExited -or (Get-Date) -gt $expiration) #check exit time
Write-Host "Timer Script Finished"
Get-Job -Name $BJName | select Id, State ,Location , Name
UpdateTime
BJManager
Start-Sleep -Milliseconds 5000 #give it some time to write to log
Stop-Transcript
Start-Sleep -Milliseconds 5000 #give it some time to stop the logging before killing process
if (-not $p.HasExited) { Stop-Process -ID $p -PassThru } # kill process after time expires
try to add percentage calculation in your script.. so you can identity that how much time it would take to complete...

Resources