How do I capture StdOut from External programs from a PowerShell script? - powershell

I write VBScript script and often times I need to run a command line program and capture the the information that was written to std out so that output can be scraped. All of this is hidden from the person that executes the VBScript.
Here is an quick and stupid example:
cmd = "ping google.com"
Set objWSH = CreateObject( "WScript.Shell" )
Set Ret = objWSH.exec(cmd)
StdOut = Ret.StdOut.ReadAll()
myarray = Split(StdOut,vbcrlf)
For each line in myarray
If Instr(line,"Average") then avg = Right(line,Len(line) - InStrRev(line," "))
Next
wscript.echo "Google.com = " & avg
My question is not "how do I ping" as I'm showing in my example code.
I need to know how to run command-line programs from within a PowerShell script so that they are not seen but in a way that I can grab the output written to std-out.

Store the result in a variable.
$result = Invoke-Expression 'ping www.google.com'
or
$result = iex 'ping www.google.com'

To be close to your vbscript but with a RegEx:
foreach ($line in (ping.exe google.com)){
if ($line -match 'Average = (\d+)ms'){
"Google.com = $($matches[1])"
}
}
Sample output:
Google.com = 12
A more PowerShell way:
$Avg = (test-connection google.com).responsetime|measure -average|select -expandproperty Average
"Google.com = $Avg"
sample output:
Google.com = 25.25

Related

Pipe stream to other process

I would like to download a 20GB dump, replace strings and pipe it to mysql.exe while it still downloads in Powershell. But I'm having issues piping the stream.
If my file was already downloaded, I could stream while replacing strings in the file to StdOut with:
Get-Content 'dump.sql' | %{ $_.replace("production_db", "staging_db") }
Or if I also download the file while streaming and replacing strings to StdOut , I could do this:
$url = 'http://MyServer.ext/dump.sql'
& {
$myHttpWebRequest = [System.Net.WebRequest]::Create($url)
$myHttpWebRequest.Headers.Add("Authorization", "Basic " + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("MyUsername:MyPassword")))
try {
$res = $myHttpWebRequest.GetResponse()
}
catch [System.Net.WebException] {
$res = $_.Exception.Response
}
if ([int] $res.StatusCode -ne 200) {
'Error: ' + [int]$res.StatusCode + " " + $res.StatusCode
} else {
$receiveStream = $res.GetResponseStream()
$encode = [System.Text.Encoding]::GetEncoding("utf-8")
$readStream = [System.IO.StreamReader]::new($receiveStream, $encode)
while (-not $readStream.EndOfStream) {
$readStream.ReadLine().replace("production_db", "staging_db")
}
$res.Close()
$readStream.Close()
}
}
But in both cases, I fail to pipe this as a stream to mysql.exe. It seams the whole stream is first loaded into memory, before being passed on to the mysql.exe process, when I append:
| & 'C:\Program Files\MySQL\MySQL Server 5.7\bin\mysql.exe' -u MyUsername -pMyPassword -h 127.0.0.1
How do I manage to pipe a stream to another process in Powershell?
We know mysql.exe will take standard input because we can run mysql.exe < myfile.sql and it works great. I found this fantastic answer about sending lines of text to the standard input of a process, and it seems to work for me. I'm using the CMD shell to test with since I don't have mysql handy:
# Setup: example commands to feed via stream to process
'echo "hello"
echo "world"
echo "bye"
exit' > 'C:\temp\file.bat'
# create streamreader from file
$readStream = [System.IO.Streamreader]::new('C:\temp\file.bat')
$encode = [System.Text.Encoding]::GetEncoding("utf-8")
For you, just update your replacement and the exe name:
# Create a process for the job
$psi = New-Object System.Diagnostics.ProcessStartInfo;
$psi.FileName = "cmd.exe"; #process file
$psi.UseShellExecute = $false; #start the process from it's own executable file
$psi.RedirectStandardInput = $true; #enable the process to read from standard input
$psi.RedirectStandardOutput = $true #send standard output to object
$p = [System.Diagnostics.Process]::Start($psi);
# Send each line of the file to the process as standard input:
while (-not $readStream.EndOfStream) {
$p.StandardInput.WriteLine(
($readStream.ReadLine().replace("l", "L"))
)
}
$readStream.Close()
# Make sure you don't accidentally close mysql before it's done processing.
# For example, add WriteLine('exit') after the While{}, then use Wait-Process.
# get the output of the process, should also wait for the process to finish
$p.StandardOutput.ReadToEnd()
$p.Close()
In my tests it's working, replacing the commands as they're read, and sending them to the process, and shows the output:
Microsoft Windows [Version 10.0.19043.1110]
(c) Microsoft Corporation. All rights reserved.
C:\>echo "heLLo"
"heLLo"
C:\>echo "worLd"
"worLd"
C:\>echo "bye"
"bye"
C:\>exit
I think you should be able to hook into $p.StandardOutput and read it as you go, but when I tried it would cause the process to hang? Maybe just use mysql logging instead.

