While loop continuously looping comparing times - powershell

$time = Get-Date -DisplayHint Time
$addTime = Read-Host -Prompt "How many minutes until computer turns off?"
$addTimeConvert = [timespan]::FromMinutes($addTime)
$addTimeConvertOut = $addTimeConvert.ToString("hh\:mm\:ss")
$newTime = $time + $addTimeConvert
Write-Host $newTime
Write-Host $time
while ($newTime -ne $time){
$time = Get-Date -DisplayHint Time
}
Stop-Computer
I want to write a script to turn off my computer after a set amount of time but for some reason the while loop continues even after they are the same time.

Rather than answer your asked question, can I offer an alternative solution to the (probable) original problem?
$minutes = Read-Host -Prompt "How many minutes until computer turns off?"
Start-Sleep -Seconds (60 * $minutes)
Stop-Computer

Related

Running a script in PowerShell that repeats every 300s that won't print Get-Date

super new to scripting and this script was found online and has been edited slightly.
I figured I'd try to make it print the time after each cycle, but can't make it print an updated time; it just repeats the first printed time.
$myshell = New-Object -com "Wscript.Shell"
$today = Get-Date -format t
while ($true)
{
$myshell.sendkeys("{F15}")
Write-Host "Last Run $today"
Start-Sleep -Seconds 300
}
I've tried "Write-Output" as well but no change.
Picture of Write-Host
Get-Date is only called once, before your loop so that's why it always displays the same date time.
If you modify your code like this:
$myshell = New-Object -com "Wscript.Shell"
$today = Get-Date -format t
while ($true)
{
$myshell.sendkeys("{F15}")
Write-Host "Last Run $(Get-Date -format t)"
Start-Sleep -Seconds 300
}
It will call Get-Date from within the loop.

Loop while week day + Business hours

I am writing a script where it will only run during M-F 8AM-5PM. The problem is after it falls off either 8-5 or M-F while() loop it just... doesn't know how to get back into the loop. This makes me think that I may be approaching my script at the wrong angle. Maybe there's a better way of doing this.
The "do something" part is it's comparing file size between the same file at 5 minutes apart. This is for checking if the file is growing/shrinking or not.
# Declare current date/time
$Weekday = [int](Get-Date).DayOfWeek
$hour = [int](Get-Date -Format HH)
while (1 -eq 1) { # always true
$Weekday = [int](Get-Date).DayOfWeek
$hour = [int](Get-Date -Format HH)
while ($Weekday -ge 1 -and $Weekday -le 5) { # weekday
$Weekday = [int](get-date).DayOfWeek # loop check for current day
while ($hour -ge 8 -and $hour -le 16) { # 8AM-5PM
$hour = [int](get-date -format HH) # loop check for current hour
# Do Something
}
else {
# Sleep until next business hour
$date = Get-Date
$date = $date.AddDays(1)
$mmddyyy = $date.ToString("MM/dd/yyy")
$nextDy = New-TimeSpan -End "$mmddyyy 08:00"
Write-Host "Start sleep timer until next 8AM"
Start-Sleep -Seconds $nextDy.TotalSeconds
}
}
else {
# Sleep until next business day
$date = Get-Date
while ($Date.DayOfWeek -ne "Monday") {$date = $date.AddDays(1)}
$mmddyyy = $date.ToString("MM/dd/yyy")
$nextBu = New-TimeSpan -End "$mmddyyy 08:00"
Write-Host "Start sleep timer until next Monday 8AM"
Start-Sleep -Seconds $nextBu.TotalSeconds
}
}
Try using scheduled task or service.
If you want to use a PowerShell script try using this:
while ($true)
{
#WeekDay
$Weekday = [int](Get-Date).DayOfWeek #loop check for current day
$hour = [int](Get-Date -Format HH)
if ($Weekday -le 5 -and $Weekday -ge 1)
{
while ($hour -ge 8 -and $hour -le 16)
{
#8AM-5PM
$hour = [int](Get-Date -Format HH) #loop check for current hour
#Do Something
}
}
$date = Get-Date
$date = $date.AddDays(1)
$mmddyyy = $date.ToString("MM/dd/yyy")
$nextDy = New-TimeSpan -End "$mmddyyy 08:00"
Write-Host "Start sleep timer until next 8AM"
Start-Sleep -Seconds $nextDy.TotalSeconds
}
I think you got it totally wrong.
Better you use a simple library function like TestFileSizeUtil to monitor file size or use WMI like shown here:
$query = "Select * from __InstanceModificationEvent WITHIN 5 WHERE TargetInstance ISA 'CIM_DataFile' AND TargetInstance.Name='C:\\Logs\\test.log'"
Register-WmiEvent -Query $query -Action {
Write-Host "Current file size is: " $Event.SourceEventArgs.NewEvent.TargetInstance.FileSize
$prevSize = $Event.SourceEventArgs.NewEvent.PreviousInstance.FileSize
$curSize = $Event.SourceEventArgs.NewEvent.TargetInstance.FileSize
if ($curSize -gt $prevSize) {
$bytes = $curSize - $prevSize
Write-Host "File grew by: $bytes bytes"
} else {
$bytes = $prevSize - $curSize
Write-Host "File reduced by: $bytes bytes"
}
}
Then, it should not matter if your script runs all day long.

