Run a shell command with arguments from powershell script - powershell

I need to extract and save a some tables from a remote SQL database using bcp. I would like to write a powershell script to invoke bcp for each table and save the data. So far I have this script that creates the necessary args for bcp. However I can not figure out how to pass the args to bcp. Every time I run the script it just shows the bcp help instead. This must be something really easy that I am not getting.
#commands bcp database.dbo.tablename out c:\temp\users.txt -N -t, -U uname -P pwd -S <servername>
$bcp_path = "C:\Program Files\Microsoft SQL Server\90\Tools\Binn\bcp.exe"
$serverinfo =#{}
$serverinfo.add('table','database.dbo.tablename')
$serverinfo.add('uid','uname')
$serverinfo.add('pwd','pwd')
$serverinfo.add('server','servername')
$out_path= "c:\Temp\db\"
$args = "$($serverinfo['table']) out $($out_path)test.dat -N -t, -U $($serverinfo['uid']) -P $($serverinfo['pwd']) -S $($serverinfo['server'])"
#this is the part I can't figure out
& $bcp_path $args

First of all, $args is an automatic variable; you can't set it, so any line like $args = foo will do nothing (even with strict mode on; though a complaint would have been nice).
Then you are only passing a single argument (the string) to the program. I contains spaces, but they are properly escaped or enclosed in parentheses, so the program only sees a single argument.
You'll need to use an array for arguments to a program, instead of a single string, if you want to store it in a variable beforehand. And you need to name it something different than $args:
$arguments = "$($serverinfo['table'])",
'out',"$($out_path)test.dat",
'-N','-t,',
'-U',"$($serverinfo['uid'])",
'-P',"$($serverinfo['pwd'])",
'-S',"$($serverinfo['server'])"
& $bcp_path $arguments
Or, what I would prefer, actually, you can simply write it out in a single line which gets rid of most of the ugliness here:
$out_path = 'c:\Temp\db'
& $bcp_path $serverinfo['table'] out $out_path\test.dat -N '-t,' -U $serverinfo['uid'] -P $serverinfo['pwd'] -S $serverinfo['server']

Some command-line apps that need to accept crazy Gangnam-style arguments with slashes, quotes, double-quotes, equals, colons, dashes, a veritable cocktail.
PowerShell, in my experience, sometimes just can't cope. So I write out to a .cmd file and execute that from cmd.exe, like so:
echo $("Running command: " + $commandLine);
$rnd = $(([string](Get-Random -Minimum 10000 -Maximum 99999999)) + ".cmd");
$commandFilePath = $(Join-Path -Path $env:TEMP -ChildPath $rnd);
echo $commandLine | Out-File -FilePath $commandFilePath -Encoding ascii;
& cmd.exe /c $commandFilePath
Make sure you output as ASCII since the default Unicode might not play nice with cmd.exe (it barked at me and showed odd characters on my first attempt).

Related

In Windows power shell, how do you extract a properties file value and save it to an env var?

