Powershell Script working fine in Visual Code but fails running from Terminal - powershell

I'm working on writing a script which will run from AzDo Pipeline to disable F5 WebServers. Below script works fine in Visual Code and does disable the server as expected . But when running from the terminal or PS window fails with the below error . Can someone please help.
$ServerInput = 'server1.abc.com'
$BIGIPBaseURL = "https://ser-f5-1.prod.abc.com"
$usr = "nilesh"
$SecurePassword='P#assword'
Write-Host "Starting the Script..."
# Initialize variables
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$BIGIPToken = $null
Write-Host -ForegroundColor Green " done!"
$DisableWebServers = $true
# Initialize functions
Write-Host "Initializing functions..." -NoNewline
$PSVersionTable
function Disable-BIGIPNode([string]$NodeName) {
# servers should use the Disable-BIGIPTelcoNode() function
Write-Host "In the Disable function"
if ($NodeName -match "(?i).*telco.*") {
Write-Host -ForegroundColor Yellow "WARNING: `"$($NodeName.ToUpper().Split('.')[0])`" is in the wrong list. telcoo hosts should be added to the TelcoServers list in your input file."
BREAK
}
else {
if ($BIGIPToken -eq $null) {
Write-Host "Now will enter the Open-Session"
Open-BIGIPSession
}
Write-Host "Disabling node `"$($NodeName.ToUpper().Split('.')[0])`" in BIG-IP..." -NoNewline
$WebRequestInput = #{
body = #{
"session" = "user-disabled"
} | ConvertTo-Json
uri = $($BIGIPBaseURL) + "/mgmt/tm/ltm/node/~Common~" + $NodeName.ToLower()
headers = #{
"Content-Type" = "application/json"
"X-F5-Auth-Token" = "$BIGIPToken"
}
method = "PATCH"
}
Write-Host $WebRequestInput
Write-Host $WebRequestInput.body
try {
Write-Host "In the final try block"
$Request = Invoke-WebRequest #WebRequestInput -UseBasicParsing -SkipCertificateCheck
}
catch {
Write-Host -ForegroundColor Red " failed!"
Write-Host -ForegroundColor Red ($_.ErrorDetails | ConvertFrom-Json).Message
}
Write-Host -ForegroundColor Green " done!"
$global:ZabbixRequestID++
}
}
function Open-BIGIPSession() {
Write-Host "Authenticating with BIG-IP API..." -NoNewline
$WebRequestInput = #{
body = #{
username = "$usr"
password = "$SecurePassword"
loginProviderName = "tmos"
} | ConvertTo-Json
uri = $ScriptInput.BIGIPBaseURL + "/mgmt/shared/authn/login"
headers = #{
"Content-Type" = "application/json"
}
method = "POST"
}
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$Request = Invoke-WebRequest #WebRequestInput -UseBasicParsing -SkipCertificateCheck
}
catch {
Write-Host -ForegroundColor Red " failed!"
Write-Host -ForegroundColor Red ($_.ErrorDetails | ConvertFrom-Json).Message
EXIT 1
}
Write-Host -ForegroundColor Green " done!"
$global:BIGIPToken = ($Request.Content | ConvertFrom-Json).token.token
}
if ($DisableWebServers) {
Write-Host "Starting the main Methord "
foreach ($Server in $($ServerInput)) {
Disable-BIGIPNode -NodeName $Server
}
}
The PowerShell version is PSVersion 7.2.2
Disabling node "SAC-DEV-WEB2" in BIG-IP...System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry
{
"session": "user-disabled"
}
In the final try block
failed!
ConvertFrom-Json: C:\Temp\Testing.ps1:49:64
Line |
49 | … Host -ForegroundColor Red ($_.ErrorDetails | ConvertFrom-Json).Messag …
| ~~~~~~~~~~~~~~~~
| Conversion from JSON failed with error: Additional text encountered after finished reading JSON content: U. Path '', line
| 3, position 4.
Its working fine when running from VsCode but fails if called with the file name from the same terminal
like .\Testing.ps1
Please help

