Run powershell script as administrator via batch file with parameter passing - powershell

When I run the script, without an administrator, via batch file it passes the parameter, but when I run the script, as an administrator, it does not pass the parameter.
I'm trying the command in the link below, but with no success:
run-script-within-batch-file-with-parameters
Command that executes the script, as an administrator, via batch file:
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File "D:\z_Batchs e Scripts\Batchs\Normaliza_LUFS\ArqsNorms_LUFS_pass.ps1' '%_vLUF%' -Verb RunAs}"
The %_vLUF% is the parameter to be passed.
Error message:
No line:1 character:4
+ & {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolic ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Start-Process], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.StartProcessCommand
Command in powershell script to receive the parameter:
Param(
[decimal]$env:_vLUF
)
What could be wrong, the command in the batch file or in the powershell script?
Test:
When the script is executed, without being an administrator, via batch file and the Parameter in the powershell script is defined as:
Parameter in powershell:
Param(
[decimal]$env:_vLUF
)
Command in the batch file running the script without being an administrator:
powershell.exe -executionpolicy remotesigned -File "D:\z_Batchs e Scripts\Batchs\Normaliza_LUFS\ArqsNorms_LUFS_pass.ps1" %_vLUF%
Note:
No need to use a named argument with the target parameter name.
Result:
Conclusion:
When the script is running, without being an administrator, via a batch file it works correctly even if the parameter used in the script is defined as an environment parameter, eg: [decimal]$env:_vLUF and regardless of the parameter value being negative, eg : -11.0.
Why Powershell when running a script without being as an administrator correctly interprets the minus sign in the argument and when run as an administrator it does not interpret the minus sign correctly is a question I leave to the experts!
However, my question was very well answered by Mr. #mklement0.

Your .ps1 script's parameter declaration is flawed:
Param(
[decimal]$env:_vLUF # !! WRONG - don't use $env:
)
See the bottom section for more information.
It should be:
Param(
[decimal] $_vLUF # OK - regular PowerShell variable
)
Parameters in PowerShell are declared as regular variables, not as environment variables ($env:).
(While environment variables can be passed as an argument (parameter value), an alternative is to simply reference them by name directly in the body of your script.)
Your PowerShell CLI call has problems too, namely with quoting.
Try the following instead:
powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -Verb RunAs powershell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File \"D:\z_Batchs e Scripts\Batchs\Normaliza_LUFS\ArqsNorms_LUFS_pass.ps1\" -_vLUF %_vLUF%'"
Specifically:
Embedded " chars. must be escaped as \" (sic) when using the Windows PowerShell CLI (powershell.exe); however, given that %_vLUF% represents a [decimal], you needn't quote it at all.
However, you appear to have hit a bug that affects PowerShell versions up to at least 7.2.4 (current as of this writing): if the argument starts with -, such as in negative number -11.0, the -File CLI parameter invariably interprets it as a parameter name - even quoting doesn't help. See GitHub issue #17519.
The workaround, as used above is to use a named argument, i.e. to precede the value with the target parameter name: -_vLUF %_vLUF%
As an aside: There's no reason to use & { ... } in order to invoke code passed to PowerShell's CLI via the -Command (-c) parameter - just use ... directly, as shown above. Older versions of the CLI documentation erroneously suggested that & { ... } is required, but this has since been corrected.
As for the broken attempt to use [decimal]$env:_vLUF as a parameter declaration:
Param(
[decimal]$env:_vLUF # !! EFFECTIVELY IGNORED
)
is effectively ignored.
However, if an environment variable _vLUF happens to be defined, it is accessible in the body of a script, independently of which parameters, if any, have been passed.
In direct invocation of your .ps1 script from your batch file, _vLUF indeed exists as an environment variable, because in cmd.exe (the interpreter of batch files), variables are invariably also environment variables - unlike in PowerShell.
That is, if %_vLUF% has a value in your batch file, a powershell child process you launch from it automatically sees it as $env:_vLUF
By contrast, if you launch an elevated process via Start-Process from such a PowerShell child process, that new, elevated process does not see the caller's environment variables - by security-minded design.
Note:
That PowerShell even syntactically accepts [decimal]$env:_vLUF as a parameter declaration should be considered a bug.
What happens is that a regular variable named env:_vLUF is indeed created and bound, if an argument passed to it, but on trying to get the value of that variable in the body of your script, it is preempted by the environment variable.
As such, an invocation can break, namely if the parameter is type-constrained and you pass a value that cannot be converted to that type ([decimal] in the case at hand).
If the invocation doesn't break, the type constraint is ignored: $env:_vLUF is invariably of type [string], as all environment variables are.

