Adding Time out section in powershell script - powershell

I am trying to test Time out after 30 seconds.
Sample code:
$a = "y"
$b = "n"
$timeout = New-TimeSpan -Seconds 30
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$stopwatch.Start()
$timeout.Seconds
$stopwatch.elapsed.Seconds
do{
if($a -eq "n"){
Write-Host "This block will never run"
break
}
if($stopwatch.elapsed.Seconds -lt $timeout.Seconds){
Write-Host "Testing this block: Time OUT!!"
break
}
}while($a -eq $b)
$stopwatch.Stop()
But the if block if($stopwatch.elapsed.Seconds -lt $timeout.Seconds) is true even $stopwatch.elapsed.Seconds value is 0 and $timeout.Seconds value is 30 in the loop and complete the code in few milliseconds and not taking 30 seconds to print the Time out statement.
Could anyone please give me pointer to resolve this issue.

A couple of things:
You don't need these two lines: $timeout.Seconds and $stopwatch.elapsed.Seconds above the loop
Your while condition should be while($a -ne $b)
The test inside the loop should read if($stopwatch.elapsed.Seconds -ge $timeout.Seconds)
Try
$a = "y"
$b = "n"
$timeout = New-TimeSpan -Seconds 30
$stopwatch = [System.Diagnostics.Stopwatch]::new()
$stopwatch.Start()
do {
if($stopwatch.Elapsed.Seconds -ge $timeout.Seconds){
Write-Host "Testing this block: Time OUT!!"
break
}
# no timeout, so proceed with what needs to be done here
# . . .
} while($a -ne $b) # loop forever unless you set $a equal to $b in the loop somewhere
$stopwatch.Stop()