Your incidental problem is that the true error message is being obscured by a follow-up error that results from attempting to parse the error record's .ErrorDetails property as JSON, which it isn't. (You report that examining the true error reveals a 401 authentication error).
I have no specific explanation for the difference in behavior you're seeing between running in Visual Studio Code vs. in a regular PowerShell console, but I have a guess:
Your Visual Studio Code session in the so-called PowerShell Integrated Console may have lingering state from earlier debugging runs, which may mask a bug in your script.
Restarting Visual Studio Code should clarify whether that is the case, but there's also a way to configure the PowerShell extension so that the problem doesn't arise to begin with - see below.
By default, code you run (debug) via the Visual Code PowerShell extension executes in the same PowerShell session, directly in the global scope.
That is, running a script being edited, say, foo.ps1, in the debugger is effectively the same as invoking it with . .\foo.ps1, i.e. it is in effect dot-sourced.
Therefore, a given debugging run can be affected by earlier runs, because the state of earlier runs lingers.
This can result in bugs going undetected, such as in the following example:
Say your script defines variable $foo and uses it throughout the script. If you debug your script at least one, $foo is now defined in the PowerShell session in the PowerShell Integrated Console.
Say you then change the name to $bar, but you forget to also replace (all) references to $foo with $bar.
Your script is now effectively broken, but you won't notice in the same session, because $foo is still around from earlier debugging runs.
However, running the script from a regular PowerShell console would surface the problem.
The obsolescent Windows PowerShell ISE exhibits the same unfortunate behavior, invariably so, but fortunately there is a solution for the PowerShell extension - see next point.
You can avoid this problem by activating the Create Temporary Integrated Console setting (via File > Preferences > Settings or Ctrl+,), which ensure that every debugging run creates a new, temporary session to run in, which starts with a clean slate:
Whenever a new temporary session is started, any previous one is automatically discarded.
A temporary session has prefix [TEMP] in the list of running shells in the integrated terminal.
You pay a performance penalty, because a new PowerShell session must be created for every run, and you lose the previous session's display output - but I suspect avoiding the pitfalls of lingering state is worth the price.
Note that, in a given temporary session, the dot-sourced invocation described above still applies, but with the lingering-state problem out of the picture, it can now be considered an advantage: After the script finishes, and before the temporary session is replaced with a new one, the variables and functions defined in the script's top-level scope are then available for inspection.

Related

Script won't run in Switch menu

function Show-Menu { #Create the Show-Menu function
param ([string]$Title = 'Functions') #Sets title
Clear-Host
Write-Host "`t6: Reboot History." -foregroundcolor white
Write-Host "`tQ: Enter 'Q' to quit."
} #close of create show menu function
#Begin Main Menu
do
{
Show-Menu #Displays created menu above
$Selection = $(Write-Host "`tMake your selection: " -foregroundcolor Red -nonewline; Read-Host)
switch ($selection) #Begin switch selection
{
#===Reboot History===
'6' {
$Workstation = $(Write-Host "Workstation\IP Address" -nonewline -foregroundcolor DarkGreen) + $(Write-Host "(Use IP for remote users)?: " -NoNewline; Read-Host)
$DaysFromToday = Read-Host "How many days would you like to go back?"
$MaxEvents = Read-Host "How many events would you like to view?"
$EventList = Get-WinEvent -ComputerName $Workstation -FilterHashtable #{
Logname = 'system'
Id = '41', '1074', '1076', '6005', '6006', '6008', '6009', '6013'
StartTime = (Get-Date).AddDays(-$DaysFromToday)
} -MaxEvents $MaxEvents -ErrorAction Stop
foreach ($Event in $EventList) {
if ($Event.Id -eq 1074) {
[PSCustomObject]#{
TimeStamp = $Event.TimeCreated
Event = $Event.Id
ShutdownType = 'Restart'
UserName = $Event.Properties.value[6]
}
}
if ($Event.Id -eq 41) {
[PSCustomObject]#{
TimeStamp = $Event.TimeCreated
Event = $Event.Id
ShutdownType = 'Unexpected'
UserName = ' '
}
}
}
pause
}
}
}
until ($selection -eq 'q') #End of main menu
Works perfectly fine if I remove the script from the switch and run it separately, but as soon as I call it from the switch it still asks for the workstation/IP, how many days, and max events, but just outputs nothing.
Here is what it looks like when it works:
How many days would you like to go back?: 90
How many events would you like to view?: 999
TimeStamp Event ShutdownType UserName
--------- ----- ------------ --------
12/23/2022 12:20:55 AM 1074 Restart Username
12/20/2022 1:00:01 AM 1074 Restart Username
12/17/2022 12:21:54 AM 1074 Restart Username
12/13/2022 8:57:40 AM 1074 Restart Username
This is what I get when I run it within the switch menu
Workstation\IP Address(Use IP for remote users)?: IP Address
How many days would you like to go back?: 90
How many events would you like to view?: 999
Press Enter to continue...:
I have tried just doing 1 day and 1 event, but same results. No errors or anything indicating a failure, so not sure how to troubleshoot this. I have had similar issues with switches in the past that were resolved with some researching into scopes, but I don't think this is the same case as it is all self contained within the switch itself.
I am at a loss, any ideas? As always, any insight into my script is greatly appreciated, even if it doesn't resolve the problem at hand.
JosefZ has provided the crucial pointer:
force synchronous to-display output with, such as with Out-Host
if you neglect to do so, the pause statement will - surprisingly - execute before the [pscustomobject] instances emitted by the foreach statement, due to the asynchronous behavior of the implicitly applied Format-Table formatting - see this answer for details.
Here's a simplified example:
switch ('foo') {
default {
# Wrap the `foreach` statement in . { ... },
# so its output can be piped to Out-Host.
. {
foreach ($i in 1..3) {
[pscustomobject] #{ prop = $i }
}
} |
Out-Host # Without this, "pause" will run FIRST.
pause
}
}
Note:
For Out-Host to format all output together it must receive all output from the foreach loop as part of a single pipeline.
Since foreach is a language statement (rather than a command, such as the related ForEach-Object cmdlet) that therefore cannot directly be used at the start of a pipeline, the above wraps it in a script block ({ ... }) that is invoked via ., the dot-sourcing operator, which executes the script block directly in the caller's context and streams the output to the pipeline.
This limitation may be surprising, but is rooted in the fundamentals of PowerShell's grammar - see GitHub issue #10967.
An all-pipeline alternative that doesn't require the . { ... } workaround would be:
1..3 |
ForEach-Object {
[pscustomobject] #{ prop = $_ } # Note the automatic $_ var.
} |
Out-Host

