How to concatenate 2 Write-Output on the same line? - powershell

I wrote a script that can return you if a software is installed or not.
I would like to give it a bit of colors but I don't know how to concatenate 2 Write-Output on the same line as -NoNewline only works for Write-Host... which in this case I cannot use:
# The function we use to give color
function Positive {
process { Write-Host $_ -ForegroundColor Green }
}
function Negative {
process { Write-Host $_ -ForegroundColor Red }
}
# Is the software installed?
# '0' = NO, is not installed
# '1' = YES, is installed
$Check = '1'
function Check_Installation($Check){
if ($Check -eq '0') {return $response = "No, is not installed" | Negative}
elseif ($Check -eq '1') {return $response = "Yes, is installed" | Positive}
}
$First_Phrase = "Let's check if the software is installed: "
Write-Output "$First_Phrase", "$response"
Check_Installation($Check)
I know I can concatenate with
[string]::Concat("$First_Phrase", "$response")
but is not working.

This works in console only as changing the foreground colour in ISE changes it for every line:
# The function we use to give color
function Set-Output {
param ($colour, $str1, $str2)
$t = $host.ui.RawUI.ForegroundColor
$host.ui.RawUI.ForegroundColor = "$colour"
$text = "$str1 $str2"
Write-Output "$text"
$host.ui.RawUI.ForegroundColor = $t
}
# Is the software installed?
# '0' = NO, is not installed
# '1' = YES, is installed
$Check = '1'
$First_Phrase = "Let's check if the software is installed: "
Switch ($check) {
( 0 ) {Set-Output -colour RED -str1 $First_Phrase -str2 "No, is not installed" }
( 1 ) {Set-Output -colour GREEN -str1 $First_Phrase -str2 "Yes, is installed" }
}
Check_Installation($Check)
All that it is doing is concatenating the two strings and changing the foreground colour.

Related

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

Powershell script : get track ID from a mkv to be used in a mkvmerge command with reorganised track or dismissing unwanted tracks

