How to configure a timeout for Read-Host in PowerShell - powershell

Like I said, this code works in PowerShell version 2, but not in PowerShell version 5.
function wait
{
$compte = 0
Write-Host "To continue installation and ignore configuration warnings type [y], type any key to abort"
While(-not $Host.UI.RawUI.KeyAvailable -and ($compte -le 20))
{
$compte++
Start-Sleep -s 1
}
if ($compte -ge 20)
{
Write-Host "Installation aborted..."
break
}
else
{
$key = $host.ui.rawui.readkey("NoEcho,IncludeKeyup")
}
if ($key.character -eq "y")
{Write-Host "Ignoring configuration warnings..."}
else
{Write-Host "Installation aborted..."
}}

The official documentation or Read-Host -? will tell that it's not possible to use Read-Host in that manner. There is no possible parameter to tell it to run with some kind of timeout.
But there are various other questions detailing how to do this in PowerShell (usually utilizing C#).
The idea seems to be to check whenever the user pressed a key using $Host.UI.RawUI.KeyAvailable and check that for the duration of your timeout.
A simple working example could be the following:
$secondsRunning = 0;
Write-Output "Press any key to abort the following wait time."
while( (-not $Host.UI.RawUI.KeyAvailable) -and ($secondsRunning -lt 5) ){
Write-Host ("Waiting for: " + (5-$secondsRunning))
Start-Sleep -Seconds 1
$secondsRunning++
}
You could use $host.UI.RawUI.ReadKey to get the key that was pressed. This solution probably would not be acceptable if you need more complex input than a simple button press. See also:
Windows PowerShell Tip of the Week - Pausing a Script Until the User Presses a Key
PowerTip: Use PowerShell to Wait for a Key Press (Hey, Scripting Guy!)

Seth, thank you for your solution. I expanded on the example you provided and wanted to give that back to the community.
The use case is a bit different here - I have a loop checking if an array of VMs can be migrated and if there are any failures to that check the operator can either remediate those until the checks clear or they can opt to "GO" and have those failing VMs excluded from the operation. If something other than GO is typed state remains within the loop.
One downside to this is if the operator inadvertently presses a key the script will be blocked by Read-Host and may not be immediately noticed. If that's a problem for anyone I'm sure they can hack around that ;-)
Write-Host "Verifying all VMs have RelocateVM_Task enabled..."
Do {
$vms_pivoting = $ph_vms | Where-Object{'RelocateVM_Task' -in $_.ExtensionData.DisabledMethod}
if ($vms_pivoting){
Write-Host -ForegroundColor:Red ("Some VMs in phase have method RelocateVM_Task disabled.")
$vms_pivoting | Select-Object Name, PowerState | Format-Table -AutoSize
Write-Host -ForegroundColor:Yellow "Waiting until this is resolved -or- type GO to continue without these VMs:" -NoNewline
$secs = 0
While ((-not $Host.UI.RawUI.KeyAvailable) -and ($secs -lt 15)){
Start-Sleep -Seconds 1
$secs++
}
if ($Host.UI.RawUI.KeyAvailable){
$input = Read-Host
Write-Host ""
if ($input -eq 'GO'){
Write-Host -ForegroundColor:Yellow "NOTICE: User prompted to continue migration without the blocked VM(s)"
Write-Host -ForegroundColor:Yellow "Removing the following VMs from the migration list"
$ph_vms = $ph_vms | ?{$_ -notin $vms_pivoting} | Sort-Object -Property Name
}
}
} else {
Write-Host -ForegroundColor:Green "Verified all VMs have RelocateVM_Task method enabled."
}
} Until(($vms_pivoting).Count -eq 0)

Also note that all this $Host.UI stuff doesn't work from the Powershell ISE.
To find out from within a script you could test for $Host.Name -eq "ConsoleHost". When true you can use the code from this topic. Otherwise you could use $Host.UI.PromptForChoice or any other way of showing a dialog box. With System.Windows.Forms.Timer you can then set a timer, and code to close the dialog box or form can be run when it expires.

Related

Script won't run in Switch menu

