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.
Related
I have searched but apparently my google foo is weak. What I need is a way to prompt for user input in the console and have the request time out after a period of time and continue executing the script if no input comes in. As near as I can tell, Read-Host does not provide this functionality. Neither does $host.UI.PromptForChoice() nor does $host.UI.RawUI.ReadKey(). Thanks in advance for any pointers.
EDIT: Much thanks to Lars Truijens for finding the answer. I have taken the code that he pointed out and encapsulated it into a function. Note that the way that I have implemented it means there could be up to one second of delay between when the user hits a key and when script execution continues.
function Pause-Host
{
param(
$Delay = 1
)
$counter = 0;
While(!$host.UI.RawUI.KeyAvailable -and ($counter++ -lt $Delay))
{
[Threading.Thread]::Sleep(1000)
}
}
Found something here:
$counter = 0
while(!$Host.UI.RawUI.KeyAvailable -and ($counter++ -lt 600))
{
[Threading.Thread]::Sleep( 1000 )
}
It's quite old now but how I solved it based on the same KeyAvailable method is here:
https://gist.github.com/nathanchere/704920a4a43f06f4f0d2
It waits for x seconds, displaying a . for each second that elapses up to the maximum wait time. If a key is pressed it returns $true, otherwise $false.
Function TimedPrompt($prompt,$secondsToWait){
Write-Host -NoNewline $prompt
$secondsCounter = 0
$subCounter = 0
While ( (!$host.ui.rawui.KeyAvailable) -and ($count -lt $secondsToWait) ){
start-sleep -m 10
$subCounter = $subCounter + 10
if($subCounter -eq 1000)
{
$secondsCounter++
$subCounter = 0
Write-Host -NoNewline "."
}
If ($secondsCounter -eq $secondsToWait) {
Write-Host "`r`n"
return $false;
}
}
Write-Host "`r`n"
return $true;
}
And to use:
$val = TimedPrompt "Press key to cancel restore; will begin in 3 seconds" 3
Write-Host $val
For people who are looking for a modern age solution with an additional constraint for exiting a PowerShell script on a pre-defined key press, the following solution might help you:
Write-Host ("PowerShell Script to run a loop and exit on pressing 'q'!")
$count=0
$sleepTimer=500 #in milliseconds
$QuitKey=81 #Character code for 'q' key.
while($count -le 100)
{
if($host.UI.RawUI.KeyAvailable) {
$key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp")
if($key.VirtualKeyCode -eq $QuitKey) {
#For Key Combination: eg., press 'LeftCtrl + q' to quit.
#Use condition: (($key.VirtualKeyCode -eq $Qkey) -and ($key.ControlKeyState -match "LeftCtrlPressed"))
Write-Host -ForegroundColor Yellow ("'q' is pressed! Stopping the script now.")
break
}
}
#Do your operations
$count++
Write-Host ("Count Incremented to - {0}" -f $count)
Write-Host ("Press 'q' to stop the script!")
Start-Sleep -m $sleepTimer
}
Write-Host -ForegroundColor Green ("The script has stopped.")
Sample script output:
Refer Microsoft document on key states for handling more combinations.
Credits: Technet Link
Here is a keystroke utility function that accepts:
Validation character set (as a 1-character regex).
Optional message
Optional timeout in seconds
Only matching keystrokes are reflected to the screen.
Usage:
$key = GetKeyPress '[ynq]' "Run step X ([y]/n/q)?" 5
if ($key -eq $null)
{
Write-Host "No key was pressed.";
}
else
{
Write-Host "The key was '$($key)'."
}
Implementation:
Function GetKeyPress([string]$regexPattern='[ynq]', [string]$message=$null, [int]$timeOutSeconds=0)
{
$key = $null
$Host.UI.RawUI.FlushInputBuffer()
if (![string]::IsNullOrEmpty($message))
{
Write-Host -NoNewLine $message
}
$counter = $timeOutSeconds * 1000 / 250
while($key -eq $null -and ($timeOutSeconds -eq 0 -or $counter-- -gt 0))
{
if (($timeOutSeconds -eq 0) -or $Host.UI.RawUI.KeyAvailable)
{
$key_ = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown,IncludeKeyUp")
if ($key_.KeyDown -and $key_.Character -match $regexPattern)
{
$key = $key_
}
}
else
{
Start-Sleep -m 250 # Milliseconds
}
}
if (-not ($key -eq $null))
{
Write-Host -NoNewLine "$($key.Character)"
}
if (![string]::IsNullOrEmpty($message))
{
Write-Host "" # newline
}
return $(if ($key -eq $null) {$null} else {$key.Character})
}
timeout.exe 5
still works from powershell. Waits 5 seconds or until key press.
Inelegant perhaps. But easy.
However,
From the Powershell_ISE it pops up a new command prompt window, and returns immediately, so it doesnt wait (From the powershell console it uses that console and does wait). You can make it wait from the ISE with a little more work (still pops up its own window tho):
if ($psISE) {
start -Wait timeout.exe 5
} else {
timeout.exe 5
}
Another option: you can use choice shell command which comes with every Windows version since Windows 2000
Choice /C yn /D n /t 5 /m "Are you sure? You have 5 seconds to decide"
if ($LASTEXITCODE -eq "1") # 1 for "yes" 2 for "no"
{
# do stuff
}
else
{
# don't do stuff
}
Stackoverflow syntax higlighting doesn't work for powershell, # means "comment" here
function ReadKeyWithDefault($prompt, $defaultKey, [int]$timeoutInSecond = 5 ) {
$counter = $timeoutInSecond * 10
do{
$remainingSeconds = [math]::floor($counter / 10)
Write-Host "`r$prompt (default $defaultKey in $remainingSeconds seconds): " -NoNewline
if($Host.UI.RawUI.KeyAvailable){
$key = $host.UI.RawUI.ReadKey("IncludeKeyUp")
Write-Host
return $key
}
Start-Sleep -Milliseconds 100
}while($counter-- -gt 0)
Write-Host $defaultKey
return $defaultKey
}
$readKey = ReadKeyWithDefault "If error auto exit( y/n )" 'y' 5
To optionally pause the script before it exits (useful for running headless scripts and pausing on error output), I appended nathanchere's answer with:
if ([Console]::KeyAvailable) { $pressedKey = [Console]::ReadKey($true); read-host; break; }
elseif ($secondsCounter -gt $secondsToWait) {
Write-Host "`r`n"
return $false;
}
The $Host.UI.RawUI.KeyAvailable seems to be buggy so I had more luck using [Console]::KeyAvailable:
function keypress_wait {
param (
[int]$seconds = 10
)
$loops = $seconds*10
Write-Host "Press any key within $seconds seconds to continue"
for ($i = 0; $i -le $loops; $i++){
if ([Console]::KeyAvailable) { break; }
Start-Sleep -Milliseconds 100
}
if ([Console]::KeyAvailable) { return [Console]::ReadKey($true); }
else { return $null ;}
}
I've been looking for a PowerShell script similar to examples found in .NET examples. To take a New-TimeSpan and display is at 1 day, 2 hours, 3 minutes, 4 seconds. Exclude where its zero, add plural "s" where needed. Anybody have that handy?
This is as far as I got:
$StartDate = (Get-Date).ToString("M/dd/yyyy h:mm:ss tt")
Write-Host "Start Time: $StartDate"
# Do Something
$EndDate = (Get-Date).ToString("M/dd/yyyy h:mm:ss tt")
Write-Host "End Time: $EndDate" -ForegroundColor Cyan
$Duration = New-TimeSpan -Start $StartDate -End $EndDate
$d = $Duration.Days; $h = $Duration.Hours; $m = $Duration.Minutes; $s = $Duration.Seconds
Write-Host "Duration: $d Days, $h Hours, $m Minutes, $s Seconds"
Why not doing it straight forward like this:
$StartDate = (Get-Date).AddDays(-1).AddMinutes(-15).AddSeconds(-3)
$EndDate = Get-Date
$Duration = New-TimeSpan -Start $StartDate -End $EndDate
$Day = switch ($Duration.Days) {
0 { $null; break }
1 { "{0} Day," -f $Duration.Days; break }
Default {"{0} Days," -f $Duration.Days}
}
$Hour = switch ($Duration.Hours) {
#0 { $null; break }
1 { "{0} Hour," -f $Duration.Hours; break }
Default { "{0} Hours," -f $Duration.Hours }
}
$Minute = switch ($Duration.Minutes) {
#0 { $null; break }
1 { "{0} Minute," -f $Duration.Minutes; break }
Default { "{0} Minutes," -f $Duration.Minutes }
}
$Second = switch ($Duration.Seconds) {
#0 { $null; break }
1 { "{0} Second" -f $Duration.Seconds; break }
Default { "{0} Seconds" -f $Duration.Seconds }
}
"Duration: $Day $Hour $Minute $Second"
Output would be :
Duration: 1 Day, 0 Hours, 15 Minutes, 3 Seconds
With 2 in each part of the duration ...
$StartDate = (Get-Date).AddDays(-2).AddHours(-2).AddMinutes(-2).AddSeconds(-2)
the result would be this:
Duration: 2 Days, 2 Hours, 2 Minutes, 2 Seconds
Of course you should wrap this in a function if you need it more than once. ;-)
And of course you can add more complex conditions if you like.
The table view isn't bad:
new-timespan -Days 1 -Hours 1 -Minutes 1 -Seconds 1 | ft
Days Hours Minutes Seconds Milliseconds
---- ----- ------- ------- ------------
1 1 1 1 0
All I'm trying to do is to see if the user input $month is in the array $months. But it's not liking something. Help?
Write-host "The script is to collect from the user High Tempature and Low Tempature for a day in degrees F."
$months = #("January", "February","March","April","May","June","July","August","September","October","November","December")
$finished = $false
while ($finished -eq $false){
$month = read-host "Enter the month";
if ($months -Contains $month)
{
write-host "Invalid entry"
$finished = $false
}
else
{
$finished = $true
}
}
You test logic is just not the good one, just reverse youy test or reverse your actions:
Write-host "The script is to collect from the user High Tempature and Low Tempature for a day in degrees F."
$months = #("January", "February","March","April","May","June","July","August","September","October","November","December")
$finished = $false
while ($finished -eq $false){
$month = read-host "Enter the month";
if ($months -Contains $month)
{
$finished = $true
}
else
{
write-host "Invalid entry"
$finished = $false
}
}
Instead of using -Contains you should just run a RegEx match using the -Match operator. Or, as you are currently testing for a negative result, use -notmatch instead. You can use your existing code, just modify it a little by joining your months with a pipe character. Like:
Write-host "The script is to collect from the user High Tempature and Low Tempature for a day in degrees F."
$months = #("January", "February","March","April","May","June","July","August","September","October","November","December")
$finished = $false
while ($finished -eq $false){
$month = read-host "Enter the month";
if ($month -notmatch ($months -join "|"))
{
write-host "Invalid entry"
$finished = $false
}
else
{
$finished = $true
}
}
Better yet, let's get rid of the If/Else and shorten this. Move the Join to where we define $Months, and then ask for a month and if it isn't a match ask for it again until it is with a While.
$months = #("January", "February","March","April","May","June","July","August","September","October","November","December") -join '|'
$month = read-host "Enter the month"
While($month -notmatch $months){
"Invalid Entry"
$month = read-host "Enter the month"
}
I have powershell code that takes an array, goes through each element and updates an SQL database on another server.
It seems to get stuck in the following code
do{
try{
$cmd_update = New-Object System.Data.SqlClient.SqlCommand($sql_update,$conn_update)
$found = $cmd_update.ExecuteNonQuery()
$num_moved=$num_moved+1
$timestamp = Get-Date
Add-Content D:\Script\Output_SCHOOLS_Default_Group.log "Moving $ip, SEP version $sep_version to $group_name, $timestamp`r"
Write-host Moving $ip, SEP version $sep_version to $group_name, $timestamp
$transactionComplete = $true
}
catch{
$transactionComplete = $false
}
} until ($transactionComplete)
It will stay in this look until $transactionComplete, which may never happen, depending on system bottlenecks.
How do I exit the look if 1 minute has passed and $transactionComplete is still false?
You can introduce one more condition that checks if the total time spent executing is more than one minute.
For example,
$MaxTime = new-timespan -Minutes 1
$StopWatch = [Diagnostics.StopWatch]::StartNew()
do{
try{
$cmd_update = New-Object System.Data.SqlClient.SqlCommand($sql_update,$conn_update)
$found = $cmd_update.ExecuteNonQuery()
$num_moved=$num_moved+1
$timestamp = Get-Date
Add-Content D:\Script\Output_SCHOOLS_Default_Group.log "Moving $ip, SEP version $sep_version to $group_name, $timestamp`r"
Write-host Moving $ip, SEP version $sep_version to $group_name, $timestamp
$transactionComplete = $true
}
catch{
$transactionComplete = $false
}
} until ($transactionComplete -or (-not $StopWatch.Elapsed))
Put this before the loop:
$start = Get-Date
And put this at the end of the catch block:
if (((Get-Date)-$start).TotalMinutes -gt 1) {
$transactionComplete = $true
}
I have searched but apparently my google foo is weak. What I need is a way to prompt for user input in the console and have the request time out after a period of time and continue executing the script if no input comes in. As near as I can tell, Read-Host does not provide this functionality. Neither does $host.UI.PromptForChoice() nor does $host.UI.RawUI.ReadKey(). Thanks in advance for any pointers.
EDIT: Much thanks to Lars Truijens for finding the answer. I have taken the code that he pointed out and encapsulated it into a function. Note that the way that I have implemented it means there could be up to one second of delay between when the user hits a key and when script execution continues.
function Pause-Host
{
param(
$Delay = 1
)
$counter = 0;
While(!$host.UI.RawUI.KeyAvailable -and ($counter++ -lt $Delay))
{
[Threading.Thread]::Sleep(1000)
}
}
Found something here:
$counter = 0
while(!$Host.UI.RawUI.KeyAvailable -and ($counter++ -lt 600))
{
[Threading.Thread]::Sleep( 1000 )
}
It's quite old now but how I solved it based on the same KeyAvailable method is here:
https://gist.github.com/nathanchere/704920a4a43f06f4f0d2
It waits for x seconds, displaying a . for each second that elapses up to the maximum wait time. If a key is pressed it returns $true, otherwise $false.
Function TimedPrompt($prompt,$secondsToWait){
Write-Host -NoNewline $prompt
$secondsCounter = 0
$subCounter = 0
While ( (!$host.ui.rawui.KeyAvailable) -and ($count -lt $secondsToWait) ){
start-sleep -m 10
$subCounter = $subCounter + 10
if($subCounter -eq 1000)
{
$secondsCounter++
$subCounter = 0
Write-Host -NoNewline "."
}
If ($secondsCounter -eq $secondsToWait) {
Write-Host "`r`n"
return $false;
}
}
Write-Host "`r`n"
return $true;
}
And to use:
$val = TimedPrompt "Press key to cancel restore; will begin in 3 seconds" 3
Write-Host $val
For people who are looking for a modern age solution with an additional constraint for exiting a PowerShell script on a pre-defined key press, the following solution might help you:
Write-Host ("PowerShell Script to run a loop and exit on pressing 'q'!")
$count=0
$sleepTimer=500 #in milliseconds
$QuitKey=81 #Character code for 'q' key.
while($count -le 100)
{
if($host.UI.RawUI.KeyAvailable) {
$key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp")
if($key.VirtualKeyCode -eq $QuitKey) {
#For Key Combination: eg., press 'LeftCtrl + q' to quit.
#Use condition: (($key.VirtualKeyCode -eq $Qkey) -and ($key.ControlKeyState -match "LeftCtrlPressed"))
Write-Host -ForegroundColor Yellow ("'q' is pressed! Stopping the script now.")
break
}
}
#Do your operations
$count++
Write-Host ("Count Incremented to - {0}" -f $count)
Write-Host ("Press 'q' to stop the script!")
Start-Sleep -m $sleepTimer
}
Write-Host -ForegroundColor Green ("The script has stopped.")
Sample script output:
Refer Microsoft document on key states for handling more combinations.
Credits: Technet Link
Here is a keystroke utility function that accepts:
Validation character set (as a 1-character regex).
Optional message
Optional timeout in seconds
Only matching keystrokes are reflected to the screen.
Usage:
$key = GetKeyPress '[ynq]' "Run step X ([y]/n/q)?" 5
if ($key -eq $null)
{
Write-Host "No key was pressed.";
}
else
{
Write-Host "The key was '$($key)'."
}
Implementation:
Function GetKeyPress([string]$regexPattern='[ynq]', [string]$message=$null, [int]$timeOutSeconds=0)
{
$key = $null
$Host.UI.RawUI.FlushInputBuffer()
if (![string]::IsNullOrEmpty($message))
{
Write-Host -NoNewLine $message
}
$counter = $timeOutSeconds * 1000 / 250
while($key -eq $null -and ($timeOutSeconds -eq 0 -or $counter-- -gt 0))
{
if (($timeOutSeconds -eq 0) -or $Host.UI.RawUI.KeyAvailable)
{
$key_ = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown,IncludeKeyUp")
if ($key_.KeyDown -and $key_.Character -match $regexPattern)
{
$key = $key_
}
}
else
{
Start-Sleep -m 250 # Milliseconds
}
}
if (-not ($key -eq $null))
{
Write-Host -NoNewLine "$($key.Character)"
}
if (![string]::IsNullOrEmpty($message))
{
Write-Host "" # newline
}
return $(if ($key -eq $null) {$null} else {$key.Character})
}
timeout.exe 5
still works from powershell. Waits 5 seconds or until key press.
Inelegant perhaps. But easy.
However,
From the Powershell_ISE it pops up a new command prompt window, and returns immediately, so it doesnt wait (From the powershell console it uses that console and does wait). You can make it wait from the ISE with a little more work (still pops up its own window tho):
if ($psISE) {
start -Wait timeout.exe 5
} else {
timeout.exe 5
}
Another option: you can use choice shell command which comes with every Windows version since Windows 2000
Choice /C yn /D n /t 5 /m "Are you sure? You have 5 seconds to decide"
if ($LASTEXITCODE -eq "1") # 1 for "yes" 2 for "no"
{
# do stuff
}
else
{
# don't do stuff
}
Stackoverflow syntax higlighting doesn't work for powershell, # means "comment" here
function ReadKeyWithDefault($prompt, $defaultKey, [int]$timeoutInSecond = 5 ) {
$counter = $timeoutInSecond * 10
do{
$remainingSeconds = [math]::floor($counter / 10)
Write-Host "`r$prompt (default $defaultKey in $remainingSeconds seconds): " -NoNewline
if($Host.UI.RawUI.KeyAvailable){
$key = $host.UI.RawUI.ReadKey("IncludeKeyUp")
Write-Host
return $key
}
Start-Sleep -Milliseconds 100
}while($counter-- -gt 0)
Write-Host $defaultKey
return $defaultKey
}
$readKey = ReadKeyWithDefault "If error auto exit( y/n )" 'y' 5
To optionally pause the script before it exits (useful for running headless scripts and pausing on error output), I appended nathanchere's answer with:
if ([Console]::KeyAvailable) { $pressedKey = [Console]::ReadKey($true); read-host; break; }
elseif ($secondsCounter -gt $secondsToWait) {
Write-Host "`r`n"
return $false;
}
The $Host.UI.RawUI.KeyAvailable seems to be buggy so I had more luck using [Console]::KeyAvailable:
function keypress_wait {
param (
[int]$seconds = 10
)
$loops = $seconds*10
Write-Host "Press any key within $seconds seconds to continue"
for ($i = 0; $i -le $loops; $i++){
if ([Console]::KeyAvailable) { break; }
Start-Sleep -Milliseconds 100
}
if ([Console]::KeyAvailable) { return [Console]::ReadKey($true); }
else { return $null ;}
}