I'm working on a PowerShell script to merge some external subtitle file with a mkv, and naming correctly the different tracks, and re-ordering them. That part works great if the tracks id are the same for all mkv... It requires me to identify before the track id and set them in my script.
Ok, that's not the fast way, but my script works fine, and can reorder the tracks as I want.
I managed to build a personalized command of mkvmerge.
But for some mkv, the track id are messed up... And I have not a small amount like those...
So, in order to treat in mass, I would like to get, via a script, the track id for the video, the audio in English, French or Japanese (if there is those tracks), and the same for subtitles in En and Fr, forced or not.
In my best hopes, it would be great to have an array with the track id, the track type and the lang track.
I may be able to manage to search for a specific language in this array, I hope it's possible, right?
I may have found a way to do what I need.
It's maybe not very clean, bit it seems to work.
############################################################################################
Clear-Host
Write-Host "Début du script de test." -ForegroundColor "black" -BackgroundColor "white"
Write-Host ""
$MediaInfo = "`"path_to\MediaInfo.exe`""
$sourceDirectory_1 = "path_to_sourceDirectory1"
# $sourceDirectory_1_log = "path_to_sourceDirectory1_log"
$VideoExtension_1 = "mkv"
$MKV_1_name = "my_mkv_name"
$MKV_1 = $sourceDirectory_1 + "\" + $MKV_1_name + ".$VideoExtension_1"
# Values to set to what we want to keep :
$NB_Audio_MKV1 = 2
$AudioTrack_1_Lang = "ja"
$AudioTrack_2_Lang = "fr"
$AudioTrack_Default_Lang = "ja"
# Values witch will be found with MediaInfo JSON
$video_id = ""
$AudioTrack_1_ID = ""
$AudioTrack_2_ID = ""
$JSON_video_position = "" # To use with $JSON_object.media.Track[ ]
$JSON_audio_1_position = "" # To use with $JSON_object.media.Track[ ]
$JSON_audio_2_position = "" # To use with $JSON_object.media.Track[ ]
# $JSON_file = "$sourceDirectory_1_log\$MKV_1_name.json"
# $MediaInfo_arguments = "--output=JSON --LogFile=`"$JSON_file`""
$MediaInfo_arguments = "--output=JSON"
$command_info = "& $MediaInfo $MediaInfo_arguments `"$MKV_1`""
# Launch begin...
$json_output = Invoke-Expression $command_info
# Write-Host "`$json_output = `n$json_output" -ForegroundColor "green"
# $JSON_object = Get-Content -Raw -Path "$JSON_file" | ConvertFrom-Json
$JSON_object = $json_output | ConvertFrom-Json
$i = 0 # Counter for video track detected
$j = 0 # Counter for audio track detected
$k = 0 # Counter for audio tracks kept
$l = 0 # Counter for track position
$JSON_VideoCount = $JSON_object.media.Track[0].VideoCount
$JSON_AudioCount = $JSON_object.media.Track[0].AudioCount
foreach ( $track in $JSON_object.media.Track ) {
# Note :
# The reel track ID is 1 minus the ID get in JSON...
# Check if there is a bug in the script
if ( $j -gt $JSON_AudioCount ) {
Write-Host "There is a problem in Audio Track detection from JSON !! Need to debug this !!" -ForegroundColor "White" -BackgroundColor "DarkRed"
Exit
}
elseif ( $i -gt $JSON_VideoCount ) {
Write-Host "There is a problem in Video Track detection from JSON !! Need to debug this !!" -ForegroundColor "White" -BackgroundColor "DarkRed"
Exit
}
if ( $track."#type" -match "Video" ) {
$video_id = $track.id - 1
$i++
$JSON_video_position = $l
}
elseif ( $track."#type" -match "Audio" ) {
$j++
if (( $track."Language" -match "$AudioTrack_1_Lang" ) -And ( $AudioTrack_1_ID -eq "" ) -And ( $k -ne $NB_Audio_MKV1)) {
$AudioTrack_1_ID = $track.id - 1
$k++
$JSON_audio_1_position = $l
}
elseif (( $track."Language" -match "$AudioTrack_2_Lang" ) -And ( $AudioTrack_2_ID -eq "" ) -And ( $k -ne $NB_Audio_MKV1)) {
$AudioTrack_2_ID = $track.id - 1
$k++
$JSON_audio_2_position = $l
}
else {
if (( $AudioTrack_1_ID -eq "" ) -And ( $AudioTrack_2_ID -eq "" )) {
write-host "There is no audio tracks that match your specifications." -ForegroundColor "Yellow"
}
else {
write-host "There is no audio tracks that match your specifications." -ForegroundColor "Yellow"
}
}
}
$l++ # Increment number of track processed
}
write-host "`t`$video_id = $video_id" -ForegroundColor "Yellow"
write-host "`t`$AudioTrack_1_ID = $AudioTrack_1_ID`tLang set in the script = $AudioTrack_1_Lang" -ForegroundColor "Yellow"
write-host "`t`$AudioTrack_2_ID = $AudioTrack_2_ID`tLang set in the script = $AudioTrack_2_Lang" -ForegroundColor "Yellow"
Write-Host "`nFin du script de test." -ForegroundColor "black" -BackgroundColor "white"
I'll integrate this into my main script, and see if there is some side effects :)

Powershell, ping results in color

been working on a script/module in PowerShell for my work and can't seem to find where/what to input to make the ping results på green if it's a success and red if they timed out.
Param (
[Parameter(Position=0,Mandatory=$true)]
[String]$StoreID
)
$StoreNetworkArray = #(
("1017","xxx.x.x.x","x.x.x.x"),
)
$checkPing = $False
foreach ($currentStore in $StoreNetworkArray) {
if($currentStore[0] -eq $StoreID) {
$checkPing = $True # StoreID matches!
$WanIP = $currentStore[1]
$BreakoutIP = $currentStore[2]
} # end if
} # end foreach
if ($checkPing) {
# Store found, lets ping!
Write-Host "Checking status for store $StoreID using ping"
Write-Host "-----------------------------------------"
Write-Host "Pinging WAN IP: $WanIP"
ping $WanIP /n 10
Write-Host "-----------------------------------------"
Write-Host "Pinging Breakout IP: $BreakoutIP"
ping $BreakoutIP /n 10
Write-Host "-----------------------------------------"
}
} # End Function Check-StorePing```
ping returns an array of strings so you can check Foreach returned line:
ping $WanIP /n 10 | foreach {
if ($_ -like '*TTL=*'){
Write-Host $_ -ForegroundColor Green
}elseif($_ -like '*timed out*'){ # you might need to localize this part
Write-Host $_ -ForegroundColor Red
}else{ # remove the last else to get rid of the extra lines before and after the ping
Write-Host $_
}
}
You can even fit all of this in a single line but for now this has a better overview. You might need to localize the if. E.g. in a german environment Request timed out is Zeitberschreitung der Anforderung.

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

Is there a way to specify a font color when using write-output

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"