function Show-Menu { #Create the Show-Menu function
param ([string]$Title = 'Functions') #Sets title
Clear-Host
Write-Host "`t6: Reboot History." -foregroundcolor white
Write-Host "`tQ: Enter 'Q' to quit."
} #close of create show menu function
#Begin Main Menu
do
{
Show-Menu #Displays created menu above
$Selection = $(Write-Host "`tMake your selection: " -foregroundcolor Red -nonewline; Read-Host)
switch ($selection) #Begin switch selection
{
#===Reboot History===
'6' {
$Workstation = $(Write-Host "Workstation\IP Address" -nonewline -foregroundcolor DarkGreen) + $(Write-Host "(Use IP for remote users)?: " -NoNewline; Read-Host)
$DaysFromToday = Read-Host "How many days would you like to go back?"
$MaxEvents = Read-Host "How many events would you like to view?"
$EventList = Get-WinEvent -ComputerName $Workstation -FilterHashtable #{
Logname = 'system'
Id = '41', '1074', '1076', '6005', '6006', '6008', '6009', '6013'
StartTime = (Get-Date).AddDays(-$DaysFromToday)
} -MaxEvents $MaxEvents -ErrorAction Stop
foreach ($Event in $EventList) {
if ($Event.Id -eq 1074) {
[PSCustomObject]#{
TimeStamp = $Event.TimeCreated
Event = $Event.Id
ShutdownType = 'Restart'
UserName = $Event.Properties.value[6]
}
}
if ($Event.Id -eq 41) {
[PSCustomObject]#{
TimeStamp = $Event.TimeCreated
Event = $Event.Id
ShutdownType = 'Unexpected'
UserName = ' '
}
}
}
pause
}
}
}
until ($selection -eq 'q') #End of main menu
Works perfectly fine if I remove the script from the switch and run it separately, but as soon as I call it from the switch it still asks for the workstation/IP, how many days, and max events, but just outputs nothing.
Here is what it looks like when it works:
How many days would you like to go back?: 90
How many events would you like to view?: 999
TimeStamp Event ShutdownType UserName
--------- ----- ------------ --------
12/23/2022 12:20:55 AM 1074 Restart Username
12/20/2022 1:00:01 AM 1074 Restart Username
12/17/2022 12:21:54 AM 1074 Restart Username
12/13/2022 8:57:40 AM 1074 Restart Username
This is what I get when I run it within the switch menu
Workstation\IP Address(Use IP for remote users)?: IP Address
How many days would you like to go back?: 90
How many events would you like to view?: 999
Press Enter to continue...:
I have tried just doing 1 day and 1 event, but same results. No errors or anything indicating a failure, so not sure how to troubleshoot this. I have had similar issues with switches in the past that were resolved with some researching into scopes, but I don't think this is the same case as it is all self contained within the switch itself.
I am at a loss, any ideas? As always, any insight into my script is greatly appreciated, even if it doesn't resolve the problem at hand.
JosefZ has provided the crucial pointer:
force synchronous to-display output with, such as with Out-Host
if you neglect to do so, the pause statement will - surprisingly - execute before the [pscustomobject] instances emitted by the foreach statement, due to the asynchronous behavior of the implicitly applied Format-Table formatting - see this answer for details.
Here's a simplified example:
switch ('foo') {
default {
# Wrap the `foreach` statement in . { ... },
# so its output can be piped to Out-Host.
. {
foreach ($i in 1..3) {
[pscustomobject] #{ prop = $i }
}
} |
Out-Host # Without this, "pause" will run FIRST.
pause
}
}
Note:
For Out-Host to format all output together it must receive all output from the foreach loop as part of a single pipeline.
Since foreach is a language statement (rather than a command, such as the related ForEach-Object cmdlet) that therefore cannot directly be used at the start of a pipeline, the above wraps it in a script block ({ ... }) that is invoked via ., the dot-sourcing operator, which executes the script block directly in the caller's context and streams the output to the pipeline.
This limitation may be surprising, but is rooted in the fundamentals of PowerShell's grammar - see GitHub issue #10967.
An all-pipeline alternative that doesn't require the . { ... } workaround would be:
1..3 |
ForEach-Object {
[pscustomobject] #{ prop = $_ } # Note the automatic $_ var.
} |
Out-Host

Powershell executing If Statement when condition is not met

