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
}
}
}
Related
Hello and good morning(:
I'm looking to see if I'm able to jump to somewhere in PS without wrapping it in a ScriptBlock; hell, I'd even be okay with that but, I'm just unsure on how to go about it.
What I'm trying to do is: add a Parameter Set to a function and if something is supplied to the parameter -GrpSelec(I know imma change it), then just skip the rest of the script and go to my $swap variable to perform the switch.
$Group1 = #("1st Group", "2nd Group")
$Group2 = #("3rd Group", "4th Group")
Function Test-Group{
param(
[ValidateSet("Group1","group2")]
[array]$GrpSelec)
if($GrpSelec){ &$swap }
$AllGroups = #("Group1", "Group2")
for($i=0; $i -lt $AllGroups.Count; $i++){
Write-Host "$($i): $($AllGroups[$i])"}
$GrpSelec = Read-Host -Prompt "Select Group(s)"
$GrpSelec = $GrpSelec -split " "
$swap = Switch -Exact ($GrpSelec){
{1 -or "Group1"} {"$Group1"}
{2 -or "Group2"} {"$Group2"}
}
Foreach($Group in $swap){
"$Group"}
}
Is something like this even possible?
I've googled a couple of similar questions which point to the invocation operator &(as shown above), and/or, a foreach which is definitely not the same lol.
take it easy on me, im just experimenting(:
How about a simple if statement?
function Test-Group {
param(
[string[]]$GrpSelec
)
if(!$PSBoundParameters.ContainsKey('GrpSelect')){
# no argument was passed to -GrpSelec,
# populate $GrpSelec in here before proceeding with the rest of the script
}
# Now that $GrpSelec has been populated, let's do the work
$swap = Switch -Exact ($GrpSelec){
{1 -or "Group1"} {"$Group1"}
{2 -or "Group2"} {"$Group2"}
}
# rest of function
}
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.
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.
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.
im curious how to use the IF statement logic further in my code. Let me elaborate with an example.
$a=1
$b=10000
if(($a=1) -or ($b=1))
{write-host ""} #Here, I want to write what ever variable was $true
#in the if statement above.... so I want it to
#write "1" which was $a and was $true in the if
#statement...
I could write more logic to accomplish this, but im wondering if the values that the if statement used can be used again in the code. Im thinking there is a "hidden" variable maybe?
($a=1) is usually an assignment statement. This is not the case in Powershell, but it sure looks like a bug. The Powershell way to do comparison is to use -eq. So,
if(($a -eq 1) -or ($b -eq1))
Now, the simple solution is a bit different. What happens, if both $a and $b happen to be 1?
$comp = 1 # Value to compare against
$a=1
$b=100
if($a -eq $comp) { write-host "`$a: $a" }
if($b -eq $comp) { write-host "`$b: $b" }
This approach is easy to understand, which is in most of the cases more important than other factors like speed.