Theo's helpful answer addresses incidental logic problems with your approach and offers a solution that probably will work, but isn't fully robust: If the activity in your loop exceeds 1 minute before the timeout condition is tested, the test won't work as intended (even with the logic problems fixed).
You have two options:
Use .TotalSeconds instead of .Seconds, for the reasons explained below.
More simply, taking advantage of the fact that [timespan] instances are directly comparable (see below), you can use:
if ($stopwatch.elapsed -gt $timeout) { # ...
As zett42 points out, [timespan] instances are directly comparable, due to implementing the .NET System.IComparable interface (as well as its generic counterpart); e.g.:
# -> $true - a timespan representing a 61-second duration
# is greater than one representing a 60-second (1-minute) duration.
[timespan]::FromSeconds(61) -gt [timespan] '00:01:00'
Therefore, as shown in the top section, you can simply directly compare $stopwatch.elapsed and $timeout - both of which are [timespan] instances.
The .Seconds property of a [timespan] instance is only the seconds component, potentially alongside larger units, such as minutes (.Minutes) and hours (.Hours)
You need the .TotalSeconds property to get the total amount of seconds (analogously, there are also .TotalDays, .TotalHours, and .TotalMinutes properties).
Also note that .Seconds is always a whole number ([int]), whereas .TotalSeconds can be a fractional value ([double]).
To illustrate the difference:
PS> [timespan] '00:01:05' | # 1 minute and 5 seconds
Select-Object Seconds, TotalSeconds
Seconds TotalSeconds
------- ------------
5 65

#sivam The issue is-
You're not applying the proper properties of the timespan command if it goes beyond 59 seconds then at 60 seconds it will consider it 1 minute.
Update the condition inside the loop if($stopwatch.elapsed.Seconds -lt $timeout.Seconds)
Try
$a = "y"
$b = "n"
$timeout = New-TimeSpan -Minutes 1
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$stopwatch.Start()
$timeout.Minutes
$stopwatch.elapsed.Minutes
do{
if($a -eq "n"){
Write-Host "This block will never run"
break
}
if($stopwatch.elapsed.Minutes -ge $timeout.Minutes){
Write-Host "Time OUT!!"
break
}
}while($a -ne $b)
$stopwatch.Stop()

Related

Date based foreach loop [duplicate]

I'm working on my first PowerShell script and can't figure the loop out.
I have the following, which will repeat $ActiveCampaigns number of times:
Write-Host "Creating $PQCampaign1 Pre-Qualified Report"
Invoke-Item "$PQCampaignPath1\PQ REPORT $PQCampaign1.qvw"
Write-Host "Waiting 1 minute for QlikView to update"
sleep -seconds 60 # Wait 1 minute for QlikView to Reload, create Report and Save.
DO{
Write-Host "Daily Qlikview Reports"
Write-Host "Wating for QlikView to create the $PQCampaign1 PQ Report"
Get-Date
Write-Host "Checking...."
sleep -seconds 1
Write-Host ""
Write-Host "Not Done Yet"
Write-Host "Will try again in 5 seconds."
Write-Host ""
sleep -seconds 5
}
Until (Test-Path "$PQCampaignPath1\$PQCampaign1 $PQReportName $ReportDate.xlsx" -pathType leaf)
Get-Date
Write-Host "Done with $PQCampaign1 PQ Report. Wait 10 seconds."
sleep -seconds 10
These parameters need to increase with one for each loop:
$PQCampaign1 (should become $PQCampaign2, then 3, etc.)
$PQCampaignPath1 (should become $PQCampaignPath2, then 3, etc.)
So if $ActiveCampaigns is set to 8 on a certain day, then this needs to repeat 8 times and the last time it must open $PQCampaign3 which lies in $PQCampaignPath8.
How can I fix this?
Use:
1..10 | % { write "loop $_" }
Output:
PS D:\temp> 1..10 | % { write "loop $_" }
loop 1
loop 2
loop 3
loop 4
loop 5
loop 6
loop 7
loop 8
loop 9
loop 10
This may be what you are looking for:
for ($i=1; $i -le $ActiveCampaigns; $i++)
{
$PQCampaign = Get-Variable -Name "PQCampaign$i" -ValueOnly
$PQCampaignPath = Get-Variable -Name "PQCampaignPath$i" -ValueOnly
# Do stuff with $PQCampaign and $PQCampaignPath
}
Here is a simple way to loop any number of times in PowerShell.
It is the same as the for loop above, but much easier to understand for newer programmers and scripters. It uses a range and foreach. A range is defined as:
range = lower..upper
or
$range = 1..10
A range can be used directly in a for loop as well, although not the most optimal approach, any performance loss or additional instruction to process would be unnoticeable. The solution is below:
foreach($i in 1..10){
Write-Host $i
}
Or in your case:
$ActiveCampaigns = 10
foreach($i in 1..$ActiveCampaigns)
{
Write-Host $i
If($i==$ActiveCampaigns){
// Do your stuff on the last iteration here
}
}
See this link. It shows you how to dynamically create variables in PowerShell.
Here is the basic idea:
Use New-Variable and Get-Variable,
for ($i=1; $i -le 5; $i++)
{
New-Variable -Name "var$i" -Value $i
Get-Variable -Name "var$i" -ValueOnly
}
(It is taken from the link provided, and I don't take credit for the code.)

Time-based while loop

I've been trying to get the while & the If loop to read in the time correctly, but it doesn't end the program when the right time has hit. I tried using single quotes '' and double quotes "", as well as different syntax like (-eq, -match, -ne) to see if any of those work....and they don't.
Program Goal: loops until it hits 07:00am
# While the value is 1.
while ($value -ne 2)
{
# Value should be 1 in order to stay in loop.
$value = 1
# Get's the time in 24hr format
$time = get-date -Format HH:mm:ss
# For Debugging; Writes out Time
Write-Output $time
# Creates a Pop-Up Windows that prevents the computer from timing out; runs every 15 minutes.
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("Operation Completed",0,"Done",0x1)
# Causes the Program to wait to send the Enter Keystroke.
Sleep 4
# Sends the Enter Keystroke.
$wshell.sendkeys('~')
# Causes the Program to wait to send the Enter Keystroke in seconds (900sec = 15 Minutes).
Sleep 4
# If Condition; If the time is over 2am then the program quits.
If ($time -eq "02:03:00")
{
# While Loop End Condition
$value = 2
# "Debugging Output"
Write-Output $value
Write-Output $time
}
Else
{
# While Loop Condition
$value = 1
# "Debugging Output"
Write-Output $value
Write-Output $time
}
}
# "Debugging Output"
Write-Output "End"
Write-Output $time
Write-Output $value
The chances are really low that your if-statement will become true. Because your while loop takes at least 8 (2x Start-Sleep and other work) seconds until a new beginning. That means the $time variable will probably never be exactly 02:03:00. In this case I would not go for the exact time. Instead I would check if it's 02:03:00 or later. Try that:
$time = Get-Date
if ($time -ge (Get-Date -Hour 02 -Minute 03 -Second 00))
{
}
This condition should do the work:
if ((Get-Date) -gt (Get-Date -Hour 7 -Minute 0 -Second 0)) {
# While Loop End Condition
$value = 2
# more actions
}
It's comparing the current time with the DateTime object with current day, but time set to 07:00:00.
Keep in mind two things:
It will allow the loop to run only between midnight and 7AM. If you want to start the script the day before you need to adjust the conditions.
It might be more readable to not use if, but put the condition directly in while() like this:
while ((Get-Date) -lt (Get-Date -Hour 7 -Minute 0 -Second 0)) {
# do something
}
Currently, you're checking for exact time, so in theory the end condition might be met, However, if it hits that specific line one second before/after, it won't stop the loop.
Finished Code (at least this version of it anyhow). Appreciate the help too :)!
<###
Program Goal: Prevent PC Timeout/Sleep; also loops until it hits 07:00am
Date: 10/14/19
Version: 1.2
###>
# Creates a Pop-Up Windows that prevents the computer from timing out; runs every 15 minutes.
$wshell = New-Object -ComObject Wscript.Shell
# Get's the time in 24hr format
$time = get-date
# While the value is not equal to 2.
while ($value -ne 2)
{
# Value should be 1 in order to stay in loop.
$value = 1
$wshell.Popup("Operation Completed", 5,"Done", 1)
# Causes the Program to wait to send the Enter Keystroke.
Sleep 2
# If Condition; If the time is over 7am then the program quits.
if ((Get-Date) -gt (Get-Date -Hour 7 -Minute 0 -Second 0))
{
# While Loop End Condition
$value = 2
}
else
{
# While Loop Condition
$value = 1
# Causes the Program to wait (900sec = 15 Minutes) to prevent PC Timeout, will re-run again after Sleep.
Sleep 900
}
}
# Cleans up the COM Object
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wshell) | Out-Null

