adding a timeout to batch/powershell - powershell

$fullnamexp = ((net user $winxp /domain | Select-String "Full Name") -replace "Full Name","").Trim();
If $winxp cannot be found, the command will hang, is there a timeout I can use with this to make it move on after 5-10 seconds? Not sure where I would put it.
Edit- I use this to pull the username:
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $tag1)
$key = $reg.OpenSubKey('SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon')
$winxp = $key.GetValue('DefaultUserName') -replace '^.*?\\'
$winxp is then a login name such as ajstepanik then I put it into: $fullnamexp = ((net user $winxp /domain | Select-String "Full Name") -replace "Full Name","").Trim();
1.21.2014 Update
$timeoutSeconds = 5
$code = {
((net user $winxp /domain | Select-String "Full Name") -replace "Full Name","").Trim(); # your commands here, e.g.
}
$j = Start-Job -ScriptBlock $code
if (Wait-Job $j -Timeout $timeoutSeconds) { $fullnamexp = Receive-Job $j }
Remove-Job -force $j

While #mjolinor may have indeed provided you an alternative approach, here is a direct answer to your general question: how do you force a timeout in PowerShell?
Wrap whatever you wish to time-limit in a script block, run that as a job, then use the Wait-Job cmdlet to time-limit the operation. Wait-Job will return either at the end of the timeout period or when the script block completes, whichever occurs first. After Wait-Job returns, you can examine the job state ($j.state) to determine whether it was interrupted or not, if it matters to you.
$timeoutSeconds = 5 # set your timeout value here
$j = Start-Job -ScriptBlock {
# your commands here, e.g.
Get-Process
}
"job id = " + $j.id # report the job id as a diagnostic only
Wait-Job $j -Timeout $timeoutSeconds | out-null
if ($j.State -eq "Completed") { "done!" }
elseif ($j.State -eq "Running") { "interrupted" }
else { "???" }
Remove-Job -force $j #cleanup
2014.01.18 Update
Here is a bit more streamlining approach that also includes the practical step of getting information out of the script block with Receive-Job, assuming what you want is generated on stdout:
$timeoutSeconds = 3
$code = {
# your commands here, e.g.
Get-ChildItem *.cs | select name
}
$j = Start-Job -ScriptBlock $code
if (Wait-Job $j -Timeout $timeoutSeconds) { Receive-Job $j }
Remove-Job -force $j

You can use Start-Sleep to pause the script:
Start-Sleep -s 5

net doesn't explicitly allow you to set a time out on it's operations, but you could check out this link on changing the ipv4 timeout for your sockets:
http://www.cyberciti.biz/tips/linux-increasing-or-decreasing-tcp-sockets-timeouts.html
The only thing else I could imagine is spawning a worker thread but I don't even know if that's possible in bash, I'm not fluid enough in it to answer that; plus it opens you up to sync problems and all sorts of multi threaded issues beyond what you're trying to accomplish quickly in a bash script to begin with! :P

Does this help?
$query = (dsquery user -samid $winxp)
if ($query) {$fullnamexp = ($query | dsget user -display)[1].trim()}
$fullnamexp

This solution doesn't work for me. remove-job -force $j takes over 5 seconds in this example.
$timeoutseconds = 1
$start = get-date
$j = start-job -scriptblock { Resolve-DnsName 1.1.1.1 }
if (wait-job $j -timeout $timeoutseconds) { $fullnamexp = receive-job $j }
remove-job -force $j
(get-date) - $start
Days : 0
Hours : 0
Minutes : 0
Seconds : 5
Milliseconds : 342
Ticks : 53426422
TotalDays : 6.18361365740741E-05
TotalHours : 0.00148406727777778
TotalMinutes : 0.0890440366666667
TotalSeconds : 5.3426422
TotalMilliseconds : 5342.6422
Here's a simple timeout example with notepad:
notepad
if (-not $(wait-process notepad 10; $?)) { stop-process -name notepad }

$watchdog = 10 #seconds
$start_time = Get-Date
$j = Start-Job -ScriptBlock{
#timeout command
if ($true) {
$i = 0
while($true) {
Write-Host "Count: $i"
Start-Sleep -Milliseconds 100
$i++
}
}
write-host "Hello"
}
while($true) {
if ($j.HasMoreData) {
Receive-Job $j
Start-Sleep -Milliseconds 200
}
$current = Get-Date
$time_span = $current - $start_time
if ($time_span.TotalSeconds -gt $watchdog) {
write-host "TIMEOUT!"
Stop-Job $j
break
}
if (-not $j.HasMoreData -and $j.State -ne 'Running') {
write-host "Finished"
break
}
}
Remove-Job $j

Related

PowerShell script that makes multiple other PowerShell windows, and makes them all run one command such as ping

