Powershell command for waiting one process to get completed - powershell

I have power shell script which will perform multiple tasks like uninstalling,removing and again installing wsp solutions.I am facing an issue like the first process is taking too long time for uninstalling solution so that other process has to wait till the first action has to get completed fully.Now i am giving sleep time but it has some problem while different machines speed gets varies.. I also aware about calling notepad like an external function but i dont want that has to happen here.Apart from that any solutions are availabe like I need to wait for first process to get complete before starting the second process.
$InstallDIR = "F:\new\source\UpdatedWSPFiles"
$Dir = get-childitem $InstallDIR -Recurse
$WSPList = $Dir | where {$_.Name -like "*.wsp*"}
Foreach ($wsp in $WSPList )
{
$WSPFullFileName = $wsp.FullName
$WSPFileName = $wsp.Name
try
{
Write-Host -ForegroundColor White -BackgroundColor Blue "Working on $WSPFileName"
Write-Host -ForegroundColor Green "Retracting Solution"
Uninstall-SPSolution -AllWebApplications -Identity "$WSPFileName" -Confirm:$false
sleep 100
Write-Host -ForegroundColor Green "Removing Solution from farm"
Remove-SPSolution -Identity "$WSPFileName" -Confirm:$false -Force
sleep 60
Write-Host -ForegroundColor Green "Adding solution to farm"
Add-SPSolution "$WSPFullFileName" -Confirm:$false
sleep 60
}

you can try start-process with the -wait switch :
PS> $p="C:\windows\System32\WindowsPowerShell\v1.0\powershell.exe"
PS> $params="-command &{ if ((Get-PSSnapin `"Microsoft.SharePoint.PowerShell`" -ErrorAction SilentlyContinue) -eq $null)
{
Add-PSSnapin `"Microsoft.SharePoint.PowerShell`"
}
Uninstall-SPSolution -AllWebApplications -Identity `"$WSPFileName`" -Confirm:$false}"
PS> Start-Process $p $params -Wait

Guess you could try
Start-Job -Name "jobname" -ScriptBlock { Uninstall-SPSolution -AllWebApplications -Identity "$WSPFileName" -Confirm:$false }
and
Wait-Job -Name "jobname" -Timeout "maximum wait time"

Related

Multithread PowerShell script