Related

Cannot access $Profile.CurrentUserAllHosts using pwsh.exe?

I want to read the automatic variable $profile.CurrentUserAllHosts, using a Deno script.
I already know how to execute pwsh.exe from Deno.
But would like to know how to invoke pwsh.exe to get the $profile.CurrentUserAllHosts value.
For example, I expect that two commands below yields the same results rather than different results:
PS> $profile.CurrentUserAllHosts
C:\Users\Flavio\Documents\PowerShell\profile.ps1
PS> pwsh.exe -noprofile -Command "{$profile.CurrentUserAllHosts}"
C:\Users\Flavio\Documents\PowerShell\Microsoft.PowerShell_profile.ps1.CurrentUserAllHosts
Note: I'm using Powershell 7.2.5, on Windows 10 and I can guarantee that pwsh.exe is the same version for all instances.
There's two things to keep in mind here:
You're running the command in PowerShell itself, so $profile is actually expanded in your parent-session due to the double-quotes in your -Command argument.
You're passing a string with a scriptblock as the -Command argument. It's not actually executed - you're just getting the result of {$profile.CurrentUserAllHosts}.ToString() which is the content of the scriptblock.
Suggestions:
Add a call operator to invoke the scriptblock in the string, ex -Command '& { $profile.CurrentUserAllHosts }'. If invoking pwsh from PowerShell you can also use pwsh.exe -noprofile -Command { $profile.CurrentUserAllHosts }.
Or provide the command as a simple string, ex. -Command '$profile.CurrentUserAllHosts'
In both alternatives, remember single quotes OR escaping the dollar-sign with backtick.

Execute a PowerShell script within RunAs in a script

I have a need to run a PowerShell script as another user (the users will actually be doing the auth) based on detected environment. The script leverages a smartcard for login. The problem I have is when the PowerShell.exe instance launches from the runas, it simply prints my command and doesn't actually run it.
Note: The filepaths have spaces that get escaped with double-`, just not shown here hence the escaped ``' in the actual command. Have also used ```".
Example:
Start-Process runas.exe "/netonly /smartcard /user:domain\$env:USERNAME ""powershell.exe -noexit -Command `'&$filePath -arg1 -arg2`' "" "
or
Start-Process runas.exe "/netonly /smartcard /user:domain\$env:USERNAME ""powershell.exe -noexit -File `'$filePath -arg1 -arg2`' "" "
File path points to the same .ps1 file. The dir is similar to: C:\Users\Username\My Documents\script.ps1
When this runs I simply get a script window that doesn't actually run, it just prints the File name. I have also tried using -File but that simply crashes (regardless of -noexit). The runas bit works fine, I get my smartcard prompt etc its just the actual PowerShell launch that I am struggling with.
These work just fine calling them directly from PowerShell Cli but when in a .ps1 it just won't work.
Any help would be greatly appreciate.
There's generally no reason to use Start-Process to run a console application such as runas.exe - unless you explicitly want the application to run in a new window, asynchronously (by default) - see this answer for more information.
Eliminating Start-Process simplifies the quoting:
runas.exe /netonly /smartcard /user:domain\$env:USERNAME "powershell.exe -noexit -File \`"$filePath\`" -arg1 -arg2"
Note the - unfortunate - need to manually \-escape the embedded " chars. (`"), which is required up to at least v7.1 - see this answer.
As for what you tried:
On Windows, passing a '...'-enclosed string to the PowerShell CLI's -Command (-c) parameter from outside PowerShell (such as via runas.exe) causes it to be interpreted as a verbatim PowerShell string, printing its content as-is (except for whitespace normalization).
You can verify this by running the following from cmd.exe, for instance:
C:\>powershell -c 'Get-Date; whatever I type here is used as-is - almost'
Get-Date; whatever I type here is used as-is - almost
Note how the multiple spaces before the word almost were normalized to a single one.
The reason is that on Windows only double-quoting ("...") has syntactic function on the command line - ' characters are interpreted verbatim and therefore passed through.
Therefore, when the -Command (-c) argument sees its argument(s) -- after command-line parsing - and then interprets the resulting space-joined, possibly double-quote-stripped arguments as PowerShell source code, a span of '...' is interpreted as a verbatim PowerShell string, as usual (see the conceptual about_Quoting_Rules help topic, which discusses the types of PowerShell string literals).
In concrete terms:
'Get-Date; whatever I type here is used as-is - almost' is parsed by the PowerShell CLI as the following arguments(!):
'Get-Date;, whatever, I, type, here, is, used, as-is, -, almost' - note how any information about the amount of whitespace that originally separated these argument is lost in the process.
These arguments are then joined with a single space to form the string that PowerShell then interprets as PowerShell source code, which yields:
'Get-Date; whatever I type here is used as-is - almost'
This amounts to a verbatim (single-quoted) PowerShell string literal, whose content is then output as-is.