I have:
for($i = 1 ; $i -le 3; $i++)
{
Start-Process powershell.exe
}
but I don't know how I would make the new windows run a ping command. Could be done with an extra script but have no idea. Thanks
Start-Process has an -ArgumentList parameter that can be used to pass arguments to the new PowerShell process.
powershell.exe and pwsh.exe have a -Command parameter that can be used to pass them a command.
You can combine the two like this:
for ($i = 1; $i -le 3; $i++)
{
Start-Process powershell.exe -ArgumentList '-NoExit',"-Command ping 127.0.0.$i"
}
If you don't use the -NoExit parameter the window will close as soon as the ping command finishes.
As mentioned in the comments to the question it is also possible to ping multiple hosts using the Test-Connection command like this:
Test-Connection -TargetName 127.0.0.1,127.0.0.2
This has a downside though in that it seems to ping one after the other rather than doing it in parallel.
Another way to do much the same thing in parallel, and probably more PowerShell style is to use jobs:
$jobs = #()
for ($i = 1; $i -le 3; $i++)
{
$jobs += Start-ThreadJob -ArgumentList $i { PARAM ($i)
Test-Connection "127.0.0.$i"
}
}
Wait-Job $jobs
Receive-Job $jobs -Wait -AutoRemoveJob
Note: Start-ThreadJob is newish. If you're still stuck on version 5 of PowerShell that comes with Windows use Start-Job instead, it spawns new processes where as Start-ThreadJob doesn't.
Nitpickers' corner
For those in the comments saying that appending to an array is slow. Strictly a more PowerShell way of doing this is given below. For three items, however, you won't be able to measure the difference and the readability of the code is way lower. It's also rather diverging from the original question.
1..3 | % { Start-ThreadJob -ArgumentList $_ { PARAM($i) Test-Connection "127.0.0.$i" } } | Wait-Job | Receive-Job -Wait -AutoRemoveJob
Here's a pinger script I have to watch multiple computers.
# pinger.ps1
# example: pinger comp001
# pinger $list
param ($hostnames)
#$pingcmd = 'test-netconnection -port 515'
$pingcmd = 'test-connection'
$sleeptime = 1
$sawup = #{}
$sawdown = #{}
foreach ($hostname in $hostnames) {
$sawup[$hostname] = $false
$sawdown[$hostname] = $false
}
#$sawup = 0
#$sawdown = 0
while ($true) {
# if (invoke-expression "$pingcmd $($hostname)") {
foreach ($hostname in $hostnames) {
if (& $pingcmd -count 1 $hostname -ea 0) {
if (! $sawup[$hostname]) {
echo "$([console]::beep(500,300))$hostname is up $(get-date)"
# [pscustomobject]#{Hostname = $hostname; Status = 'up'; Date = get-date} # format-table waits for 2 objects
$sawup[$hostname] = $true
$sawdown[$hostname] = $false
}
} else {
if (! $sawdown[$hostname]) {
echo "$([console]::beep(500,300))$hostname is down $(get-date)"
# [pscustomobject]#{Hostname = $hostname; Status = 'down'; Date = get-date}
$sawdown[$hostname] = $true
$sawup[$hostname] = $false
}
}
}
sleep $sleeptime
}
Example usage (it beeps):
.\pinger comp001,comp002
comp001 is up 07/13/2022 12:07:59
comp002 is up 07/13/2022 12:08:00

Add Write-Progress to Get-Job/Wait-Job

