start-process in PowerShell 3.0 script doesn't work but it was working in 2.0 - powershell

I upgraded our PS version to 3.0 and some of our scripts stopped working. After a lot of debugging I realized there is an issue with the Start-Process command.
Basically, when I run the Start-Process directly in the PowerShell cmd it does run the program with the correct arguments. However, when I run the script, it won't give any error but the program will not run.
The script is a bit long, but this is the way I can test the snippet that's failing.
$SERVER = 'servername'
$PORT = 'xxxx'
$TPath = 'E:\epicor\PowerShell\export\POAutomation\'
$User = 'username'
$Psw = 'password'
$Import = 'PO Combined'
$file = $TPath + 'AutomaticPOHeaders.csv'
$DMTPATH = 'E:\epicor\Epicor905\Client\dmt.exe'
$Param = "-ArgumentList `'-user=`"$User`" -pass=`"$Psw`" -server=$SERVER -port=$PORT -import=`"$Import`" -source=`"$file`" -add=true -update=false -noui=true`'"
Start-Process $DMTPATH $Param -Wait
"Finished"
I even wrote to a log file to check if the $Param string is well formed and if the Start-Process command is also well written. When I copy paste the strings in the log file into the PS command line, they run successfully.
I've been stuck with this more than 4 hours now.
Thanks in advance.

i dont know what dmt is waiting for but this command runs successfully on ps V3.
are you sure about you argumentlist parameter ? and seems to be a mess with your quotes
slight changes : use echoargs.exe instead of DMT and add a switch to not open a new window :
$DMTPATH = 'echoargs.exe'
$Param = "-ArgumentList `'-user=`"$User`" -pass=`"$Psw`" -server=$SERVER -port=$PORT -import=`"$Import`" -source=`"$file`" -add=true -update=false -noui=true`'"
Start-Process -nonewwindow $DMTPATH $Param -Wait
"Finished"
results :
Arg 0 is <-ArgumentList>
Arg 1 is <'-user=username>
Arg 2 is <-pass=password>
Arg 3 is <-server=servername>
Arg 4 is <-port=xxxx>
Arg 5 is <-import=PO Combined>
Arg 6 is <-source=E:\epicor\PowerShell\export\POAutomation\AutomaticPOHeaders.csv>
Arg 7 is <-add=true>
Arg 8 is <-update=false>
Arg 9 is <-noui=true'>
Command line:
"C:\Windows\EchoArgs.exe" -ArgumentList '-user="username" -pass="password" -server=servername -port=xxxx -import="PO Combined" -source="E:\epicor\PowerShell\export\POAutomation\AutomaticPOH
aders.csv" -add=true -update=false -noui=true'
Finished
Can you try to start dmt from cmd.exe ? something like :
$p=#("/C";"dmt.exe";"-user'test'" ....)
Start-Process -NoNewWindow cmd.exe $p

I run into the same problem. I noticed if the -noui=true is removed, it seems to work.

Related

How can I elevate Powershell while keeping the current working directory AND maintain all parameters passed to the script?

