I'm trying to create an if and else statement within PowerShell that exits my code if the $value variable has a 0 or 1.
The below is my code which is executed remotely:
$MsgBox = {
Add-Type -AssemblyName PresentationFramework
$ButtonType = [System.Windows.MessageBoxButton]::OKCancel
$MesssageIcon = [System.Windows.MessageBoxImage]::Warning
$MessageBody = "Please save anything open in your current Web Browser, this includes Google Chrome, Microsoft Edge, and Mozilla FireFox and close each one respectively. Click Ok when done saving and closing browsers. IT Will be deleting your Cache and Cookies.
-IT"
$MessageTitle = "Read Me"
$Result = [System.Windows.MessageBox]::Show($MessageBody, $MessageTitle,$ButtonType,$MesssageIcon)
Switch ($Result){
'Ok' {
return "1"
}
'Cancel' {
return "0"
}
}
}
$value = invoke-ascurrentuser -scriptblock $MsgBox -CaptureOutput
$exit = "0"
Write-Host $value
if ($value.Equals($exit)){
Write-Host "Script Exiting"
exit
}
else{
Write-Host "Continuing Script"
}
Am I comparing the wrong way in PowerShell?
I tested the code with Write-Host and my scriptblock returns 0 or 1 as planned.
When it gets to the If and Else statement it doesn't seem to compare value and exit, and will instead go straight to the else statement and Write "Continuing Script"
I've also tried with $value -eq 0 and $value -eq "0".
Invoke-AsCurrentUser, from the third-party RunAsUser module, when used with -CaptureOutput invariably outputs text, as it would appear in the console, given that a call to the PowerShell CLI, is performed behind the scenes (powershell.exe, by default).
The captured text includes a trailing newline; to trim (remove) it, call .TrimEnd() before comparing values:
# .TrimEnd() removes the trailing newline.
$value = (Invoke-AsCurrentuser -scriptblock $MsgBox -CaptureOutput).TrimEnd()
if ($value -eq '0') {
Write-Host "Script Exiting"
exit
}
else {
Write-Host "Continuing Script"
}
Related
I'm a beginner at powershell script. I've written the following script but can't seem to get it to work. When the script is run, after every minute it should output the number of minutes elapsed into a notepad. This should continue non-stop until the user intervenes. If the user presses the "s" key then the script will pause and they'll be asked if they want to continue the script or exit the script. If they type "y" the script will continue from where it left off, else it will stop and exit.
Below is my code:
$myshell = New-Object -com "Wscript.Shell"
$i=1
do
{
$key = if ($host.UI.RawUI.KeyAvailable) {
$host.UI.RawUI.ReadKey('NoEcho, IncludeKeyDown')
}
if ($key.Character -eq 's') {
write-host -nonewline "Continue? (Y/N) "
$response = read-host
if ( $response -ne "Y" ) {exit}
}
Start-Sleep -Seconds 60
$myshell.sendkeys("$i.")
$i++
} while ($true)
This doesn't seem to do anything at all. Any assistance will be appreciated
This question already has answers here:
do until user input yes/no
(3 answers)
How to Use -confirm in PowerShell
(11 answers)
Closed 3 years ago.
I want to create a confirmation box. I want to have a popup box that says that it appears that this is the factory key. Do you wish to continue? and if so proceed with the rest of the script if not, exit script.
The following is my script:
if (Test-Path $USB2\sources\install3.swm) {
$caption = "*** IT APPEARS THIS IS A FACTORY KEY ***"
$message = "Are you Sure You Want To Proceed:"
[int]$defaultChoice = 1
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "I understand, flash over it."
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No"
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
$choiceRTN = $host.UI.PromptForChoice($caption, $message, $options, $defaultChoice)
if ( $choiceRTN -ne 1 ) {
"Your Choice was Yes"
} else {
Stop-Process -Id $PID
}
}
See the bit, $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no). Where I am stuck is the "Your Choice was yes" I don't need that... I just need it to continue with the script if yes. How do I do that?
Consider using the System.Windows.MessageBox...
Add-Type -AssemblyName PresentationFramework
[System.Windows.MessageBox]::Show('It appears that this is the factory key. Do you wish to continue?', 'Confirmation', 'YesNo');
For your use case,
# You need this to use System.Windows.MessageBox
Add-Type -AssemblyName 'PresentationFramework'
# By default, you're blasting the USB drive
$continue = 'Yes'
# If you see the file, prompt the user
if ( $(Test-Path -Path "${USB2}\sources\install3.swm") ) {
$caption = "*** IT APPEARS THIS IS A FACTORY KEY ***"
$message = "Are you Sure You Want To Proceed:"
$continue = [System.Windows.MessageBox]::Show($message, $caption, 'YesNo');
}
if ($continue -eq 'Yes') {
# The user either (a) said "yes" to the "do you prompt" or
# (b) the file didn't exist.
Write-Output "Flash over..."
# ...do the flashing here...
} else {
# The user said "no" to the flash prompt
Write-Output "Terminating process..."
Start-Sleep 1
}
The message box will return Yes or No, and you can use that in your control flow to determine next steps. If that includes closing the window, call exit.
The below prompts for input, stores the clicked value in a variable, and returns it as a string. Checking to see the value of $var and proceeding from there should be simple enough.
$var = [System.Windows.MessageBox]::Show('It appears this is a factory key. Do you wish to continue?','DialogTitle','YesNoCancel','Warning')
echo("The value of the dialog box is $($var)")
I'm just a typical admin trying to make a simple script for some IT assistants in remote offices, to make domain joins easier while minimizing potential errors. The script's end game is to run the one-liner command Add-Computer -DomainName $DomainToJoin -OUPath $LocDN -NewName $WS_NewName -Restart.
But the whole script is supposed to include input validation for the computer's new name as well as for the target OU for the two-letter code for the remote office.
Googling for code snippets for days, esp. from sites like yours, was very helpful. But the problem I have now is I couldn't find the right codes to combine Read-Host , input length validation, and TRAP to work together without losing the value for my variables.
Pardon my coding as obviously I'm no real PS scripter, and I know the wrong portions of it are very basic. I would want to spend more time if I had the luxury, but I would really so much appreciate it if you could point me in the right direction.
Thank you so much in advance.
Please see my code below:
# START: Display name and purpose of invoked script
$path = $MyInvocation.MyCommand.Definition
Clear-Host
Write-Host $path
Write-Host " "
Write-Host "This script will allow you to do the following in a single-step process:"
Write-Host "(1) RENAME this computer"
Write-Host "(2) JOIN it to MYDOMAIN"
Write-Host "(3) MOVE it to a target OU"
Write-Host "(4) REBOOT"
Write-Host " "
Pause
# Function: PAUSE
Function Pause ($Message = "Press any key to continue . . . ") {
if ((Test-Path variable:psISE) -and $psISE) {
$Shell = New-Object -ComObject "WScript.Shell"
$Button = $Shell.Popup("Click OK to continue.", 0, "Script Paused", 0)
}
else {
Write-Host -NoNewline $Message
[void][System.Console]::ReadKey($true)
Write-Host
}
Write-Host " "
}
# Function: Define the parameters
Function Define-Parameters {
# Specify new computer name, with validation and TRAP
$WS_NewName = $null
while ($null -eq $WS_NewName) {
[ValidateLength(8,15)]$WS_NewName = [string](Read-Host -Prompt "NEW NAME of computer (8-15 chars.)" )
TRAP {"" ;continue}
}
Write-Host " "
# Domain to join.
$DomainToJoin = 'mydomain.net'
# Specify the target OU, with validation and trap
$baseOU='OU=Offices OU,DC=mydomain,DC=net'
$OU2 = $null
while ($null -eq $OU2) {
[ValidateLength(2,2)]$OU2 = [string](Read-Host -Prompt 'Target OU (TWO-LETTER code for your office)' )
TRAP {"" ;continue}
}
Write-Host " "
$LocDN = "OU=$OU2,$baseOU"
}
# Function: Summary and confirmation screen for defined parameters.
Function Confirm-Parameters {
Write-Host "==========================================================================="
Write-Host "Please confirm that you are joining this computer to
$DomainToJoin (MYDOMAIN)"
Write-Host "with the following parameters:"
Write-Host ""
Write-Host ""
Write-Host "Computer's NEW NAME: $WS_NewName"
# Write-Host "Domain to Join: $DomainToJoin"
Write-Host "TARGET mission OU: $OU2"
}
# Call Define-Parameters Function
Define-Parameters
# Call Confirm-Parameters Function
Confirm-Parameters
<#
Some more code here
#>
# FINAL COMMAND if all else works: Join the computer to the domain, rename it, and restart it.
# Add-Computer -DomainName $DomainToJoin -OUPath $LocDN -NewName $WS_NewName -Restart
In your code, you have a lot of things defined very strangely. Your functions create a new scope and the variables you're trying to define therein will disappear after calling them unless you change the variable scope (in this case, to $script: or $global:). Also, to use functions, you need to define them first (your Pause doesn't actually do anything)
Here's something you can do with your Define-Parameters function (I suggest looking at Get-Verb)
# Domain to join.
$DomainToJoin = 'mydomain.net'
# Function: Define the parameters
Function Get-Parameters {
do {
$global:WS_NewName = Read-Host -Prompt 'NEW NAME of computer (8-15 chars)'
} until ($WS_NewName.Length -gt 7 -and $WS_NewName.Length -lt 16)
''
do {
$global:OU2 = Read-Host -Prompt 'Target OU (TWO-LETTER code for your office)'
} until ($OU2 -match '^[a-z]{2}$')
''
$OU2 = "OU=$global:OU2,OU=Offices OU,DC=mydomain,DC=net"
}
I'd strongly recommend moving away from the ISE to do your testing and test in an actual powershell console.
Perhaps Try/Catch block instead of trap?
Try {
[ValidatePattern('^\w{8,15}$')]$compname=read-host 'enter comp name' -ErrorAction Stop
[ValidatePattern('^\w{2}$')]$OU=read-host 'enter OU name' -ErrorAction Stop
}
Catch {
$ErrorMessage = $_.Exception.Message
$ErrorLineNumber = $_.InvocationInfo.ScriptLineNumber
$ErrorCommandName = $_.InvocationInfo.InvocationName
Write-Error -Message "The error message was: <$ErrorMessage>, script line: <$ErrorLineNumber>, command name: <$ErrorCommandName>"
exit 255
}
I have a script that starts with a folder picker dialog, however I understand that POSH can't execute scripts outside of ISE like that (STA vs. MTA), so I have a separate script to dot-source it.
I have error handling in the first script in case the user presses Cancel:
if ($Show -eq "OK") {
return $objForm.SelectedPath
} else {
Write-Error "Operation cancelled by user."
exit
}
Now I need for the 2nd script (the one calling the first script) to detect the same Cancellation.
This is what I've got so far:
"Choose a folder containing the items to be moved..."
""
try {
powershell -STA -File "C:\Test\Script.ps1"
""
"Operation completed. An event log has been created:"
(Resolve-Path .\).Path +"\log.txt"
""
"Press any key to continue..."
""
$x = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
} catch {
if ($LastExitCode -ne 0) { exit $LastExitCode }
Write-Host "User cancelled the operation."
""
"Press any key to continue..."
""
$x = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
That gives me a nasty looking multi-line Write Error Exception in red text.
At C:\Test\Script.ps1:27 char:30
+ $folder = Select-FolderDialog <<<< #contains user's selected folder
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Select-FolderDialog
I'm not sure why it's generating an error message referencing the other script, since the other script runs fine (from ISE of course).
Desired Output:
If user cancels folder picker, I just want a nice clean error message to display:
User cancelled the operation.
Press any key to continue.
Edit
Here is the folder picker script I have. It works fine in ISE but when you Right Click and choose Run with Powershell it just launches a blank prompt window. To prevent the end user from accidentally editing the script I would like for it to run from outside ISE. BTW, I am using POSH 2.
# Function for folder picker dialog
Function Select-FolderDialog
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$objForm = New-Object System.Windows.Forms.FolderBrowserDialog
# Default location is script's location
$objForm.SelectedPath = (Resolve-Path .\).Path
$objForm.Description = "Select Folder"
$Show = $objForm.ShowDialog()
If ($Show -eq "OK")
{Return $objForm.SelectedPath}
Else
{
Write-Error "Operation cancelled by user."
Exit
}
}
$folder = Select-FolderDialog #contains user's selected folder
Keep just your function in your 2nd script, dot source the file to load the function, then place the $folder = Select-FolderDialog call in your main script, a la:
"Choose a folder containing the items to be moved..."
""
try {
. "C:\Test\Script.ps1" # this dot sources the function from the second file (no need to call a separate Powershell process to do this, as data from that process won't be returned to the primary process calling it)
$folder = Select-FolderDialog # this runs the function in your original script, storing the result in the $folder variable
""
"Operation completed. An event log has been created:"
(Resolve-Path .\).Path +"\log.txt"
""
"Press any key to continue..."
""
$x = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
} catch {
if ($LastExitCode -ne 0) { exit $LastExitCode }
Write-Host "User cancelled the operation."
""
"Press any key to continue..."
""
$x = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
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 ;}
}