How can I check if the PowerShell profile script is running from an SSH session?

I'm trying to work around a bug in Win32-OpenSSH, where -NoProfile -NoLogo is not respected when using pwsh.exe (Core) and logging in remotely via SSH/SCP. One way (of several) I tried, was to add the following in the very beginning of my Microsoft.PowerShell_profile.ps1 profile.
function IsInteractive {
$non_interactive = '-command', '-c', '-encodedcommand', '-e', '-ec', '-file', '-f'
-not ([Environment]::GetCommandLineArgs() | Where-Object -FilterScript {$PSItem -in $non_interactive})
}
# No point of running this script if not interactive
if (-not (IsInteractive)) {
exit
}
...
However, this didn't work with a remote SSH, because when using [Environment]::GetCommandLineArgs() with pwsh.exe, all you get back is:
C:\Program Files\PowerShell\6\pwsh.dll
regardless whether or not you are in an interactive session.
Another way I tried, was to scan through the process tree and look for the sshd parent, but that was also inconclusive, since it may run in another thread where sshd is not found as a parent.
So then I tried looking for other things. For example conhost. But on one machine conhost starts before pwsh, whereas on another machine, it starts after...then you need to scan up the tree and maybe find an explorer instance, in which case it is just a positive that the previous process is interactive, but not a definite non-interactive current process session.
function showit() {
$isInter = 'conhost','explorer','wininit','Idle',
$noInter = 'sshd','pwsh','powershell'
$CPID = ((Get-Process -Id $PID).Id)
for (;;) {
$PNAME = ((Get-Process -Id $CPID).Name)
Write-Host ("Process: {0,6} {1} " -f $CPID, $PNAME) -fore Red -NoNewline
$CPID = try { ((gwmi win32_process -Filter "processid='$CPID'").ParentProcessId) } catch { ((Get-Process -Id $CPID).Parent.Id) }
if ($PNAME -eq "conhost") {
Write-Host ": interactive" -fore Cyan
break;
}
if ( ($PNAME -eq "explorer") -or ($PNAME -eq "init") -or ($PNAME -eq "sshd") ) {
# Write-Host ": non-interactive" -fore Cyan
break;
}
""
}
}
How can I check if the profile script is running from within a remote SSH session?
Why am I doing this? Because I want to disable the script from running automatically through SSH/SCP/SFTP, while still being able to run it manually (still over SSH.) In Bash this is a trivial one-liner.
Some related (but unhelpful) answers:
Powershell test for noninteractive mode
How to check if a Powershell script is running remotely