Why PowerShell.exe There is no way to dot source a script?

The help document says that the script will be executed in 'dot-sourced' mode, but it doesn't. why?
I read the full text of the help document and couldn't find the reason, so I came for help.
PS> PowerShell.exe -File '.\dot-source-test.ps1'
PS> $theValue
PS> . '.\dot-source-test.ps1'
PS> $theValue
theValue
PS>
The content of 'dot-source-test.ps1' is $theValue = 'theValue'.
If the value of File is a file path, the script runs in the local scope ("dot-sourced"), so that the functions and variables that the script creates are available in the current session.
about_PowerShell_exe - PowerShell | Microsoft Docs
To prevent conceptual confusion:
In order to dot-source a script, i.e. execute it directly in the caller's scope (as opposed to a child scope, which is the default), so that the script's variables, function definitions, ... are seen by the caller:
In the current PowerShell session, just use ., the dot-sourcing operator, directly:
# Dot-source in the caller's scope.
# When executed at the prompt in an interactive PowerShell session,
# the script's definitions become globally available.
. '.\dot-source-test.ps1'
Via powershell.exe, the Windows PowerShell CLI[1]:
Note: Whatever dot-sourcing you perform this way is limited to the child process in which powershell.exe runs and its PowerShell session; it has no impact on the caller's session.
Dot-sourcing via the CLI makes sense only in two scenarios:
Scenario A: You're passing commands via the (possibly positionally implied) -Command (-c) parameter that relies on definitions that must first be dot-sourced from a script file, and you want the session to exit automatically when the commands have finished executing.
Scenario B: You're entering a (possibly nested) interactive PowerShell session into which you want to dot-source (pre-load) definitions from a script file; as any interactive session, you will need to exit it manually, typically with exit.
Scenario A: Pre-load definitions, execute commands that rely on them, then exit:
The following starts a (new) PowerShell session as follows:
Script file .\dot-source-test.ps1 is dot-sourced, which defines variable $theValue in the caller's (here: the global) scope.
The value of $theValue is output.
The new session is automatically exited on completing the commands.
PS> powershell -c '. .\dot-source-test.ps1; $theValue'
theValue
Scenario B: Enter a (new) interactive session with pre-loaded definitions:
Simply add the -noexit switch in order to enter an interactive session in which script file .\dot-source-test.ps1 has been dot-sourced:
powershell -noexit -c '. .\dot-source-test.ps1'
# You're now in a (new) interactive session in which $theValue is defined,
# and which you must eventually exit manually.
Note:
If neither -File nor a command (via explicit or implied -Command / -c) are specified, -noexit is implied.
Because -c is needed here for dot-sourcing, -noexit must be specified to keep the session open.
While using -File for dot-sourcing instead - powershell -noexit -File '.\dot-source-test.ps1' - works too, I suggest avoiding it for conceptual reasons:
While it is technically true that a script passed to -File is dot-sourced in the new session, that is (a) unexpected, given that scripts executed from inside a session are not (they run in a child scope) and (b) by far the most typical use case for -File is to execute a given script and then exit - in which case the aspect of dot-sourcing is irrelevant.
As such, it is better to think of this behavior as an implementation detail, and it is unfortunate that the CLI help mentions it so prominently - causing the confusion that prompted this question.
[1] The same applies analogously to the PowerShell [Core] 7+ CLI, pwsh, except that it defaults to -File rather than -Command.
It's about the way the path to the file is passed through the command line. See the below example when used in Command Prompt. (test.ps1 contains the line $theValue = 'theValue')
Without using the "-File" toggle, it's treated differently, as an argument to be passed to the PowerShell process being triggered.
Seeing the same thing when calling in PowerShell.
The specific part you reference is under the " -File" toggle, which needs to be used.
If the value of File is "-", the command text is read from standard input. Running powershell -File - without redirected standard input starts a regular session. This is the same as not specifying the File parameter at all.
If the value of File is a file path, the script runs in the local scope ("dot-sourced"), so that the functions and variables that the script creates are available in the current session.
(source: Microsoft Docs > About PowerShell.exe)