function Test-IsAdministrator
{
$Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$Principal = New-Object System.Security.Principal.WindowsPrincipal($Identity)
$Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Test-IsUacEnabled
{
(Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System).EnableLua -ne 0
}
if (!(Test-IsAdministrator))
{
if (Test-IsUacEnabled)
{
[string[]]$argList = #('-NoProfile', '-NoExit', '-File', $MyInvocation.MyCommand.Path)
$argList += $MyInvocation.BoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key)", "$($_.Value)"}
$argList += $MyInvocation.UnboundArguments
Start-Process PowerShell.exe -Verb Runas -WorkingDirectory $pwd -ArgumentList $argList
return
}
else
{
throw "You must be an administrator to run this script."
}
}
If I run the script above, it successfully spawns another PowerShell instance with elevated privileges but the current working directory is lost and automatically set to C:\Windows\System32. Bound Parameters are also lost or incorrectly parsed.
After reading similar questions I learned that when using Start-Process with -Verb RunAs, the -WorkingDirectory argument is only honored if the target executable is a .NET executable. For some reason PowerShell 5 doesn't honor it:
The problem exists at the level of the .NET API that PowerShell uses behind the scenes (see System.Diagnostics.ProcessStartInfo), as of this writing (.NET 6.0.0-preview.4.21253.7).
Quote from this related question:
In practice - and the docs do not mention that - the -WorkingDirectory parameter is not respected if you start a process elevated (with administrative privileges, which is what -Verb RunAs - somewhat obscurely - does): the location defaults to $env:SYSTEMROOT\system32 (typically, C:\Windows\System32).
So the most common solution I've seen involves using -Command instead of -File. I.E:
Start-Process -FilePath powershell.exe -Verb Runas -ArgumentList '-Command', 'cd C:\ws; & .\script.ps1'
This looks really hack-ish but works. The only problem is I can't manage to get an implementation that can pass both bound and unbound parameters to the script being called via -Command.
I am trying my hardest to find the most robust implementation of self-elevation possible so that I can nicely wrap it into a function (and eventually into a module I'm working on) such as Request-AdminRights which can then be cleanly called immediately in new scripts that require admin privileges and/or escalation. Pasting the same auto-elevation code at the beginning of every script that needs admin rights feels really sloppy.
I'm also concerned I might be overthinking things, and to just leave elevation to the script level instead of wrapping it into a function.
Any input at all is greatly appreciated.
Note: On 15 Nov 2021 a bug was fixed in the code below in order to make it work properly with advanced scripts - see this answer for details.
The closest you can get to a robust, cross-platform self-elevating script solution that supports:
both positional (unnamed) and named arguments
while preserving type fidelity within the constraints of PowerShell's serialization (see this answer)
preserving the caller's working directory.
On Unix-like platforms only: synchronous, same-window execution with exit-code reporting (via the standard sudo utility).
is the following monstrosity (I certainly wish this were easier):
Note:
For (relative) brevity, I've omitted your Test-IsUacEnabled test, and simplified the test for whether the current session is already elevated to [bool] (net.exe session 2>$null)
You can drop everything between # --- BEGIN: Helper function for self-elevation. and # --- END: Helper function for self-elevation. into any script to make it self-elevating.
If you find yourself in repeated need of self-elevation, in different scripts, you can copy the code into your $PROFILE file or - better suited to wider distribution - convert the dynamic (in-memory) module used below (via New-Module) into a regular persisted module that your scripts can (auto-)load. With the Ensure-Elevated function available available via an auto-loading module, all you need in a given script is to call Ensure-Elevated, without arguments (or with -Verbose for verbose output).
# Sample script parameter declarations.
# Note: Since there is no [CmdletBinding()] attribute and no [Parameter()] attributes,
# the script also accepts *unbound* arguments.
param(
[object] $First,
[int] $Second,
[array] $Third
)
# --- BEGIN: Helper function for self-elevation.
# Define a dynamic (in-memory) module that exports a single function, Ensure-Elevated.
# Note:
# * In real life you would put this function in a regular, persisted module.
# * Technically, 'Ensure' is not an approved verb, but it seems like the best fit.
$null = New-Module -Name "SelfElevation_$PID" -ScriptBlock {
function Ensure-Elevated {
[CmdletBinding()]
param()
$isWin = $env:OS -eq 'Windows_NT'
# Simply return, if already elevated.
if (($isWin -and (net.exe session 2>$null)) -or (-not $isWin -and 0 -eq (id -u))) {
Write-Verbose "(Now) running as $(("superuser", "admin")[$isWin])."
return
}
# Get the relevant variable values from the calling script's scope.
$scriptPath = $PSCmdlet.GetVariableValue('PSCommandPath')
$scriptBoundParameters = $PSCmdlet.GetVariableValue('PSBoundParameters')
$scriptArgs = $PSCmdlet.GetVariableValue('args')
Write-Verbose ("This script, `"$scriptPath`", requires " + ("superuser privileges, ", "admin privileges, ")[$isWin] + ("re-invoking with sudo...", "re-invoking in a new window with elevation...")[$isWin])
# Note:
# * On Windows, the script invariably runs in a *new window*, and by design we let it run asynchronously, in a stay-open session.
# * On Unix, sudo runs in the *same window, synchronously*, and we return to the calling shell when the script exits.
# * -inputFormat xml -outputFormat xml are NOT used:
# * The use of -encodedArguments *implies* CLIXML serialization of the arguments; -inputFormat xml presumably only relates to *stdin* input.
# * On Unix, the CLIXML output created by -ouputFormat xml is not recognized by the calling PowerShell instance and passed through as text.
# * On Windows, the elevated session's working dir. is set to the same as the caller's (happens by default on Unix, and also in PS Core on Windows - but not in *WinPS*)
# Determine the full path of the PowerShell executable running this session.
# Note: The (obsolescent) ISE doesn't support the same CLI parameters as powershell.exe, so we use the latter.
$psExe = (Get-Process -Id $PID).Path -replace '_ise(?=\.exe$)'
if (0 -ne ($scriptBoundParameters.Count + $scriptArgs.Count)) {
# ARGUMENTS WERE PASSED, so the CLI must be called with -encodedCommand and -encodedArguments, for robustness.
# !! To work around a bug in the deserialization of [switch] instances, replace them with Boolean values.
foreach ($key in #($scriptBoundParameters.Keys)) {
if (($val = $scriptBoundParameters[$key]) -is [switch]) { $null = $scriptBoundParameters.Remove($key); $null = $scriptBoundParameters.Add($key, $val.IsPresent) }
}
# Note: If the enclosing script is non-advanced, *both*
# $scriptBoundParameters and $scriptArgs may be present.
# !! Be sure to pass #() when $args is $null (advanced script), otherwise a scalar $null will be passed on reinvocation.
# Use the same serialization depth as the remoting infrastructure (1).
$serializedArgs = [System.Management.Automation.PSSerializer]::Serialize(($scriptBoundParameters, (#(), $scriptArgs)[$null -ne $scriptArgs]), 1)
# The command that receives the (deserialized) arguments.
# Note: Since the new window running the elevated session must remain open, we do *not* append `exit $LASTEXITCODE`, unlike on Unix.
$cmd = 'param($bound, $positional) Set-Location "{0}"; & "{1}" #bound #positional' -f (Get-Location -PSProvider FileSystem).ProviderPath, $scriptPath
if ($isWin) {
Start-Process -Verb RunAs $psExe ('-noexit -encodedCommand {0} -encodedArguments {1}' -f [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd)), [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($serializedArgs)))
}
else {
sudo $psExe -encodedCommand ([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd))) -encodedArguments ([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($serializedArgs)))
}
}
else {
# NO ARGUMENTS were passed - simple reinvocation of the script with -c (-Command) is sufficient.
# Note: While -f (-File) would normally be sufficient, it leaves $args undefined, which could cause the calling script to break.
# Also, on WinPS we must set the working dir.
if ($isWin) {
Start-Process -Verb RunAs $psExe ('-noexit -c Set-Location "{0}"; & "{1}"' -f (Get-Location -PSProvider FileSystem).ProviderPath, $scriptPath)
}
else {
# Note: On Unix, the working directory is always automatically inherited.
sudo $psExe -c "& `"$scriptPath`"; exit $LASTEXITCODE"
}
}
# EXIT after reinvocation, passing the exit code through, if possible:
# On Windows, since Start-Process was invoked asynchronously, all we can report is whether *it* failed on invocation.
exit ($LASTEXITCODE, (1, 0)[$?])[$isWin]
}
}
# --- END: Helper function for self-elevation.
"Current location: $($PWD.ProviderPath)"
# Call the self-elevation helper function:
# * If this session is already elevated, the call is a no-op and execution continues,
# in the current console window.
# * Otherwise, the function exits the script and re-invokes it with elevation,
# passing all arguments through and preserving the working directory.
# * On Windows:
# * UAC will prompt for confirmation / adming credentials every time.
# * Of technical necessity, the elevated session runs in a *new* console window,
# asynchronously, and the window running the elevated session remains open.
# Note: The new window is a regular *console window*, irrespective of the
# environment you're calling from (including Windows Terminal, VSCode,
# or the (obsolescent) ISE).
# * Due to running asynchronously in a new window, the calling session won't know
# the elevated script call's exit code.
# * On Unix:
# * The `sudo` utility used for elevation will prompt for a password,
# and by default remember it for 5 minutes for repeat invocations.
# * The elevated script runs in the *current* window, *synchronously*,
# and $LASTEXITCODE reflects the elevated script's exit code.
# That is, the elevated script runs and returns control to the non-elevated caller.
# Note that $LASTEXITCODE is only meaningful if the elevated script
# sets its intentionally, via `exit $n`.
# Omit -Verbose to suppress verbose output.
Ensure-Elevated -Verbose
# For illustration:
# Print the arguments received in diagnostic form.
Write-Verbose -Verbose '== Arguments received:'
[PSCustomObject] #{
PSBoundParameters = $PSBoundParameters.GetEnumerator() | Select-Object Key, Value, #{ n='Type'; e={ $_.Value.GetType().Name } } | Out-String
# Only applies to non-advanced scripts
Args = $args | ForEach-Object { [pscustomobject] #{ Value = $_; Type = $_.GetType().Name } } | Out-String
CurrentLocation = $PWD.ProviderPath
} | Format-List
Sample call:
If you save the above code to file script.ps1 and invoke it as follows:
./script.ps1 -First (get-date) -Third ('foo', 'bar') -Second 42 #{ unbound=1 } 'last unbound'
you'll see the following:
In the non-elevated session, which triggers the UAC / sudo password prompt (Windows example):
Current location: C:\Users\jdoe\sample
VERBOSE: This script, "C:\Users\jdoe\sample\script.ps1", requires admin privileges, re-invoking in a new window with elevation...
In the elevated session (which on Unix runs transiently in the same window):
VERBOSE: (Now) running as admin.
VERBOSE: == Arguments received:
PSBoundParameters :
Key Value Type
--- ----- ----
First 10/30/2021 12:30:08 PM DateTime
Third {foo, bar} Object[]
Second 42 Int32
Args :
Value Type
----- ----
{unbound} Hashtable
last unbound String
CurrentLocation : C:\Users\jdoe\sample
I figured out a really short solution:
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
$CommandLine = "-NoExit -c cd '$pwd'; & `"" + $MyInvocation.MyCommand.Path + "`""
Start-Process powershell -Verb runas -ArgumentList $CommandLine
Exit
}
}
#Elevated script content after that

How to correctly escape spaces and backslashes in command line arguments?

I had some issues passing an array of strings to a command in PowerShell, so I'm debugging my script. I'm using the EchoArgs.exe program found in the PowerShell Community Extension Project (PSCX).
If I execute this script:
Import-Module Pscx
cls
$thisOne = 'this_one\';
$secondOne = 'second one\';
$lastOne = 'last_one'
$args = $thisOne `
, "the $secondOne" `
, "the_$lastOne"
EchoArgs $args
I get this result:
Arg 0 is <this_one\>
Arg 1 is <the second one" the_last_one>
Command line:
"C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" this_one\ "the second one\" the_last_one
It seems that if a string contains spaces, the last backslash escapes the double quote. In fact all seems working if I escape only that backslash:
Import-Module Pscx
cls
$thisOne = 'this_one\';
$secondOne = 'second one\\';
$lastOne = 'last_one'
$args = $thisOne `
, "the $secondOne" `
, "the_$lastOne"
EchoArgs $args
with this result:
Arg 0 is <this_one\>
Arg 1 is <the second one\>
Arg 2 is <the_last_one>
Command line:
"C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" this_one\ "the second one\\" the_last_one
Is there a "smart" way in PowerShell (i.e. a cmdlet) to escape any string in order to avoid such issues?
Try using Start-Process instead. It has an $Arguments parameter that would suit this better.
See here: PowerShell - Start-Process and Cmdline Switches

remote Multiple invocations using Powershell

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 }
}

Invoke-Command Powershell parameter issue

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
}

Executing an exe with arguments using Powershell

This is what I want to execute:
c:\Program Files (x86)\SEQUEL ViewPoint\viewpoint.exe /Setvar((POSTSTR $POSTSTR)(POSTEND $POSTEND)) /G:C:\viewpointfile.vpt /D:C:($BEGDATE to $TODDATE).xls
This is what I have tried:
$a = "/Setvar((POSTSTR $POSTSTR)(POSTEND $POSTEND))"
$b = "/G:C:\viewpointfile.vpt"
$c = "/D:C:($BEGDATE to $TODDATE).xls"
$Viewpoint = "c:\Program Files (x86)\SEQUEL ViewPoint\viewpoint.exe"
&$Viewpoint $a $b $c
When I execute this I receive an error stating:
File C:\viewpointfile.vpt "/D:C:($BEGDATE to $TODDATE).xls" not found!
I'm not sure where it gets the extra quotes from. If I run the command with just $a and $b it runs fine.
Any help would be greatly appreciated. Thanks! :)
Update
manojlds suggested echoargs so here it the output from it:
&./echoargs.exe $viewpoint $a $b $c
Arg 0 is C:\Program Files (x86)\SEQUEL ViewPoint\viewpoint.exe
Arg 1 is /Setvar((POSTSTR 20101123)(POSTEND 20111123))
Arg 2 is /G:C:\viewpointfile.vpt
Arg 3 is /D:C:(2010-11-23 to 2011-11-23 PM).xls
It appears that all the arguments are being passed properly. When I run this as a command in cmd.exe it executes perfectly. So something on Powershells end must be messing up the output.
Is there any other way to go about executing this command using Powershell?
I've found the method blogged by Joel Bennett to be the most reliable when calling legacy commands
http://huddledmasses.org/the-problem-with-calling-legacy-or-native-apps-from-powershell/
I've had to use this when calling LogParser from Powershell:
set-alias logparser "C:\Program Files (x86)\Log Parser 2.2\LogParser.exe"
start-process -NoNewWindow -FilePath logparser -ArgumentList #"
"SELECT * INTO diskspaceLP FROM C:\Users\Public\diskspace.csv" -i:CSV -o:SQL -server:"Win7boot\sql1" -database:hsg -driver:"SQL Server" -createTable:ON
"#
Get echoargs.exe from Powershell community extension ( http://pscx.codeplex.com/ ) to figure out the arguments that Powershell sends to your exe.
$a = "/Setvar((POSTSTR $POSTSTR)(POSTEND $POSTEND))"
$b = "/G:C:\viewpointfile.vpt"
$c = "/D:C:($BEGDATE to $TODDATE).xls"
$echoArgs = ".\echoargs.exe"
&$echoArgs $a $b $c
You seem to be passing the arguments fine however, but the viewpoint.exe seems to be acting up. I don't see what you are doing here:
$c = "/D:C:($BEGDATE to $TODDATE).xls"
After C: there is no \ and also your error message that you have pasted shows $BEGDATE and $TODDATE verbatim, which is not possible as they would have been substituted with their values.
If I can't run a command like this it usually works for me with Invoke-Expression. Can't test yours though.
Invoke-Expression "$viewpoint $a $b $c"