Write-Progress not rendering at all when called in a loop - powershell

The script I'm working on is a ticket reserving bot for student events, it waits in the specified loop until sales start, and the write-progress is supposed to let you know when they do start. I'm fairly new to PowerShell and I'm sorry if the code is a sore for the eyes. I'm just baffled since this part did work earlier today, even though nothing about the loop changed iirc.
Here is the relevant part of the script:
While ($currentDate -lt $purchaseTime){
$currentDate = Get-Date
$waitTime = (New-TimeSpan -End $purchaseTime).TotalSeconds
Write-Progress -Activity "Waiting until sales start" -SecondsRemaining $waitTime
Start-Sleep -Milliseconds 10
If ($currentDate -ge $purchaseTime){continue}
}
I also tried this:
Do {
$currentDate = Get-Date
$waitTime = (New-TimeSpan -End $purchaseTime).TotalSeconds
Write-Progress -Activity "Waiting until sales start" -SecondsRemaining $waitTime
Start-Sleep -Milliseconds 201
} until ($currentDate -gt $purchaseTime)
I tried changing the loop from While to Do and even If statements but nothing changed. Can anyone solve this? I'm not getting any errors either, it just won't render.
EDIT 1
This is how the value is fetched, $jsonObject is made out of a GET request, and below is its value.
$purchaseTime = $jsonObject.model.product.dateSalesFrom
"2022-11-25T11:00:00+02:00"
The format shouldn't be the issue, since they've been the same throughout the process of me writing this script. And it used to with that formatting too.
I tried inserting the code suggested by Mathias before that declaration, but it didn't change the end result.
Did he mean I should insert it within the loop? Would that screw it up since I really need it to be this static place in time in order for the script to work as intended. My PSVersion is 5.1. Should I post the entire script for clarity?
EDIT 2.
I have implemented the code that Santiago suggested as the answer, but nothing has changed. The progress still won't render. Could the issue be with how the date from the jsonobject is formatted? The code Santiago posted runs and works on its own in a powershell instance, so I doubt my settings or anything like that is not working as intended.
Here is what the loop currently looks like:
$nowDate = Get-Date
$targetDate = Get-Date $jsonObject.model.product.dateSalesFrom
$timeSpan = $targetDate - $nowDate
$stopWatch = [System.Diagnostics.Stopwatch]::StartNew()
Do {
$progess = #{
Activity = 'Waiting until sales start'
SecondsRemaining = ($timeSpan - $stopWatch.Elapsed).TotalSeconds
}
Write-Progress #progess
Start-Sleep -Milliseconds 200
}
until($stopWatch.Elapsed -ge $timeSpan)

Proof of concept
This example shows how you can achieve your your do loop (I prefer to use do in this case, though it could be done with a while loop too) for exactly 1 minute displaying a progressive countdown. I believe a StopWatch shines for this use case, and it simplifies the code a lot.
$nowDate = Get-Date
$targetDate = $nowDate.AddMinutes(1)
$timeSpan = $targetDate - $nowDate
$stopWatch = [System.Diagnostics.Stopwatch]::StartNew()
do {
$progess = #{
Activity = 'Waiting until sales start'
SecondsRemaining = ($timeSpan - $stopWatch.Elapsed).TotalSeconds
}
Write-Progress #progess
Start-Sleep -Milliseconds 200
}
until($stopWatch.Elapsed -ge $timeSpan)
Using this same logic, you would only need to change the following:
$nowDate = Get-Date
$targetDate = Get-Date $jsonObject.model.product.dateSalesFrom
This would make your loop run until Friday, November 25, 2022 6:00:00 AM.

I have solved the issue.
The problem lies within the $progressPreference variable. I had set it to silentlyContinue to help with WebRequests being smoother, but unbeknownst to me it also affected the Write-Progress cmdlet. It works fine now.

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.)

Adding Time out section in powershell script

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()

Using Time in a conditional Statement in Powershell

I'm trying to just get the groundwork for running some code with a time conditional. But I can't seem to grasp how to add time into the equation. Any Powershell people out there?
The $EndDate doesnt matter anymore. I just tried to use it as a way to understand how powershell uses conditional
$StartTime=(Get-Date)
$EndDate=[datetime]”00:00”
if ($StartDate -gt "00:00" -and $StartTime -lt "11:59")
{
Write-Host "It is time to work"
}
else
{
Write-Host "it is time to go"
}
$StartTime
My code right now should say its time to go but should say its time to work because as of right now its only 11:56 AM ET.
If you want to compare against the time of day, use the TimeOfDay TimeSpan exposed by [datetime] - PowerShell will automatically convert the right-hand "HH:mm" string into a meaningful TimeSpan that can be compared against:
$StartTime = Get-Date
if($StartTime.TimeOfDay -gt "00:00" -and $StartTime.TimeOfDay -le "12:00"){
# AM
}
else{
# PM
}
These two things are usually true.
(get-date) -gt '00:00'
True
(get-date) -lt '23:59'
True
Check for unset variables:
set-strictmode -v 1

How to implement PowerShell callbacks correctly with System.RuntimeCaching.MemoryCache

I'm investigating how to build cache expiry handlers with PowerShell. The below script indicates that goal and the expected output.
$RemovedAction = {
param([System.Runtime.Caching.CacheEntryRemovedArguments]$Arguments)
$Key = $Arguments.CacheItem.Key
$Item = $Arguments.CacheItem
$RemovedObject = $Arguments.CacheItem.Value
$RemovedReason = $Arguments.RemovedReason
Write-Host -ForegroundColor Yellow "About to be removed from cache: $Key :
$RemovedReason"
}
# Build a cache, create a policy that references the callback and add items to the cache.
$MC = New-Object System.Runtime.Caching.MemoryCache('Main')
$Policy = New-Object System.Runtime.Caching.CacheItemPolicy
$Policy.AbsoluteExpiration = (Get-DAte).ToUniversalTime().AddSeconds(4) #N.B. It's always in UTC.
$Policy.RemovedCallback = $RemovedAction
$A = [PSCustomObject]#{ Name = 'Albert'; Age = 21 }
$B = [PSCustomObject]#{ Name = 'Bob'; Age = 21 }
[void]$MC.Set('A',$A, $Policy)
[void]$MC.Set('B',$B, $Policy)
Start-Sleep -Seconds 1
Write-Host -ForegroundColor Green "Expect Bob to be expired immediately..."
[Void]$MC.Remove('B') # Expect to see Bob be expired.
Write-Host -ForegroundColor Green "Expect Albert to be expired due to the timeout, clearly it's in the cache at the start of the sleep."
$MC
Start-Sleep -Seconds 6
Write-Host -ForegroundColor Green "See, Albert has gone, as it's not in the cache, but it does not look as thouh the delegate was triggered."
$MC
The result of this is that the explicit example of removing "Albert" works as expected, with the handler being called with the correct argument, however the implicit example with the absolute expiration time does not seem to work as expected.
In the latter case it seems that the item does expire from the cache (as we can see from the fact it doesn't get printed out) however it does not appear that the callback was indeed called.
I suspect this may be a threading issue, but it is not clear to me what is going on here.
This is a stepping stone to implementing a custom change monitor callback.
This answer is a year late, but for anyone who finds there way here - expiration doesn't trigger the callback. The first time you call for the data AFTER it expires is what triggers the callback. See MemoryCache AbsoluteExpiration acting strange
Start-Sleep -Seconds 6
$MC.Get('A') | out-null
Write-Host -ForegroundColor Green "See, Albert has gone, as it's not in the cache, but it does not look as thouh the delegate was triggered."

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"