As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 9 years ago.
I'm looking for different examples of custom Powershell prompt function implementations. If you have a custom implementation of your own please post the script. Links to existing resources are good as well.
Bonus points for posting a screenshot of what your prompt actually looks like (a preview).
This is modified version of jaykul's prompt. The benefit is that
-there is a current history id, so you can invoke previous items from history very easily (you know the id)
-it's a little reminder - I add my tasks to the prompt so I don't forget them (see the sshot)
function prompt {
$err = !$?
$origOfs = $ofs;
$ofs = "|"
$toPrompt = "$($global:__PromptVars)"
$ofs = $origOfs;
if ($toPrompt.Length -gt 0) {
Write-Host "$($toPrompt) >" -ForegroundColor Green -NoNewline }
$host.UI.RawUI.WindowTitle = "PS1 > " + $(get-location)
# store the current color, and change the color of the prompt text
$script:fg = $Host.UI.RawUI.ForegroundColor
# If there's an error, set the prompt foreground to "Red"
if($err) { $Host.UI.RawUI.ForegroundColor = 'Red' }
else { $Host.UI.RawUI.ForegroundColor = 'Yellow' }
# Make sure that Windows and .Net know where we are at all times
[Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath
# Determine what nesting level we are at (if any)
$Nesting = "$([char]0xB7)" * $NestedPromptLevel
# Generate PUSHD(push-location) Stack level string
$Stack = "+" * (Get-Location -Stack).count
# Put the ID of the command in, so we can get/invoke-history easier
# eg: "r 4" will re-run the command that has [4]: in the prompt
$nextCommandId = (Get-History -count 1).Id + 1
# Output prompt string
# Notice: no angle brackets, makes it easy to paste my buffer to the web
Write-Host "[${Nesting}${nextCommandId}${Stack}]:" -NoNewLine
# Set back the color
$Host.UI.RawUI.ForegroundColor = $script:fg
if ($toPrompt.Length -gt 0) {
$host.UI.RawUI.WindowTitle = "$($toPrompt) -- " + $host.UI.RawUI.WindowTitle
}
" "
}
function AddTo-Prompt($str) {
if (!$global:__PromptVars) { $global:__PromptVars = #() }
$global:__PromptVars += $str
}
function RemoveFrom-Prompt($str) {
if ($global:__PromptVars) {
$global:__PromptVars = #($global:__PromptVars | ? { $_ -notlike $str })
}
}
Here's mine:
function prompt {
# our theme
$cdelim = [ConsoleColor]::DarkCyan
$chost = [ConsoleColor]::Green
$cloc = [ConsoleColor]::Cyan
write-host "$([char]0x0A7) " -n -f $cloc
write-host ([net.dns]::GetHostName()) -n -f $chost
write-host ' {' -n -f $cdelim
write-host (shorten-path (pwd).Path) -n -f $cloc
write-host '}' -n -f $cdelim
return ' '
}
It uses this helper function:
function shorten-path([string] $path) {
$loc = $path.Replace($HOME, '~')
# remove prefix for UNC paths
$loc = $loc -replace '^[^:]+::', ''
# make path shorter like tabs in Vim,
# handle paths starting with \\ and . correctly
return ($loc -replace '\\(\.?)([^\\])[^\\]*(?=\\)','\$1$2')
}
Here is my prompt function
function prompt() {
if ( Test-Wow64 ) {
write-host -NoNewLine "Wow64 "
}
if ( Test-Admin ) {
write-host -NoNewLine -f red "Admin "
}
write-host -NoNewLine -ForegroundColor Green $(get-location)
foreach ( $entry in (get-location -stack)) {
write-host -NoNewLine -ForegroundColor Red '+';
}
write-host -NoNewLine -ForegroundColor Green '>'
' '
}
i often use posh as a calc, so i set $ans variable.
https://connect.microsoft.com/PowerShell/feedback/ViewFeedback.aspx?FeedbackID=386493
PS > 100
100
PS > $ans * 9
900
PS > $ans*$ans
810000
Here's mine. Just has the history ID at each command so I can easily identify the ID of the command. I also use the windowtitle to give me the current working directory rather than have it displayed in the prompt itself.
106 > cat function:\prompt
$history = #(get-history)
if($history.Count -gt 0)
{
$lastItem = $history[$history.Count - 1]
$lastId = $lastItem.Id
}
$nextCommand = $lastId + 1
$Host.ui.rawui.windowtitle = "PS " + $(get-location)
$myPrompt = "$nextCommand > "
if ($NestedPromptLevel -gt 0) {$arrows = ">"*$NestedPromptLevel; $myPrompt = "PS-nested $arrows"}
Write-Host ($myPrompt) -nonewline
return " "
One thing that many people forget is to deal with in custom prompts is the nested prompt. Note that I check $nestedPromptLevel and add an arrow for each nested level.
Andy
I tend to re-type
function prompt { "PS> " }
every time I am preparing examples I can copy/paste to someone, especially when I'm in cumbersome long paths which would only distract.
And I still plan to write a decent prompt function which shows me the drive and a useful approximation on the location by either using the current directory (without the path that led to there) or (if it's numeric) the next higher level too. But that's probably pretty specific to my own filesystem here. And I never was bothered enough by the default prompt to actually do it :-)
Related
I want to create the "Select-Multiple" function.
The function takes some parameters, but the most important one is the list of options.
Let's say
#("First Option", "Second Option")
Then the function will display something like:
a All
b First Option
c Second Option
d Exit
Choose your option: > ...
The "Choose your option: > ..." text, will be repeated as long as:
User choose "All" or "Exit" option
User will choose all possible options (other than "All" and "Exit")
At the end the function returns the List of options chosen by the user.
Simple. But... I'd like to highlight the options already chosen by the user.
So if the user chose "b", then "b First Option" gets green colour.
Is it possible to do something like that, without using Clear-Host, as I don't want to clear previous steps?
I attach you my "Select-Multiple" function in powershell, sorry if that's ugly written, but I don't use powershell that often.
function Select-Multiple {
Param(
[Parameter(Mandatory=$false)]
[string] $title,
[Parameter(Mandatory=$false)]
[string] $description,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$options,
[Parameter(Mandatory=$true)]
[string] $question
)
if ($title) {
Write-Host -ForegroundColor Yellow $title
Write-Host -ForegroundColor Yellow ("-"*$title.Length)
}
if ($description) {
Write-Host $description
Write-Host
}
$chosen = #()
$values = #()
$offset = 0
$all = "All"
$values += #($all)
Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline
Write-Host $all
$offset++
$options.GetEnumerator() | ForEach-Object {
Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline
$values += #($_)
Write-Host $_
$offset++
}
$exit = "Exit"
$values += #($exit)
Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline
Write-Host $exit
$answer = -1
while($chosen.Count -ne $options.Count) {
Write-Host "$question " -NoNewline
$selection = (Read-Host).ToLowerInvariant()
if (($selection.Length -ne 1) -or (([int][char]($selection)) -lt 97 -or ([int][char]($selection)) -gt (97+$offset))) {
Write-Host -ForegroundColor Red "Illegal answer. " -NoNewline
}
else {
$answer = ([int][char]($selection))-97
$value = $($values)[$answer]
if ($value -eq $exit) {
return $chosen
}
if ($value -eq $all) {
return $options
}
else {
if ($chosen.Contains($value)) {
Write-Host -ForegroundColor Red "The value $value was already chosen."
}
else {
$chosen += ($value)
}
}
}
if ($answer -eq -1) {
Write-Host -ForegroundColor Red "Please answer one letter, from a to $([char]($offset+97))"
}
$answer = -1;
}
return $chosen
}
Because of how the console window works, you can't just recolor an existing line. Once you've written something to the console, the only way you can modify it is by overwriting it. This is no different when applying colors.
To understand why this is the case, let's go over how text is colored in PowerShell. Let's use the following command as an example:
Write-Host "Test" -ForegroundColor Green
Here is a (simplified) step by step overview of what this command will do:
Set the ForegroundColor property of the console to green
Write "Test" to the console
Set the ForegroundColor property of the console to whatever it was previously
This explains why we are unable to change the color of text that has already been written to the console. If you want to color a piece of text, you are required to set the console color before writing the text to the console.
However, there are a couple ways to create the same visual effect. In fact there are exactly two ways. One of them is clearing the console and re-writing everything which you mentioned you don't want to do, so let's talk about the other way first.
Overwriting Individual Lines
Let me preface this by saying that this does not work very well with the PowerShell ISE. If you decide to use this, you will have to debug it by either using the normal PowerShell console, or an IDE that supports this. This is also the more complicated option, so if you don't want to deal with the complexity of it, the second option would be the way to go.
The console window allows you to retrieve and set the cursor position by using Console.CursorLeft and Console.CursorTop. You can also use Console.SetCursorPosition() if you need to set both of them at the same time. There is also $Host.UI.RawUI.CursorPosition, but it's long and has some strange side effects when paired with Read-Host, so I would not recommend using it. When you write output to the console, it will write the output to wherever the cursor happens to be. We can use this to our advantage by setting the cursor's position to the beginning of the line we want to change the color of, then overwriting the normal text with colored text or vice versa.
In order to do this, all we need to do is keep track of which option is on which line. This is pretty simple, especially if you have an array of options that is in the same order that you printed them to the console in.
Here is a simple script I made that does exactly this:
$options = $("Option 1", "Option 2", "Option 3")
$initialCursorTop = [Console]::CursorTop
# An array to keep track of which options are selected.
# All entries are initially set to $false.
$selectedOptionArr = New-Object bool[] $options.Length
for($i = 0; $i -lt $options.Length; $i++)
{
Write-Host "$($i + 1). $($options[$i])"
}
Write-Host # Add an extra line break to make it look pretty
while($true)
{
Write-Host "Choose an option>" -NoNewline
$input = Read-Host
$number = $input -as [int]
if($number -ne $null -and
$number -le $options.Length -and
$number -gt 0)
{
# Input is a valid number that corresponds to an option.
$oldCursorTop = [Console]::CursorTop
$oldCursorLeft = [Console]::CursorLeft
# Set the cursor to the beginning of the line corresponding to the selected option.
$index = $number - 1
[Console]::SetCursorPosition(0, $index + $initialCursorTop)
$choice = $options[$index]
$isSelected = $selectedOptionArr[$index]
$choiceText = "$($number). $($choice)"
if($isSelected)
{
Write-Host $choiceText -NoNewline
}
else
{
Write-Host $choiceText -ForegroundColor Green -NoNewline
}
$selectedOptionArr[$index] = !$isSelected
[Console]::SetCursorPosition($oldCursorLeft, $oldCursorTop)
}
# Subtract 1 from Y to compensate for the new line created when providing input.
[Console]::SetCursorPosition(0, [Console]::CursorTop - 1)
# Clear the input line.
Write-Host (' ' * $Host.UI.RawUI.WindowSize.Width) -NoNewline
[Console]::CursorLeft = 0
}
The main advantage of this approach is that it doesn't need to clear the entire console in order to update text. This means you can display whatever you want above it without worrying about it being cleared every time the user inputs something. Another advantage is that this performs a minimal number of operations in order to accomplish the task.
The main disadvantage is that this is relatively volatile. This requires you to use exact line numbers, so if something happens that offsets some of the lines (such as one option being multiple lines), it will more than likely cause some major issues.
However, these disadvantages can be overcome. Since you have access to $Host.UI.RawUI.WindowSize.Width which tells you how many characters you can put on a single line, we know that any string with a length greater than this will be wrapped onto multiple lines. Another option is just to keep track of which line the cursor starts on, then you can clear all text between the starting position and where the cursor currently is.
Clearing the Console
This approach is much simpler because you don't have to worry about what is on which line or where the cursor is at all. The idea is that you simply clear the entire console, then re-write everything with the changes you want to make. This is the nuclear approach, but it's also the most reliable.
Here is the same example as above using this approach instead:
$options = $("Option 1", "Option 2", "Option 3")
# An array to keep track of which options are selected.
# All entries are initially set to $false.
$selectedOptionArr = New-Object bool[] $options.Length
while($true)
{
Clear-Host
for($i = 0; $i -lt $options.Length; $i++)
{
if($selectedOptionArr[$i])
{
Write-Host "$($i + 1). $($options[$i])" -ForegroundColor Green
}
else
{
Write-Host "$($i + 1). $($options[$i])"
}
}
Write-Host # Add an extra line break to make it look pretty
Write-Host "Choose an option>" -NoNewline
$input = Read-Host
$number = $input -as [int]
if($number -ne $null -and
$number -le $options.Length -and
$number -gt 0)
{
# Input is a valid number that corresponds to an option.
$index = $number - 1
$choice = $options[$index]
$selectedOptionArr[$index] = !$selectedOptionArr[$index]
}
}
The main advantage of this approach is that it's super simple and easy to understand.
The main disadvantage is that this clears the entire console every time the user inputs something. In most cases this isn't a huge problem, but it can be with large data sets.
I agree with Trevor Winge, I it is most likely not possible to change earlier console outputs appearance, but it is for certain not worth your while. There are certain limitations of the console, and that is ok since that is what GUIs are for. I hope you feel encouraged by this situation to look into Windows.Froms or WPF. Make use of the controls! checkboxes would be interesting for your scenario. I know that is not exactly what you asked for, but it is garanteed a journey that is worth your time. When i started my first GUI with powershell, i was astonished how much i could accomplish with little afford. Stackoverflow is full of examples.
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...
Is there something we can use in PowerShell to ask users to select one item from an array of items? For example, I like how Inquirer.js can do it.
I have also seen PoshGui, but it seems too much work to create just a simple prompt.
The reason we want something similar is that we need to provide deployment scripts for our clients and make deployment guides as easy as possible. Asking users to select one item on a screen is much better than asking them to insert some guid to a config file.
Do you have any suggestions for user prompts for arrays?
You can also try ps-menu module:
https://www.powershellgallery.com/packages/ps-menu
Sample:
I've used the Out-GridView cmdlet for this in the past. When used with the -PassThru switch it allows the selected item to be passed to a variable. The example image you've shown, when written using Out-GridView (ogv if you want to use the alias) is:
$task = Read-Host -Prompt "What do you want to do?"
if ($task -eq "Order a pizza") {
$pizza_sizes = #('Jumbo','Large','Standard','Medium','Small','Micro')
$size = $pizza_sizes | Out-GridView -Title "What size do you need?" -PassThru
Write-Host "You have selected $size"
}
There are many considerations to take into account with this, the windows might not appear where you want them to and they may appear behind others. Also, this is a very simple example that obviously needs error handling and other aspects built in. I'd suggest some testing or to get a second opinion from others on SO.
You can be as creative as you like of course..
Here's a small function that builds a console menu:
function Simple-Menu {
Param(
[Parameter(Position=0, Mandatory=$True)]
[string[]]$MenuItems,
[string] $Title
)
$header = $null
if (![string]::IsNullOrWhiteSpace($Title)) {
$len = [math]::Max(($MenuItems | Measure-Object -Maximum -Property Length).Maximum, $Title.Length)
$header = '{0}{1}{2}' -f $Title, [Environment]::NewLine, ('-' * $len)
}
# possible choices: didits 1 to 9, characters A to Z
$choices = (49..57) + (65..90) | ForEach-Object { [char]$_ }
$i = 0
$items = ($MenuItems | ForEach-Object { '[{0}] {1}' -f $choices[$i++], $_ }) -join [Environment]::NewLine
# display the menu and return the chosen option
while ($true) {
cls
if ($header) { Write-Host $header -ForegroundColor Yellow }
Write-Host $items
Write-Host
$answer = (Read-Host -Prompt 'Please make your choice').ToUpper()
$index = $choices.IndexOf($answer[0])
if ($index -ge 0 -and $index -lt $MenuItems.Count) {
return $MenuItems[$index]
}
else {
Write-Warning "Invalid choice.. Please try again."
Start-Sleep -Seconds 2
}
}
}
You can use it like below:
$menu = 'Pizza', 'Steak', 'French Fries', 'Quit'
$eatThis = Simple-Menu -MenuItems $menu -Title "What would you like to eat?"
switch ($eatThis) {
'Pizza' {
$menu = 'Jumbo', 'Large', 'Standard', 'Medium', 'Small', 'Micro'
$eatThat = Simple-Menu -MenuItems $menu -Title "What size do you need?"
Write-Host "`r`nEnjoy your $eatThat $eatThis!`r`n" -ForegroundColor Green
}
'Steak' {
$menu = 'Well-done', 'Medium', 'Rare', 'Bloody', 'Raw'
$eatThat = Simple-Menu -MenuItems $menu -Title "How would you like it cooked?"
Write-Host "`r`nEnjoy your $eatThat $eatThis!`r`n" -ForegroundColor Green
}
'French fries' {
$menu = 'Mayonaise', 'Ketchup', 'Satay Sauce', 'Piccalilly'
$eatThat = Simple-Menu -MenuItems $menu -Title "What would you like on top?"
Write-Host "`r`nEnjoy your $eatThis with $eatThat!`r`n" -ForegroundColor Green
}
}
Result:
All of the answers are correct, but I also wrote a few reusable PowerShell helper functions. Readme. I auto-generate basic looking WinForms. Looks ugly, but works.
https://github.com/Zerg00s/powershell-forms
$selectedItem = Get-FormArrayItem (Get-ChildItem)
$Delete = Get-FormBinaryAnswer "Delete file?"
$newFileName = Get-FormStringInput "Enter new file name" -defaultValue "My new file"
# -------------------------------------------------------------------------------
# Prepare the list of inputs that user needs to populate using an interactive form
# -------------------------------------------------------------------------------
$preDeployInputs = #{
suffix = ""
SPSiteUrl = "https://ENTER_SHAREPOINT_SITE.sharepoint.com"
TimeZone = "Central Standard Time"
sendGridRegistrationEmail = "ENTER_VALID_EMAIL_ADDRESS"
sendGridRegistrationPassword = $sendGridPassword
sendGridRegistrationCompany = "Contoso & Tailspin"
sendGridRegistrationWebsite = "https://www.company.com"
fromEmail = "no-reply#DOMAIN.COM"
}
$preDeployInputs = Get-FormItemProperties -item $preDeployInputs -dialogTitle "Fill these required fields"
Unfortunately, there's little that is built in, and that little is hard to discover - see below.
Potentially providing a dedicated Read-Choice cmdlet or enhancing Read-Host is being discussed in GitHub issue #6571.
The $host.ui.PromptForChoice() method supports presenting a menu of choices, but it has limitations:
The choices are presented on a single line (which may wrap).
Only single-character selectors are supported.
The selector character must be a part of the menu-item text.
Submitting a selection always requires pressing Enter
A ? option is invariably offered, even if you don't want to / need to provide explanatory text for each menu item.
Here's an example:
# The list of choices to present.
# Specfiying a selector char. explicitly is mandatory; preceded it by '&'.
# Automating that process while avoiding duplicates requires significantly
# more effort.
# If you wanted to include an explanation for each item, selectable with "?",
# you'd have to create each choice with something like:
# [System.Management.Automation.Host.ChoiceDescription]::new("&Jumbo", "16`" pie")
$choices = '&Jumbo', '&Large', '&Standard', '&Medium', 'Sma&ll', 'M&icro'
# Prompt the user, who must type a selector character and press ENTER.
# * Each choice label is preceded by its selector enclosed in [...]; e.g.,
# '&Jumbo' -> '[J] Jumbo'
# * The last argument - 0 here - specifies the default index.
# * The default choice selector is printed in *yellow*.
# * Use -1 to indicate that no default should be provided
# (preventing empty/blank input).
# * An invalid choice typed by the user causes the prompt to be
# redisplayed (without a warning or error message).
$index = $host.ui.PromptForChoice("Choose a Size", "Type an index and press ENTER:", $choices, 0)
"You chose: $($choices[$index] -replace '&')"
This yields something like:
I'm trying to overwrite a line in PowerShell written with Write-Host (I have a process that's running in a loop and I want to show percentage updated on the screen). What I've tried to do is this:
Write-Host -NoNewline "`rWriting $outputFileName ($i/$fileCount)... $perc%"
but instead of overwriting the line it stays on the same line and appends to it.
what am I missing here?
Thanks
You cannot overwrite a line in a Powershell window. What you can do is blank the window with cls(Clear-Host):
# loop code
cls
Write-Host "`rWriting $outputFileName ($i/$fileCount)... $perc%"
# end loop
But what you should really be using is Write-Progress, a cmdlet built specifically for this purpose:
# loop code
Write-Progress -Activity "Writing $outputFileName" -PercentComplete $perc
# end loop
More on Write-Progress here: http://technet.microsoft.com/en-us/library/hh849902.aspx
As a tweak to Raf's answer above, You don't have to wipe the screen every time to update your last line.
Calling Write-Host with -NoNewLine and carriage return `r is enough.
for ($a=0; $a -le 100; $a++) {
Write-Host -NoNewLine "`r$a% complete"
Start-Sleep -Milliseconds 10
}
Write-Host #ends the line after loop
It not perfect but here is a script which has a spinning character in place. The part that lets you do this is:
$origpos = $host.UI.RawUI.CursorPosition
$origpos.Y += 1
Get the current position and save it so that we can keep referring to it. As you progress you change the $host.UI.RawUI.CursorPosition. Since it was previously saved you can reset it back $host.UI.RawUI.CursorPosition = $origpos. You should be able to experiment with that.
$scroll = "/-\|/-\|"
$idx = 0
$job = Invoke-Command -ComputerName $env:ComputerName -ScriptBlock { Start-Sleep -Seconds 10 } -AsJob
$origpos = $host.UI.RawUI.CursorPosition
$origpos.Y += 1
while (($job.State -eq "Running") -and ($job.State -ne "NotStarted"))
{
$host.UI.RawUI.CursorPosition = $origpos
Write-Host $scroll[$idx] -NoNewline
$idx++
if ($idx -ge $scroll.Length)
{
$idx = 0
}
Start-Sleep -Milliseconds 100
}
# It's over - clear the activity indicator.
$host.UI.RawUI.CursorPosition = $origpos
Write-Host 'Complete'
Remove-Variable('job')
$job = Start-Job -ScriptBlock { Start-Sleep -Seconds 10 }
while (($job.State -eq "Running") -and ($job.State -ne "NotStarted"))
{
Write-Host '.' -NoNewline
Start-Sleep -Seconds 1
}
Write-Host ""
So as log as you remember where you want to go back to then you can use this logic. This will not work properly in ISE. You can also use `b as a back space character as well.
I know, thats quite old, but i was in the same Situation und modified the Solution from Boluwade Kujero, just because writing blank lines before writing the new output may result in a "flickering" output.
So in the following function, I just do overwrite the existing line, write blanks until reaching the old cursorposition, and go back to the last character of the new line.
In addition i added an optical progressbar. Progress is calculated by the function through given Parameters:
function Write-Status
{
param([int]$Current,
[int]$Total,
[string]$Statustext,
[string]$CurStatusText,
[int]$ProgressbarLength = 35)
# Save current Cursorposition for later
[int]$XOrg = $host.UI.RawUI.CursorPosition.X
# Create Progressbar
[string]$progressbar = ""
for ($i = 0 ; $i -lt $([System.Math]::Round($(([System.Math]::Round(($($Current) / $Total) * 100, 2) * $ProgressbarLength) / 100), 0)); $i++) {
$progressbar = $progressbar + $([char]9608)
}
for ($i = 0 ; $i -lt ($ProgressbarLength - $([System.Math]::Round($(([System.Math]::Round(($($Current) / $Total) * 100, 2) * $ProgressbarLength) / 100), 0))); $i++) {
$progressbar = $progressbar + $([char]9617)
}
# Overwrite Current Line with the current Status
Write-Host -NoNewline "`r$Statustext $progressbar [$($Current.ToString("#,###").PadLeft($Total.ToString("#,###").Length)) / $($Total.ToString("#,###"))] ($($( ($Current / $Total) * 100).ToString("##0.00").PadLeft(6)) %) $CurStatusText"
# There might be old Text behing the current Currsor, so let's write some blanks to the Position of $XOrg
[int]$XNow = $host.UI.RawUI.CursorPosition.X
for ([int]$i = $XNow; $i -lt $XOrg; $i++) {
Write-Host -NoNewline " "
}
# Just for optical reasons: Go back to the last Position of current Line
for ([int]$i = $XNow; $i -lt $XOrg; $i++) {
Write-Host -NoNewline "`b"
}
}
Use the function like this:
For ([int]$i=0; $i -le 8192; $i++) {
Write-Status -Current $i -Total 8192 -Statustext "Running a long Task" -CurStatusText "Working on Position $i"
}
The result will be a running progressbar that will look like this (in a single line):
Running a long Task ██████████████████░░░░░░░░░░░░░░░░░ [4.242 /
8.192] ( 51,78 %) Working on Position 4242
Hope this will help someone else
You can use the .NET console class to do exactly what you want where you want it.
Works in console windows only and not the ISE.
cls
[Console]::SetCursorPosition(40,5)
[Console]::Write('Value of $i = ')
[Console]::SetCursorPosition(40,7)
[Console]::Write('Value of $j = ')
For ($i = 1; $i -lt 11; $i++)
{
[Console]::SetCursorPosition(57,5)
[Console]::Write($i)
for ($j = 1; $j -lt 11; $j++)
{
[Console]::SetCursorPosition(57,7)
[Console]::Write("$j ")
Start-Sleep -Milliseconds 200
}
Start-Sleep -Milliseconds 200
}
[Console]::SetCursorPosition(40,5)
[Console]::Write(" `n")
[Console]::SetCursorPosition(40,7)
[Console]::Write(" `n")
[Console]::SetCursorPosition(0,0)
If the goal is strictly to overwrite powershell console prompt line (the current line with the cursor) then all the answers here work only to an extent, and in some ways doing more than is desired.
Raf's and Craig's answers that use the Clear-Host cmdlet (cls) in their first line, like Dullson noted, are doing too much. Blanking the entire screen assumes the things cleared are no longer important for viewing which may not be true. Sometimes these are necessary to make sense of the current line.
Raf's Write-Progress solution is a powerful cmdlet but seems like an overkill for just overwriting the current line.
Raf's Write-Host proposal, Matt's submission and Dullson's tweak are all good where only one character position at a definite screen position needs updating or where the succeeding line text is longer in length than the current. If not, the succeeding line text would only overwrite the current line to the extent of its length leaving those parts of the succeeded line whose length position is longer than the new to remain in view together with the new line.
For example, if the previous value was 10 and the new value is 9 what would be shown is 90. The 9 just overwrites the portion of the preceding value that is equal to its length - 1. So the solutions work well for increments but not so well for decrements where length of value reduces compared to previous.
The following block shows how to guarantee total (visual) overwrite of the current line text with a new one.
$LongString = "This string is long"
$ShortString = "This is short"
#Simulate typing a string on the console line
$L = 1
While ($L -le $LongString.Length)
{
$Sub = $LongString.Substring(0,$L)
Write-Host "`r$Sub" -NoNewline
$L++
# This sleep is just to simulate manual typing delay
Start-Sleep -Milliseconds 20
}
# Now blank out the entire line with the space character " "
# The quantity of spaces should be equal to the length of the current text
# Which in this case is contained in $Sub.Length
$Blank = " "
For($L = 1; $L -le $Sub.Length; $L++)
{
$Blank = $Blank + " "
}
Write-Host "`r$Blank" -NoNewline
# Overwrite the blank console line with the new string
$L = 1
While ($L -le $ShortString.Length)
{
$Sub = $ShortString.Substring(0,$L)
Write-Host "`r$Sub" -NoNewline
$L++
# This sleep is just to simulate delay in manual typing
Start-Sleep -Milliseconds 20
}
# The following is not required if you want the Powershell prompt
# to resume to the next line and not overwrite current console line.
# It is only required if you want the Powershell prompt to return
# to the current console line.
# You therefore blank out the entire line with spaces again.
# Otherwise prompt text might be written into just the left part of the last
# console line text instead of over its entirety.
For($L = 1; $L -le $Sub.Length; $L++)
{
$Blank = $Blank + " "
}
Write-Host "`r$Blank" -NoNewline
Write-Host "`r" -NoNewline
This one I got from a blog post by Thomas Rayner. He uses ANSI Escape Sequences to save the cursor position [s and update the cursor position [u
$E=[char]27
Then save the current cursor position using the save escape sequence:
"${E}[s"
Usage: Use the update sequence ${E}[u to tell PS where to start the string:
1..10 | %{"${E}[uThere are $_ s remaining"; Start-Sleep -Seconds 1}
Does not work in the ISE however.
I know links get stale but it is here today.
Try
for ($i=1;$i -le 100;$i++){Write-Host -NoNewline "`r" $i;sleep 1}
https://241931348f64b1d1.wordpress.com/2017/08/23/how-to-write-on-the-same-line-with-write-output/
This method worked for me to write output value in a loop until its status changed to "Succeeded". Ensure you set the cursor up by required number of lines and it overwrites the same line
while($val -ne 1)
{
if($taskstates.Tasks.state[0] -eq "Succeeded" -and $taskstates.Tasks.state[1] -eq "Succeeded" -and $taskstates.Tasks.state[2] -eq "Succeeded" -and $taskstates.Tasks.state[3] -eq "Succeeded")
{
$val = 1
}
#Clear-Host
$taskstates.Tasks.StartTime[0].ToString() +" "+ $taskstates.Tasks.name[0] +" is "+ $taskstates.Tasks.state[0]
$taskstates.Tasks.StartTime[1].ToString() +" "+ $taskstates.Tasks.name[1] +" is "+ $taskstates.Tasks.state[1]
$taskstates.Tasks.StartTime[2].ToString() +" "+ $taskstates.Tasks.name[2] +" is "+ $taskstates.Tasks.state[2]
$taskstates.Tasks.StartTime[3].ToString() +" "+ $taskstates.Tasks.name[3] +" is "+ $taskstates.Tasks.state[3]
$taskstates = Get-ASRJob -Name $failoverjob.Name
"ASR VMs build is in Progress"
Start-Sleep 5
[console]::setcursorposition($([console]::Cursorleft ),$([console]::CursorTop - 4))
}
I'm late to the party. Here's a proof of concept I recently discovered and adapted for my purposes. This example overwrites the line.
$count = 1
# Used for calculating the max number length for padding trailing spaces
$totalCount = 100
#Get current cursor position
$curCursorPos = New-Object System.Management.Automation.Host.Coordinates
$curCursorPos.X = $host.ui.rawui.CursorPosition.X
$curCursorPos.Y = $host.ui.rawui.CursorPosition.Y
# Counter code
While ($count -le 100) {
# Keep cursor in the same position on the same line
$host.ui.rawui.CursorPosition = $curCursorPos
# Display with padded trailing spaces to overwrite any extra digits
$pad = ($totalCount -as [string]).Length
# Display the counter
Write-Host "$(([string]$count).Padright($pad))" -NoNewline -ForegroundColor Green
# Run through the example quickly
Start-Sleep -Milliseconds 100
#increment $count
$count++
}
You can experiment with Write-Host -NoNewline property, by keeping it or removing it, to see which looks better for you.
I like below code...
$dots = ""
while (!$isTrue) {
if ($dots -eq "...") {
$dots = ""
}
else {
$dots += "."
}
Write-Host -NoNewLine "`rLoading$dots"
Start-Sleep 1
}
You can use $Host.UI.RawUI.WindowSize.Width to find the display width and then use .PadRight to fill up the line with spaces. This avoids having to clear the screen with each loop, the issue of characters persisted from the last loop, having to manipulate cursor position, or having to write a custom function or lots of cumbersome code, e.g.:
# only works in a console window
If ($Host.Name -eq "ConsoleHost")
{
Write-Host 'Starting...'
# find the max line length of the console host
$maxLineLength = $Host.UI.RawUI.WindowSize.Width
# loop a few times
For ($i = 1; $i -le 10; $i++)
{
# for the sake of demonstration, generate a random-length string of letters
$randStringLength = Get-Random -Minimum 1 -Maximum $maxLineLength
$randCharIndex = Get-Random -Minimum 65 -Maximum (65+26) # A = ASCII 65
$randChar = ([char]$randCharIndex)
$myString = [string]$randChar*$randStringLength
# overwrite at the current console line
Write-Host ("`r"+$myString.PadRight($maxLineLength," ")) -NoNewline
# pause briefly before going again
Start-Sleep -Milliseconds 200
}
Write-Host 'Done.'
}
Another option in PowerShell 7.2+ is to use the minimal Write-Progress view $PSStyle.Progress.View = Minimal:
# only works in a console window
If ($Host.Name -eq "ConsoleHost")
{
# loop a few times
For ($i = 1; $i -le 10; $i++)
{
# for the sake of demonstration, generate a random-length string of letters
$randStringLength = Get-Random -Minimum 1 -Maximum 500
$randCharIndex = Get-Random -Minimum 65 -Maximum (65+26) # A = ASCII 65
$randChar = ([char]$randCharIndex)
$myString = [string]$randChar*$randStringLength
# overwrite at the current console line
Write-Progress -Activity $i -Status $myString
# pause briefly before going again
Start-Sleep -Milliseconds 200
}
}
Alot of good suggestions here...
I use the WindowTitle bar for monitoring the status of my scripts, indicate where I am within my code, & the current progress.
For($t = 0; $t -le 100; $t++) {
$Host.UI.RawUI.WindowTitle = "Progress - $t% complete"
Start-Sleep -Milliseconds 10
}
I'll even insert updated "position" info within my code, to indicate where I'm at within my code:
$Host.UI.RawUI.WindowTitle = "Querying index..."
$Host.UI.RawUI.WindowTitle = "Updating search field..."
$Host.UI.RawUI.WindowTitle = "Conducting Robocopy..."
and of course when it's completed:
$Host.UI.RawUI.WindowTitle = "Script completed."
I have a powershell script that gives some status output via write-output. I am intentionally not using write-host because the output may be captured and written to a logfile like this:
./myscript.ps1 | out-file log.txt
But if the output is not redirected it would be nice to have colored output on the console, because the script is producing a lot of different status messages. I know that colored output is possible with write-host but the status messages should be pipeable.
Any ideas how to solve this?
I have tried this extra function and it basically works fine:
function Write-ColorOutput($ForegroundColor)
{
# save the current color
$fc = $host.UI.RawUI.ForegroundColor
# set the new color
$host.UI.RawUI.ForegroundColor = $ForegroundColor
# output
if ($args) {
Write-Output $args
}
else {
$input | Write-Output
}
# restore the original color
$host.UI.RawUI.ForegroundColor = $fc
}
# test
Write-ColorOutput red (ls)
Write-ColorOutput green (ls)
ls | Write-ColorOutput yellow
The result of this particular test is a little bit funny though: we really get lines in red, green and yellow but the table header is in red, i.e. the color of the the first call of the function.
This way:
function Green
{
process { Write-Host $_ -ForegroundColor Green }
}
function Red
{
process { Write-Host $_ -ForegroundColor Red }
}
Write-Output "this is a test" | Green
Write-Output "this is a test" | Red
Separate the results on the pipeline from the status messages in the console.
E.g., use a function like this in your script:
function write-status( $status ){
$status | write-host -fore green -back red; #send a status msg to the console
$status | write-output; #send a status object down the pipe
}
I would also recommend you use one of the following cmdlets over write-host for outputting status messages from your scripts:
write-debug
write-error
write-verbose
write-warning
The appearance of these status messages will vary depending on the cmdlet used. In addition, the user can disable specific levels of status using the $(warning|error|verbose|debug)preference variables, or capture specific status messages using the -(warning|error|verbose|debug)variable common cmdlet parameters.
I had the same problem, so I share my solution which I think works quite well:
Write-ColorOutput "Hello" Green Black -NoNewLine
Write-ColorOutput " World" Red
This is the Cmdlet to use it
function Write-ColorOutput
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$False,Position=1,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)][Object] $Object,
[Parameter(Mandatory=$False,Position=2,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)][ConsoleColor] $ForegroundColor,
[Parameter(Mandatory=$False,Position=3,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)][ConsoleColor] $BackgroundColor,
[Switch]$NoNewline
)
# Save previous colors
$previousForegroundColor = $host.UI.RawUI.ForegroundColor
$previousBackgroundColor = $host.UI.RawUI.BackgroundColor
# Set BackgroundColor if available
if($BackgroundColor -ne $null)
{
$host.UI.RawUI.BackgroundColor = $BackgroundColor
}
# Set $ForegroundColor if available
if($ForegroundColor -ne $null)
{
$host.UI.RawUI.ForegroundColor = $ForegroundColor
}
# Always write (if we want just a NewLine)
if($Object -eq $null)
{
$Object = ""
}
if($NoNewline)
{
[Console]::Write($Object)
}
else
{
Write-Output $Object
}
# Restore previous colors
$host.UI.RawUI.ForegroundColor = $previousForegroundColor
$host.UI.RawUI.BackgroundColor = $previousBackgroundColor
}
I know this post is ancient, but this could come handy to someone out there.
I wanted to change colors and the accepted answer was not the best solution. In my eyes, the following code is better solution as it takes advantage of the native PowerShell functionality:
EDIT:
# Print User message using String Array $message
function PrintMessageToUser {
param(
[Parameter( `
Mandatory=$True, `
Valuefrompipeline = $true)]
[String]$message
)
begin {
$window_private_data = (Get-Host).PrivateData;
# saving the original colors
$saved_background_color = $window_private_data.VerboseBackgroundColor
$saved_foreground_color = $window_private_data.VerboseForegroundColor
# setting the new colors
$window_private_data.VerboseBackgroundColor = 'Black';
$window_private_data.VerboseForegroundColor = 'Red';
}
process {
foreach ($Message in $Message) {
# Write-Host Considered Harmful - see http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/
# first way how to correctly write it
#Write-host $message;
Write-Verbose -Message $message -Verbose;
# second correct way how to write it
#$VerbosePreference = "Continue"
#Write-Verbose $Message;
}
}
end {
$window_private_data.VerboseBackgroundColor = $saved_background_color;
$window_private_data.VerboseForegroundColor = $saved_foreground_color;
}
} # end PrintMessageToUser
I have the same problem, I need to log the output by screen with colors in interactive way and send that output to a file in autometed way.
My solution is to use a parameter to indicate the output type ('Screen' or 'File'), then the function can decide how to render de output.
function Write-Color([String[]]$Text, [ConsoleColor[]]$Color, [ConsoleColor]$BackgroundColor = ([console]::BackgroundColor), $OutputType='Screen') {
switch ($OutputType) {
'Screen' {
for ($i = 0; $i -lt $Text.Length; $i++) {
Write-Host $Text[$i] -Foreground $Color[$i] -NoNewLine -BackgroundColor $BackgroundColor
}
Write-Host
break
}
'File' {
# Assuming $OFS built-in Variable is an space
write-output "$Text"
break
}
Default {
throw '$OutputType must be "Screen" or "File".'
}
}
}
$CodeBlock = {
param ($OutputType)
Write-Color -T "=== STARTING ===" -C Cyan -B Gray -O $OutputType
Write-Color -T 'Date: ', (Get-Date).ToString('yyyy-MM-dd hh:mm:ss') -C Green, Yellow -O $OutputType
Write-Color -T 'Processing..' -C Cyan -O $OutputType
Write-Color -T 'Date: ', (Get-Date).AddSeconds(3).ToString('yyyy-MM-dd hh:mm:ss') -C Green, Yellow -O $OutputType
Write-Color -T "=== ENDING ===" -C Cyan -B Gray -O $OutputType
}
$Dir = 'D:\Tmp' # Set your output directory
#### Screen Test ####
& $CodeBlock -OutputType 'Screen'
& $CodeBlock -OutputType 'File'
### File Test ####
# This file have unwanted newlines, notice the IO redirection with "*>"
& $CodeBlock -OutputType 'Screen' *> "$Dir\Screen.log"
& $CodeBlock -OutputType 'File' > "$Dir\File.log"