How to download a file to $env:temp directory using powershell from cmd console? - powershell

I can download a file to a constant location from cmd console using powershell like this
C:\Users\Weijun>powershell -command "(new-object System.Net.WebClient).DownloadFile('https://example.com/example.zip', 'D:\example.zip')"
but i need to download it to system temp directory,using the powershell variable $env:temp,the following doesn't work.(variable not expanded in single-quote)
powershell -command "(new-object System.Net.WebClient).DownloadFile('https://example.com/example.zip', '$env:temp\example.zip')"
how to pass variable as parameter in method invoke?
UPDATE: it is a typo for $env:temp:,I amend it.

tl;dr:
From outside of PowerShell (cmd.exe - Command Prompt or batch file), use escaped double-quoted strings - \"...\" - inside the overall command string - "..." - passed to powershell -command for strings that need interpolation (e.g., expansion of variable references such as $env:temp):
powershell -command "(new-object System.Net.WebClient).DownloadFile('https://example.com/example.zip', \"$env:temp\example.zip\")"
You not only need to fix the incidental problem that Nkosi points out - removing the extraneous : at the end of $env:temp: - but you must generally either not quote the path at all (argument syntax), or use escaped double quotes (expression syntax or values with shell metacharacters) - single-quoted strings don't work, because PowerShell treats their content as a literal and won't expand the environment-variable reference.[1]
For a description of PowerShell's two fundamental parsing modes - argument syntax vs. expression syntax - see Get-Help about_Parsing.
Simplified examples:
Without quoting (argument syntax):
c:\>powershell.exe -noprofile -command "write-output $env:temp\example.zip"
C:\Users\jdoe\AppData\Local\Temp\example.zip
With double-quoting (expression syntax or if the value contains shell metacharacters such as , or |):
c:\>powershell.exe -noprofile -command "write-output \"$env:temp\example.zip\""
C:\Users\jdoe\AppData\Local\Temp\example.zip
Note: When called from the outside, PowerShell requires embedded " chars. to be escaped as \" - unlike inside PowerShell, where `" must be used.
Applied to your command: with (escaped) double-quoting, which is needed, because you're using .NET method syntax, which is parsed with expression syntax:
powershell -command "(new-object System.Net.WebClient).DownloadFile('https://example.com/example.zip', \"$env:temp\example.zip\")"
[1] If you run the same command from PowerShell, you won't see the problem, because the command as a whole is enclosed in "...", which means that the current PowerShell session will expand $env:temp - before it is passed to powershell.exe.
However, when called from cmd.exe, this interpolation is NOT applied - hence the need to either omit quoting or use embedded (escaped) double-quoting.

I would use Invoke-WebRequest with -OutFile:
powershell -command "Invoke-WebRequest -Uri 'http://example.com/file-to-get.txt' -OutFile $env:temp\file-to-get.txt"
NB this requires PowerShell 3.0 (if I remember correctly).

Related

How to solve problem with quotes in arguments on calling a PowerShell script with -File option?