What does hyphen/dash parameter mean to PowerShell?

I've found out that, if I pass only a dash to an argument of PowerShell 5.1 script on Windows 10, like this:
powershell.exe -File Test.ps1 -
I get a strange error message saying:
C:\path\Test.ps1 : Cannot process argument because the value of argument "name" is not valid. Change the value o f the "name" argument and run the operation again.
CategoryInfo : InvalidArgument: (:) [Test.ps1], PSArgumentException
FullyQualifiedErrorId : Argument,Test.ps1
The Test.ps1 is only:
echo "foo"
The actual problem I face though is that, when the script declares any mandatory parameter:
param (
[Parameter(Mandatory)]
$value
)
echo "foo"
Then executing the script the same way (with - argument) does nothing at all. No output. No error message. It just hangs for a few seconds. And then a control returns to a command prompt.
C:\path>powershell.exe -File Test.ps1 -
C:\path>_
What does the - mean to PowerShell (5.1)?
On the contrary, with PowerShell 2.0 on Windows 7, I get script usage in this case:
C:\path>powershell.exe -File Test.ps1 -
Test.ps1 [-value] <Object> [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
C:\path>_
What makes sense (a missing mandatory parameter).
And without the mandatory parameter declaration, the script works (prints it output):
C:\path>powershell.exe -File Test.ps1 -
foo
C:\path>_
The behavior should be considered a bug - something that starts with - but isn't a valid parameter name should be passed as a positional argument rather than reporting an error.
The bug affects:
Windows PowerShell (as of v5.1.18362.145) - it is unclear if a fix will ever be made.
As you state, whether you (a) get the error message (... the value of argument "name" is not valid ...) or (b) the - is quietly ignored depends on whether your parameter has a parameter attribute such as [Parameter(Mandatory)][1] and/or your param() block has a [CmdletBinding()] attribute (if so, (b) applies).
PowerShell Core 6.x - that is, the problem will be fixed in v7 (current as of this writing: v7.0.0-preview.3); I don't know if 6.2.2, the stable version current as of this writing, will be fixed - we'll see what happens to the bug report on GitHub you've filed.
As for a workaround (works analogously in PowerShell Core):
Use -Command instead of -File.
While that changes the semantics of how the command line is parsed[2], in simple cases such as this one the difference won't matter:
C:\> powershell -Command ./Test.ps1 - # note the "./"
Note the ./, because using -Command (-c) makes PowerShell parse the arguments as if they were PowerShell code, and the usual restrictions re executing scripts by filename only apply (to prevent accidental execution of a file in the current directory, you need a path component to explicitly signal that intent, hence prefix ./ or .\ is needed).
If your script file path needed quoting, you'd have to use quoting and prepend &, the call operator; e.g.:
C:\> powershell -Command "& \"./Test.ps1\" -"
[1] Adding a [Parameter()] attribute to a declared parameter implicitly makes the enclosing script/function an advanced one, in which case different parsing rules apply. The [CmdletBinding[] attribute, which is applied to a param(...) block as a whole, explicitly marks a script / function as an advanced one.
[2] See this answer for the differences between how -File and -Command arguments are parsed.
This isn't an answer but I'm curious as well. If you've already figured it out I'd be interested in what you found. Otherwise, in case it helps, Powershell -h says everything after -file is the script and any arguments passed to it.
-File
Runs the specified script in the local scope ("dot-sourced"), so that the
functions and variables that the script creates are available in the
current session. Enter the script file path and any parameters.
File must be the last parameter in the command, because all characters
typed after the File parameter name are interpreted
as the script file path followed by the script parameters.
Tokenizer reads it in as a CommandArgument.
Powershell >> $Errors = $Null
Powershell >> [System.Management.Automation.PSParser]::Tokenize("powershell -file test.ps1 -", [ref]$Errors)[3]
Content : -
Type : CommandArgument
Start : 26
Length : 1
StartLine : 1
StartColumn : 27
EndLine : 1
EndColumn : 28
So it seems like the issue is further up the chain but I couldn't find a simple way to call the Parser functions to test.
I do see that there's a case that shouldn't occur where the error is swallowed and null returned which might cause it to just stop like it does in your example

Open a powershell window from an existing powershell window, and keep it open after running code

I can open a powershell window from an existing one and provide it code to run here
But I can't get the second window to stay open once the code has run
Here's what I want to run in the first powershell window, and I would like the powershell window that opens to stay open after running the code (currently, it closes immediately)
start powershell { ECHO "hi" }
Note
I tried some suggestions here but not having any luck
Also, I got a fix (of sorts) using something like start powershell { ECHO "hi"; TIMEOUT 20 } but that's not going to keep the window permanently open
PowerShell -Command {Write-Host "Test" } -NoExit
from about Powershell. To start the Powershell in a new window you can use:
start-process powershell.exe -ArgumentList "-noExit", "-command","Write-host 'TEST'; Write-Host 'Test2'"
Important -Command must be the last parameter (about Powershell):
When the value of Command is a string, Command must be the last parameter specified because any characters typed after the command are interpreted as the command arguments.
Generally:
If you pass a command (-Command) or script file (-File) to execute to PowerShell's CLI (powershell.exe in Windows PowerShell, pwsh.exe in PowerShell [Core] v6+), PowerShell by default executes the command / script and then exits.
With a command or script specified for execution, you need to add the -NoExit switch if you want the new session to remain open.
Caveat: (Unless you call directly from within PowerShell with a script block), a positional argument - i.e., one neither preceded by -Command nor -File - is implicitly bound to:
-Command in Windows PowerShell
-File in PowerShell [Core] v6+.
Therefore, it's advisable to use the target parameter name explicitly.
With Start-Process (whose built-in alias on Windows - but not on Unix - is start):
Note: The primary use of Start-Process is to launch an independent process asynchronously in a new window. However, the latter isn't supported on Unix-like platforms, where Start-Process's utility is therefore limited.
start powershell { ECHO "hi" } happens to work from PowerShell (except that the window closes right after executing the command), but it's important to note that you cannot actually pass script blocks to Start-Process, only strings.
Start-Process accepts an executable name (implied -FilePath parameter), and an array of string arguments (implied -ArgumentList / -Args parameter).
If you pass a script block ({ ... }), it is automatically stringified, meaning that its literal string contents are used (stripped of the { and }) as the (only) -ArgumentList string value.
Thus, bypassing the unnecessary script-block creation, your command - with -NoExit applied as desired - should be (with explicitly named parameters; note that -Command and its argument must come last):
Start-Process -FilePath powershell -ArgumentList '-NoExit -Command ECHO "hi"'
Note:
While passing arguments individually, as an array to -ArgumentList is arguably conceptually cleaner, it is actually better to pass all arguments as a single string, using embedded quoting as necessary, due to a longstanding bug - see GitHub issue #5576.
Trying to open an interactive shell in a new window as a different user, via the -Credential parameter, is broken up to at least PowerShell 7.1, resulting in keyboard input getting blocked both in the new window and in the caller's window - see this answer for a workaround with runas.exe and GitHub issue #12129.