How can I measure the window height (number of lines) in powershell?

The maximum number of lines in my environment is 47.
Can I measure this value programmatically?
To complement Christian.K's helpful answer:
A simpler and slightly more efficient method for obtaining the host object is to use the $Host automatic variable rather than the Get-Host cmdlet.
You're only guaranteed to have access to window-size information in the PowerShell console host, i.e., when running in a console (terminal) window
It is at a given host's discretion whether to expose this information, and, as Christian states, the PowerShell ISE does not - even though it arguably should, given that it has a console subsystem built in.
To test whether your code is running in a console (a.k.a terminal) or not, use
$Host.UI.SupportsVirtualTerminal - $True indicates that the host is a console.
If running in the PowerShell console host:
You can access the console's properties either:
via $Host.UI.RawUI (as in Christian's answer), which is an object of type [System.Management.Automation.Internal.Host.InternalHostRawUserInterface] that - in the PowerShell console host only - wraps the .NET [Console] class (see below).
via the .NET [Console] class; e.g., to get the window height (count of rows) this way, use:
[Console]::WindowHeight
Sample $Host.UI.RawUI output:
PS> $Host.UI.RawUI
ForegroundColor : DarkYellow
BackgroundColor : DarkMagenta
CursorPosition : 0,58
WindowPosition : 0,0
CursorSize : 25
BufferSize : 160,9999
WindowSize : 160,75
MaxWindowSize : 160,94
MaxPhysicalWindowSize : 303,94
KeyAvailable : True
WindowTitle : Windows PowerShell
If running in the PowerShell ISE:
Written as of PowerShell version 5.1
Most of the properties in $Host.UI.RawUI are not populated and will return their data type's default value:
PS> $Host.UI.RawUI # in the ISE
ForegroundColor : -1
BackgroundColor : -1
CursorPosition : 0,0
WindowPosition :
CursorSize :
BufferSize : 166,0
WindowSize :
MaxWindowSize :
MaxPhysicalWindowSize :
KeyAvailable :
WindowTitle : Windows PowerShell ISE
The only size-related information that is available is the buffer width (166 in the sample output above).
There is no point in trying to use the .NET [Console] class in the ISE, except to query / set the character encoding used for communication with external programs, [Console]::OutputEncoding:[1]
Initially in a session, trying to use the window-related members of [Console] causes exceptions, because the ISE does not allocate a console window on startup:
# In a pristine session.
PS> [Console]::WindowHeight
The handle is invalid. # EXCEPTION
While the ISE allocates a - hidden - console window on demand, namely the first time you run a console application in your session, that hidden console window's properties, as then reported on via [Console], do not reflect the properties of the simulated console that the ISE presents to the user.
# chcp is a console application, so when it is run,
# the ISE allocates a (hidden) console window,
# after which the [Console] API becomes technically usable,
# but DOESN'T REPORT MEANINGFUL VALUES.
PS> chcp >$null; [Console]::WindowHeight
72 # No exception, but the value is meaningless.
[1] Note that the ISE defaults to the system's legacy ANSI code page, whereas regular console windows default to the OEM code page. Thus, these two environments decode output from external programs differently by default.
You could try something like
$(Get-Host).UI.RawUI.WindowSize.Height
Note a couple of things however:
Using the RawUI might not be particular "portable", depending on where your script runs. In "ISE" for example, that property returns nothing, while on the console it will work.
For more difference here between the console and ISE, run $(Get-Host).UI.RawUI and compare the output.
In other PowerShell hosts, besides the console or ISE, it might yet be still different.
There is also a BufferSize, which can be bigger than the WindowSize. The later being only the part that is currently "viewable" by the user.
Your question sounds a little like a xy problem. Consider explaining (as an edit to your question or a new question), what you're actually trying to achieve, i.e. why you need to know the number of lines?
The functionality I really need is git status, git diff in powershell_ise with highlight and output paging.
It seems that the current version of powershell_ise does not solve the problem itself.
Although not completely satisfactory, I have devised a practical solution and created the following function.
source code
function hhd-git-diff
{
[CmdletBinding()]
param
(
)
$res = git diff
hhd-git-colored-output -INPUT_OBJECT $res
}
function hhd-git-status
{
[CmdletBinding()]
param
(
)
$res = git status
hhd-git-colored-output -INPUT_OBJECT $res
}
function hhd-git-colored-output
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelinebyPropertyName=$true)]
[System.Object]
$INPUT_OBJECT
)
$lineNum = 1
$INPUT_OBJECT |
Out-String |
foreach {
$_ -split "\n" |
foreach {
if($lineNum % [Console]::WindowHeight -eq 0)
{
Read-Host "continue? press any key..."
}
$lineNum++
if($_ -like "diff --git *")
{
Write-Host ""
Write-Host ""
Write-Host ""
Write-Host ("#"*80) -ForegroundColor Cyan
Write-Host $_ -ForegroundColor Cyan
Write-Host ("#"*80) -ForegroundColor Cyan
Read-Host "continue? press any key..."
$lineNum = 1
}
elseif($_ -like "--- *")
{
}
elseif($_ -like "+++ *")
{
}
elseif($_ -like "-*")
{
Write-Host $_ -ForegroundColor Red
}
elseif($_ -like "+*")
{
Write-Host $_ -ForegroundColor Green
}
elseif($_ -like "On branch *")
{
Write-Host ("#"*80) -ForegroundColor Cyan
Write-Host $_ -ForegroundColor Cyan
}
elseif($_ -like "Your branch is*")
{
Write-Host $_ -ForegroundColor Cyan
Write-Host ("#"*80) -ForegroundColor Cyan
}
elseif($_ -like "Changes to be committed:*")
{
Write-Host ("-"*80) -ForegroundColor Green
Write-Host $_ -ForegroundColor Green
}
elseif($_ -like "Changes not staged for commit:*")
{
Write-Host ("-"*80) -ForegroundColor Red
Write-Host $_ -ForegroundColor Red
}
elseif($_ -like "Untracked files:*")
{
Write-Host ("-"*80) -ForegroundColor Black
Write-Host $_ -ForegroundColor Black
}
elseif($_ -like "*modified:*")
{
Write-Host $_ -ForegroundColor Yellow
}
elseif($_ -like "*deleted:*")
{
Write-Host $_ -ForegroundColor Red
}
else
{
Write-Host $_ -ForegroundColor Gray
}
}
}
}
screen shot