I have a PowerShell script that checks if a file is present in a folder. The problem is: This script works as it should, but it's very slowly. I must check 10K Pcs for a statistic / day.
I want to use Invoke-Command, but I can't use it because not all clients have enabled WinRM.
Is it possible to make this script multithread without WinRM?
Here is my code:
function Show-Menu {
param (
[string]$Title = 'Debug'
)
cls
Write-Host "================ $Title ================"
Write-Host "Ziel Clients werden in der Datei C:\temp\srv.txt angegeben!" -ForegroundColor Red
Write-Host "1: Detailansicht alle Clients" -ForegroundColor Green
Write-Host "2: Today Crash" -ForegroundColor Green
Write-Host "3: Detailansich einzelner Client" -ForegroundColor Green
Write-Host "Q: 'Q' zum beenden."
Write-Host "Script wird ausgefuehrt als:"
whoami
}
do {
Show-Menu
$input = Read-Host "Nummer angeben"
switch ($input) {
'1' {
cls
Write-Host "Detailansicht alle Clients"
$computers = Get-Content C:\temp\srv.txt
foreach ($computer in $computers) {
Write-Host -foregroundcolor "green" "Verarbeite $computer..."
if ( ! (Test-Connection $computer -Count 1 -Quiet)) {
Write-Host -foregroundcolor "red" "$computer ist offline"
continue
}
$path = Test-Path "\\$computer\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*" -Include *dump*
Get-Item "\\$computer\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*"
If ($path -eq $true ) { Write-Host $computer 'Dumps are present' }
Else { Write-Host $computer 'Dumps are not present' }
pause
}
}
'2' {
cls
Write-Host "Today Crash"
$computers = Get-Content C:\temp\srv.txt
foreach ($computer in $computers) {
Write-Host -foregroundcolor "green" "Verarbeite $computer..."
if ( ! (Test-Connection $computer -Count 1 -Quiet)) {
Write-Host -foregroundcolor "red" "$computer ist offline"
continue
}
$result = Get-ChildItem -Path "\\$computer\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*" | Where-Object { $_.LastWriteTime -ge (Get-Date).Date }
}
$result | Out-GridView
}
'3' {
cls
Write-Host "Detailansich einzelner Client"
$computer = Read-Host -Prompt 'Client angeben'
$result = Get-ChildItem -Path "\\$computer\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*"
$result | Out-GridView
}
}
}
until ($input -eq 'q')
While background jobs - started via Start-Job - do permit running in parallel, they run in child processes, and are therefore both resource-intensive and slow; furthermore, you cannot throttle their use, i.e. you cannot (directly) control how many child process at most are permitted to run simultaneously
Thread jobs, by contrast, run as threads in-process and therefore require fewer resources and are much faster than child-process-based background jobs; furthermore, throttling (limiting the number of threads permitted to run simultaneously) is supported.
Thread jobs are started via the Start-ThreadJob cmdlet, which comes with PowerShell [Core] v6+ and in Windows PowerShell can be installed on demand with, e.g., Install-Module ThreadJob -Scope CurrentUser.
You simply call Start-ThreadJob instead of Start-Job, and use the standard *-Job cmdlets to manage such thread jobs - the same way you'd manage a Start-Job-launched background job.
In the simplest case, you can simply wait for all threads to complete:
# PowerShell [Core] 6+
# Windows PowerShell with the ThreadJob module installed.
Get-Content C:\temp\srv.txt | ForEach-Object {
$computer = $_
Start-ThreadJob { # create a thread job and output an object representing it
Write-Host -ForegroundColor Green "Processing $using:computer..."
# ...
Get-Item "\\$using:computer\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*"
# ...
}
} | Receive-Job -Wait -AutoRemoveJob
Note the use of the $using: scope specifier, which is needed to access the caller's $computer value; this requirement applies equally to background jobs, thread jobs, and remoting.
By default, up to 5 threads are allowed to run simultaneously; you can use the -ThrottleLimit parameter to modify this value, but note that increasing this value beyond what your hardware can support can actually slow things down.
Output sequencing isn't guaranteed; that is, the outputs aren't guaranteed to correspond to the order in which the computer names were specified.
In PowerShell 7+, there's an even easier way to run threads in parallel, via ForEach-Object's
-Parallel parameter:
# PowerShell 7+
Get-Content C:\temp\srv.txt | ForEach-Object -Parallel {
Write-Host -foregroundcolor Green "Processing $_..."
# ...
Get-Item "\\$_\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*"
# ...
}
The comments above regarding output sequencing and throttling apply equally here, but note that the computer names are now provided as input via the pipeline, so the usual automatic $_ variable can be used inside the thread script block.

how to automate Wusa with remoting and overcoming the lack of wait cmd

Scenario: Taking a list of kb files and executing them remotely with WUSA install of target machines across network.
The flow is like this:
enter a powershell session W/ target computer
for each loop $KB in $List
wusa.exe $kb
wait 'til installed
back to the wusa.exe for next device in $list
Code:
# SET UP ERROR-HANDLING FOR THE PS SESSION
$ErrorActionPreference = "continue"
# DECLARE VARIABLE THAT CONTAINS LIST OF PATCHES AVAILABLE FOR INSTALL
$PatchList = (Get-ChildItem -Path C:\WinPatch -recurse | Where-Object {$_.Extension -eq '.msu'})
# ESTABLISH LOOP TO ITERATE THRU PATCHES
foreach ($Patch in $PatchList)
{
Try
{
Write-Host ("`n Preparing to install: " + $Patch) -ForegroundColor Yellow
Write-Host ("`n Installing...") -ForegroundColor Magenta
$SB = {
$arglist = "$Patch", "/quiet", "/norestart"
Start-Process -FilePath "C:\windows\system32\wusa.exe" -ArgumentList $arglist -Wait}
Invoke-Command -ScriptBlock $SB
Write-Host "`n Installation complete`n" -ForegroundColor Green
}
Catch
{
[System.Exception]
Write-Host "Installation failed with Error -- $Error()" -ForegroundColor Red
$Error.Clear()
}
}
# RESTART OPTIONS
$Ans1 = Read-Host "`n Would you like to restart this computer now (Type Y for yes or N for no)"
if ($Ans1 -eq 'Y' -or $Ans1 -eq 'y')
{
Remove-Item -Path C:\WinPatch\*.msu -Force
Write-Host "`n This computer will restart in 5 seconds..." -ForegroundColor Yellow
Start-Sleep -Seconds 5
Restart-Computer -Force
}
else
{
# TEST COMPUTER FOR PS VERSION
$Tester = test-wsman -computername localhost | Select-Object -Property ProductVersion
if ($Tester.ProductVersion.EndsWith("1.0"))
{
Write-Host "`n This computer has PS v1.0 installed and you will have to open Task Scheduler to schedule restart" -ForegroundColor Red
Read-Host "`n Press ENTER to continue..."
}
elseif ($Tester.ProductVersion.EndsWith("2.0"))
{
Write-Host "`n This computer has PS v2.0 installed and you will have to open Task Scheduler to schedule restart" -ForegroundColor Red
Read-Host "`n Press ENTER to continue..."
}
else
{
# SCHEDULE RESTART
Import-Module PSScheduledJob
$RST = Read-Host -Prompt "`n Enter date/time to restart computer...format is --> mm/dd/yyyy hh:mmAM/PM"
$Ans2 = Read-Host -Prompt "`n You entered: $RST... if this is correct, enter Y for yes"
if ($Ans2 -eq 'Y' -or $Ans2 -eq 'y')
{
$Nomen = Read-Host -Prompt "`n Enter name for scheduled restart "
$Trig = New-JobTrigger -Once -At $RST
Register-ScheduledJob -Name $Nomen -Trigger $Trig -ScriptBlock { Restart-Computer -force } -RunAs32
}
else
{
Write-Host "`n Please restart the script and try again" -ForegroundColor Red
}
}
}
Break
PsExec.exe -u $domain\$username -p $password -h -s -accepteula \\$computer wusa.exe /quiet 'C:\Program Files\WindowsPowershell\Modules\windows10.0-kb5005112-x64.msu' /wait /forcerestart
This works for me. If you wrap this in an Invoke-Command block you can run this remotely. :-)
Thanks,

