Does powershell have an equivalent to popen? - powershell

I need to be able to launch a process and read the output into a variable. Then based on the return of the command I can choose to show the full output or just a selected subset.
So to be clear, I want to launch a text based process (psexec actually) and read the output from that command (stdout, stderr, etc) into a variable rather than have it directly outputted to the console.

You left off some details regarding what kind of process, but I think this article from the Powershell Team Blog has whatever you'd like to do, either from piping the executable's output somewhere or utilizing System.Diagnostics.Process.
Now that the second option sounds like what you want to do, you can use the ProcessStartInfo class to feed in true as the RedirectStandardOutput property, and then read from the StandardOutput property of the Process object to do whatever you want with the output. StandardError works identically.

As far as reading stuff into variables is concerned, you should just be able to do something like
$output = ps
This will only capture stdout, though, not the verbose, warning or error streams. You can get the exit code of the previous command by testing the special variable $?.
I think a little more information would be of use to provide a more complete answer, but hopefully this is some way towards what you're looking for.

The PowerShell Community Extensions includes Start-Process. This actually returns a System.Diagnostics.Process.
> $proc = Start-Process pslist -NoShellExecute
However, while this returns the Process object it does not allow you to redirect the output prior to executing. To do that one can create their own process and execute it by first modifying the ProcessStartInfo members:
> $proc = New-Object System.Diagnostics.Process
> $proc.StartInfo = New-Object System.Diagnostics.ProcessStartInfo("pslist.exe")
> $proc.StartInfo.CreateNoWindow = $true
> $proc.StartInfo.UseShellExecute = $false
> $proc.StartInfo.RedirectStandardOutput = $true
> $proc.Start()
> $proc.StandardOutput.ReadToEnd()

Related

Can I tee unbuffered program output in Powershell?

I'm trying to use Putty's plink.exe as part of a Powershell script, and am having trouble teeing the output.
Some of the commands invoke an interactive response (eg: entering password). Specifically, I'm testing against an Isilon.
Example code:
$command = '&"C:\Program Files\Putty\plink.exe" root#10.0.0.141 -pw "password" -t -batch "isi auth users create testuser --set-password"'
iex $command
Expected result:
I get a prompt password:
I enter the password
I get a prompt confirm:
I enter the password again
Command ends
If I try to tee the output, using iex $command | tee-object -variable result or even just redirect with iex $command *>test.log, the prompt text doesn't show up until after I've responded to it. While still technically functional, if you don't know exactly what prompt to expect, it's useless.
I've tried using Start-Transcript, but that doesn't capture the output at all. I've also tried using plink's -sshlog argument, but that logs way too much, in a less than readable format.
Is there any way to have stdout be unbuffered in the console, and also have it stored in a variable?
To answer some potential questions:
-This is to be run in an environment that doesn't allow modules, so can't use Posh-SSH.
-The Powershell version available isn't new enough to use the built-in openssh functionality.
This is all about redirecting streams.
When you use redirection, all outputs are redirected from the streams, and passed to be written to file. When you execute:
Write-Host "Some Text" *>out.txt
You don't see any output and it is all redirected to the file.
Key Note: Redirection works on a (simplification) line by line basis, as
the redirection works by writing to the file one line at a time.
Similarly, when you use Tee-Object, all outputs are redirected from the stream and down the pipeline. This is passed to the cmdlet Tee-Object. Tee-Object takes the input, and then writes that input to both the variable/file you want and to the screen. This happens After the input has been gathered and processed.
This means that both redirection and the Tee-Object commands work on a line by line basis. This makes sense both redirection and the Tee-Object commands work this way because it is hard to deal with things like deleting characters, moving around and editing text dynamically while trying to edit and maintain an open file at the same time. It is only designed for a one-way once the statement is complete, output.
In this case, when running it interactively, the password: prompt is written to the screen and you can respond.
When redirecting/Teeing the output, the password: text prompt is redirected, and buffered, awaiting your response. This makes sense because the statement has not completed yet. You don't want to send half a statement that could change, or half an object down the pipeline. It is only after you complete the statement (e.g. entering in the password + enter) that the whole statement is passed down the stream/pipeline. Once the whole statement is sent, then it is redirected/output Tee'd and can be displayed.
#Bill_Stewart is correct, in the sense that you should pick either an interactive prompt, or a fully automated solution.
Edit: To add some more information from comments.
If we use Tee-Object it relies on the Pipeline. Pipelines can only pass complete objects down the pipeline (e.g. complete strings inc. New Line). Pipelines have to interact with other commands like ForEach-Object or Select-Object, and they can't handle passing incomplete data to them. That's how the PowerShell console works, and you can't change it.
Similarly, redirection works line by line. The underlying reason why, I will explain why in a moment.
So, if you want to interact with it character by character, then you are dealing with streams. And if you want to deal with streams directly, it's 100 times more complicated because you can't use the convenience of the PowerShell console, you have to directly run of the process manually and handle all the input and output yourself.
To start, you have to manually launch the process. To do this we use the System.Diagnostics.Process class. The Pseudocode looks something like this:
$p = [System.Diagnostics.Process]::New()
$p.StartInfo.RedirectStandardOutput = $true
$p.StartInfo.RedirectStandardError = $true
$p.StartInfo.RedirectStandardInput = $true
$p.StartInfo.UseShellExecute = $false
#$p.StartInfo.CreateNoWindow = $true
$p.StartInfo.FileName = "plink.exe"
$p.StartInfo.Arguments = 'root#10.0.0.141 -pw "password" -t -batch "isi auth users create testuser --set-password"'
$p.EnableRaisingEvents = $true
....
We essentially create the process, specify that we are going to redirect the stdout (StartInfo.RedirectStandardOutput = $true), as well as the stdin to something else for us to handle. How do we know when to read the data? Well, the class has the Process.OutputDataReceived Event. You bind to this event to read in the additional data. But:
The OutputDataReceived event indicates that the associated Process has
written a line, terminating with a newline character, to its
redirected StandardOutput stream.
Remarks
So even the process class revolves around newlines for streaming data. This is why even redirects *> work on a line by line basis. PowerShell, and cmd, etc. all use the Process class as a basis to run processes. They all bind to this same event and methods to do their processing. Hence, why everything revolves around newlines and statement completions.
(big breath) So. You still want to interactively work with things one character at a time? well then you can't use the convenience of events. You will have to fall back to using a Stream Reader and directly binding to the Process.StandardOutput Property. Unfortunately this is where I stop, and say that to accomplish this
is beyond the scope of SO, and will require much more research to accomplish.