I want to call a small PowerShell (v5.1) script from Windows 10 Pro command line with arguments.
param($ReqURL, $JSONString)
Write-Host $ReqURL # for debug
Write-Host $JSONString # for debug
Invoke-RestMethod -Uri $ReqURL -Method Patch -ContentType "application/json" -Body $JSONString
Calling the script in a PowerShell console works fine:
.\http-patch.ps1 -ReqURL myRESTApi -JSONString '{"lastname": "Bunny"}'
Calling the script from the Windows Command Prompt (cmd.exe) works when I escape the double quotes, like this:
PowerShell .\http-patch.ps1 -ReqURL myRESTApi -JSONString '{\"lastname\": \"Bunny\"}'
But when I use the -File option, it fails because the $JSONString inside the script is equal to '{"lastname"::
PowerShell -File "C:\work\LV Demo\http-patch.ps1" -ReqURL myRESTApi -JSONString '{\"lastname\": \"Bunny\"}'
I assume here are some problems with quotes, but I can't find the right way.
When you use the -File parameter of powershell.exe, the Windows PowerShell CLI, only double quotes (") are recognized as having syntactic function when calling from outside PowerShell, such as from cmd.exe:
Therefore, switch from ' to " (the embedded " chars. require escaping as \" (sic) either way):
:: From cmd.exe
PowerShell -File "C:\work\LV Demo\http-patch.ps1" -JSONString "{\"lastname\": \"Bunny\"}" -ReqURL myRESTApi
By contrast, when you use -Command, '...' strings are recognized, after unescaped " chars. - the only ones with syntactic function during initial command-line parsing - have been stripped, because the resulting tokens are then interpreted as PowerShell code.
For guidance on when to use -Command vs. -File and the differences in resulting parsing, see this answer.
From inside PowerShell, there's rarely a need to to call another PowerShell instance, given that .ps1 files can be invoked directly, in-process.
In cases where you do need to call the CLI from inside PowerShell - say you need to call the CLI of the other PowerShell edition, PowerShell (Core)'s pwsh.exe, from powershell.exe or vice versa - the best choice is to use a script block ({ ... }) (which works only when calling from inside PowerShell), because that:
allows you to focus solely on PowerShell's own quoting and escaping requirements.
supports receiving typed output (objects other than strings), behind-the-scenes XML-based serialization, albeit with limited type fidelity - see this answer.
# From PowerShell
# Note how the " chars. now need NO escaping.
# (If you were to use a "..." string, you'd escape them as `" or "")
PowerShell {
& C:\work\LV Demo\http-patch.ps1" -JSONString '{"lastname": "Bunny"}' -ReqURL myRESTApi
}
While you can call via individual arguments, analogous to how you must call from outside PowerShell, you'll not only lose the benefit of typed output, but you'll also run in a longstanding bug up to PowerShell 7.2.x that requires manual escaping of embedded " chars. as \ when calling external programs - see this answer - as evidenced by one of your own attempts (here, using '...' is perfectly fine, because PowerShell recognizes it):
# !! Note the unexpected need to \-escape the embedded " chars.
PowerShell -File .\http-patch.ps1 -JSONString '{\"lastname\": \"Bunny\"}' -ReqURL myRESTApi
# Variant with double quotes.
# !! Note the *double* escaping: first with ` - for the sake of PowerShell itself -
# !! then with \ due to the bug.
PowerShell -File .\http-patch.ps1 -JSONString "{\`"lastname\`": \`"Bunny\`"}" -ReqURL myRESTApi

Unexpected token '}' when passing string to PowerShell script

I have a script that accepts two strings, each are delimited by '|-|'. The second string can contain any character other than '.
To call the script I do:
PowerShell .\script.ps1 -arg1 'Hello|-|World' -arg2 'random|-|dwua0928]43d}'
It raises an error on the character }. How can I resolve this?
Edit:
It also has an issue with the - from the delimiters and raises the same error:
At line:1 char:54
+ .\Script.ps1 -arg2 <redacted>|-|<redacted> ...
+ ~
Missing expression after unary operator '-'.
At line:1 char:53
+ .\Script.ps1 -arg1 <redacted>|-|<redacted> ...
+ ~
Expressions are only allowed as the first element of a pipeline.
It does that at every instance of |-|. It does it with the brackets if I change the delimiter to /-/:
At line:1 char:168
+ ... <redacted>/-/<redacted>!OW}:Lj<redacted> ...
+ ~
Unexpected token '}' in expression or statement.
At line:1 char:181
+ ... /-/<redacted>cO0}e]k<redacted> ...
+ ~
Unexpected token '}' in expression or statement.
The beginning of the script is this:
Param
(
[switch] $enable,
[string] $arg1,
[string] $arg2
)
There's generally no need to call the PowerShell CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell (Core) 7+) from inside PowerShell.
Assuming you're running Windows PowerShell (or, to generalize, the same edition of PowerShell), you can invoke your script directly:
.\script.ps1 -arg1 'Hello|-|World' -arg2 'random|-|dwua0928]43d}'
If you still want to call the CLI - which is expensive, because it involves creating a child process, and also results in a loss of type fidelity - use a script block ({ ... }), but note that this works only from inside PowerShell (though it works across both PowerShell editions, i.e., you can call pwsh.exe from Windows PowerShell, and powershell.exe from PowerShell (Core) 7+)
powershell { .\script.ps1 -arg1 'Hello|-|World' -arg2 'random|-|dwua0928]43d}' }
If you were to call from outside PowerShell, it is best to use the -File CLI parameter rather than the (implied in Windows PowerShell) -Command parameter:
powershell -File .\script.ps1 -arg1 "Hello|-|World" -arg2 "random|-|dwua0928]43d}"
While this works from inside PowerShell too, the disadvantage compared to the { ... } approach is that the output is only ever text, whereas the { ... } approach is - within limits - capable of preserving the original data types - see this answer for more information.
In order to make a -Command-based command line work, enclose the PowerShell code in "..." as a whole, in which case the embedded '...' strings are passed as such:
powershell -Command ".\script.ps1 -arg1 'Hello|-|World\' -arg2 'random|-|dwua0928]43d}'"
For a comprehensive overview of the PowerShell CLI, see this post.
As for what you tried:
You called powershell.exe with neither -File (-f) nor -Command (-c), which defaults to -Command (note that, by contrast, pwsh, the PowerShell (Core) 7+ CLI, defaults to -File).
When -Command is used, PowerShell's command-line processing first strips all subsequent arguments of any syntactic (i.e., unescaped) " characters, namely from "..."-enclosed arguments, joins the resulting tokens with spaces, and then interprets the resulting string as PowerShell code.
In your case, the fact that you used '...' quoting around the arguments is irrelevant:
PowerShell invariably translates the original quoting into "..." quoting when calling external programs (as they can only be expected to "..." quoting, not also '...' quoting)
However, it only uses "..." quoting if needed, which is solely based on whether the argument at hand contains spaces.
Neither of your arguments, verbatim Hello|-|World and random|-|dwua0928]43d}, contained spaces, so they were placed as-is on the command line for powershell.exe (though note that even if they did contain spaces and had been enclosed in "..." as a result, PowerShell's command-line processing would have stripped the " chars.)
The net result was that the powershell.exe child process tried to execute the following PowerShell code, which predictably breaks with the errors shown in your question:
# !! Note the absence of quoting around the -arg1 and -arg2 values.
.\script.ps1 -arg1 Hello|-|World -arg2 random|-|dwua0928]43d}
You can easily provoke the error you saw as follows from a PowerShell session:
# -> Error "Missing expression after unary operator '-'."
Write-Output Hello|-|World

I can't run the code when I use powershell.exe -command

Cold you help me with following problem?
How to run the
"{0:n10}" -f 21.21
code in
powershell.exe -command "& {}"
argument?
There's no reason to use "& { ... }" in order to invoke code passed to PowerShell's CLI via the -Command (-c) parameter - just use "..." directly.
Older versions of the CLI documentation erroneously suggested that & { ... } is required, but this has since been corrected.
Unescaped " characters on the command line are stripped before the resulting argument(s) following -Command (-c) are interpreted as PowerShell code. " characters that need to be preserved as part of the PowerShell command to execute therefore need escaping.
From PowerShell's perspective, using \" works, in both editions.
Additionally, to prevent inadvertent whitespace normalization (folding of multiple spaces into one), the entire command to pass to -Command (-c) should be passed inside a single, "..." string overall:
Therefore (note that this assumes calling from outside PowerShell):
powershell.exe -Command " \"{0:n10}\" -f 21.21"
However, when calling from cmd.exe, specifically, \" escaping may break cmd.exe's syntax, in which case a different form of escaping of embedded " chars. is required - the following works robustly:
"^"" (sic) with powershell.exe, the Windows PowerShell CLI:
powershell.exe -Command " "^""{0:n10}"^"" -f 21.21"
"" with pwsh.exe, the PowerShell (Core) 7+ CLI:
pwsh.exe -Command " ""{0:n10}"" -f 21.21"
See this answer for details.

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.

How do I include ampersands in a powershell command in a batch file?

I am trying to download a Google sheet via a batch file. This works:
powershell -Command "Invoke-WebRequest https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/export?exportFormat=tsv -OutFile output.tsv"
When I specify which sheet/tab I want by adding &gid=1234, this breaks:
powershell -Command "Invoke-WebRequest https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/export?exportFormat=tsv&gid=1234 -OutFile output.tsv"
The error is:
The ampersand (&) character is not allowed. The & operator is reserved
for future use; wrap an ampersand in double quotation marks ("&") to
pass it as part of a string.
How do I wrap the ampersand in quotes without breaking the outer quotes for the Command parameter?
The URL embedded inside the "..." string passed to powershell -Command must be quoted too, because an unquoted & has special meaning to PowerShell too (though in Windows PowerShell it is currently only reserved for future use; in PowerShell Core it can be used post-positionally to run a command as a background job).
The simplest option is to use embedded '...' quoting, as suggested by Olaf, because ' chars. don't need escaping inside "...". '...' strings in PowerShell are literal strings, which is fine in this case, given that the URL contains no variable references.
powershell -Command "Invoke-WebRequest 'https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/export?exportFormat=tsv&gid=1234' -OutFile output.tsv"
If embedded "..." quoting is needed for string interpolation, use \" (sic) to escape the embedded (") chars. (note that inside PowerShell, you'd need to use `" or "" instead):
powershell -Command "Invoke-WebRequest \"https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/export?exportFormat=tsv&gid=1234\" -OutFile output.tsv"