Using PowerShell to start batch file for secure wipe USB drive

I'm using PowerShell to start a bat file that wipes a USB drive that connected.
If I use the script without Start-Process it works fine, but I'm wanting to connect multiple drives and have it wipe them simultaneously.
The script:
Register-WmiEvent -Class Win32_VolumeChangeEvent -SourceIdentifier VolumeChange
Write-Host (Get-Date -Format s) " Beginning script..."
do {
$newEvent = Wait-Event -SourceIdentifier volumeChange
$eventType = $newEvent.SourceEventArgs.NewEvent.EventType
$eventTypeName = switch ($eventType) {
1 {"Configuration changed"}
2 {"Device arrival"}
3 {"Device removal"}
4 {"docking"}
}
Write-Host (Get-Date -Format s) " Event detected = " $eventTypeName
if ($eventType -eq 2) {
$driveLetter = $newEvent.SourceEventArgs.NewEvent.DriveName
$driveLabel = ([wmi]"Win32_LogicalDisk='$driveLetter'").VolumeName
Write-Host (Get-Date -Format s) " Drive name = " $driveLetter
Write-Host (Get-Date -Format s) " Drive label = " $driveLabel
# Execute process if drive matches specified condition(s)
if ($driveLabel -eq 'BBIFREE_01' -or $drivelabel -eq 'HD10') {
Write-Host (Get-Date -Format s) " Starting task in 3 seconds..."
Start-Sleep -Seconds 3
Start-Process -FilePath D:\wipe.bat $driveLetter, $driveLabel
Copy-Item -Path D:\Utilities1 -Destination $driveLetter -Recurse
$driveEject = New-Object -ComObject Shell.Application
$driveEject.Namespace(17).ParseName($driveLetter).InvokeVerb("Eject")
}
}
Remove-Event -SourceIdentifier VolumeChange
} while (1 -eq 1) #Loop until next event
Unregister-Event -SourceIdentifier VolumeChange
The bat file contents:
set arg1=%1
set arg2=%2
format %args1% /FS:NTFS /p:1 /V:%args2% /x /y
EDIT
To clarify: the script is to run continously on a specific PC where it should start the bat file (as in wipe the disk securely) every time it detects a disk being connected.
If I use:
D:\wipe.bat -ArgumentList `"$driveLetter",`"$driveLabel"
then it starts the wiping on 1 disk, and on 1 disk only.
I need it to detect multiple disks, that's why I used Start-Process, seeing as I thought it would run on the background and keep watching for new events.
EDIT2
I changed the code to avoid using -ArgumentList, see above.
If I put the echo command in my batch file as requested:
set arg1=E:
set arg2=BBIFREE_01
ECHO ECHO IS ON
ECHO ECHO IS ON
So I see the commands in the bat file, but it doesn't execute and goes straight for the copy command.
This is a slightly modified version of a Script I wrote a while back, I don't have time right now to confirm it works 100% but it should at least point you in the right direction, it just threads the actual wiping so it can handle other jobs in the background, then uses a global popup to warn when one is done to prevent having to block while the job is finishing.
Should be able to handle any number of devices at once, it uses PowerShell's Format-Volume command instead, but you could put a call to the BAT file inside the job instead.
$USBWhiteList = #( #Add wildcard items here, if a USB matches one it will be wiped.
"USB0*"
"*WIPE"
)
Enum EventNames{ Changed = 1 ; Inserted = 2 ; Removed = 3 ; Docking = 4 } #Names for events
Register-WmiEvent -Class win32_VolumeChangeEvent -SourceIdentifier volumeChange -ErrorAction SilentlyContinue #register the event
do{
Write-Host "Monitoring for Disk events..." -Fore Yellow
$Event = Wait-Event -SourceIdentifier volumeChange #wait for a disk event
$EventType = [EventNames]$Event.SourceEventArgs.NewEvent.EventType #get the type of the event
Write-Host "Drive $($EventType), Processing..." -Fore Yellow -NoNewline
$Volume = Get-Volume -DriveLetter $Event.SourceEventArgs.NewEvent.DriveName -ErrorAction SilentlyContinue #get the volume details
$IsMatch = ($USBWhiteList|? {$Volume.FileSystemLabel -like $_}).Count -gt 0 #does it match our whitelist?
if (($EventType -eq [EventNames]::Inserted) -and $IsMatch){ #if a disk was inserted which matches the whitelist...
Write-Host "Volume $($Volume.DriveLetter): '$($Volume.FileSystemLabel)', Found, Wiping!" -Fore Green
Start-Job -ScriptBlock { param ($Volume) #Perform the wipe inside a job
$Disk = Get-Partition -DriveLetter $Volume.DriveLetter | Get-Disk
Clear-Disk -Number $Disk.Number -RemoveData -Confirm:$false
New-Partition -DiskNumber $Disk.Number -UseMaximumSize -IsActive -DriveLetter $Volume.DriveLetter
Format-Volume -FileSystem NTFS -DriveLetter $Volume.DriveLetter -Confirm:$false
Add-Type -AssemblyName 'System.Windows.Forms' #warn (globally) when it is finished, don't need to run wait/recieve job.
[System.Windows.Forms.MessageBox]::Show("Finished Wiping Disk $($Volume.DriveLetter)","Please Remove Disk")
} -ArgumentList $Volume | Out-Null
} else {
Write-Host "Ignoring" -Fore Red
}
Remove-Event -SourceIdentifier volumeChange
} while (1) #this should be modified to quit after x disks or something, the below commands won't get exec'd - could also use a Try/Finally and Ctrl+C the script.
Get-Job | Remove-Job -Force
Unregister-Event -SourceIdentifier volumeChange