How to skip part of PowerShell script by starting script with flags or arguments

I am writing a script that presents the user with a menu of functions, but I also want to be able to run the script automatically from task scheduler which would mean I would need to skip the menu portion. Is there a way to do this with flags or arguments when starting the script (like "script.ps1 -auto" to skip the coding containing the menu, or just "script.ps1" to start)
I've performed internet searches for this, but have not yet found anything that I think is applicable. I'm not even sure if this is possible given the lack of information I've found (or not found).
script.ps1
script.ps1 -auto
Not to the point where error messages are applicable
You can use the [switch] parameter type in your param block.
param( [switch] $auto )
if ($auto) {
# here goes the code if the parameter auto is set
}
else {
}
See also this answer on SO, on how to handle command-line parameters with PowerShell.

Get output when using .run

I'm trying to run a program that will web scrape from Pastebin using PowerShell. I used the following code to do so:
Set Wshell = CreateObject("WScript.Shell")
Wshell.Run "%ComSpec% /c powershell & $result = Invoke-WebRequest
""https://pastebin.com/raw/wAhYB4UY"" & $result.content ", 0, True
$result.content will bring up everything I need from Pastebin. How can I transfer $result.content to a VBScript variable?
I know this is possible using the Exec() method as demonstrated here, but I can't use it because I want my code to stay hidden, which to my knowledge is not possible with Exec() (without having a window popping and closing)
I also don't want to use File I/O in Powershell because that can really complicate other things I want my program to do in the future; however, If absolutely no options are available, then I can use it.
EDIT: Some readers pointed out that my script only consists of running Powershell, so why not program my script in PowerShell? Well, not everything I am planning for this script to do can be done in PS. for example, I want my script to type some stuff outside of PS. I also want to wait until the user has pressed a certain key, in my case PrtSc (which will create a popup a message using MsgBox).
$Path = 'https://pastebin.com/raw/etc'
$Raw = Invoke-WebRequest $Path
$Raw.Content
What do you want to do with the data that using VBScript is the preferable tool?