How to send keystroke to executable?

How can I enter a keystroke programmatically through a PowerShell script?
Write-Host -ForegroundColor Green 'Loading...'
Function EnterKey {
[Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
#Where I want to get "|" keystroke programmatically
[System.Windows.Forms.SendKeys]::SendWait("{|}")
}
Function StartUp {
Write-Host "Environment"
$exe = ([IO.Path]::Combine("D:\7ZipApp\7ZipApp\7ZipApp\bin\Debug","7ZipApp.exe"))
& $exe 3 # argument 3 = 'Run local Sync'
EnterKey
Read-Host -Prompt $exe.ToString()
}
StartUp
Write-Host -ForegroundColor Green 'Loading...'
function StartUp {
Write-Host 'Environment'
$exe = Join-Path "D:\7ZipApp\7ZipApp\7ZipApp\bin\Debug" "7ZipApp.exe"
#& $exe 3 # argument 3 = 'Run local Sync'
start $exe -ArgumentList 3
Write-Host 'Type {|} to continue'
while ((Read-Host) -ne '{|}') {}
Read-Host -Prompt $exe.ToString()
}
StartUp
I have to go with the crowd here (from the comments):
I would abandon your approach. Too problematic.
My question was why you want to do it
The correct solution, then, is to get the author of 7zipapp.exe to fix the program so it stops doing that or to add a command-line parameter that prevents this behavior.
That said, if you want a total hack, and this program only takes ONE input, at the end presumably, then the below appears to work. I would use sparingly, perhaps never use it, but rather get the program fixed, but in my testing, this worked.
PowerShell:
$exe = 'C:\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe'
'\r\n' | & $exe
Annoying C# program:
using static System.Console;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
WriteLine("I will force you to hit Enter to exit.");
ReadLine();
}
}
}

How to run interactive commands in another application window from powershell

