Executing a command within powershell with newlines in arguments - powershell

Below is a powershell script I am trying to get working. Its designed to run with GitHub for Windows git shell and I have am trying to call "Hub" (https://github.com/github/hub), but thats not relevant. I can call it fine except in one of the command line parameters I want to have some newlines. Is there some way to do this? The new lines are being interpreted as the end of the command.
The newlines are the $msg variable. So the (abbreviated) output is something like:
hub pull-request -m "PFR-1
https://linktoitem/PFR-1
"
But the command line just sees: hub pull-request -m "PFR-1
$branch = $Global:GitStatus.branch
$segments = $branch.split("/")
iex "git push -u origin $branch"
if ($segments.Length -eq 2 -and $segments[1] -match "^\w+\-\d+$")
{
$jiraItem = $segments[1].ToUpper()
$msg = "$jiraItem`r`n``r`nnhttps://someurl.atlassian.net/browse/$jiraItem"
echo "hub pull-request -m ""$msg"" -b kiandra-projects:develop -h kiandra-projects:$branch"
iex "& hub pull-request -m ""$msg"" -b kiandra-projects:develop -h kiandra-projects:$branch"
}
else
{
iex "hub pull-request -b kiandra-projects:develop -h kiandra-projects:$branch"
}

Have you tried a here-string?
$MultilineString = #"
Stuff
More stuff
"#
# in your case:
$msg = #"
$jiraItem
https://someurl.atlassian.net/browse/$jiraItem
"#
Note: The closing "# must be on its own line
Also, check http://chocolatey.org/packages/Posh-GitHub, it may do what you need

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.

Converting a shell script to PowerShell

After switching an application from Linux to Windows, I need to convert a shell script to a Windows equivalent. My choices were basically batch and PowerShell and I decided to give a shot to PowerShell.
For anyone interested, it's a local check for Check_MK to get information about SoftEther installed version and the number of sessions with performance data.
The initial shell script was as follow:
#!/bin/sh
cmd=$(/usr/local/vpnserver/vpncmd localhost:port /server /password:password /in:/usr/lib/check_mk_agent/local/vpncmd.txt)
version=$(echo "$cmd" | head -4 | tail -1)
sessions=$(echo "$cmd" | grep Sessions | awk '$1=$1' | cut -c21-22)
if [ -z "$version" ]; then
echo "3 VPN_Version - Can't get the information from vpncmd"
else
echo "0 VPN_Version - SoftEther VPN Server $version"
fi
if [ -z "$sessions" ]; then
echo "3 VPN_Sessions - Can't get the information from vpncmd"
else
echo "P VPN_Sessions sessions=$sessions;2;2"
fi
I basically got everything working except the 2 hardest lines of code:
cd "C:\Program Files\SoftEther VPN Server"
$cmd = vpncmd localhost:port /server /password:password /in:vpncmd.txt
$version=
$sessions=
if($version -eq $null) {
echo "3 VPN_Version - Can't get the information from vpncmd"
} else {
echo "0 VPN_Version - SoftEther VPN Server $version"
}
if($sessions -eq $null) {
echo "3 VPN_Sessions - Can't get the information from vpncmd"
} else {
echo "P VPN_Sessions sessions=$sessions;2;2"
}
I need help with going from the head, tail, grep, awk and cut one liners to whatever is equivalent in PowerShell. I read about Get-Content but I'm not sure if it's the most efficient way to do this and would like to prevent going from 1 line definition to 10 lines if that's possible to be as efficient in PowerShell.
Sample output of vpncmd's output: https://pastebin.com/J5FcHzHK
with the data being an array of lines & the word Version appearing multiple times in the actual source, the code needs to change a tad. in this version, it uses the way that -match works on an array to give the whole line as a result. that requires working on the output line to parse the desired data.
$Version = ($Vpncmd_Output -match '^Version \d{1,}\.\d{1,}' -split 'Version ' )[-1].Trim()
$SessionCount = [int]($Vpncmd_Output -match 'Number of Sessions\s+\|').Split('|')[-1].Trim()
$Version
$SessionCount
output ...
4.29 Build 9680 (English)
0
using the data in your PasteBin post, and presuming that is a multiline string, not an array of strings, this seems to work [grin] ...
$Vpncmd_Output -match '(?m)Number of Sessions\s+\|(?<Sessions>.*)'
$Matches.Sessions
# output = 0
$Vpncmd_Output -match '(?m)Version (?<Version>.+)'
$Matches.Version
# output = 4.29 Build 9680 (English)
i tried to combine the regex into one, but failed. [blush] the way i have it requires two passes, but it does work.

Why is the upstream command called SO many times?

Currently, I'm writing a PowerShell module which automatically configures aliases for all git commands, inspired by git-sh.
Then I wrote functions below.
The Enable-GitAliases function is the entry point to configure aliases automatically.
it collects git's subcommands by Get-GitCommands, which parses git --help -a to get all git's subcommands.
Then it defines the wrapper functions for the collected git commands.
My question is: why is git --help -a called so many times (possibly infinitely) when invoking Enable-GitAliases, which causing significant slow down?
After writing the code, I found Enable-GitAliases takes too much time (I've never seen it finishes).
According to the Task Manager, the git --help -a command is launched and exits repeatedly.
I expected the git --help -a command is called only once.
Actually, Get-GitCommands | % { echo $_ } calls git --help -a only once.
What is the difference, and what is best way to fix?
function Get-GitCommands {
-Split (git --help -a | select-string -pattern '^ [-a-zA-Z0-9.]+\s*')
}
function Enable-GitAliases($avoidConflicts = $true) {
Get-GitCommands | % {
$aliasName = $_
if (-not ($avoidConflicts -and (Get-Command $aliasName 2> $null) -ne $null)) {
Enable-GitAliases $aliasName
}
}
}
function Enable-GitAlias($commandName) {
$wrapper = #'
function global:{0} {{
git {0} $args
}}
'# -f $commandName
Invoke-Expression $wrapper
}
You call Enable-GitAliases recursively, but is this intended?
Is your intention this?
function Enable-GitAliases($avoidConflicts = $true) {
Get-GitCommands | % {
$aliasName = $_
if (-not ($avoidConflicts -and (Get-Command $aliasName 2> $null) -ne $null)) {
# Enable-GitAliases -> Enable-GitAlias
Enable-GitAlias $aliasName
}
}
}

Word separator in powershell

I need to open a folder on a FTP server. The folder name [DAILY Files Folder] has SPACE between the words. If I try it in CMD I can use double quotes and it works. but in power shell I need the double quotes to run the actual command! does anyone know how can I add the space in my folder name without using double quotes?
$cmd = #(
"cd /DAILY Files Folder",
"put $file",
"bye"
)
[String]($cmd | & $program -pw $pass "$user#$hst" 2>&1)
Escape nested double quotes with backticks (`):
$cmd = #(
"cd `"/DAILY Files Folder`"",
"put $file",
"bye"
)
[String]($cmd | & $program -pw $pass "$user#$hst" 2>&1)
or use single quotes as the outer quotes if you don't use variables inside the string:
$cmd = #(
'cd "/DAILY Files Folder"',
"put $file",
'bye'
)
[String]($cmd | & $program -pw $pass "$user#$hst" 2>&1)
you can use the back tick character (`) to escape each space:
DAILY` Files` Folder

Powershell variable for cmd argument

I want to use a powershell variable for a cmd argument but I don't know how to make it.
function iptc($file)
{
$newcredit = correspondance($credit)
$cmd = '& C:\exiftool\exiftool.exe -S -t -overwrite_original -Credit=$newcredit $file.FullName'
Invoke-Expression $cmd
}
For example newcredit can be "James" but in my case when I run the command -Credit will only be "$newcredit".
Regards
Single quotes (' ') do not expand variable values in the string. You can address this by either using double quotes (" "):
$cmd = "& C:\exiftool\exiftool.exe -S -t -overwrite_original -Credit=$newcredit $file.FullName"
Or, by the method I most often use, by using string formatting:
$cmd = '& C:\exiftool\exiftool.exe -S -t -overwrite_original -Credit={0} {1}' -f $newcredit, $file.FullName
If either of the parameters has a space in it then the parameter will need to be surrounded by double quotes in the output. In that case I would definitely use string formatting:
$cmd = '& C:\exiftool\exiftool.exe -S -t -overwrite_original -Credit="{0}" "{1}"' -f $newcredit, $file.FullName