Why does the definition $id = 1 need? - powershell

I try to customize my profile on Windows PowerShell.
Referring a book, I wrote the following code in Microsoft.Powershell_profile.ps1.
function Prompt
{
$id = 1
$historyItem = Get-History -Count 1
if($historyItem)
{
$id = $historyItem.Id +1
}
Write-Host -ForegroundColor DarkGray "`n[$(Get-Location)]"
Write-Host -NoNewLine "PS:$id > "
$host.UI.RawUI.WindowTitle = "$(Get-Location)"
"`b"
}
I can understand most of how the code works, but not understand the $id = 1 (line 3).
Why does it need this code? $id is defined in line 7, so $id = 1 isn't need here, is it?
So, I try to execute this code and the without $id = 1 code. To me, there's no difference.
Why is $id = 1 added to this code?

It is needed if $historyItem is not defined. Another way to write the same function, maybe this is more clear:
function Prompt
{
$historyItem = Get-History -Count 1
if($historyItem)
{
$id = $historyItem.Id +1
}
else
{
$id = 1
}
Write-Host -ForegroundColor DarkGray "`n[$(Get-Location)]"
Write-Host -NoNewLine "PS:$id > "
$host.UI.RawUI.WindowTitle = "$(Get-Location)"
"`b"
}

It isn't required. The code you posted needs it because it needlessly distinguishes between "has history" and "doesn't have history". If you removed the line $id = 1 and started a new PowerShell instance you'd have an empty $id as long as the command history remains empty.
You could get the same result as the code from your question by simply running
$id = (Get-History -Count 1).Id + 1
because (Get-History -Count 1).Id evaluates to an empty result if the history is empty, which is automatically cast to 0 for the addition operation.

I just want to point out that it can also be written in a more terse way like this:
$id = if($historyItem) { $historyItem.Id +1 } else { 1 }

Related

Basic multiple if statement logic flow syntax in PowerShell

I can't seem to understand how to continue to another IF statement. What I'm trying to do is:
IF Action 1 succeed then do X AND go to Action 2, else log it and RETURN to the next loop
If Action 2 succeed then do X AND go to Action 3, else log it and RETURN to the next loop
I am having issues with "AND go to Action 2" after action 1. I tried various ways but obviously the script below does not work. It can do Action 1 the test-connection, and if it succeeds will export the log else it will log a failure and RETURN to the next loop. HOWEVER, I cannot make it do the next action if successful.
$hostname = Import-Csv C:\Users\jackie.cheng\Desktop\TestComputers.csv
$hostname | % {
if (Test-Connection $_.hostname -count 1)
{Write-Host "$($_.hostname) Test-Connection Succeeded"
$array += [pscustomobject]#{
Name = $currentobject.hostname
Status = "Test-Connection Success"}
}
else {Write-Host "$($_.hostname) Test-Connection Failed"
$array2 += [pscustomobject]#{
Name = $currentobject.hostname
Status = "Failed Test-Connection"}
} return
if (Test-Connection $_.hostname -count 1)
{Write-Host "Second action ran"}
else {Write-Host "Second action failed"} return
}
Within the script block of a Foreach-Object, which is the full name of the command with the alias %, you can in fact use return to skip any statements after the return, and continue on to the next loop.
In your case you simply need to move your return into your else blocks like this:
If (Success Condition){
Actions to do on success
}
else{
Actions to do on fail
return
}
If (Success Condition 2){
...
}
else{
...
return
}
As mentioned in the comments to your question, be sure to read up on how return works in PowerShell as it is somewhat unusual and behaves differently in different contexts.
I would do it this way, just output the objects for either case. Notice the indenting to help see the structure of the code. Return is usually not needed. -ea 0 means "-erroraction silentlycontinue".
Import-Csv TestComputers.csv |
% {
if (Test-Connection $_.hostname -count 1 -ea 0) {
[pscustomobject]#{
Name = $_.hostname
Status = $true}
}
else {
[pscustomobject]#{
Name = $_.hostname
Status = $false}
}
}
Name Status
---- ------
yahoo.com True
microsoft.com False

