Including a script does not work when path has special characters - powershell

I am trying to include a script inside another which is in the same folder, using the dot syntax:
. '.\BaseScript.ps1'
The path to the scripts has a folder with square brackets in the name. Even though I am using relative paths, the path error still occurs.
Moving it to some other path without special characters in the name works fine.
How can such a case be catered while using relative paths?

Unfortunately, PowerShell treats the operands to the . dot-sourcing and & call operators (which includes implicit invocation[1] ) as wildcard expressions, which causes problems with paths that contain [, which is a wildcard metacharacter.
The solution is to escape the [ as `[; e.g., to dot-source a script whose path is ./test[1]/script.ps1:
# Note: '/' and '\' can be used interchangeably in PowerShell.
. ./test`[1]/script.ps1
Important: If the path is relative, it must start with ./ or .\ (see below for full paths).
Note: [ is a wildcard metacharacter, because you can use it to express character ranges ([a-z]) or sets ([56]); while ] is clearly also needed, it is sufficient to escape [.
This unfortunate requirement, which also affects other contexts, is the subject of GitHub issue #4726.
Alternatively - and bizarrely - as Theo's helpful answer shows, the need for this escaping goes away if you use a full path.
# Dot-source 'script.ps1` from the same location as the running
# script ($PSScriptRoot).
# (Use $PWD to refer to the *currrent* dir.)
# Because this results in a *full* path, there is no need to escape, strangely.
. $PSScriptRoot/script.ps1
[1] &, which can invoke any command and runs commands written in PowerShell code in a child scope, isn't strictly needed for invocation; e.g. & ./path/to/script.ps1 and ./path/to/script.ps1 are equivalent; however, & is required if the path is quoted and/or contains variable references - see this answer.

The way around this seems to be using a complete path to the second script:
. (Join-Path -Path $PSScriptRoot -ChildPath 'SecondScript.ps1')

#Theo has a useful workaround, but the cause is that [] must be escaped in paths.
$path = 'C:\path\`[to`]\script.ps1'

Related

Powershell invoke tool with argument containing a wildcard

I'm writing a tool that receives an argument that is a file pattern as follows:
mytool arg1 *
Here is the problem: The character * is being resolved to the contents of the current directory, almost like the ls command.
If I escape the wildcard using grave (`) it is also received by the command.
How can I solve that problem?
Use a verbatim (single-quoted) string ('...')[1] to prevent the globbing (pathname extension) that PowerShell performs on Unix-like platforms (only) when calling external programs:
mytool arg1 '*'
I have no explanation for why character-individual escaping (`*) doesn't work (as of PowerShell 7.2.6) - arguably, it should; the problem has been reported in GitHub issue #18038.
On Windows, PowerShell performs no globbing, and *, '*', and `* are all passed as verbatim * to an external program.
As you state, given that you're on Windows, it must be the Python-based tool you're calling that performs globbing.
[1] An expandable (double-quoted) string ("...") string works too, but there's no need for one unless you truly need expansion (string interpolation).

How do I invoke a command using a variable in powershell?

I want to invoke the shutdown.exe executable in powershell and it is located on C:\WINDOWS\System32\shutdown.exe.
Since we already got the C:\WINDOWS in the variable $env:windir I want to just use it and concatenate the rest of the path into the command. I was trying this:
PS C:\Users\shina> .\$env:windir\System32\shutdown.exe -s
But I got the following error:
.\$env:windir\System32\shutdown.exe: The term '.\$env:windir\System32\shutdown.exe' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
How can I accomplish this?
You can use:
& $env:windir\System32\shutdown.exe -s
Or
. $env:windir\System32\shutdown.exe -s
(note the space).
& is the call operator. You can read more about it here:
Runs a command, script, or script block. The call operator, also known as the "invocation operator", lets you run commands that are stored in variables and represented by strings or script blocks.
. is the dot sourcing operator. You can read more about it here:
Runs a script in the current scope so that any functions, aliases, and variables that the script creates are added to the current scope, overriding existing ones.
The dot sourcing operator is followed by a space. Use the space to distinguish the dot from the dot (.) symbol that represents the current directory.

Run command line in PowerShell

I know there are lots of posts regarding this, but nothing worked for me.
I am trying to run this command line in PowerShell:
C:/Program Files (x86)/ClamWin/bin/clamd.exe --install
I have this in PowerShell:
&"C:/Program Files (x86)/ClamWin/bin/clamd.exe --install"
But all this does is execute clamd.exe, ignoring the --install parameter
How can I get the full command line to run?
Josef Z's comment on the question provides the solution:
& "C:/Program Files (x86)/ClamWin/bin/clamd.exe" --install # double-quoted exe path
or, given that the executable path is a literal (contains no variable references or subexpressions), using a verbatim (single-quoted) string ('...'):
& 'C:/Program Files (x86)/ClamWin/bin/clamd.exe' --install # single-quoted exe path
As for why your own solution attempt failed: The call operator, &, expects only a command name/path as an argument, not a full command line.
Invoke-Expression accepts an entire command line, but that complicates things further and can be a security risk.
As for why this is the solution:
The need for quoting stands to reason: you need to tell PowerShell that C:/Program Files (x86)/ClamWin/bin/clamd.exe is a single token (path), despite containing embedded spaces.
&, the so-called call operator, is needed, because PowerShell has two fundamental parsing modes:
argument mode, which works like a traditional shell, where the first token is a command name, with subsequent tokens representing the arguments, which only require quoting if they contain shell metacharacters (chars. with special meaning to PowerShell, such as spaces to separate tokens);
that is why --install need not, but can be quoted (PowerShell will simply remove the quotes for you before passing the argument to the target executable.)
expression mode, which works like expressions in programming languages.
PowerShell decides based on a statement's first token what parsing mode to apply:
If the first token is a quoted string - which we need here due to the embedded spaces in the executable path - or a variable reference (e.g., $var ...), PowerShell parses in expression mode by default.
A quoted string or a variable reference as an expression would simply output the string / variable value.
However, given that we want to execute the executable whose path is stored in a quoted string, we need to force argument mode, which is what the & operator ensures.
Generally, it's important to understand that PowerShell performs nontrivial pre-processing of the command line before the target executable is invoked, so what the command line looks like in PowerShell code is generally not directly what the target executable sees.
If you reference a PowerShell variable on the command line and that variable contains embedded spaces, PowerShell will implicitly enclose the variable's value in double quotes before passing it on - this is discussed in this answer to the linked question.
PowerShell's metacharacters differ from that of cmd.exe and are more numerous (notably, , has special meaning in PowerShell (array constructor), but not cmd.exe - see this answer).
To simplify reuse of existing, cmd.exe-based command lines, PowerShell v3 introduced the special stop-parsing symbol, --%, which turns off PowerShell's normal parsing of the remainder of the command line and only interpolates cmd.exe-style environment-variable references (e.g., %USERNAME%).

file transfer using ssh sftpg3

I am writing a Perl script to do a secure file transfer using SSH sftpg3.exe
But I am having issue to accessing the source file.
the script able to pick the file from C:\xx\t.txt while running it from the directory
It is not showing error C:\Program is not a valid command.
my $sftpPath="C:\\Program Files\\client";
my $srcPath="C:\\xx\\test.txt";
my $trgCommand=$sftpPath." -D $srcPath user#host:/tmp/";
my $result=system("$trgCommand");
while running this script from C:\ directory it is running without error but I can not see the file in destination server.
Could you please help me sort out this file path issue ?
I want to run it from O:\ and it will pick the target file and sftpg3.exe from C:\ drive and do the file transfer (in ASCII mode) successfully.
try the below code
my $cmd="sftpg3.exe " . "$src_path user#host:";
system("C:\\Program Files\\Client\");
system($cmd);
Thanks.
You might have interpolation of #host in your third line because you are using double quotes (""). Do you have use strict and use warnings turned on? There might also be an issue with the space () in the path.
use strict;
use warnings;
use feature 'say';
my $sftp_path = q{"C:\Program Files\Client\sftpg3.exe"};
my $src_path = 'C:\xx\test.txt';
my $result = system( $sftp_path, '-D', $src_path, 'user#host:/tmp/' );
say $result;
Let's look at what I did.
When you tab-complete a path like C:\Program Files\foo in Windows cmd, it usually wraps them in double quotes if there is a space inside the path. So we use the q operator, which is equivalent to single quotes, and put double quotes inside. That also gives us the benefit that we don't have to escape the backslash \. Also see quote-like operators in perlop.
Same goes for the source path.
system allows you to pass all the arguments to the program you want to run as arguments to itself and will take care of the quoting
The double quote in "user#host:" will try to expand #host as an array. That doesn't work, because it's not defined. So there's a warning that you probably didn't see because you did not use strict and use warnings. Instead of the double quotes, use single quotes.
I used $sftp_path instead of $sftpPath because there is a convention in Perl to use underscores and no capital letters. We like camels, but not in our variable names. :)

How to get the path of a batch script without the trailing backslash in a single command?

Suppose I wish to get the absolute path of a batch script from within the batch script itself, but without a trailing backslash. Normally, I do it this way:
SET BuildDir=%~dp0
SET BuildDir=%BuildDir:~0,-1%
The first statement gets the path with the trailing backslash and the second line removes the last character, i.e. the backslash. Is there a way to combine these two statements into a single line of code?
Instead of removing the trailing backslash, adding a trailing dot is semantically equivalent for many software.
C:\Windows is equivalent to C:\Windows\.
echo %dp0
>C:\Windows\
echo %dp0.
>C:\Windows\.
For example, robocopy accepts only directories without trailing spaces.
This errors out:
robocopy "C:\myDir" %~dp0
This is successful:
robocopy "C:\myDir" %~dp0.
Only with delayed expansion when you write both statements into the same line:
set BuildDir=%~dp0&&set BuildDir=!BuildDir:~0,-1!
But that kinda defies the purpose.
I'd like to point out that it is not safe to use the substring tricks on variables that contain file system paths, there are just too many symbols like !,^,% that are valid folder/file names and there is no way to properly escape them all
FOR /D seems to strip the trailing backslash, so here is a version that uses for:
setlocal enableextensions enabledelayedexpansion&set _CD=%CD%&cd /D "%~dp0"&(FOR /D %%a IN ("!CD!") DO ((cd /D !_CD!)&endlocal&set "BuildDir=%%~a"))
This requires Win2000 and will probably fail if the batch file is on a UNC path
You can use the modifiers ability of the FOR variables.
for %%Q in ("%~dp0\.") DO set "BuildDir=%%~fQ"
The %%~fQ results to the required path without trailing backslash (nor \.)
And this creates still a valid absolute path for the case, when the batch file is in a root directory on any drive.
This solution returns D:\, instead of D: only by simply removing the trailing
character.
Example script "c:\Temp\test.cmd":
#set BuildDir=%~dp0.
#echo Build directory: "%BuildDir%"
Run in console:
d:\> cmd /C c:\Temp\test.cmd
Build directory: "c:\Temp\."
The simplest solution that worked for me was
Instead of using : SET currentPath=%~dp0
USE : SET currentPath=%cd%