Powershell uninstall app with custom Wix flag command line arg - powershell

I created a custom Wix flag command line argument (FLAG = "remove") to bypass some custom messageboxes during an uninstall. For a windows batch command, the command looks like this:
AppInstaller.exe /quiet /uninstall FLAG="remove"
I want to convert this command to work in Powershell but am having trouble with the syntax.
I've tried the following:
Start-Process ./AppInstaller.exe /s FLAG="remove" -Wait
Start-Process ./ProductivityAppInstaller.exe -ArgumentList /s FLAG="remove" -Wait
Powershell doesn't seem to recognize my custom Wix argument. I got the error "A positional parameter cannot be found that accepts argument 'FLAG=remove'".

You have the right idea about using -ArgumentList. However, you need to enclose the arguments in double quotes and backtick escape any double quote literals that you need to pass to the executable.
Start-Process -FilePath "./AppInstaller.exe" -ArgumentList "/s FLAG=`"remove`"" -Wait
-ArgumentList expects a string array to be passed to it. Under the covers, PowerShell joins those array elements by a space (). If you provide the parameter a value that is a single string with the .exe arguments separated by a space, you achieve the same result. Since PowerShell attempts to perform string expansion when it detects a double quote pair, you need to indicate to PowerShell to not do that when it is undesirable. By escaping a double quote, PowerShell will skip the expansion of that escaped double quote.
A slightly alternative approach is to create an array of your arguments. Then pass the array into the -ArgumentList parameter. You will still have to include your double quotes literally as part of your string by surrounding argument with single quotes or doing a backtick escape.

Related

How to pass array parameters to a powershell script via command line or scheduled task?

I wrote a PowerShell script which accepts a string array parameter. It is defined in the param section of the script like this:
param
(
[Parameter(Position=0)]
[string[]]$TemplateNames,
...
)
When calling the script via a PowerShell command-line, I can easily pass multiple strings to this parameter by using the following syntax:
.\Script.ps1 -TemplateNames #("Name1","Name2")
However, I need to call this script in a scheduled task and no matter what syntax I use, I can't get PowerShell to interpret the array correctly. The #(...) syntax doesn't work, passing the strings separated with a comma also doesn't work, putting the array in brackets also doesn't work and no matter how I escape the parameter or even the entire script call in single quotes, double quotes or even multiple single or double quotes, it just won't work. The same problem occurs also when calling the script manually via the Windows command line like this:
Powershell.exe -NoProfile -NoLogo -NonInteractive -ExecutionPolicy Bypass -File "C:\Script\Script.ps1" -TemplateNames ...
How can I pass an array to a PowerShell script in a scheduled task or when calling it via the Windows command line?
When using -command the problem of the misinterpretation can be circumvented:
powershell -command "& .\script.ps1 'Name1','Name2'"
This works because the string beetween the quotes is not (mis)interpreted.
When using double quotes you have to use three of them:
powershell -command "& .\script.ps1 """Name1""","""Name2""""
The logic of removed quotes is strange. A test with a program that simply echos the parameters shows that a variant with two quotes yields the same result as the one with three.

Powershell inline commands on command line, quotation marks and new line

I have a powershell script that takes a parameter from the command line.
The passed parameter is a string with new line characters but I cannot get it to work as I wish.
Example running from command line:
powershell.exe -command "& {& 'C:\My Scripts\Script.ps1' "Some Text`nWoo Hoo"}"
The script above is only passed the text Some. If I use single quotes around the passed text the `n newline character is interpreted literally, not as a newline.
I'm sure there is some way to solve this!
thanks
Since your overall command string is (of necessity when calling from cmd.exe) double-quoted, you must escape the embedded double quotes as \" (even though inside PowerShell it is ` that serves as the escape character, as used in the embedded `n escape sequence):
# From cmd.exe
powershell.exe -command "& 'C:\My Scripts\Script.ps1' \"Some Text`nWoo Hoo\""
Also note that there's never a need to use -Command "& { ... }" when calling the PowerShell CLI - just -Command "..." is sufficient.
In your case, embedded double-quoting (\"...\"), i.e. use of an (escaped) expandable string is a must in order to get interpolation of the `n escape sequence to an actual newline.
In cases where a verbatim (literal) string is sufficient, you can bypass the need to escape by using single-quoting ('...').
See the bottom section of this answer for more information about string literals in PowerShell.

Passing newline character to PowerShell via Cmd

I'm trying to run a PowerShell script from Windows cmd.exe. The input to the PowerShell script is a string, which contains newline characters using PowerShell backtick escaping - i.e:
`r`n
For demonstration purposes, the input string is then written to the console, and also dumped to a file.
The issue I have is that when the script is run from cmd.exe using the syntax
powershell.exe script.ps1 "TEST`r`nTEST"
The newline characters in the string are not treated as newline, and are included literally in both the console output and the output text file.
TEST`r`nTEST
However, if I run this from a PowerShell environment, I get the expected result (i.e. the newline characters are parsed correctly, and a newline is inserted in the appropriate location).
TEST
TEST
Similarly, if I pass in \r\n instead of the escaped newline characters through Windows cmd.exe, and do a .replace in the PowerShell script
$date = $data.replace("\r\n","`r`n")
I get the expected output:
TEST
TEST
Is anyone able to shed some light on why this happens?
The test script is as follows:
param([string]$data) # data to send
Write-Host $data
[IO.File]::WriteAllText('d:\temp.txt', $data)
return 0
And the file is called from the command line as:
powershell.exe script.ps1 "TEST`r`nTEST"
The script is running on Windows Server 2012 R2, using PowerShell v4.0
tl;dr
Use -Command and pass the entire PowerShell command as a single string; e.g.:
C:\> powershell -NoProfile -Command "script.ps1 \"TEST`r`nTEST\""
TEST
TEST
Note how the internal " instances are escaped as \", which PowerShell requires when called from the outside (alternatively, for full robustness, use "^"" (sic) in Windows PowerShell and "" in PowerShell (Core) v6+).
In your specific case,
powershell -NoProfile -Command script.ps1 "TEST`r`nTEST" would have worked too, but generally that only works as intended if the string has no embedded spaces.
Given that -Command is the default up to PSv5.1, your command - as currently posted - should work as-is.
As of PowerShell v5.1, arguments passed to powershell.exe from the outside:
ARE subject to interpretation by PowerShell, including string interpolation, by default and when you use -Command (i.e., specifying neither -File nor -Command currently defaults to -Command).
Caveat: The default behavior will change in v6: -File will be the default then - see the relevant change on GitHub.
are NOT subject to interpretation if you use -File to invoke a script - (after potential interpretation by cmd.exe) PowerShell treats all arguments as literal strings.
Caveat: This behavior is currently being discussed with respect to v6, given that it is overtly problematic in at least one case: trying to pass Boolean values.
Optional reading: Why you should pass the entire PowerShell command as a single argument when using -Command:
When you use -Command with multiple arguments, PowerShell essentially assembles them into a single command line behind the scenes before executing it.
Any "..."-quoting around the individual arguments is lost in the process, which can have unexpected results; e.g.:
C:\> powershell -NoProfile -Command "& { $args.count }" "line 1`r`nline 2"
3 # !! "line 1`r`nline 2" was broken into 3 arguments
Given that the outer "..." quoting was removed in the process of parsing the command line, the actual command line that PowerShell ended up executing was:
C:\ PS> & { $args.Count } line 1`r`nline 2
3
To illustrate why, let's look at an equivalent command that uses explicit quoting:
C:\ PS> & { $args.Count } "line" "1`r`nline" "2"
In other words: After the enclosing " were removed, the resulting token was broken into multiple arguments by spaces, as usual.
The parameter will need to be reinterpreted as a PowerShell string. Will this get you down the road?
The reason your -replace did not work is that the original string actually contains a backtick. It needs to be escaped in the search string.
C:\src\t>type p1.ps1
Param([string]$s)
Write-Host $s
$p = Invoke-Expression `"$s`"
Write-Host $p
$p2 = $s -replace "``r``n","`r`n"
Write-Host $p2
C:\src\t>powershell -noprofile -file .\p1.ps1 "TEST`r`nTEST"
TEST`r`nTEST
TEST
TEST
TEST
TEST
Carriage return and Linefeed are bytes with values 13 and 10, you can't write them, you can't see them.
As a convenience, when writing PowerShell code, the language will let you write:
"`r`n"
in a double quoted string, and when processing PowerShell source code (and at no other time), it will read those and replace them with bytes value 13 and 10.
It is this line of code in the PowerShell tokenizer which does it.
There is nothing special about backtick-n to the cmd.exe interpreter, and nothing special about having it in a string - you can put it there in a single quoted string
'`n'
or replacing it in a string - except that you have to note when the replacement happens. e.g. in your comment:
For example, if you pass in 'r'n and then replace 'r'n with 'r'n, the 'r'n is still output literally
Because your code
-replace "`r`n"
becomes
-replace "[char]13[char]10"
and your string passed in from outside contains
`r`n
and they don't match. Backtick-n in a string isn't magic, strings are not all interpreted by the PowerShell engine as PowerShell code, nor are parameters, or anything. And it's only in that context - when you write your -replace code, that is when the swap for actual newline characters happens.

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

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).