Efficient way to find and replace many strings in a large text file

The Text file contains a software output on a time domain analysis. 10800 seconds simulation and 50 nodes being considered. We have 540,000 strings to be replaced in 540 MB text file with 4.5 million lines.
Which is currently projected to take more than 4 days. Something is going wrong. Don't know what. Please suggest me a better efficient approach.
Below is the function which does the find and replace.
To replace the string the script goes through the original text file line by line at the same time it generates a duplicate file with replaced strings. So another 540 MB file with 4.5 million lines will be generated at the end of the script.
Function ReplaceStringsInTextFile
{
$OutputfilebyLine = New-Object -typename System.IO.StreamReader $inputFilePathFull
$uPreviousValue = 0
$time = 60
$u = 0; $LastStringWithoutFindResult = 0
$lineNumber = 0
while ($null -ne ($line = $OutputfilebyLine.ReadLine())) {
$lineNumber = $lineNumber + 1
if ($time -le $SimulationTimeSeconds) # time simulation start and end checks
{
# 10800 strings corresponds to one node
# there are 50 nodes.. Thus 540,000 values
# $StringsToFindFileContent contains strings to find 540,000 strings
# $StringsToReplaceFileContent contains strings to replace 540,000 strings
$StringToFindLineSplit = -split $StringsToFindFileContent[$time-60]
$StringToReplaceLineSplit = -split $StringsToReplaceFileContent[$time-60]
if($u -le $NumberofNodes-1)
{
$theNode = $Nodes_Ar[$u]
$StringToFindvalue = $StringToFindLineSplit[$u]
$StringToReplacevalue = $StringToReplaceLineSplit[$u]
if (($line -match $theNode) -And ($line -match $StringToFindvalue)){
$replacedLine = $line.replace($StringToFindvalue,$StringToReplacevalue)
add-content -path $WriteOutputfilePathFull -value "$replacedLine"
$uPreviousValue = $u
$checkLineMatched = 1
if (($line -match $LastNodeInArray)) {
$time = $time + 1
$LastStringWithoutFindResult = 0
}
} elseIf (($line -match $LastNodeInArray) -And ($checkLineMatched -eq 0)) {
$LastStringWithoutFindResult = $LastStringWithoutFindResult + 1
} else {
#"Printing lines without match"
add-content -path $WriteOutputfilePathFull -value "$line"
$checkLineMatched = 0
}
}
if ($checkLineMatched -eq 1) {
# incrementing the value of node index to next one in case the last node is found
$u = $uPreviousValue + 1
if ($u -eq $Nodes_Ar.count) {
$u = 0
$timeElapsed = (get-date -displayhint time) - $startTime
"$($timeElapsed.Hours) Hours $($timeElapsed.Minutes) Minutes $($timeElapsed.Seconds) Seconds"
}
}
}
# Checking if the search has failed for more than three cycles
if ($LastStringWithoutFindResult -ge 5) { # showing error dialog in case of search error
[System.Windows.Forms.MessageBox]::Show("StringToFind Search Fail. Please correct StringToFind values. Aborting now" , "Status" , 0)
$OutputfilebyLine.close()
}
}
$OutputfilebyLine.close()
}
The above function is the last part of the script. Which is taking the most time.
I had run the script in under 10 hours 1 year ago.
Update The script sped up running after 4 hours and suddenly time to complete projection reduced from 4 days to under 3 hours. The script finished running in 7 hours and 9 minutes. However i am not sure what made the sudden change in speed other than asking the question on stack overflow :)
As per the suggestion by https://stackoverflow.com/users/478656/tessellatingheckler
I have avoided writing one line at a time using
add-content -path $WriteOutputfilePathFull -value "$replacedLine"
Instead i am now writing ten thousand lines at a time using add-content
$tenThousandLines = $tenThousandLines + "`n" + $replacedLine
And at the appropriate time I am using add-content to write 10,000 lines at one go like below. The if block follows my methods logic
if ($lineNumber/10000 -gt $tenThousandCounter){
clear-host
add-content -path $WriteOffpipeOutputfilePathFull -value "$tenThousandLines"
$tenThousandLines = ""
$tenThousandCounter = $tenThousandCounter + 1
}
I have encountered system out of memmory exception error when trying to add 15,000 or 25,000 lines at a time. After using this the time required for the operation has reduced from 7 hours to 5 hours. And at another time to 2 hours and 36 minutes.

