Find variables within a string in PowerShell - powershell

I've a sample code like this:
[string] $Text = "This is a string text with some $($variable.Option) and $otherOption and that's it".
Now what I would like to know is it possible to split that $Text into standard strings and variables separately? So that when I pass this $Text variable to a method it's able to extract $($variable.Option) separately?
I know it's a long shot but maybe it's not being processed right away at assignment time?
The ultimate goal is to create a better version of method I wrote for making colorful powershell:
function Write-Color([String[]]$Text, [ConsoleColor[]]$Color = "White", [int]$StartTab = 0, [int] $LinesBefore = 0,[int] $LinesAfter = 0) {
$DefaultColor = $Color[0]
if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host "`n" -NoNewline } } # Add empty line before
if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host "`t" -NoNewLine } } # Add TABS before text
if ($Color.Count -ge $Text.Count) {
for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -ForegroundColor $Color[$i] -NoNewLine }
} else {
for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host $Text[$i] -ForegroundColor $Color[$i] -NoNewLine }
for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -ForegroundColor $DefaultColor -NoNewLine }
}
Write-Host
if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host "`n" } } # Add empty line after
}
Usually I can assign colors by doing something like
write-color -Text "[View][$($singleView.Title)]",
"[Row Limit: $($singleView.RowLimit)]",
"[Paged: $($singleView.Paged)]",
"[Default View: $($singleView.DefaultView)]",
"[Style ID: $($singleView.StyleID)]" -Color Yellow, Green, Red, Gray, Green
But this means I get colors for the whole "line". If i would like to get normal text colors in one color and variables being in second color I would have to do something like this:
write-color -Text "[View: ", "$($singleView.Title)", "]",
"[Row Limit: ", "$($singleView.RowLimit)", "]" `
-Color Yellow, Green, Yellow, Yellow, Green, Yellow
It's not bad.. but I just thought if this can be accomplished in a better way where simple text is one color and variables are the 2nd. If I would want to go even further and have $true being in Green and False being in Red that would also require some parsing.

Variables will expand inside double quotes. Once that is done there is no history to speak of.
You have one of two options here. You can use the format operator to send in string with placeholders for your variables.
Use the format operator
ss64 on Using the format operator
# Create a formatted string with variable placeholders
$Text = "This is a string text with some {0} and {1} and that's it"
# Pass that string into a function and put in the variables
$stringParameterinFunction -f $variable.Option, $otherOption
String Expansion
If you really wanted what I think you are asking for then you can delay the string expansion by use single quotes on the original string. Note the single quote inside the string was escaped.
$Text = 'This is a string text with some $($variable.Option) and $otherOption and that''s it.'
# Pass that string into a function and put in the variables
$ExecutionContext.InvokeCommand.ExpandString($stringParameterinFunction)

Related

Is there any way to colour text previously written using "Write-Host" in powershell?

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.

How can I increase the maximum number of characters read by Read-Host?

I need to get a very long string input (around 9,000 characters), but Read-Host will truncate after around 8,000 characters. How can I extend this limit?
The following are possible workarounds.
Workaround 1 has the advantage that it will work with PowerShell background jobs that require keyboard input. Note that if you are trying to paste clipboard content containing new lines, Read-HostLine will only read the first line, but Read-Host has this same behavior.
Workaround 1:
<#
.SYNOPSIS
Read a line of input from the host.
.DESCRIPTION
Read a line of input from the host.
.EXAMPLE
$s = Read-HostLine -prompt "Enter something"
.NOTES
Read-Host has a limitation of 1022 characters.
This approach is safe to use with background jobs that require input.
If pasting content with embedded newlines, only the first line will be read.
A downside to the ReadKey approach is that it is not possible to easily edit the input string before pressing Enter as with Read-Host.
#>
function Read-HostLine ($prompt = $null) {
if ($prompt) {
"${prompt}: " | Write-Host
}
$str = ""
while ($true) {
$key = $host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown");
# Paste the clipboard on CTRL-V
if (($key.VirtualKeyCode -eq 0x56) -and # 0x56 is V
(([int]$key.ControlKeyState -band [System.Management.Automation.Host.ControlKeyStates]::LeftCtrlPressed) -or
([int]$key.ControlKeyState -band [System.Management.Automation.Host.ControlKeyStates]::RightCtrlPressed))) {
$clipboard = Get-Clipboard
$str += $clipboard
Write-Host $clipboard -NoNewline
continue
}
elseif ($key.VirtualKeyCode -eq 0x08) { # 0x08 is Backspace
if ($str.Length -gt 0) {
$str = $str.Substring(0, $str.Length - 1)
Write-Host "`b `b" -NoNewline
}
}
elseif ($key.VirtualKeyCode -eq 13) { # 13 is Enter
Write-Host
break
}
elseif ($key.Character -ne 0) {
$str += $key.Character
Write-Host $key.Character -NoNewline
}
}
return $str
}
Workaround 2:
$maxLength = 65536
[System.Console]::SetIn([System.IO.StreamReader]::new([System.Console]::OpenStandardInput($maxLength), [System.Console]::InputEncoding, $false, $maxLength))
$s = [System.Console]::ReadLine()
Workaround 3:
function Read-Line($maxLength = 65536) {
$str = ""
$inputStream = [System.Console]::OpenStandardInput($maxLength);
$bytes = [byte[]]::new($maxLength);
while ($true) {
$len = $inputStream.Read($bytes, 0, $maxLength);
$str += [string]::new($bytes, 0, $len)
if ($str.EndsWith("`r`n")) {
$str = $str.Substring(0, $str.Length - 2)
return $str
}
}
}
$s = Read-Line
More discussion here:
Console.ReadLine() max length?
Why does Console.Readline() have a limit on the length of text it allows?
https://github.com/PowerShell/PowerShell/issues/16555

Create variable using a string + second variable

In the beginning of this script I've set some variables ($numof0 = 3, $numof1 = 5, etc.). I'd like to write all those variables to the console, but I would like to be more consise than the 10 write-host statements below.
Write-Host "There are $numOf0 0's"
Write-Host "There are $numOf1 1's"
Write-Host "There are $numOf2 2's"
Write-Host "There are $numOf3 3's"
Write-Host "There are $numOf4 4's"
Write-Host "There are $numOf5 5's"
Write-Host "There are $numOf6 6's"
Write-Host "There are $numOf7 7's"
Write-Host "There are $numOf8 8's"
Write-Host "There are $numOf9 9's"
I figured since all the variables have the same beginning ($numof) and all end with an increasing number, I could do something like this..
$j=0
while($j -lt 10){
$final = '$numof'+"$j"
write-host "There are $final $j's"
$j++
}
Obviously the variable $final is just a string though, and when printed to the console does not show the contents of the corresponding $numofX variable I'd like printed.
Is there a way to create one variable ($final) using a string and another variable (string '$numof' and variable '$j') and still have it reference the original contents of the $numOfX variable?
Invoke-Expression can evaluate your string as if it were typed as a command
$j=0
while($j -lt 10){
$final = Invoke-Expression -Command ('$numof'+"$j")
write-host "There are $final $j's"
$j++
}

PowerShell - Overwriting line written with Write-Host

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."

Referencing Powershell array index produces unexpected results when referenced with string

I am trying to find out why the following occurs if you have
$arr = #("Filename1", "Filename2")
for($i =0; $i -le $arr.Length -1; $i++) {
write-host ".\"$arr[$i]
write-host ".\$arr[$i]"
write-host $arr[$i]
}
So taking just one loop through it produces:
".\ Filename1"
".\ Filename1 Filename2[0]"
"Filename1"
Just referencing the array[index] will produce the correct value, but if I concatenated with a string it places a space between the string and value. When placed within the string I assume it is dumping the entire contents because it is evaluating $array then evaluating $i ending up with
".\ filename1 filename2[index number]"
But if I assign the individual value to a separate variable and concatenate it with a string there is no space? Why is that:
Example:
$name = $arr[$i]
write-host ".\$name"
output = ".\filename1"
which is correct.
You have to do:
write-host ".\$($arr[$i])"
so that it is evaluated as array indexing.
It would be the case with something like accessing properties of an object or key of hash etc within the string:
PS > $a = #{test="A";test2="B"}
PS > write-host "$a.test"
System.Collections.Hashtable.test
PS > write-host "$($a.test)"
A
Another alternative is to use string formatting, especially useful when you have lots of variables / objects in the string:
write-host (".\{0}" -f $arr[$i])
Your code should look like this:
$arr = #("Filename1", "Filename2")
#for($i =0; $i -le $arr.Length-1; $i++) {
for($i =0; $i -le $arr.Length; $i++) {
write-host ".\"$arr[$i]
#write-host ".\$arr[$i]"
write-host ".\$($arr[$i])"
write-host $arr[$i]
}