Powershell get return value from visual basic script

I have some old code in visual basic script. Instead of re-writing this old code into PowerShell, I'd like to call the VB scripts from PowerShell and capture their return value.
How can I get the return value of a visual basic script in powershell?
Something like this:
$returnValue = Invoke-Command -ScriptBlock{.\vbs\legacyVbsFunction.vbs}
The visual basic function may look like this
Function MyFunction() As Double
Return 3.87 * 2
End Function
It sounds like you want to capture a VBScript's (stdout) output:
$output = cscript.exe //nologo .\vbs\legacyVbsFunction.vbs
Note that $output will either be a single string - if the script outputs just 1 line - or an array of strings in case of multi-line output.
For example, assuming that .\vbs\legacyVbsFunction.vbs contains the following code:
Function MyFunction
MyFunction = 3.87 * 2
End Function
' Call the function and print it to stdout.
Wscript.Echo(MyFunction)
You could capture the output and convert it to a [double] as follows:
[double] $output = cscript.exe //nologo .\vbs\legacyVbsFunction.vbs
$output then contains 7.74.
You can actually embed a vbscript function right in powershell using a com object called ScriptControl, but it only works in 32-bit powershell, C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe: Embed VBS into PowerShell
function Call-VBScript
{
$sc = New-Object -ComObject ScriptControl
$sc.Language = 'VBScript'
$sc.AddCode('
Function MyFunction
MyFunction = 3.87 * 2
End Function
')
$sc.CodeObject
}
$vb = Call-VBScript
$returnvalue = $vb.MyFunction()
"returnvalue is " + $returnvalue
I found out you can run a job as 32-bit:
$returnvalue =
start-job {
function Call-VBScript {
$sc = New-Object -ComObject MSScriptControl.ScriptControl.1
$sc.Language = 'VBScript'
$sc.AddCode('
Function MyFunction
MyFunction = 3.87 * 2
End Function
')
$sc.CodeObject
}
$vb = call-vbscript
$vb.MyFunction()
} -runas32 | wait-job | receive-job
"returnvalue is " + $returnvalue
You don't really have an exit code in the VBS, you have a return for a function.
To actually have a return code you must close the script with something like:
wscript.quit(0)
or
wscript.quit(returncode)
In the powershell script you must execute the vbs something like this:
(Start-Process -FilePath "wscript.exe" -ArgumentList "Script.vbs" -Wait -Passthru).ExitCode

trying to run .bat file along with some conditional loops in a .ps1 file

i want to run some conditional looping code along with other task like running a .bat file in one .ps1 file. I dont have sound knowledge in poweershell but i am trying to do this.
i tried to run the file but only batch file is executing successfully but not the code that needs to perform a different tasks in the process.
i tried using this command to run .batch file
cmd.exe /k ""c:\Users\ADM_SPANCHADULA\Desktop\status_check\status.bat" & powershell"
which is executing fine. but the remaining part
cd C:\Users\ADM_SPANCHADULA\Desktop\status_check
cmd.exe /c "c:\Users\ADM_SPANCHADULA\Desktop\status_check\status.bat"
"hello world " | Out-Host
$a=0
Do {
$output1 = Compare-Object (get-content "Job_status.txt") (get-content "done.txt")
$output2 = Compare-Object (get-content "Job_status.txt") (get-content "warn.txt")
if ( $output1 = "" ) {
$a = 1
$a|Out-Host
} elseif ( $output2 = "" ) {
$a = 1
$a|Out-Host
} else {
echo "looping "
}
} while ( $a -gt 0 )
is not executing .
can someone please help me out.

PowerShell - Sorry, we couldn't find Microsoft.PowerShell.Core\FileSystem::

I'm trying to modify the script created by Boe Prox that combines multiple CSV files to one Excel workbook to run on a network share.
When I run it locally, the script executes great and combines multiple .csv files into one Excel workbook.
Clear-Host
$OutputFile = "ePortalMonthlyReport.xlsx"
$ChildDir = "C:\MonthlyReport\*.csv"
cd "C:\MonthlyReport\"
echo "Combining .csv files into Excel workbook"
. C:\PowerShell\ConvertCSVtoExcel.ps1
Get-ChildItem $ChildDir | ConvertCSVtoExcel -output $OutputFile
echo " "
But when I modify it to run from a network share with the following changes:
Clear-Host
# Variables
$OutputFile = "ePortalMonthlyReport.xlsx"
$NetworkDir = "\\sqltest2\dev_ePortal\Monthly_Report"
$ChildDir = "\\sqltest2\dev_ePortal\Monthly_Report\*.csv"
cd "\\sqltest2\dev_ePortal\Monthly_Report"
echo "Combining .csv files into Excel workbook"
. $NetworkDir\ConvertCSVtoExcel.ps1
Get-ChildItem $ChildDir | ConvertCSVtoExcel -output $OutputFile
echo " "
I am getting an error where it looks like it using the network path twice and I am not sure why:
Combining .csv files into Excel workbook
Converting \sqltest2\dev_ePortal\Monthly_Report\001_StatsByCounty.csv
naming worksheet 001_StatsByCounty
--done
opening csv Microsoft.PowerShell.Core\FileSystem::\sqltest2\dev_ePortal\Monthly_Report\\sqltest2\dev_ePortal\Monthly_Report\001_StatsByCounty.csv) in excel in temp workbook
Sorry, we couldn't find Microsoft.PowerShell.Core\FileSystem::\sqltest2\dev_ePortal\Monthly_Report\\sqltest2\dev_ePortal\Monthly_Report\001_StatsByCounty.csv. Is it possible it was moved, renamed or deleted?
Anyone have any thoughts on resolving this issue?
Thanks,
Because in the script it uses the following regex:
[regex]$regex = "^\w\:\\"
which matches a path beginning with a driveletter, e.g. c:\data\file.csv will match and data\file.csv will not. It uses this because (apparently) Excel needs a complete path, so if the file path does not match, it will add the current directory to the front of it:
#Open the CSV file in Excel, must be converted into complete path if no already done
If ($regex.ismatch($input)) {
$tempcsv = $excel.Workbooks.Open($input)
}
ElseIf ($regex.ismatch("$($input.fullname)")) {
$tempcsv = $excel.Workbooks.Open("$($input.fullname)")
}
Else {
$tempcsv = $excel.Workbooks.Open("$($pwd)\$input")
}
Your file paths will be \\server\share\data\file.csv and it doesn't see a drive letter, so it hits the last option and jams $pwd - an automatic variable of the current working directory - onto the beginning of the file path.
You might get away if you edit his script and change the regex to:
[regex]$regex = "^\w\:\\|^\\\\"
which will match a path beginning with \\ as OK to use without changing it, as well.
Or maybe edit the last option (~ line 111) to say ...Open("$($input.fullname)") as well, like the second option does.
Much of the issues are caused in almost every instance where the script calls $pwd rather than $PSScriptRoot. Replace all instances with a quick find and replace.
$pwd looks like:
PS Microsoft.PowerShell.Core\FileSystem::\\foo\bar
$PSScriptRoot looks like:
\\foo\bar
The second part i fixed for myself is what #TessellatingHeckler pointed out. I took a longer approach.
It's not the most efficient way...but to me it is clear.
[regex]$regex = "^\w\:\\"
[regex]$regex2 = "^\\\\"
$test = 0
If ($regex.ismatch($input) -and $test -eq 0 ) {
$tempcsv = $excel.Workbooks.Open($input)
$test = 1 }
If ($regex.ismatch("$($input.fullname)") -and $test -eq 0) {
$tempcsv = $excel.Workbooks.Open("$($input.fullname)")
$test = 1}
If ($regex2.ismatch($input) -and $test -eq 0) {
$tempcsv = $excel.Workbooks.Open($input)
$test = 1 }
If ($regex2.ismatch("$($input.fullname)") -and $test -eq 0) {
$tempcsv = $excel.Workbooks.Open("$($input.fullname)")
$test = 1}
If ($test -eq 0) {
$tempcsv = $excel.Workbooks.Open("$($PSScriptRoot)\$input")
$test = 0 }

Powershell get repadmin /istg in array?

Is this possible?
I'm brand new to powershell and am currently in the process of converting a vbscript script to Powershell. The following one-liner command seems to do exactly what the entire vbscript does:
Repadmin /istg
which outputs
Repadmin: running command /istg against full DC ST-DC7.somestuff.com
Gathering topology from site BR-CORP (ST-DC7.somestuff.com):
Site ISTG
================== =================
Portland ST-DC4
Venyu ST-DC5
BR-Office ST-DC3
BR-CORP ST-DC7
The problem is I need to return this info (namely the last 4 lines) as objects which contain a "Site" and "ISTG" field. I tried the following:
$returnValues = Repadmin /istg
$returnValues
But this didin't return anything (possibly because Repadmin writes out the lines instead of actually returning the data?)
Is there a way to get the Info from "Repadmin /istg" into an array?
Here's one possible way, using regular expressions:
$output = repadmin /istg
for ( $n = 10; $n -lt $output.Count; $n++ ) {
if ( $output[$n] -ne "" ) {
$output[$n] | select-string '\s*(\S*)\s*(\S*)$' | foreach-object {
$site = $_.Matches[0].Groups[1].Value
$istg = $_.Matches[0].Groups[2].Value
}
new-object PSObject -property #{
"Site" = $site
"ISTG" = $istg
} | select-object Site,ISTG
}
}
You have to start parsing the 10th item of output and ignore empty lines because repadmin.exe seems to insert superflous line breaks (or at least, PowerShell thinks so).