I am having an issue with my If/Else statement where it will successfully prompt the user until either a "Y/y" or an "N/n" are entered and will store the proper response in the $input variable outside of the Do/Until loop but will execute the first block of code in the following If Statement whether $input is equal to "Y/y" or "N/n"
How can I make it so the If Statement will only execute when $input is equal to "Y/y" and otherwise if it's "N/n" just execute the empty else and move on to the next $program in $InstallList?
I've tried using an ElseIf and checking for "N/n" but it still only executes the first If Statement.
I've also put Write-Host for $input as a check after it leaves the Do/Until loop and it is the correct input but all falls apart when it moves on to executing the If/Else Statement.
Please help.
foreach($program in $InstallList){
if($program -notin $Installed){
$input = ""
do {
$input = Read-Host -Prompt "$($Program) is not installed would you like to install now? (y/n)"
}
until(($input -eq "y") -or ($input -eq "n"))
if($input -eq "y")
{
Write-ProgressHelper -Message "Installing $($Program)" -StepNumber ($stepCounter++)
Start-Sleep -Seconds 3
Start-Process $Software.$program -Wait
Write-Host "$($Software) installed`n"
}
else {}
else{}
}
Abraham Zinala correctly states that use of $input as a custom variable should be avoided, because it is an automatic variable whose value is managed by PowerShell itself, namely to reflect pipeline input.
It is unfortunate that PowerShell even lets you assign a value, which is a problem that affects other automatic variables too - see this answer.
Technically, as long as you stay within the same scope, you can get away with using $input for custom purposes - though it should still be avoided.
Since this constraint is fulfilled in your code, use of $input is not the source of your problem; there is no obvious problem with your code.
Here's version of your code that avoids use of $input and streamlines ensuring that the user input is valid via the -in operator:
foreach ($program in $InstallList) {
if ($program -notin $Installed) {
do {
$response = Read-Host -Prompt "$($Program) is not installed would you like to install now? (y/n)"
} until ($response -in 'y', 'n')
if ($response -eq 'y') {
'yes' # ... install
}
}
}

Where Command not Working? [duplicate]

Very new to coding in general, so I fear I am missing something completely obvious. I want my program to check for a file. If it is there, just continue the code. If it has not arrived, continue cheking for a given amount of time, or untill the file shows up. My loop works on its own, so when i only select the do-part in Powershell ISE, it works. But when i try running it inside the if statement, nothing happens. The loops doesnt begin.
$exists= Test-Path $resultFile
$a = 1
if ($exists -eq "False")
{
do
{
$a++
log "Now `$a is $a "
start-sleep -s ($a)
$exists= Test-Path $resultFile
write-host "exists = $exists"
}
while (($a -le 5) -and ($exists -ne "True"))
}
Another way of doing this is using a while loop:
$VerbosePreference = 'Continue'
$file = 'S:\myFile.txt'
$maxRetries = 5; $retryCount = 0; $completed = $false
while (-not $completed) {
if (Test-Path -LiteralPath $file) {
Write-Verbose "File '$file' found"
$completed = $true
# Do actions with your file here
}
else {
if ($retryCount -ge $maxRetries) {
throw "Failed finding the file within '$maxRetries' retries"
} else {
Write-Verbose "File not found, retrying in 5 seconds."
Start-Sleep '5'
$retryCount++
}
}
}
Some tips:
Try to avoid Write-Host as it kills puppies and the pipeline (Don Jones). Better would be, if it's meant for viewing the script's progress, to use Write-Verbose.
Try to be consistent in spacing. The longer and more complex your scripts become, the more difficult it will be to read and understand them. Especially when others need to help you. For this reason, proper spacing helps all of us.
Try to use Tab completion in the PowerShell ISE. When you type start and press the TAB-key, it will automatically propose the options available. When you select what you want with the arrow down/up and press enter, it will nicely format the CmdLet to Start-Sleep.
The most important tip of all: keep exploring! The more you try and play with PowerShell, the better you'll get at it.
As pointed out in comments, your problem is that you're comparing a boolean value with the string "False":
$exists -eq "False"
In PowerShell, comparison operators evaluate arguments from left-to-right, and the type of the left-hand argument determines the type of comparison being made.
Since the left-hand argument ($exists) has the type [bool] (a boolean value, it can be $true or $false), PowerShell tries to convert the right-hand argument to a [bool] as well.
PowerShell interprets any non-empty string as $true, so the statement:
$exists -eq "False"
is equivalent to
$exists -eq $true
Which is probably not what you intended.

Powershell loop only if condition is true

Very new to coding in general, so I fear I am missing something completely obvious. I want my program to check for a file. If it is there, just continue the code. If it has not arrived, continue cheking for a given amount of time, or untill the file shows up. My loop works on its own, so when i only select the do-part in Powershell ISE, it works. But when i try running it inside the if statement, nothing happens. The loops doesnt begin.
$exists= Test-Path $resultFile
$a = 1
if ($exists -eq "False")
{
do
{
$a++
log "Now `$a is $a "
start-sleep -s ($a)
$exists= Test-Path $resultFile
write-host "exists = $exists"
}
while (($a -le 5) -and ($exists -ne "True"))
}
Another way of doing this is using a while loop:
$VerbosePreference = 'Continue'
$file = 'S:\myFile.txt'
$maxRetries = 5; $retryCount = 0; $completed = $false
while (-not $completed) {
if (Test-Path -LiteralPath $file) {
Write-Verbose "File '$file' found"
$completed = $true
# Do actions with your file here
}
else {
if ($retryCount -ge $maxRetries) {
throw "Failed finding the file within '$maxRetries' retries"
} else {
Write-Verbose "File not found, retrying in 5 seconds."
Start-Sleep '5'
$retryCount++
}
}
}
Some tips:
Try to avoid Write-Host as it kills puppies and the pipeline (Don Jones). Better would be, if it's meant for viewing the script's progress, to use Write-Verbose.
Try to be consistent in spacing. The longer and more complex your scripts become, the more difficult it will be to read and understand them. Especially when others need to help you. For this reason, proper spacing helps all of us.
Try to use Tab completion in the PowerShell ISE. When you type start and press the TAB-key, it will automatically propose the options available. When you select what you want with the arrow down/up and press enter, it will nicely format the CmdLet to Start-Sleep.
The most important tip of all: keep exploring! The more you try and play with PowerShell, the better you'll get at it.
As pointed out in comments, your problem is that you're comparing a boolean value with the string "False":
$exists -eq "False"
In PowerShell, comparison operators evaluate arguments from left-to-right, and the type of the left-hand argument determines the type of comparison being made.
Since the left-hand argument ($exists) has the type [bool] (a boolean value, it can be $true or $false), PowerShell tries to convert the right-hand argument to a [bool] as well.
PowerShell interprets any non-empty string as $true, so the statement:
$exists -eq "False"
is equivalent to
$exists -eq $true
Which is probably not what you intended.

powershell Import-csv pop up

so i am working with a bit of code that after setting a static location for the CSV i am using for import. When i run the code it seems to run until i get the Windows can't open this File pop. you know the one with what do you want to do options, like user the web services to find the correct program. i am copying the code so hopefully someone can point on where i made this fubar. just for note before i made the CSV static the Script asked you to type the location in every time so maybe i missed a setting there
if ($args[0] -eq $null)
{
$userNameFile = D:\lync_creation\userlog.csv
$userNameFile = $usernamefile -replace '"',""}
else
{$usernamefile = $args[0]}
if ($userNameFile -ne "")
{$csv=import-csv $userNameFile}
else
{"Could not find a valid .csv with the user information."
exit}
foreach($c in $csv)
# enable for lync
{
"Enabling " + $c.Identity + " for Lync 2010"
Enable-csuser -identity $c.Identity -registrarpool pool01.west.com –sipaddresstype EmailAddress
}
write-host "Press any key to continue..."
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown,AllowCtrlC")
You are seriously over complicating things if you're just going to use a static location for your CSV file.
$csv = Import-CSV D:\lync_creation\userlog.csv
foreach($c in $csv){
"Enabling $($c.Identity) for Lync 2010"
Enable-csuser -identity $c.Identity -registrarpool pool01.west.com –sipaddresstype EmailAddress
}
write-host "Press any key to continue..."
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown,AllowCtrlC")
The first line imports the CSV file into a variable.
The next 4 loops through all entries in that variable, write the host who it's enabling and then enables the person.
The last 2 lines give a Press any key to continue message and then waits for a key press before continuing.