I have a properties file with entries like the below ...
...
USERNAME=myuser
...
In my Makefile, I have the below which uses Unix like commands to get the value of the variables ...
export USERNAME=$(shell grep USERNAME my_properties.txt | cut -d'=' -f 2-)
However, in a Windows power shell (maybe command prompt is the right phrase?), the above doesn't work because "grep" is not a standard command (among others). What's the equivalent way to extract a property from a properties file in a Windows power shell environment?
We could achieve this in PowerShell by following the below steps
Read the contents of the file
Convert the contents into key-value pairs
Create environment variable with the required value
(you can combine the steps if you like, I've kept them separate for better understanding)
Here's the script
$content = Get-Content .\user.properties -raw
$hashTable = ConvertFrom-StringData -StringData $content
$Env:USERNAME = $hashTable.USERNAME
Assuming that cmd.exe is the default shell:
export USERNAME=$(shell powershell -noprofile -c "(Select-String 'USERNAME=(.+)' my_properties.txt).Matches.Group[1]")
Note: -NoProfile suppresses loading of PowerShell's profiles, which unfortunately happens by default. Should you need the -File parameter to execute a script file, you may additionally need -ExecutionPolicy Bypass, unless your effective execution policy allows script execution.
The above uses the PowerShell CLI's -c (-Command) parameter to pass a command that uses the Select-String cmdlet, PowerShell's grep analog.
A closer analog to your command would be the following, which additionally uses -split, the string-splitting operator (showing the raw PowerShell command only; place it inside the "..." above):
((Select-String USERNAME my_properties.txt) -split '=', 2)[-1]

Pass Powershell Variables to Command Prompt line

I want to make a PowerShell script that can be used to connect computers to various client's SonicWall VPNs (specifically through Global VPN and NetExtender). I would like to have it be like a user interface to prompt the user (which will set the variables) and then use that information to pass through to command lines in the command prompt.
I want to be able to have information entered in be applied in the cmd line in the script.
I have tried using the MobileConnect connection through (Using the the app from the app store) and connecting with the Microsoft VPN client, but that does not grab all the network information; specifically DNS servers.
The best way is to install either Global VPN or NetExtender and connect through cmd line; that way will grab the entire network information.
This is the basic command to run it:
Function Connect-VPN {
Set-Location -Path "C:\Program Files (x86)\SonicWALL\SSL-VPN\NetExtender"
cmd /c "NECLI connect -s address:4433 -u Uname -p Password -d Domain -A"
Set-Location -Path C:\
}
Basically, you change the directory and execute the commands with those arguments.
I would like to prompt in POSH, create the variables with the user input, then have those arguments passed down.
What I have right now is:
param(
[string]$Testadd ,
[string]$Testun ,
[string]$TestPW ,
[string]$TestDom
)
If ($Testadd -eq "")
{$Testadd = (Read-Host "test")
}
If ($Testun -eq "")
{$Testun = (Read-Host "test")
}
If ($TestPW -eq "")
{$TestPW = (Read-Host "test")
}
If ($TestDom -eq "")
{$TestDom = (Read-Host "test")
}
Set-Location -Path "C:\Program Files (x86)\SonicWALL\SSL-VPN\NetExtender"
cmd /c "NECLI connect -s "$($Testadd)" -u "$($Testun)" -p "$($TestPW)" -d "$($TestDom)" -A"
Set-Location -Path C:\
The problem is that the all the arguments come out null. I do not know if it is possible, but I wanted to see.
You can try to build the string before running the cmd
param (
[string]$Testadd,
[string]$Testun,
[string]$TestPW,
[string]$TestDom
)
If ($Testadd -eq "")
{
$Testadd = (Read-Host "testadd")
}
If ($Testun -eq "")
{
$Testun = (Read-Host "testun")
}
If ($TestPW -eq "")
{
$TestPW = (Read-Host "testpw")
}
If ($TestDom -eq "")
{
$TestDom = (Read-Host "testdom")
}
Set-Location -Path "C:\Program Files (x86)\SonicWALL\SSL-VPN\NetExtender"
#build the string before
$cmd = "NECLI connect -s " + $($Testadd) + " -u " + $($Testun) + " -p " + $($TestPW) + " -d " + $($TestDom) + " -A"
# Or even like this
$cmd = "NECLI connect -s $Testadd -u $Testun -p $TestPW -d $TestDom -A"
# exec command
cmd /c $cmd
Set-Location -Path C:\
To add to #Desinternauta, I suspect it is how the command is interpreting the quotes and the variables. i.e. when you write out the string as you have it, it adds spaces:
$b = "b"
Write-Host "a"$($b)"c"
Outputs:
a b c
The good news is that double quoted strings allow you to embed the variables into the string:
cmd /c "NECLI connect -s $Testadd -u $Testun -p $TestPW -d $TestDom -A"
Calling external exe / commands that use cmd.exe, require special consideration and outing specifics. You also do not need to call cmd.exe directly, as that will just happen. This is a well documented this. For example:
PowerShell: Running Executables
The Call Operator &
Why: Used to treat a string as a SINGLE command. Useful for dealing
with spaces.
In PowerShell V2.0, if you are running 7z.exe (7-Zip.exe) or another
command that starts with a number, you have to use the command
invocation operator &.
The PowerShell V3.0 parser do it now smarter, in this case you don’t
need the & anymore .
Details: Runs a command, script, or script block. The call operator,
also known as the "invocation operator," lets you run commands that
are stored in variables and represented by strings. Because the call
operator does not parse the command,it cannot interpret command
parameters
# Example:
& 'C:\Program Files\Windows Media Player\wmplayer.exe' "c:\videos\my home video.avi" /fullscreen
Start-Process (start/saps)
Why: Starts a process and returns the .Net process object Jump if
-PassThru is provided. It also allows you to control the environment in which the process is started (user profile, output redirection
etc). You can also use the Verb parameter (right click on a file, that
list of actions) so thatyou can, for example, play a wav file.
Details: Executes a program returning the process object of the
application. Allows you to control the action on a file (verb
mentioned above) and control the environment in which the app is run.
You also have the ability to wait on the processto end. You can also
subscribe to the processes Exited event.
#Example:
#starts a process, waits for it to finish and then checks the exit code.
$p = Start-Process ping -ArgumentList "invalidhost" -wait -NoNewWindow -PassThru
$p.HasExited
$p.ExitCode
10. Stop-Parsing Symbol --%
Why: Its a quick way to handle program arguments that are not standard. Also its the new cool way to do it.
Details: The stop-parsing symbol (--%), introduced in Windows PowerShell 3.0, directs Windows PowerShell to refrain from interpreting input as Windows PowerShell commands or expressions. When calling an executable program in Windows PowerShell, placethe stop-parsing symbol before the program arguments.
After the stop-parsing symbol --% , the arguments up to the end of the line (or pipe, if you are piping) are passed as is.
#Examples:
# icacls in V2
# You must use escape characters to prevent PowerShell from misinterpreting the parentheses.
icacls X:\VMS /grant Dom\HVAdmin:`(CI`)`(OI`)F
# In V3 you can use the stop-parsing symbol.
icacls X:\VMS --% /grant Dom\HVAdmin:(CI)(OI)F
See also:
Using Windows PowerShell to run old command line tools (and their weirdest parameters)
Solve Problems with External Command Lines in PowerShell
Quoting
About Quoting Rules
A Story of PowerShell Quoting Rules