Simple Reminder Script doesn't work

Since, I am little forgetful, I am making a simple Powershell script, which will remind me of certain specified things at given time. Below is my code, which goes into infinite loops.
$whsehll = New-Object -ComObject Wscript.shell
$Reminder = Read-Host "What time do you want a reminder of?"
$Subject = Read-Host "What do you want me to remind you?"
$Time = Get-Date -Format hh:mm
do {
Start-Sleep -Seconds 1
}while (($Reminder -ge $Time))
$whsehll.Popup("Hello, I am here to remind you of $Subject")
I have done all wayouts, but I am unable to work it out.
P.S. I ran a Get-date command first on powershell, and Got the time pattern as 03:00 instead 15:00hrs, so I assume that userinput should also be in 12 hrs clock pattern?
P.P.S. I did try other work-arounds like toggling with comparing options -gt -ge -lt -le but none of it works. Please help.
Change the following
$Time = Get-Date -Format hh:mm
do {
Start-Sleep -Seconds 1
to
do {
Start-Sleep -Seconds 1
$Time = Get-Date -Format hh:mm
and it will work.
You should take the current time each Loop, and not compare to the Initial time, when entered the loop.
Or use HH:mm for 24h format, as AgentK stated.
Note, that if $reminder ist 02:34, it will notify you at 02:35, because of -ge instead of -gt (or 14:34/35 when used HH:mm as Format).
For me it is easier to read/understand to just use a while Loop:
$wshshell = New-Object -ComObject Wscript.shell
$Reminder = Read-Host "What time do you want a reminder of?"
$Subject = Read-Host "What do you want me to remind you?"
while ($Reminder -gt (Get-Date -Format HH:mm))
{
Start-Sleep -Seconds 1
}
$wshshell.Popup("Hello, I am here to remind you of $Subject")
Fixed up your script a little and now it seems to be working:
$whsehll = New-Object -ComObject Wscript.shell
[datetime]$Reminder = Read-Host "What time do you want a reminder of?"
$Subject = Read-Host "What do you want me to remind you?"
$Time = Get-Date
do {
Start-Sleep -Seconds 1
}
while ($Reminder -ge $Time)
$whsehll.Popup("Hello, I am here to remind you of $Subject")
PS. If you using 24H format time you want to use capital H, like HH:mm

PowerShell show elapsed time

I have a script that kicks off an event and waits for the user to press any key to stop the script execution. I was trying to find a way to show a timer (how long the script has been running) while waiting for user input at Read-Host. Is there a way to accomplish this?
This works
$Time = [System.Diagnostics.Stopwatch]::StartNew()
while ($true) {
$CurrentTime = $Time.Elapsed
write-host $([string]::Format("`rTime: {0:d2}:{1:d2}:{2:d2}",
$CurrentTime.hours,
$CurrentTime.minutes,
$CurrentTime.seconds)) -nonewline
sleep 1
if ($Host.UI.RawUI.KeyAvailable -and ("q" -eq $Host.UI.RawUI.ReadKey("IncludeKeyUp,NoEcho").Character)) {
Write-Host "Exiting now"
break;
}
}
This gave me the output I was after :)
$StartTime = $(get-date)
$elapsedTime = $(get-date) - $StartTime
$totalTime = "{0:HH:mm:ss}" -f ([datetime]$elapsedTime.Ticks)
From the article Measuring Elapsed Time in Powershell (archived copy):
Assuming that the variable $script:StartTime was set at the
beginning of your script, elapsed time can be determined using either
of the following methods:
$elapsedTime = new-timespan $script:StartTime $(get-date)
or
$elapsedTime = $(get-date) - $script:StartTime
Both methods work exactly the same and produce a System.TimeSpan object.
So, using the above example, you could set $script:StartTime before Read-Host and then call
$elapsedTime = $(get-date) - $script:StartTime after.
Using a timer class (RIP poshtips) gave me something like this:
$Time = [System.Diagnostics.Stopwatch]::StartNew()
while ($NoEvent) {
$CurrentTime = $Time.Elapsed
write-host $([string]::Format("`rTime: {0:d2}:{1:d2}:{2:d2}",
$CurrentTime.hours,
$CurrentTime.minutes,
$CurrentTime.seconds)) -nonewline
sleep 1
#Handle event
if(event){$NoEvent = false}
}
Where $NoEvent is your event/boolean (key press func, etc.).
I'm happy with this little function expanded on from Xelco52's answer
$startTime = $(get-date)
write-host "Elapsed:00:00:00"
$NoEvent = $true
While ($NoEvent)
{
Start-Sleep 1
$elapsedTime = new-timespan $startTime $(get-date)
write-host "Elapsed:$($elapsedTime.ToString("hh\:mm\:ss"))"
#Handle event
if(event){$NoEvent = $false}
}
TLDR answer
$StartTime = $(get-date)
#...do something...
$elapsedTime = $(get-date) - $StartTime
Most of the times $elapsedTime.TotalSeconds or $elapsedTime.TotalMilliseconds is what you want.

How can you set a time limit for a PowerShell script to run for?

I want to set a time limit on a PowerShell (v2) script so it forcibly exits after that time limit has expired.
I see in PHP they have commands like set_time_limit and max_execution_time where you can limit how long the script and even a function can execute for.
With my script, a do/while loop that is looking at the time isn't appropriate as I am calling an external code library that can just hang for a long time.
I want to limit a block of code and only allow it to run for x seconds, after which I will terminate that code block and return a response to the user that the script timed out.
I have looked at background jobs but they operate in a different thread so won't have kill rights over the parent thread.
Has anyone dealt with this or have a solution?
Thanks!
Something like this should work too...
$job = Start-Job -Name "Job1" -ScriptBlock {Do {"Something"} Until ($False)}
Start-Sleep -s 10
Stop-Job $job
Here's my solution, inspired by this blog post. It will finish running when all has been executed, or time runs out (whichever happens first).
I place the stuff I want to execute during a limited time in a function:
function WhatIWannaDo($param1, $param2)
{
# Do something... that maybe takes some time?
Write-Output "Look at my nice params : $param1, $param2"
}
I have another funtion that will keep tabs on a timer and if everything has finished executing:
function Limit-JobWithTime($Job, $TimeInSeconds, $RetryInterval=5)
{
try
{
$timer = [Diagnostics.Stopwatch]::StartNew()
while (($timer.Elapsed.TotalSeconds -lt $TimeInSeconds) -and ('Running' -eq $job.JobStateInfo.State)) {
$totalSecs = [math]::Round($timer.Elapsed.TotalSeconds,0)
$tsString = $("{0:hh}:{0:mm}:{0:ss}" -f [timespan]::fromseconds($totalSecs))
Write-Progress "Still waiting for action $($Job.Name) to complete after [$tsString] ..."
Start-Sleep -Seconds ([math]::Min($RetryInterval, [System.Int32]($TimeInSeconds-$totalSecs)))
}
$timer.Stop()
$totalSecs = [math]::Round($timer.Elapsed.TotalSeconds,0)
$tsString = $("{0:hh}:{0:mm}:{0:ss}" -f [timespan]::fromseconds($totalSecs))
if ($timer.Elapsed.TotalSeconds -gt $TimeInSeconds -and ('Running' -eq $job.JobStateInfo.State)) {
Stop-Job $job
Write-Verbose "Action $($Job.Name) did not complete before timeout period of $tsString."
} else {
if('Failed' -eq $job.JobStateInfo.State){
$err = $job.ChildJobs[0].Error
$reason = $job.ChildJobs[0].JobStateInfo.Reason.Message
Write-Error "Job $($Job.Name) failed after with the following Error and Reason: $err, $reason"
}
else{
Write-Verbose "Action $($Job.Name) completed before timeout period. job ran: $tsString."
}
}
}
catch
{
Write-Error $_.Exception.Message
}
}
... and then finally I start my function WhatIWannaDo as a background job and pass it on to the Limit-JobWithTime (including example of how to get output from the Job):
#... maybe some stuff before?
$job = Start-Job -Name PrettyName -Scriptblock ${function:WhatIWannaDo} -argumentlist #("1st param", "2nd param")
Limit-JobWithTime $job -TimeInSeconds 60
Write-Verbose "Output from $($Job.Name): "
$output = (Receive-Job -Keep -Job $job)
$output | %{Write-Verbose "> $_"}
#... maybe some stuff after?
I know this is an old post, but I have used this in my scripts.
I am not sure if its the correct use of it, but the System.Timers.Timer that George put up gave me an idea and it seems to be working for me.
I use it for servers that sometimes hang on a WMI query, the timeout stops it getting stuck.
Instead of write-host I then output the message to a log file so I can see which servers are broken and fix them if needed.
I also don't use a guid I use the servers hostname.
I hope this makes sense and helps you.
$MyScript = {
Get-WmiObject -ComputerName MyComputer -Class win32_operatingsystem
}
$JobGUID = [system.Guid]::NewGuid()
$elapsedEventHandler = {
param ([System.Object]$sender, [System.Timers.ElapsedEventArgs]$e)
($sender -as [System.Timers.Timer]).Stop()
Unregister-Event -SourceIdentifier $JobGUID
Write-Host "Job $JobGUID removed by force as it exceeded timeout!"
Get-Job -Name $JobGUID | Remove-Job -Force
}
$timer = New-Object System.Timers.Timer -ArgumentList 3000 #just change the timeout here
Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action $elapsedEventHandler -SourceIdentifier $JobGUID
$timer.Start()
Start-Job -ScriptBlock $MyScript -Name $JobGUID
Here is an example of using a Timer. I haven't tried it personally, but I think it should work:
function Main
{
# do main logic here
}
function Stop-Script
{
Write-Host "Called Stop-Script."
[System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.CloseAsync()
}
$elapsedEventHandler = {
param ([System.Object]$sender, [System.Timers.ElapsedEventArgs]$e)
Write-Host "Event handler invoked."
($sender -as [System.Timers.Timer]).Stop()
Unregister-Event -SourceIdentifier Timer.Elapsed
Stop-Script
}
$timer = New-Object System.Timers.Timer -ArgumentList 2000 # setup the timer to fire the elapsed event after 2 seconds
Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier Timer.Elapsed -Action $elapsedEventHandler
$timer.Start()
Main
How about something like this:
## SET YOUR TIME LIMIT
## IN THIS EXAMPLE 1 MINUTE, BUT YOU CAN ALSO USE HOURS/DAYS
# $TimeSpan = New-TimeSpan -Days 1 -Hours 2 -Minutes 30
$TimeSpan = New-TimeSpan -Minutes 1
$EndTime = (Get-Date).AddMinutes($TimeSpan.TotalMinutes).ToString("HH:mm")
## START TIMED LOOP
cls
do
{
## START YOUR SCRIPT
Write-Warning "Test-Job 1...2...3..."
Start-Sleep 3
Write-Warning "End Time = $EndTime`n"
}
until ($EndTime -eq (Get-Date -Format HH:mm))
## TIME REACHED AND END SCRIPT
Write-Host "End Time reached!" -ForegroundColor Green
When using hours or days as a timer, make sure you adjust the $TimeSpan.TotalMinutes
and the HH:mm format, since this does not facilitate the use of days in the example.
I came up with this script.
Start-Transcript to log all actions and save them to a file.
Store the current process ID value in the variable $p then write it to screen.
Assign the current date to the $startTime variable.
Afterwards I assign it again and add the extra time to the current date to the var $expiration.
The updateTime function return what time there is left before the application closes. And writes it to console.
Start looping and kill process if the timer exceeds the expiration time.
That's it.
Code:
Start-Transcript C:\Transcriptlog-Cleanup.txt #write log to this location
$p = Get-Process -Id $pid | select -Expand id # -Expand selcts the string from the object id out of the current proces.
Write-Host $p
$startTime = (Get-Date) # set start time
$startTime
$expiration = (Get-Date).AddSeconds(20) #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 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 "------------------------------------------------------------------"
}
#-----------------
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
}
until ($p.HasExited -or (Get-Date) -gt $expiration) #check exit time
Write-Host "done"
Stop-Transcript
if (-not $p.HasExited) { Stop-Process -ID $p -PassThru } # kill process after time expires