Infinite loop in comparing Time Powershell

I've been trying to create code in PowerShell that's gonna run if the time is below a specific time but upon experimenting it always gets stuck in an infinite loop executing even when the condition is already not satisfied
here's my code:
$a = Get-Date
Write-Host $a
[datetime]$time = "05/12/2016 1:57:00 AM"
DO
{
if($a -gt $time) {break}
Write-Host "not yet time"
if($a -gt $time) {break}
}while( $a -le $time)
CLS
Write-Host "done"
But it still is running even at 1:59 AM already. Any ideas on how to properly compare time? I want it to run till a specific time or I want it to run at least 2 hours.
Since you only assign the output from Get-Date to $a once, it's never updated, and you're performing the exact same comparison (times 3) every time the loop runs.
Call Get-Date in the loop body or conditional to get the updated time:
[datetime]$time = "05/12/2016 1:57:00 AM"
do {
Write-Host "not yet time"
}while($(Get-Date) -le $time)
cls
Write-Host "done"

Loop X number of times

I'm working on my first PowerShell script and can't figure the loop out.
I have the following, which will repeat $ActiveCampaigns number of times:
Write-Host "Creating $PQCampaign1 Pre-Qualified Report"
Invoke-Item "$PQCampaignPath1\PQ REPORT $PQCampaign1.qvw"
Write-Host "Waiting 1 minute for QlikView to update"
sleep -seconds 60 # Wait 1 minute for QlikView to Reload, create Report and Save.
DO{
Write-Host "Daily Qlikview Reports"
Write-Host "Wating for QlikView to create the $PQCampaign1 PQ Report"
Get-Date
Write-Host "Checking...."
sleep -seconds 1
Write-Host ""
Write-Host "Not Done Yet"
Write-Host "Will try again in 5 seconds."
Write-Host ""
sleep -seconds 5
}
Until (Test-Path "$PQCampaignPath1\$PQCampaign1 $PQReportName $ReportDate.xlsx" -pathType leaf)
Get-Date
Write-Host "Done with $PQCampaign1 PQ Report. Wait 10 seconds."
sleep -seconds 10
These parameters need to increase with one for each loop:
$PQCampaign1 (should become $PQCampaign2, then 3, etc.)
$PQCampaignPath1 (should become $PQCampaignPath2, then 3, etc.)
So if $ActiveCampaigns is set to 8 on a certain day, then this needs to repeat 8 times and the last time it must open $PQCampaign3 which lies in $PQCampaignPath8.
How can I fix this?
Use:
1..10 | % { write "loop $_" }
Output:
PS D:\temp> 1..10 | % { write "loop $_" }
loop 1
loop 2
loop 3
loop 4
loop 5
loop 6
loop 7
loop 8
loop 9
loop 10
This may be what you are looking for:
for ($i=1; $i -le $ActiveCampaigns; $i++)
{
$PQCampaign = Get-Variable -Name "PQCampaign$i" -ValueOnly
$PQCampaignPath = Get-Variable -Name "PQCampaignPath$i" -ValueOnly
# Do stuff with $PQCampaign and $PQCampaignPath
}
Here is a simple way to loop any number of times in PowerShell.
It is the same as the for loop above, but much easier to understand for newer programmers and scripters. It uses a range and foreach. A range is defined as:
range = lower..upper
or
$range = 1..10
A range can be used directly in a for loop as well, although not the most optimal approach, any performance loss or additional instruction to process would be unnoticeable. The solution is below:
foreach($i in 1..10){
Write-Host $i
}
Or in your case:
$ActiveCampaigns = 10
foreach($i in 1..$ActiveCampaigns)
{
Write-Host $i
If($i==$ActiveCampaigns){
// Do your stuff on the last iteration here
}
}
See this link. It shows you how to dynamically create variables in PowerShell.
Here is the basic idea:
Use New-Variable and Get-Variable,
for ($i=1; $i -le 5; $i++)
{
New-Variable -Name "var$i" -Value $i
Get-Variable -Name "var$i" -ValueOnly
}
(It is taken from the link provided, and I don't take credit for the code.)