Invoke-Expression -Command Not Finding File Even Though It Exists

all,
I am trying to run a command in Power Shell and I am told the file does not exist as it stands right now for the Power Shell code. If I do the same in DOS, it finds the file and executes correctly. The only difference is that there is an additional open and closed double quote around the file name. Can this be done in Power Shell the same way? If so, how can this be done as I am not familiar with Power Shell.
DOS:
IF EXISTS F:\lvcecstos.CSV F:\LaserVault\ContentExpress\ContentExpress.exe /CID:1 /CSV:<b>"F:\lvcecstos.CSV"</b> /ClearSubscription
Power Shell:
Invoke-Expression -Command "F:\LaserVault\ContentExpress\ContentExpress.exe /CID:1 /CSV:<b>F:\lvcecstos.csv</b> /ClearSubscription"
Thanks in advance for any suggestions. I am told by the Laservault engineers that created this software package that we use that the double quotes around the file name is required. Doesn't make sense to me as to why though, but this is something I am unable to get around.
You can add double quotes around the file path as follows:
Invoke-Expression -Command 'F:\LaserVault\ContentExpress\ContentExpress.exe /CID:1 /CSV:"F:\lvcecstos.csv" /ClearSubscription';
Notice how the command is wrapped in single quotes instead of double quotes.
Ultimately, it depends on how the ContentExpress.exe program is interpreting the file path, though. For all you know, it could be appending the valued passed to "CSV" to the "current working directory."
You can also use the Start-Process cmdlet, instead of Invoke-Expression or Invoke-Command.
$Program = 'F:\LaserVault\ContentExpress\ContentExpress.exe';
$ArgumentList = '/CID:1 /CSV:"F:\lvcecstos.csv" /ClearSubscription';
Start-Process -Wait -NoNewWindow -FilePath $Program -ArgumentList $ArgumentList;
If you have PowerShell v3 (which you should) you could use the "magic parameter" --% (see here).
& F:\LaserVault\ContentExpress\ContentExpress.exe --% /CID:1 /CSV:"F:\lvcecstos.csv" /ClearSubscription
Otherwise trying to preserve double quotes around arguments could become very, very painful. See for instance this answer to a similar question.
$lvcecstos = "F:\lvcecstos.CSV"
If(Test-Path $lvcecstos)
{
Invoke-Expression -Command "& F:\LaserVault\ContentExpress\ContentExpress.exe /CID:1 /CSV:$lvcecstos /ClearSubscription"
}