I'm trying to run a .bat file calling a shell .ps1 file.
I've tested my script directly in powershell and there it works.
But when I run the .bat, a error occurs saying to me something like [ The string started with: (...) does not contain the terminator " ]
My .bat file:
powershell.exe -command "& C:\Users\I\Desktop\teste.ps1"
My .ps1 file:
$scripts = 'C:\Users\I\Desktop\Teste_0.1\Teste\Teste_run.bat', 'C:\Users\I\Desktop\Teste_0.2\Teste\Teste_run.bat','C:\Users\I\Desktop\Teste_0.3\Teste\Teste_run.bat' |%{ Start-Job –scriptblock (iex "[Scriptblock] { $_ } ")}| wait-job
To call another script from powershell, I don't think you need to use -command. You should just be able to call the script directly.
powershell -nol -noe C:\Users\I\Desktop\teste.ps1
Alternately, here is another clever way to do it.
#echo off
more +4 "%~dpnx0" >> temp.ps1 && powershell -nol -noe .\temp.ps1
exit /b
$scripts = 'C:\Users\I\Desktop\Teste_0.1\Teste\Teste_run.bat', 'C:\Users\I\Desktop\Teste_0.2\Teste\Teste_run.bat','C:\Users\I\Desktop\Teste_0.3\Teste\Teste_run.bat' |%{ Start-Job –scriptblock (iex "[Scriptblock] { $_ } ")}| wait-job
Related
Inside a powershell script, I'm running a command which starts a new powershell as admin (if I'm not and if needed, depending on $arg) and then runs the script.
I'm trying to redirect stdout and stderr to the first terminal.
Not trying to make things easier, there are arguments too.
param([string]$arg="help")
if($arg -eq "start" -Or $arg -eq "stop")
{
if(![bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544"))
{
Start-Process powershell -Verb runas -ArgumentList " -file servicemssql.ps1 $arg"
exit
}
}
$Services = "MSSQLSERVER", "SQLSERVERAGENT", "MSSQLServerOLAPService", "SSASTELEMETRY", "SQLBrowser", `
"SQLTELEMETRY", "MSSQLLaunchpad", "SQLWriter", "MSSQLFDLauncher"
function startsql {
"starting SQL services"
Foreach ($s in $Services) {
"starting $s"
Start-Service -Name "$s"
}
}
function stopsql {
"stopping SQL services"
Foreach ($s in $Services) {
"stopping $s"
Stop-Service -Force -Name "$s"
}
}
function statussql {
"getting SQL services status"
Foreach ($s in $Services) {
Get-Service -Name "$s"
}
}
function help {
"usage: StartMssql [status|start|stop]"
}
Switch ($arg) {
"start" { startsql }
"stop" { stopsql }
"status" { statussql }
"help" { help }
"h" { help }
}
Using the following answers on SO doesn't work:
Capturing standard out and error with Start-Process
Powershell: Capturing standard out and error with Process object
How to deal with the double quote inside double quote while preserving the variable ($arg) expansion ?
PowerShell's Start-Process cmdlet:
does have -RedirectStandardOut and -RedirectStandardError parameters,
but syntactically they cannot be combined with -Verb Runas, the argument required to start a process elevated (with administrative privileges).
This constraint is also reflected in the underlying .NET API, where setting the .UseShellExecute property on a System.Diagnostics.ProcessStartInfo instance to true - the prerequisite for being able to use .Verb = "RunAs" in order to run elevated - means that you cannot use the .RedirectStandardOutput and .RedirectStandardError properties.
Overall, this suggests that you cannot directly capture an elevated process' output streams from a non-elevated process.
A pure PowerShell workaround is not trivial:
param([string] $arg='help')
if ($arg -in 'start', 'stop') {
if (-not (([System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators'))) {
# Invoke the script via -Command rather than -File, so that
# a redirection can be specified.
$passThruArgs = '-command', '&', 'servicemssql.ps1', $arg, '*>', "`"$PSScriptRoot\out.txt`""
Start-Process powershell -Wait -Verb RunAs -ArgumentList $passThruArgs
# Retrieve the captured output streams here:
Get-Content "$PSScriptRoot\out.txt"
exit
}
}
# ...
Instead of -File, -Command is used to invoke the script, because that allows appending a redirection to the command: *> redirects all output streams.
#soleil suggests using Tee-Object as an alternative so that the output produced by the elevated process is not only captured, but also printed to the (invariably new window's) console as it is being produced:
..., $arg, '|', 'Tee-Object', '-FilePath', "`"$PSScriptRoot\out.txt`""
Caveat: While it doesn't make a difference in this simple case, it's important to know that arguments are parsed differently between -File and -Command modes; in a nutshell, with -File, the arguments following the script name are treated as literals, whereas the arguments following -Command form a command that is evaluated according to normal PowerShell rules in the target session, which has implications for escaping, for instance; notably, values with embedded spaces must be surrounded with quotes as part of the value.
The $PSScriptRoot\ path component in output-capture file $PSScriptRoot\out.txt ensures that the file is created in the same folder as the calling script (elevated processes default to $env:SystemRoot\System32 as the working dir.)
Similarly, this means that script file servicemssql.ps1, if it is invoked without a path component, must be in one of the directories listed in $env:PATH in order for the elevated PowerShell instance to find it; otherwise, a full path is also required, such as $PSScriptRoot\servicemssql.ps1.
-Wait ensures that control doesn't return until the elevated process has exited, at which point file $PSScriptRoot\out.txt can be examined.
As for the follow-up question:
To go even further, could we have a way to have the admin shell running non visible, and read the file as we go with the Unix equivalent of tail -f from the non -privileged shell ?
It is possible to run the elevated process itself invisibly, but note that you'll still get the UAC confirmation prompt. (If you were to turn UAC off (not recommended), you could use Start-Process -NoNewWindow to run the process in the same window.)
To also monitor output as it is being produced, tail -f-style, a PowerShell-only solution is both nontrivial and not the most efficient; to wit:
param([string]$arg='help')
if ($arg -in 'start', 'stop') {
if (-not (([System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators'))) {
# Delete any old capture file.
$captureFile = "$PSScriptRoot\out.txt"
Remove-Item -ErrorAction Ignore $captureFile
# Start the elevated process *hidden and asynchronously*, passing
# a [System.Diagnostics.Process] instance representing the new process out, which can be used
# to monitor the process
$passThruArgs = '-noprofile', '-command', '&', "servicemssql.ps1", $arg, '*>', $captureFile
$ps = Start-Process powershell -WindowStyle Hidden -PassThru -Verb RunAs -ArgumentList $passThruArgs
# Wait for the capture file to appear, so we can start
# "tailing" it.
While (-not $ps.HasExited -and -not (Test-Path -LiteralPath $captureFile)) {
Start-Sleep -Milliseconds 100
}
# Start an aux. background that removes the capture file when the elevated
# process exits. This will make Get-Content -Wait below stop waiting.
$jb = Start-Job {
# Wait for the process to exit.
# Note: $using:ps cannot be used directly, because, due to
# serialization/deserialization, it is not a live object.
$ps = (Get-Process -Id $using:ps.Id)
while (-not $ps.HasExited) { Start-Sleep -Milliseconds 100 }
# Get-Content -Wait only checks once every second, so we must make
# sure that it has seen the latest content before we delete the file.
Start-Sleep -Milliseconds 1100
# Delete the file, which will make Get-Content -Wait exit (with an error).
Remove-Item -LiteralPath $using:captureFile
}
# Output the content of $captureFile and wait for new content to appear
# (-Wait), similar to tail -f.
# `-OutVariable capturedLines` collects all output in
# variable $capturedLines for later inspection.
Get-Content -ErrorAction SilentlyContinue -Wait -OutVariable capturedLines -LiteralPath $captureFile
Remove-Job -Force $jb # Remove the aux. job
Write-Verbose -Verbose "$($capturedLines.Count) line(s) captured."
exit
}
}
# ...
I have a batch file named bar.cmd with a single line: ECHO %Foo%.
How can I set Foo in a Powershell script so that when I call & .\bar.cmd, it will print Bar?
To set an environment variable in PowerShell:
Set-Item Env:foo "bar"
or
$env:foo = "bar"
If you want to do it the other way around:
When you run cmd.exe to execute a shell script (.bat or .cmd file) in PowerShell, the variable gets set in that running instance of cmd.exe but is lost when that cmd.exe instance terminates.
Workaround: Run the cmd.exe shell script and output any environment variables it sets, then set those variables in the current PowerShell session. Below is a short PowerShell function that can do this for you:
# Invokes a Cmd.exe shell script and updates the environment.
function Invoke-CmdScript {
param(
[String] $scriptName
)
$cmdLine = """$scriptName"" $args & set"
& $Env:SystemRoot\system32\cmd.exe /c $cmdLine |
Select-String '^([^=]*)=(.*)$' | ForEach-Object {
$varName = $_.Matches[0].Groups[1].Value
$varValue = $_.Matches[0].Groups[2].Value
Set-Item Env:$varName $varValue
}
}
I appreciate you taking the time to read this.
My issue is as follows: I'm trying to create a program that uses powershell to do the following:
Take a table generated outside of powershell
Loop calls to a powershell script with the parameters from the table
The powershell script calls a special type of .cmd file and then runs commands on it that are located in a different shared location.
Now my problem is with the 3rd point.
I'm currently using the following to call my script (and the arguements are just hard coded to get it working, they'll be generated by the calls from step 2 later on):
powershell.exe -ExecutionPolicy Bypass -Command {invoke-command -file \\sharedlocation\test5.ps1 -computername server1121 -argumentlist 7058,Jason}
The inside of test5.ps1 is currently:
param(
[Parameter(Mandatory=$true)]
[string] $Var1,
[Parameter(Mandatory=$true)]
[string] $Var2
)
$CommandsPath = "\\sharedlocation\testcommands.cmd"
$path = "C:\"+$Var1+"\TOOLS\"+$Var2+"launchtool.cmd"
$scriptPath = [scriptblock]::Create($path)
$out | invoke-command {PARAM($MyArg) $scriptPath } -ArgumentList $CommandsPath
I've also tried using
$CommandsPath = "\\sharedlocation\testcommands.cmd"
$path = "C:\"+$Var1+"\TOOLS\"+$Var2+"\launchtool.cmd & " + $CommandsPath
$scriptPath = [scriptblock]::Create($path)
$out | invoke-command {$scriptPath }
I've also tried to call with hardcoded testcommands instead of them being in a file.
Now my problem is in both cases, it DOES run launchtool.cmd, but it doesn't pass the testcommands.cmd file.
However when on the machine i run
C:\7058\TOOLS\Jason\launchtool.cmd & \\sharedlocation\testcommands.cmd
It works fine.
Any ideas what I'm doing wrong?
Try, invoke-expression "cmd.exe /c C:\7058\TOOLS\Jason\launchtool.cmd & \sharedlocation\testcommands.cmd"
cmd.exe /c is my best way to ensure consistency between cmd and powershell
Is the UNC Path accessible from powershell? Copy the testcommands.cmd to a local path and try if it works!
$CommandsPath = "\\sharedlocation\testcommands.cmd"
if(Test-Path $CommandsPath)
{
$path = "C:\"+$Var1+"\TOOLS\"+$Var2+"\launchtool.cmd & " + $CommandsPath
$scriptPath = [scriptblock]::Create($path)
$out | invoke-command {$scriptPath }
}
I need to pass a comma delimited parameter to a batch file via powershell and can't seem to get it to work. Here's how I call the batch file if I call it directly in powershell:
PS C:\Users\Mike> type zz.cmd
#echo off
echo/* = [%*}
echo/1 = [%1]
echo/2 = [%2]
echo/3 = [%3]
pause
PS C:\Users\Mike> cmd /c zz "q,w,e"
* = [q,w,e}
1 = [q]
2 = [w]
3 = [e]
Press any key to continue . . .
If I use cmd /c zz """q,w,e""" or cmd /c zz '"q,w,e"' I will get "q,w,e" for arg 1. This is good. However, I must call powershell using Invoke-Command. When doing this, the script doesn't work:
powershell.exe Invoke-Command -ScriptBlock { "cmd /c E:\\npoess\\oo\\WoD\\zzz" '"q,w,e"'}
Any idea how to get the powershell call from the command prompt to get "q,w,e" as one parameter to the batch file?
Thanks,
-Mike
this invoke-command without all the quotes works for passing a for me :
I think that the quotes around the variables passes the set as a string instead of separate values.
Invoke-Command -ScriptBlock {
cmd /c C:\scripts\zz.cmd q,w,e
}
I have this snippet of code
$actDate=Get-Date -Format 'yyyy-MM-dd'
Start-job -name "FMLE" -command { cmd.exe /c 'c:\Program Files (x86)\Adobe\Flash Media Live Encoder 3.2\FMLEcmd.exe' /p C:\tasks\testing_2\testing 2_$actDate.xml /ap username:password /ab username:password /l C:\Users\acruz\AppData\Local\Temp\temp.log }
I know for sure, that the var $actDate is not being replaced at the line, how shuld I do that?
My two questions are: how to replace the $actDate for its value and how to save the result of the job to one log
Thanks for your help
EDIT
This does not works either:
$actDate = (Get-Date -Format 'yyyy-MM-dd')
$Args = ("/p C:\tasks\testing_2\testing 2_$actDate.xml","/ap username:password", "/ab uysername:password", "/l C:\Users\acruz\AppData\Local\Temp\temp.log")
$Args
$j = Start-job -name "FMLE" -ScriptBlock { & 'c:\Program Files (x86)\Adobe\Flash Media Live Encoder 3.2\FMLEcmd.exe' #args } -ArgumentList $args
Get-Job $j.Id
Receive-Job -Job $j | Out-File 'C:\Users\acruz\AppData\Local\Temp\temp.log' -encoding ASCII -append -force
Although $Args has the right information...
For your first question, you need to include the path using double quotes. A suggestion if you can then remove the space in the testing 2
"C:\tasks\testing_2\testing2_$actDate.xml"
To log result of the job use Receive-Job cmdlet.
One more try:
Try to put all paths in double quotes and then surround everything with a single quote after the cmd.exe /c part as shown below. Try to achieve something simpler with a simple task and then try to add complexity
$job = Start-Job -name "Hel" -Command { cmd.exe /c '"C:\Program Files (x86)\Mozilla Firefox\firefox.exe" /?'}
I was able to make it work by doing it like this:
Start-job -Verbose -ScriptBlock {
$actDate = Get-Date -Format yyyy-MM-dd
cd "c:\Program Files (x86)\Adobe\Flash Media Live Encoder 3.2\"
.\FMLEcmd.exe /p "C:\site.com.mx\tasks\test_23445678\test 23445678_$actDate.xml" /ap user:password /ab user:password /l C:\site.com.mx\task.log
}
By doing it with -command it does not work, cause it does not replace the variable at all. Also, if I do it with -ArgumentList either was replacing the variable $actDate, so I though that may be by adding the whole script within the block it was work... and indeed, it did it...
So I don't know why it does not works, but this is a fix for me.