powershell.exe -ex bypass -command

I have a working powershell script, i Need to convert the entire script to run in single line of code with powershell.exe -ex bypass -command ....
The reason in the background is script should not run as .ps1 file instead i can run as single command. Am looking help in this... am quite new to powershell..but tried to manage the script below.. i need to convert it to run from command as single line of code..
# Config
$logFileName = "Application" # Add Name of the Logfile (System, Application, etc)
$path = "c:\Intel\" # Add Path, needs to end with a backsplash
# do not edit
$exportFileName = $logFileName + (get-date -f yyyyMMdd) + ".evt"
$logFile = Get-WmiObject Win32_NTEventlogFile | Where-Object {$_.logfilename -eq $logFileName}
$logFile.backupeventlog($path + $exportFileName)
You don't need -ex (short for -ExecutionPolicy) if you're not using a file, because it only applies to files.
To make it one line, you basically replace newlines with ;.
But -Command isn't the best idea for this. You're going to have to be careful about properly escaping all the quotes and pipes throughout your code.
You can look into -EncodedCommand, whereby you Base64 encode your code, and pass it all as one string.
If you check powershell.exe /? it has an example at the bottom:
# To use the -EncodedCommand parameter:
$command = 'dir "c:\program files" '
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
powershell.exe -encodedCommand $encodedCommand
If you could explain more about your reasons for not wanting to use a file, it may be helpful in getting answers that are more appropriate for your situation.

sqlcmd. How to pass in a variable that includes a colon and a space?