Powershell: Passing command options forward slash changing to backward

I am trying to use the invoke (ii) command to open an access database that has command line options. What I would like to have executed is below (yes there is a space in the name of the access database). The database is in the same folder as the Powershell script.
What I want: program name.accdb /cmd Rester
What I get: program name.accdb \cmd Rester
The exact commands I am using are:
$Path_To_EXE = "program name.accdb /cmd Rester"
&ii $Path_To_EXE
I am new to Powershell and have done some searching but can't seem to find an answer. I can create a work around by creating a separate .bat file but that seems like going backwards.
Thoughts?
You should also give a shot to the start-process cmdlet :
$Path_To_EXE = "c:\program.exe"
#Notice the simple quotes ...
$Arguments = #( "name.accdb", '/cmd' , "Rester" )
start-process -FilePath $Path_To_EXE -ArgumentList $Arguments -Wait
I'm not quite sure of the format of the answer you'll get tough ...
for database interaction, I'll rather use JGreenwell's Approach, since the answer that you'll get will be much easier to read/debug ...
Let me know if it works.
If you want to run a VBA script while passing it a parameter with powershell:
$aApp = New-Object -ComObject access.application
$aApp.Application.OpenCurrentDatabase("some program.accdb")
$aApp.Application.Run("VBAScriptName", [ref] "Raster")
First according to Microsoft Support you can use ;; for /cmd from the command line. Second because of the way call quotes and dequotes variables you have to include the /cmd flag separate from the variable (well, its the easiest way). Third, you might consider creating a new com-object to handle running Access with Powershell as it allows for a lot more options (just ask and I can add some examples of this). This being said try:
$Path_To_EXE = "program name.accdb"
&ii $Path_To_EXE ;;Rester #Try ;;"Rester" if it doesn't work.
#if that works then its a problem in Rester
#fyi another way is:
$Path_To_EXE = #("program name.accdb", ";;Rester")
&ii $Path_To_EXE
If you want to use an ActiveX Object Controller to open and perform operations on Access look at this blog from technet <- Read the link there are pitfalls to avoid.
$adOpenStatic = 3
$adLockOptimistic = 3
$objConnection = New-Object -com "ADODB.Connection"
$objRecordSet = New-Object -com "ADODB.Recordset"
$objConnection.Open("Provider = Microsoft.Jet.OLEDB.4.0; Data Source = C:\Scripts\Test.mdb")
$objRecordset.Open("Select * From Computers", $objConnection,$adOpenStatic,$adLockOptimistic)
$objRecordSet.AddNew()
$objRecordSet.Fields.Item("ComputerName").Value = "atl-ws-001"
$objRecordSet.Fields.Item("SerialNumber").Value = "192ATG43R"
$objRecordSet.Update()
$objRecordSet.Close()
$objConnection.Close()

Output from external exe and my custom objects in powershell

(Sorry for strange title, haven't come up with anything better..)
Background
I use nunit-console to test my assemblies. It is called like this (simplified):
function Test-ByNunit {
param($assembly, $tempFile = 'c:\temp\nunit.xml')
& <path-to-nunit-console> $assembly /nologo /xml:$tempFile #othparam
}
Test-ByNunit c:\temp\myAssembly.dll
I have no problem with this, it works fine.
Problem
nunit-console should output its messages as so far. That means - if not captured, it should send them to screen, otherwise it could be stored in file (Test-ByNunit $dll | set-content path)
I'd like to return somehow information about each test-case that was run (the info is stored in the /xml file) in form of array of PSObject objects.
Question
Do you have any tip how to return the info and still leave nunit output its messages?
If I simply write it to output, the function will return array of strings (output from nunit-console) and array of my objects. Then redirection to output file will store my objects as well, but I'd like just display them in console window.
The only possibility that could work is to use [ref], but I'd like to avoid it.
(this is not only about nunit-console, but of course it is general question)
If I got the task right then Out-Host should help:
function Get-WithOutHost {
# external output is redirected to the host
cmd /c dir | Out-Host
# normal output to be reused later
Get-Process
}
# call
$result = Get-WithOutHost
# now $result holds the data to use, external output is on the screen
EDIT: of course this is not enough if external output should be reused, too, not just shown