Disk cleanup using PowerShell

I am trying to write a script for cleaning disks. All works except running cleanmgr. Can someone advise?
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace(0xA)
$computername = Read-Host -Prompt 'Enter machine name'
foreach ($hosts in $computername) {
#$temp = get-ChildItem "env:\TEMP"
#$temp2 = $emp.Value
#$swtools = "c:\SWTOOLS\*"
$ccmcache = "\\$hosts\c$\Windows\ccmcache\*"
$temp = "\\$hosts\C$\Users\*****\AppData\Local\Temp\*"
#$WinTemp = "c:\Windows\Temp\*"
$FreespaceBefore = (Get-WmiObject -Class win32_logicaldisk -ComputerName $hosts -Filter "DeviceID='C:'" | select Freespace).FreeSpace/1GB
#Write-Output "Disk Space Before"
Write-Output "$FreespaceBefore"
Write-Host "Removing Junk files in $ccmcache on $hosts." -ForegroundColor Green
Remove-Item -Recurse $ccmcache -Force -Verbose
Write-Host "Removing Junk files in $temp on $hosts." -ForegroundColor DarkYellow
Remove-Item -Recurse $temp -Force -Verbose
Write-Host "Finally now , Running Windows disk Clean up Tool" -ForegroundColor Cyan
Invoke-Command -ComputerName pc -ScriptBlock {Start-Process cleanmgr.exe}
#Write-Output "Disk Space after"
#Write-Output "$diskspaceafter"
$([char]7)
Sleep 1
$([char]7)
Sleep 1
Write-Host "I finished the cleanup task,Succefully " -ForegroundColor Yellow
}
2# Running Disk Clean up Tool
write-Host "Finally now , Running Windows disk Clean up Tool" -ForegroundColor Cyan
cleanmgr /sagerun:1 /VERYLOWDISK | out-Null
$([char]7)
Sleep 1
$([char]7)
Sleep 1
I know this question is old but for anyone still looking...
This link could provide a couple options, like the one below
foreach ($computer in $computers){
if(!(Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet))
{write-host "cannot reach $computer" -f red}
else {& \\$computer\C$\Windows\System32\cleanmgr.exe /sagerun:1}}