I've been struggling with this for a while now. I'm trying to invoke a sql script and pass two variables into it using sqlcmd. I'm doing all this in PowerShell.
Here's what I've got:
$time = '12:00 AM'
$date = '06/20/2014'
$result = sqlcmd -U username -P password -i "c:\path\to\script.sql" -v date=$date -v time=$time
This fails with the following error message:
sqlcmd : Sqlcmd: 'time=12:00 AM': Invalid argument. Enter -? for help.
After some experimentation, I've discovered that the problem is the colon and the space in $time. If I remove the colon and the space $time = '1200AM', the command executes without any error.
Unfortunately, the script that I'm executing wants the exact format "12:00 AM".
Things that I've tried that didn't work:
$time="12\:00\ AM"
$time="12\\:00\\ AM"
$time="12"+":00"+" AM"
$time="12"+":00"
$time="12"+":"+"00"
These all respond with similar Invalid argument failures. The last few attempts were the solution from this similar post. They don't work.
I have also tried placing the string values directly in the sqlcmd invocation, like so:
$result = sqlcmd -U username -P password -i "c:\path\to\script.sql" -v date=$date -v time="12\:00\ AM".
No dice, and anyways, I need to read the time in from somewhere else, so I need the $time variable.
I (finally) found a solution that worked for sqlcmd from a Powershell script. (Using invoke-sqlcmd was not an option for me)
I needed to pass an absolute path containing a colon in a variable (e.g., C:\rootdir\subdir). This worked from a regular command prompt, but I couldn't get it to work from a Powershell script. I came up with an ugly kludge, passing the parts before and after the colon in two variables, then reassembling it in the SQL script.
But then it failed when the path contained a space (e.g., C:\root dir\subdir).
So I finally found a solution that fixed both colons and spaces. It involved enclosing the path text in double quotes, then enclosing the double-quoted path text in an outer set of single quotes. After building the full sqlcmd in a variable, it looked something like this:
SQLCMD <other args> -v RootPath='"C:\root dir\subdir"'
(That's an outer set of single quotes (') and an inner set of double quotes (")).
This also worked if the path didn't have a colon, e.g., \\nodename\root dir\subdir. This had been a problem when I tried to split the path around an assumed colon. I'm still not sure why both outer single quotes and inner double quotes are necessary, but that was the only version that worked for me.
ADDENDUM: This only worked for Powershell 5, and broke when my script was run from Powershell 4. To make it work on both, I found I needed to enclose internal spaces in single quotes, e.g.,
SQLCMD <other args> -v RootPath='"C:\root' 'dir\subdir"'
I slightly modified your PowerShell script by adding spaces before and after each variable assignment operator like this:
$time = '12:00 AM'
$date = '06/20/2014'
$result = sqlcmd -U username -P password -i "c:\path\to\script.sql" -v date = $date -v time = $time
It works for me (tested on PowerShell 2.0).
Alright, I figured a solution out. Hopefully it will be useful to other people somewhere down the road.
I switched from sqlcmd to Powershell's Invoke-Sqlcmd. This STILL gave me problems, so I had to fiddle around with it a little. Here's my end result.
# import Invoke-Sqlcmd
Add-PSSnapin SqlServerCmdletSnapin100
Add-PSSnapin SqlServerProviderSnapin100
$time = "12:01 AM"
$date = "07/22/2014"
$datetime = "time='$time'", "date='$date'" # save to $datetime as an array
$result = Invoke-Sqlcmd -Username username -Password password -InputFile "c:\path\to\sql\script.sql" -Variable $datetime
Note that the following DOES NOT WORK:
$datetime = "time='"+$time+"'", "date='"+$date+"'"
This was the first thing I tried, and it resulted in an invalid argument exception.
First, I would try -v "date=$date" -v "time=$time".
If that didn't work, I'd try this:
$time = '12:00 AM'
$date = '06/20/2014'
$time = 'time=' + $time
$date = 'date=' + $date
$result = sqlcmd -U username -P password -i "c:\path\to\script.sql" -v $date -v $time
When PowerShell executes a program, it parses the line, resolves any PowerShell in it, and then tries to execute the string. It ends up feeling like you need an extra layer of abstraction that you don't really see in traditional shell scripts or batch scripts.

PowerShell: Start a process with unquoted arguments

My PowerShell script should start an external executable with specified parameters. I have two strings: The file name, and the arguments. This is what process starting APIs usually want from me. PowerShell however fails at it.
I need to keep the executable and arguments in a separate strings because these are configured elsewhere in my script. This question is just about using these strings to start the process. Also, my script needs to put a common base path in front of the executable.
This is the code:
$execFile = "SomeSetup.exe"
$params = "/norestart /verysilent"
& "$basePath\$execFile" $params | Out-Host
# Pipe to the console to wait for it to finish
This is the actual result (does not work with this program):
Process file name: "C:\My\Base path\SomeSetup.exe"
Process command line: "/norestart /verysilent"
This is what I'd expect to have (this would work):
Process file name: "C:\My\Base path\SomeSetup.exe"
Process command line: /norestart /verysilent
The problem is that the setup recognises the extra quotes and interprets the two arguments as one - and doesn't understand it.
I've seen Start-Process but it seems to require each parameter in a string[] which I don't have. Splitting these arguments seems like a complicated shell task, not something I'd do (reliably).
What could I do now? Should I use something like
& cmd /c "$execFile $params"
But what if $execFile contains spaces which can well happen and usually causes much more headache before you find it.
You can put your parameters in an array:
$params = "/norestart", "/verysilent"
& $basepath\$execFile $params
When you run a legacy command from Powershell it has to convert the powershell variables into a single string that is the legacy command line.
The program name is always enclosed in quotes.
Any parameters that contain a space character are enclosed in double
quotes (this is of course the source of your problem)
Each element of an array forms a separate argument.
So given:
$params = "/norestart /verysilent"
& "$basePath\$execFile" $params
Powershell will run the command:
"\somepath\SomeSetup.exe" "/norestart /verysilent"
The solution is to store separate arguments in an array:
$params = "/norestart","/verysilent"
& "$basePath\$execFile" $params
will run:
"\somepath\SomeSetup.exe" /norestart /verysilent
Or if you already have a single string:
$params = "/norestart /verysilent"
& "$basePath\$execFile" ($params -split ' ')
will work as well.
$execFile = "SomeSetup.exe"
$params = "/norestart /verysilent"
Invoke-Expression ($basePath + "\" + $execFile + " " +$params)
Try it this way:
& $execFile /norestart /verysilent
Bill
Just use single quotes:
$execFile = "SomeSetup.exe"
$params = "/norestart /verysilent"
& "'$basePath\$execFile' $params" | Out-Host
# Pipe to the console to wait for it to finish
Also I would use join-path instead of concatenating the two strings:
$path = Join-Path $basePath $execFile
& "$path $params" | out-host