Color Guessing Game in Powershell [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed last year.
Improve this question
I'm working on writing a script in PowerShell for a Color guessing game. The computer randomly picks a color then the player tries to guess the color. I had it working up until I switched some lines of code in the script in an attempt to get these two variables to display correctly. Now, I can't get the code to run past the point where a player declares if they want to play the game or not. My current error is with a do loop, where the console doesn't see that I have a while loop, and so throws an error and won't run the rest of the code.
I managed to get the formatting fixed with Visual Studio Code's format document feature, but I still can't get this one while loop problem figured out.
Write-Host ''; 'Hello again my friend!'; ''
$name = Read-Host "What is your name?"
Write-Host ''; "It's good to see you again, $name! Would you like to guess my favorite color?"; ''
$command = Read-Host #'
"How do you answer? (Yes or No?)"
1. Yes (y)
2. No (n)
3. Quit (q)
Enter Choice
'#
switch -Wildcard ($command) {
'Y' { 'Yes!'; '' }
{ $_ -eq 'y' -or $_ -like 'Ye*' } {
Write-Host "This will be fun! Let us begin!"
Break
}
'N' { 'No!'; '' }
{ $_ -eq 'n' -or $_ -like 'No*' } {
Write-Host "That's too bad, perhaps another day!"
Exit
}
'Q' { 'Quit'; '' }
{ $_ -eq 'q' -or $_ -like 'qu*' } {
Write-Host 'So long!'
Exit
}
default {
'Invalid Command, Please start over.'
Exit
}
}
[string]$playagain = 'y'
[int]$playerwins = 0
[int]$compwins = 0
[int]$totalguesses = 0
[int]$playergames = 0
[int]$compgames = 0
[int]$round = 1
[int]$game = 1
$cpuchoice = $color
while ($playagain -eq 'y') {
Write-Host ''; "Game $game!"; ''
$cpuchoice = #([System.Enum]::GetValues([System.ConsoleColor])) | Get-Random -Count 1
do {
Write-Host "Round $round! What is my favorite color?"; ''
$listcolor = Read-Host "Would you like to see a list of available colors? Choose 'y' for yes, and 'n' for no."
if ($listcolor -eq 'y') {
[System.Enum]::GetValues([System.ConsoleColor])
}
elseif ($listcolor -eq 'n') {
Write-Host "Suit yourself, let's start."
}
else {
Write-Host "Your choice was invalid. Please choose 'y' for yes, or 'n' for no."
}
do {
$playerchoice = Read-host "Enter your guess"
} while (([System.Enum]::GetValues([System.ConsoleColor])) -notcontains $playerchoice) {
if ($playerchoice -eq $cpuchoice ) {
Write-Host "You win, my favorite color is $cpuchoice." -ForegroundColor $cpuchoice; ''
$playerwins = $playerwins + 1
$totalguesses = $totalguesses + 1
}
elseif ($playerchoice -ne $cpuchoice ) {
Write-Host "You lose, try again."; ''
$playerguesses += $playerchoice
$playerguesses = $playerguesses.Split(',')
$totalguesses = $totalguesses + 1
Write-Host "Here are your guesses so far: "
$playerguesses
''
}
$round = $round + 1
}
until($playerwins -eq 1) {
$playergames = $playergames + 1
Write-Host "You've won this round and have won $playergames games." -ForegroundColor Green
Write-Host "Your total guesses: $totalguesses."
Write-Host "Your wins - $playergames" -ForegroundColor Yellow
Write-Host "Computer wins - $compgames" -ForegroundColor Yellow
''
}
$playagain = Read-Host "I enjoyed this game. Would you like to challenge again, $name? Y or N"
while (("y", "n") -notcontains $playagain) {
if ($playagain -eq "y") {
Write-Host "I look forward to our next battle!"; ''
$playerwins = 0
$compwins = 0
$game = $game + 1
}
elseif ($playagain -eq "n") {
Write-Host "Thank you for playing!"
exit
}
}
}
}
The do loop that causes the error is the one that starts with "Write-Host "Round $round!" after the first while statement.
Any help is appreciated! Thanks!
Your program is being parsed like this:
while ($playagain -eq 'y')
{
# do loop #1
do
{
# do loop #2
do {
}
while (([System.Enum]::GetValues([System.ConsoleColor])) -notcontains $playerchoice)
# floating script block #1
# (doesn't execute the scriptblock, but it gets sent to the output stream instead)
{
if ($playerchoice -eq $cpuchoice ) {
... etc ...
}
# try to invoke a cmdlet "until" with 2 parameters
# i.e. ($playerwins -eq 1) and { ... }
until ($playerwins -eq 1) {
$playergames = $playergames + 1
... etc ...
}
# while loop #1
while (("y", "n") -notcontains $playagain) {
...
}
}
}
The error is telling you the first do (do loop #1) doesn't have a trailing while or until.
There's no clear and simple fix I can offer to make your code run short of a significant rework because there's a number of issues (e.g. the floating script block #1, the dangling until and the while \ until-less do), but that's what the current error is saying anyway...

How can I allow only Y/N to be input in Read-Host without having to press Enter in Powershell?

I'm trying to find a way to have something like a Read-Host to ask the user if they want to output to the file listed or not. With this I want them to either press y or n and then the code continues rather than then pressing y/n then pressing enter as well. At the moment this all works well but again it's not quite what I'm wanting.
I've tried looking into Readkey and SendKeys (to push Enter for the user) but neither work as they seem to only execute after the user has pushed Enter on the Read-Host. I'm still very new to Powershell so I'm not entirely sure whether it's actually possible or not and I've spent too much time googling/testing to find an answer that works. If I was to use Write-Host or something to do this, it needs to not show up in the log.
I've included the necessary part of my script below. It basically asks the user if the file location is correct. If it is they press y and it uses it for the output, otherwise if they push n then it loads the FolderBrowserDialog for them to select the folder they want.
I should also note this is all within a Tee-object as this code is what determines where the Tee-object output goes to.
$OutputYN = Read-Host "Do you want the output file generated to $startDirectory\FolderList.txt? (Y/N)"
If (“y”,”n” -notcontains $OutputYN) {
Do {
$OutputYN = Read-Host "Please input either a 'Y' for yes or a 'N' for no"
} While (“y”,”n” -notcontains $OutputYN)
}
if ($OutputYN -eq "Y") {
$OutputLoc = $startDirectory
}
elseif ($OutputYN -eq "N") {
$OutputLocDir = New-Object System.Windows.Forms.FolderBrowserDialog
$OutputLocDir.Description = "Select a folder for the output"
$OutputLocDir.SelectedPath = "$StartDirectory"
if ($OutputLocDir.ShowDialog() -eq "OK") {
$OutputLoc = $OutputLocDir.SelectedPath
$OutputLoc = $OutputLoc.TrimEnd('\')
}
}
EDIT:
I should have been a little more clear. I had already tried message box type stuff as well but I'd really prefer if there is a way that the user types in a y or a n. I'm not really interested in a popup box that the user has to click. If it's not possible then so be it.
Readkey is the right way.
Use the following as template.
:prompt while ($true) {
switch ([console]::ReadKey($true).Key) {
{ $_ -eq [System.ConsoleKey]::Y } { break prompt }
{ $_ -eq [System.ConsoleKey]::N } { return }
default { Write-Error "Only 'Y' or 'N' allowed!" }
}
}
write-host 'do it' -ForegroundColor Green
:prompt gives the outer loop (while) a name which can be used in the switch statement to directly break out entirely via break prompt (and not within the switch statement).
Alternative (for Windows):
Use a MessageBox.
Add-Type -AssemblyName PresentationFramework
$messageBoxResult = [System.Windows.MessageBox]::Show("Do you want the output file generated to $startDirectory\FolderList.txt?" , 'Question' , [System.Windows.MessageBoxButton]::YesNo , [System.Windows.MessageBoxImage]::Question)
switch ($messageBoxResult) {
{ $_ -eq [System.Windows.MessageBoxResult]::Yes } {
'do this'
break
}
{ $_ -eq [System.Windows.MessageBoxResult]::No } {
'do that'
break
}
default {
# stop
return # or EXIT
}
}
Not sure if this is possible in the console. But when I need the user to write one answer of a specified set, I use a do-until-loop like:
Do {
$a = Read-Host "Y / N"
} until ( 'y', 'n' - contains $a )
try this:
$title = 'Question'
$question = 'Do you want the output file generated to $startDirectory\FolderList.txt?'
$choices = New-Object Collections.ObjectModel.Collection[Management.Automation.Host.ChoiceDescription]
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&Yes'))
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&No'))
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
if ($decision -eq 0) {
Write-Host 'Yes'
} else {
Write-Host 'No'
}
If you are on Windows, you can do it :
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
$result = [System.Windows.Forms.MessageBox]::Show('Do you want the output file generated to $startDirectory\FolderList.txt?' , "Question" , [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Question)
if ($result -eq 'Yes') {
"Yes"
}
else
{
"No"
}

How to count the process using PowerShell?

I would like to count how many times that I check the result.
This the part of the process looks like:
function GetValueResult
{
...Some Process...
$Result = $ReturnCode
}
GetValueResult
if ($Result -eq 0)
{
Write-Host "The process Pass"
Restart-Computer
}
elseif ($Result -eq 1)
{
Write-Host "Try again"
GetValueResult
#This part I need to know how many times that I call this function "GetValueResult"
}
Anyone can help me how to count how many times that I call the function "GetValueResult"?
If it already 3 times, than I need to do further action. Thanks for help.
You could add a simple loop inside your function and output an object with both the ResultCode and the number of tries:
function GetValueResult {
# initialize to error code
$ResultCode = -1
for ($numTries = 0; $numTries -lt 3; $numTries++) {
# ...Some Process...
# exit the loop if we have a correct $ResultCode
if ($ResultCode -eq 0) { break }
Write-Host "Try again"
}
# output an object with the resultcode and the number of tries
[PsCustomObject]#{
ResultCode = $ResultCode
Tries = $numTries + 1
}
}
$result = GetValueResult
if ($result.ResultCode -eq 0) {
Write-Host "The process Pass"
Restart-Computer
}
else {
Write-Host "Bad result after $($result.Tries) tries.."
}
You can use increment for this purpose.
$Result = 0
foreach (...){
$Result++
if ($Result -ge 3){
...your action...
}
}
Also, I can't find any loop in your script, so how would you count in this case? Please share more details about what are you trying to achieve with it, perhaps you need to redesign it from the beginning.

PowerShell GUI Output

I'm writing a small PowerShell script, with GUI, that processes the Ping command. Currently I have the script reading input from the user to determine the IP Address / URL to ping, and then displaying the output to a rich-text-box.
However, currently, the command COMPLETES then writes the entire output at once. I want it to display each line of output in real time - so that it appears the same way that it would running the command in the shell.
When the Ping button is pushed, the following function is called (and I think this is where the issue is):
Function FNPing {
$OutputBox.Text = "Please Wait..."
$ping = ping ($InputBox.text)
$OutputBox.Text = ""
foreach ($line in $ping) {
$OutputBox.Appendtext($line+[char]13+[char]10)
}
}
I imagine that the issue can probably be solved in the ForEach statement, I'm just not aware of how to do it! All help is appreciated!
I would try using the test-connection cmdlet. The problem is that ping an external exe so all you are getting out of it is a blob of text when it completes. The only way to get the output of ping while it is running is going to be by using start-process and redirecting the output (this is quite messy).
With test-connection you won't get a pretty summary but the info is all there. For the summary info, measure-object can help. Here's an imitation of ping's output that should get you started:
function fnping {
$outputBox.Text = "Please Wait..."
$count = 4
$results = test-connection $inputbox.Text -count $count | foreach { $outputBox.AppendText("Reply from $($_.ProtocolAddress): bytes=$($_.ReplySize) time=$($_.ResponseTime)ms TTL=$($_.TimeToLive)`r`n"); $_ }
$summary = $results | measure-object -Property ResponseTime -Average -Minimum -Maximum
$lost = $count - $summary.Count
$percentLost = $lost * 100.0 / $count
$outputBox.AppendText("Packets: Sent = $count, Received = $($summary.Count), Lost = $lost ($($percentLost)% loss)`r`n")
$outputBox.AppendText("Minimum = $($summary.Minimum)ms, Maximum = $($summary.Maximum)ms, Average = $($summary.Average)ms`r`n")
}
Edit
Actually I stand corrected. Assigning the result to a variable ($ping) is causing powershell to wait for the output stream to be closed. You can easily do what you want, with foreach-object. Here I use a small helper function to clear the output box before writing the to the box:
function WriteEach-Object() {
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[object[]]$inputs
)
begin { $outputBox.Text = "" }
process { $inputs | foreach { $outputBox.AppendText($_) } }
end { $outputBox.AppendText("`r`n") }
}
function fnping() {
$outputBox.Text = "Please Wait..."
ping $inputBox.Text | writeeach-object
}
You need to add BEGIN {} PROCESS {} and END {} statements to control how your function works. If no such are given, Powershell assumes that everything is in the END {} statement, processing everything in one go.
Changing into this
Function FNPing {
BEGIN {
}
PROCESS {
$OutputBox.Text = "Please Wait..."
$ping = ping ($InputBox.text)
$OutputBox.Text = ""
foreach ($line in $ping) {
$OutputBox.Appendtext($line+[char]13+[char]10)
}
}
END {
}
}
should do the trick. Alternatively, you can replace Function with Filter, which assumes the code resides in the PROCESS {} block.
For more info: http://technet.microsoft.com/en-us/magazine/hh413265.aspx