I'm using the below code to display the results of PowerShell Jobs with a timeout of 120 seconds. I would like to enhance this code by incorporating Write-Progress (based on number of jobs completed). I tried using this example as a reference, however, when I try to incorporate that code, the progress bar displays briefly after all the jobs are all done already.
$Jobs = #()
$ForceStoppedIds = #{}
$Jobs += Get-Job
$Jobs | Wait-Job -Timeout 120 | Out-Null
$Jobs | ?{$_.State -eq 'Running'} | Stop-Job -PassThru | %{$ForceStoppedIds[$_.Id] = $true}
foreach ($Job in $Jobs) {
$Name = $Job.Name
$Output = (Get-Job -Name $Name | Receive-Job)
if ($ForceStoppedIds.Contains($Job.Id)) {
Write-Output "$($Name) - Device unable to process request within 2 minutes"
} else {
Write-Output $Output
}
}
Wait-Job -Timeout 120 will block the thread until the specified timeout or all jobs have completed, hence, is not possible to display progress and wait for them at the same time.
There are 2 alternatives that I can think of, the first one would be to create a proxy command / proxy function around this cmdlet to extend it's functionality.
These blogs demonstrate how to do it:
https://devblogs.microsoft.com/scripting/proxy-functions-spice-up-your-powershell-core-cmdlets/
https://devblogs.microsoft.com/powershell/extending-andor-modifing-commands-with-proxies/
proxy function
You can also follow the indications from this helpful answer.
The other alternative is to define your own function that does a similar work as Wait-Job but, instead of blocking the thread, you can add a loop that will run based on 2 conditions:
That the elapsed time is lower than or equal to the Timeout we passed as argument to the function (we can use Diagnostics.Stopwatch for this).
And, that the jobs are still Running (the $jobs List<T> is still populated).
Note, the function below should work in most cases however is purely for demonstration purposes only and should not be relied upon.
First we define a new function that can be used to display progress as well as wait for our jobs based on a timeout:
using namespace System.Collections.Generic
using namespace System.Diagnostics
using namespace System.Threading
using namespace System.Management.Automation
function Wait-JobWithProgress {
[cmdletbinding()]
param(
[parameter(Mandatory, ValueFromPipeline)]
[object[]] $InputObject,
[parameter()]
[double] $TimeOut
)
begin {
$jobs = [List[object]]::new()
}
process {
foreach($job in $InputObject) {
$jobs.Add($job)
}
}
end {
$timer = [Stopwatch]::StartNew()
$total = $jobs.Count
$completed = 0.1
$expression = { $true }
if($PSBoundParameters.ContainsKey('TimeOut')) {
$expression = { $timer.Elapsed.TotalSeconds -le $TimeOut }
}
while((& $expression) -and $jobs) {
$remaining = $total - $completed
$average = $timer.Elapsed.TotalSeconds / $completed
$estimate = [math]::Round($remaining * $average)
$status = 'Completed Jobs: {0:0} of {1} - ETC: {2}s' -f $completed, $total, $estimate
$progress = #{
Activity = 'Waiting for Jobs'
PercentComplete = $completed / $total * 100
Status = $status
}
Write-Progress #progress
$id = [WaitHandle]::WaitAny($jobs.Finished, 200)
if($id -eq [WaitHandle]::WaitTimeout) {
continue
}
# output this job
$jobs[$id]
# remove this job
$jobs.RemoveAt($id)
$completed++
}
# Stop the jobs not yet Completed and remove them
$jobs | Stop-Job -PassThru | ForEach-Object {
Remove-Job -Job $_
"Job [#{0} - {1}] did not complete on time and was removed." -f $_.Id, $_.Name
} | Write-Warning
Write-Progress #progress -Completed
}
}
Then for testing it, we can create a few jobs with a random timer:
0..10 | ForEach-Object {
Start-Job {
Start-Sleep (Get-Random -Minimum 5 -Maximum 15)
[pscustomobject]#{
Job = $using:_
Result = 'Hello from [Job #{0:D2}]' -f $using:_
}
}
} | Wait-JobWithProgress -TimeOut 10 |
Receive-Job -AutoRemoveJob -Wait | Format-Table -AutoSize

Powershell Animation Till the last command

My Script has a lot of commands that taking time.
How to make powershell animation till the last commands runs. I don't want start-sleep
I tried Wait-Job but not working.
I just got this function and it uses Start-sleep I can change it if it will affect
function ProcessingAnimation($scriptBlock) {
$cursorTop = [Console]::CursorTop
try {
[Console]::CursorVisible = $false
$counter = 0
$frames = '|', '/', '-', '\'
$jobName = Start-Job -ScriptBlock $scriptBlock
while($jobName.JobStateInfo.State -eq "Running") {
$frame = $frames[$counter % $frames.Length]
Write-Host "$frame" -NoNewLine
[Console]::SetCursorPosition(0, $cursorTop)
$counter += 1
Start-Sleep -Milliseconds 125
}
# Only needed if you use a multiline frames
Write-Host ($frames[0] -replace '[^\s+]', ' ')
}
finally {
[Console]::SetCursorPosition(0, $cursorTop)
[Console]::CursorVisible = $true
}
}
Write-Host "Getting System Information..."
ProcessingAnimation { Wait-Job -Name $WindowsVer }
command1
command2
command3
command4
command5
command6
$WindowsVer = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").ReleaseId

How to Correctly Check if a Process is running and Stop it