I have another command line program which I invoke from my powershell script and would like to run some interactive commands in that window once it is opened from power shell.
In other words - I do a Invoke-Item $link_to_app which opens up the interactive command line for that application and now I would like to use the application specific commands from within powershell scripts.
e.g. app.exe -help to invoke the help command of the app.exe.
Any pointers would help. Thanks!
Try this:
$app = 'app.exe -help'
Invoke-Expression $app
Tested with this and it worked as expected:
$pingTest = 'ping -n 8 127.0.0.1'
Invoke-Expression $pingTest
From your expanded explanation you appear to want to run 2 commands within the same command prompt. This is possible, however, I'm not sure it will work in your scenario. For example:
test1.bat:
echo "hello!"
test2.bat: echo "goodbye!"
$batchTest = "test1.bat && test2.bat"
cmd /c $batchTest
output:
D:\Test>echo "hello!"
"hello!"
D:\Test>echo "goodbye!"
"goodbye!"
Hope this helps.
I'm not sure, but I think what you want is the ability to have a script send input to and receive output from another program, where the other program has "state" that your script needs to be able to interact with. Below is an example of a script that drives CMD.EXE. CMD has state, such as current working directory and environment variables.
Note, that you could do what the other answerer suggested and just start the program, give all the input on the command line, and then do what you need to with the output. However for CMD if you need to make decisions based on the output, and then give CMD more input based on the previous output, you'd have to save and restore the environment and current working directories between each time you executed CMD. The approach below doesn't require that.
However the approach below does have several caveats. First it is dependent on the PS "host". It works (for me) on the command line PS, but not in ISE. This dependency is due to using the Raw host interface to determine if a key is available. Second it is timing dependent, based on the behavior of CMD (or whatever you use instead). You'll see a few sleep commands in the script. I had to experiment a whole lot to get this script to show CMD's output for a particular sub-command when that command was entered, versus CMD giving the output of previous commands after another command was entered. Comment out the sleeps to see what I mean. Third it is easy to hang Powershell. Killing CMD in task manager gets you out of the hung state, which I had to do many times.
You'll see that I added a couple of commands that the script deals with specially. This is to demonstrate that input to command can come from a PS script (versus input from the keyboard).
$global:ver++
if ($ExecutionContext.Host.name -match "ISE Host$") {
write-warning "This script relies on RawUI functionality not implemented in ISE"
return
}
$in = $null
$flExiting = $false
$doDebug = $false
function dot-debug {param($color)
if ($doDebug) {
write-host "." -NoNewline -ForegroundColor $color
}
}
#function dot-debug {param($color) }
$procInfo = new diagnostics.processstartinfo
$procInfo.RedirectStandardOutput=1
$procInfo.RedirectStandardInput=1
$procInfo.RedirectStandardError=1
$procInfo.FileName="cmd.exe"
$procInfo.UseShellExecute=0
$p=[diagnostics.process]::start($procInfo)
$outBuf = new char[] 4096
write-host "Version $ver"
sleep -Milliseconds 300
do {
dot-debug red
# This while loop determines whether input is available from either
# CMD's standard output or from the user typing. You don't want to
# get stuck waiting for input from either one if it doesn't really have input.
:WaitIO while ($true) {
if (-1 -ne $p.StandardOutput.peek()) {
dot-debug yellow
$cnt = $p.StandardOutput.read( $outBuf, 0, 4096)
} else {
dot-debug Gray
if ($host.ui.rawui.KeyAvailable -or $flExiting) {break}
}
$str = $outBuf[0..($cnt-1)] -join ""
write-host "$str" -NoNewline
while (-1 -eq ($rc =$p.StandardOutput.peek())) {
if ($host.ui.rawui.KeyAvailable -or $flExiting) {
break WaitIO
}
dot-debug DarkGray
sleep -milli 200
}
dot-debug cyan
}
dot-debug green
# read-host echoes input, so commands get echoed twice (cmd also echoes)
#
# $host.ui.rawui.ReadKey("NoEcho, IncludeKeyDown") doesn't work on ISE,
# but does work in the PS cli shell
if ($in -ne "exit") {$in = read-host}
if ($in -eq "td") { # toggle debug
$doDebug = -not $doDebug
$p.StandardInput.WriteLine( "echo debug toggled")
sleep -milli 300
continue
}
if ($in -eq "xxx") {
# Example of script driven output being sent to CMD
$p.StandardInput.WriteLine( "echo This is a very long command that I do not want to have to type in everytime I want to use it")
# You have to give CMD enough time to process command before you read stdout,
# otherwise stdout gets "stuck" until the next time you write to stdin
sleep -milli 1
continue
}
if ($in -eq "exit") {
$flExiting = $true
$p.StandardInput.WriteLine($in)
continue
}
foreach ($char in [char[]]$in) {
$p.StandardInput.Write($char)
}
$p.StandardInput.Write("`n")
sleep -milli 1
} until ($p.StandardOutput.EndOfStream)