Using Powershell To Distribute Script Level Jobs On Remote Servers

More of a theory question...
I have a powershell script that exists on three servers. In this example the three servers are:
server1
server2
server3
I am using another machine, server4, to call script C:\ExampleScript.ps1 remotely using Invoke-Command while specifying the remote machine via the ComputerName parameter. The ultimate goal of the script is to detect whether powershell is running, if it is not, then the computer is "not busy" and can open up the script being called remotely. If the computer is "busy", move onto the next server and continue on through the three machines until all the parameter values have been exhausted. If all machines are busy, it would be ideal if there was a way to periodically check the processes and see if they are still open. In this way, execution of the script can be balanced across the various machines, in an albeit primitive fashion.
Consider the following code:
$servers = "server1","server2","server3"
$data = "param1", "param2", "param3", "param4", "param5", "param6"
#somehow loop through the different servers/data using the above arrays
$job = Invoke-Command $servers[0] {
$ProcessActive = Get-Process powershell -ErrorAction SilentlyContinue
if($ProcessActive -eq $null)
{
"Running"
Invoke-Command -ComputerName $env:computername -FilePath C:\ExampleScript.ps1 -ArgumentList $data[0]
}
else
{
"Busy go to next machine"
}
} -AsJob
Wait-Job $job
$r = Receive-Job $job
$r
The expected result trying to be achieved is attempting to load balance the script across the machines based on whether there is an active powershell process, if not move onto the next machine and perform the same test and subsequent possible execution. The script should go through all the values as specified in the $data array (or whatever).
I found this question interesting, so I wanted to give it a try.
$servers = "server1","server2","server3"
$data = New-Object System.Collections.ArrayList
$data.AddRange(#("param1", "param2", "param3", "param4", "param5", "param6"))
$jobs = New-Object System.Collections.ArrayList
do
{
Write-Host "Checking job states." -ForegroundColor Yellow
$toremove = #()
foreach ($job in $jobs)
{
if ($job.State -ne "Running")
{
$result = Receive-Job $job
if ($result -ne "ScriptRan")
{
Write-Host " Adding data back to que >> $($job.InData)" -ForegroundColor Green
$data.Add($job.InData) | Out-Null
}
$toremove += $job
}
}
Write-Host "Removing completed/failed jobs" -ForegroundColor Yellow
foreach ($job in $toremove)
{
Write-Host " Removing job >> $($job.Location)" -ForegroundColor Green
$jobs.Remove($job) | Out-Null
}
# Check if there is room to start another job
if ($jobs.Count -lt $servers.Count -and $data.Count -gt 0)
{
Write-Host "Checking servers if they can start a new job." -ForegroundColor Yellow
foreach ($server in $servers)
{
$job = $jobs | ? Location -eq $server
if ($job -eq $null)
{
Write-Host " Adding job for $server >> $($data[0])" -ForegroundColor Green
# No active job was found for the server, so add new job
$job = Invoke-Command $server -ScriptBlock {
param($data, $hostname)
$ProcessActive = Get-Process powershell -ErrorAction SilentlyContinue
if($ProcessActive -eq $null)
{
# This will block the thread on the server, so the JobState will not change till it's done or fails.
Invoke-Command -ComputerName $hostname -FilePath C:\ExampleScript.ps1 -ArgumentList $data
Write-Output "ScriptRan"
}
} -ArgumentList $data[0], $env:computername -AsJob
$job | Add-Member -MemberType NoteProperty -Name InData -Value $data[0]
$jobs.Add($job) | Out-Null
$data.Remove($data[0])
}
}
}
# Just a manual check of $jobs
Write-Output $jobs
# Wait a bit before checking again
Start-Sleep -Seconds 10
} while ($data.Count -gt 0)
Basically I create an array, and keep it constantly populated with one job for each server.
Data is removed from the list when a new job starts, and is added back if a job fails. This is to avoid servers running the script with the same data/params.
I lack a proper environment to test this properly at the moment, but will give it a whirl at work tomorrow and update my answer with any changes if needed.