What is the correct way of determining if a process is running, for example FireFox, and stopping it?
I did some looking around and the best way I found was this:
if((get-process "firefox" -ea SilentlyContinue) -eq $Null){
echo "Not Running"
}
else{
echo "Running"
Stop-Process -processname "firefox"
}
Is this the ideal way of doing it? If not, what the correct way of doing so?
The way you're doing it you're querying for the process twice. Also Lynn raises a good point about being nice first. I'd probably try something like the following:
# get Firefox process
$firefox = Get-Process firefox -ErrorAction SilentlyContinue
if ($firefox) {
# try gracefully first
$firefox.CloseMainWindow()
# kill after five seconds
Sleep 5
if (!$firefox.HasExited) {
$firefox | Stop-Process -Force
}
}
Remove-Variable firefox
If you don't need to display exact result "running" / "not runnuning", you could simply:
ps notepad -ErrorAction SilentlyContinue | kill -PassThru
If the process was not running, you'll get no results. If it was running, you'll receive get-process output, and the process will be stopped.
#jmp242 - the generic System.Object type does not contain the CloseMainWindow method, but statically casting the System.Diagnostics.Process type when collecting the ProcessList variable works for me. Updated code (from this answer) with this casting (and looping changed to use ForEach-Object) is below.
function Stop-Processes {
param(
[parameter(Mandatory=$true)] $processName,
$timeout = 5
)
[System.Diagnostics.Process[]]$processList = Get-Process $processName -ErrorAction SilentlyContinue
ForEach ($Process in $processList) {
# Try gracefully first
$Process.CloseMainWindow() | Out-Null
}
# Check the 'HasExited' property for each process
for ($i = 0 ; $i -le $timeout; $i++) {
$AllHaveExited = $True
$processList | ForEach-Object {
If (-NOT $_.HasExited) {
$AllHaveExited = $False
}
}
If ($AllHaveExited -eq $true){
Return
}
Start-Sleep 1
}
# If graceful close has failed, loop through 'Stop-Process'
$processList | ForEach-Object {
If (Get-Process -ID $_.ID -ErrorAction SilentlyContinue) {
Stop-Process -Id $_.ID -Force -Verbose
}
}
}
To start with process-killing, here python, my 2 cents:
Get-Process python3.9|Stop-Process
Thanks #Joey. It's what I am looking for.
I just bring some improvements:
to take into account multiple processes
to avoid reaching the timeout when all processes have terminated
to package the whole in a function
function Stop-Processes {
param(
[parameter(Mandatory=$true)] $processName,
$timeout = 5
)
$processList = Get-Process $processName -ErrorAction SilentlyContinue
if ($processList) {
# Try gracefully first
$processList.CloseMainWindow() | Out-Null
# Wait until all processes have terminated or until timeout
for ($i = 0 ; $i -le $timeout; $i ++){
$AllHaveExited = $True
$processList | % {
$process = $_
If (!$process.HasExited){
$AllHaveExited = $False
}
}
If ($AllHaveExited){
Return
}
sleep 1
}
# Else: kill
$processList | Stop-Process -Force
}
}

How to know when a command isn't responding in powershell

I have a powershell script which launch a command line utility DacIESvcCli.exe
DacIESvcCli.exe sends me a response and when I receive it I take the status, which can be "Running" or "Completed"
My problem is that sometimes the call hangs and I never get a response. The following script can run 3 days without interruption.
How can I prevent this from happening?
$myCounter = 0
while($myCounter -lt 5){
Write "start of the while counter : " $myCounter
$exportResponse = C:\DAC\DacIESvcCli.exe -s "myserver.database.windows.net" -u "mylogin#myserver" -p "mypassword" -requestid 'e1e34eee-1aaa-4cc9-8c48-3a2239fe1bff' -status
$exportStatus = $exportResponse[10].split(" ")[1].toString()
Write $exportStatus
$myCounter++
}
Here is the output
start of the while counter :
0
Completed
start of the while counter :
1
Completed
start of the while counter :
2
Completed
start of the while counter :
3
_
... and it never ends.
Here is part of my script for Travis Heseman
param($username, $password, $serverName, $requestId)
$consecutiveFailedAttempt = 0
while( $exportStatus -ne "Completed" -and $exportStatus -ne "Failed" -and $exportStatus -ne "TooLong"){
if($exportStatus -ne "FirstRun"){
Start-Sleep -m 60000 # Wait 1 min
}
$currentNow = Get-Date
$job = Start-Job { param($s, $u, $p, $r) C:\DAC\DacIESvcCli.exe -s $s -User $u -p $p -requestid $r -status } -ArgumentList #($serverName, $username, $password, $requestId)
Wait-Job $job -Timeout 60 # if the command takes longer than 60 sec we timeout and retry
Stop-Job $job
$exportResponse = Receive-Job $job
Remove-Job $job
if($exportResponse[10]){
$exportStatus = $exportResponse[10].split(" ")[1].toString()
$consecutiveFailedAttempt = 0;
}else{
$currentNow = Get-Date
$whileMessage = "Time out we retry " + $currentNow
$whileMessage | Out-File $File -append
$exportStatus = "Unknown"
$consecutiveFailedAttempt++;
}
if($consecutiveFailedAttempt -gt 10){
$exportStatus = "TooLong"
}
}
# do wantever you want with the export status