From within Powershell and Powershell ISE, Powershell scripts that exist in locations with a space in the path do not execute, or at least their output is not shown in the command window.
I have a Powershell script (helloworld.ps1) that contains only the following code:
Write-Host "Hello World"
If the script is executed from:
"C:\Temp\helloworld.ps1"
The output is: Hello World
If the script is executed from:
"C:\Program Files\Microsoft Office\helloworld.ps1"
The output is blank.
Note that the path is surrounded with quotes when executed (otherwise, obviously, I would have errors). I've duplicated this same problem on multiple machines in multiple environments, so it doesn't seem to be a configuration issue.
For what reason is Powershell output hidden if the script itself is executed from a path that contains a space?
If you do this:
"C:\Temp\helloworld.ps1"
That's a quoted string and PowerShell will simply output it.
If you want to execute a quoted string as a command, you need the & (call or invocation) operator:
& "C:\Temp\helloworld.ps1"
Otherwise you can write it without the quotes and PowerShell will understand that it's a command:
C:\Temp\helloworld.ps1
If the script's path or filename contains a space and you want to run it, you have to use & and quote the path and filename.
To execute a file, put its path without any quotes around it:
PS C:\> C:\Temp\helloworld.ps1
If the path has spaces in it, escape spaces using a back-tick (`):
PS C:\> C:\Program` Files\Microsoft` Office\helloworld.ps1
As long as a quote isn't the first character, it will work.
C":\Program Files\Microsoft Office\helloworld.ps1"
Hello World
Use a . before the path
C:>. "C:\Program Files\Microsoft Office\helloworld.ps1"
Related
I want to write a batch file named install.bat that will run a Powershell script file install.ps1 as Administrator. The content of the batch file install.bat as simple as below:
#echo off
PowerShell -NoExit -Command "Start-Process PowerShell -Verb RunAs '-NoExit -Command "Set-Location ''%cd%''; .\install.ps1"'"
In this batch script, I call a Powershell to run the Start-Process command. This Start-Process command would invoke another Powershell as Administrator. The second Powershell would run Set-Location command to set working directory to %cd% then invoke the install.ps1 script. An error happens with the Set-Location command if i run the install.bat file from a folder with more than 1 consecutive space characters in its name, for example:
D:\New folder
install.bat script will run normally with folder has 1 space character in its name, for example:
D:\New folder
but if a folder has more than 1 consecutive space character like:
D:\New folder
then Set-Location will searching for folder:
D:\New folder
and will show this error incase D:\New folder does not exist:
Can anyone explain this error and give me a solution?
The first thing that I looked with your script was the various control characters (single-quotes, double-quotes, etc.). Nothing obvious stood out.
I looked for other examples, but double-spaces in path names is not necessarily a common occurrence.
So I poked around and did some tests in a Command Prompt box, created a comparable test directory, and checked to see what outputs I could get.
First I sent the Current Directory variable to a PowerShell command to echo the value:
C:\temp\test\test 123>powershell -Command "Write-Host %cd%"
The output had stripped one of the space characters from the path name as you had described (double-quotes added for clarity):
"C:\temp\test\test 123"
Then I tried it with the variable within single-quotes:
C:\temp\test\test 123>powershell -Command "Write-Host '%cd%'"
This output preserved the space characters of the path name correctly (double-quotes added for clarity):
"C:\temp\test\test 123"
Then I tried it with the variable within two pairs of single-quotes as you had used in your script:
C:\temp\test\test 123>powershell -Command "Write-Host ''%cd%''"
This output also stripped one of the space characters from the path name, and it also added a space character to the front of the returned value (double-quotes added for clarity):
" C:\temp\test\test 123"
So it seems that your script might get corrected by changing to one pair of single-quotes surrounding the Current Directory variable call instead of the two pairs of single-quotes.
I'm not sure if you had another purpose for using the two pairs of single-quotes, and I suppose that most processes might not care about the extra leading space on the value return.
I hope that this helps with your script.
This is a deceptively complex issue, but I'll do my best to explain the problem.
I have a simple wrapper script as follows called VSYSCopyPathToClipboard.ps1:
param (
[Parameter(Mandatory,Position = 0)]
[String[]]
$Path,
[Parameter(Mandatory=$false)]
[Switch]
$FilenamesOnly,
[Parameter(Mandatory=$false)]
[Switch]
$Quotes
)
if($FilenamesOnly){
Copy-PathToClipboard -Path $Path -FilenamesOnly
}else{
Copy-PathToClipboard -Path $Path
}
Copy-PathToClipboard is just a function I have available that copies paths/filenames to the clipboard. It's irrelevant to the issue, just assume it does what it says.
The way the wrapper is called is through the Windows right click context menu. This involves creating a key here: HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\.
Mine looks like this:
The command is as follows:
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -Path $files" "%1"
And similarly for the "Copy as Filename":
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -FilenamesOnly -Path $files" "%1"
I am using a tool here called SingleInstanceAccumulator. This allows me to pass multiple selected files to a single instance of PowerShell. If I didn't use this program and ran my command with multiple files selected it would launch multiple instances of PowerShell for each file selected. It's the next best thing to creating your own shell extension and implementing IPC etc.
This has been working great until today when I encountered a file with a single quote in its filename (I.E.testing'video.mov) and the entire script failed. It's failing because the delimiter I'm using with SingleInstanceAccumulator is also a single quote and PowerShell sees no matching quote... thus errors out.
I could fix this if my variables were static by just doubling up the offending single quote, but since my parameters are files I have no opportunity to escape the single quote beyond renaming the file itself ... which is a non-solution.
So now I have no clue how to handle this.
My first try at solving the problem was as such:
Create a batch file and redirect my registry command to it.
Change the SingleInstanceAccumulator delimiter to '/' (All files will be separated by a forward slash.)
Replace the offending single quote to two single quotes.
Replace the '/' delimiters with single quotes.
Finally pass the whole argument list back to Powershell.
This image demonstrates how the above process looks:
This is the batch file's code:
#echo off
setlocal EnableDelayedExpansion
:: This script is needed to escape filenames that have
:: a single quote ('). It's replaced with double single
:: quotes so the filenames don't choke powershell
:: echo %cmdcmdline%
set "fullpath=%*"
echo Before
echo !fullpath!
echo ""
echo After
set fullpath=%fullpath:'=''%
set fullpath=%fullpath:/='%
echo !fullpath!
:: pwsh.exe -noprofile -windowstyle hidden -command "%~dpn0.ps1 -Path !fullpath!
pause
Once I got that wired up I started celebrating ... until I hit a file with an ampersand (&) or an exclamation point (!). Everything fell apart again. I did a whole bunch of google-fu with regards to escaping the & and ! characters but nothing suggested worked at all for me.
If I pass 'C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing&video.mov' into my batch file, I get 'C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing back.
It truncates the string at the exact position of the ampersand.
I feel like there has to be a way to solve this, and that I'm missing something stupid. If I echo %cmdcmdline% it shows the full commandline with the &, so it's available somehow with that variable.
In conclusion: I'm sorry for the novel of a post. There is a lot of nuance in what I'm trying to accomplish that needs to be explained. My questions are as follows:
Can I accomplish this with Powershell only and somehow pre-escape single quotes?
Can I accomplish this with a batch file, and somehow pre-escape & and ! (and any other special characters that would cause failure)?
Any help at all would be hugely appreciated.
Edit1:
So in the most hideous and hackish way possible, I managed to solve my problem. But since it's so horrible and I feel horrible for doing it I am still looking for a proper solution.
Basically, to recap, when I do either of these variable assignments:
set "args=%*"
set "args=!%*!"
echo !args!
& and ! characters still break things, and I don't get a full enumeration of my files. Files with & get truncated, etc.
But I noticed when I do:
set "args=!cmdcmdline!"
echo !args!
I get the full commandline call with all special characters retained:
C:\WINDOWS\system32\cmd.exe /c ""C:\Tools\scripts\VSYSCopyPathToClipboardTest.bat" /C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\KylieCan't.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\The !Rodinians - Future Forest !Fantasy - FFF Trailer.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\Yelle - Je Veu&x Te Voir.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\Erik&Truffaz.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\my_file'name.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing&video.mov/"
So what I did was simply strip out the initial C:\WINDOWS\system32\cmd.exe /c ""C:\Tools\scripts\VSYSCopyPathToClipboardTest.bat" part of the string:
#echo off
setlocal enableDelayedExpansion
set "args=!cmdcmdline!"
set args=!args:C:\WINDOWS\system32\cmd.exe=!
set args=!args: /c ""C:\Tools\scripts\VSYSCopyPathToClipboard.bat" =!
set args=!args:'=''!
set args=!args:/='!
set args=!args:~0,-1!
echo !args!
pwsh.exe -noprofile -noexit -command "%~dpn0.ps1 -Path !args!
And... it works flawlessly. It handles any crazy character I throw at it without needing to escape anything. I know It's totally the most degenerate garbage way of approaching this, but not finding a solution anywhere leads me to desperate measures. :)
I am probably going to make the string removal a bit more universal since it literally breaks if I change the filename.
I am still VERY much open to other solutions should anyone know of a way to accomplish the same thing in a more elegant way.
A fully robust solution based on PowerShell's -Command (-c) CLI parameter that can handle ' characters in paths as well as $ and ` ones requires a fairly elaborate workaround, unfortunately:[1]
Use an aux. cmd.exe call that echoes the $files macro as-is and pipe that to pwsh.exe; make SingleInstanceAccumulator.exe double-quote the individual paths (as it does by default), but use no delimiter (d:"") in order to effectively output a string in the form "<path 1>""<path 2>""...
Make pwsh.exe reference the piped input via the automatic $input variable and split it into an array of individual paths by " (removing empty elements that are a side effect of splitting with -ne ''). The necessity for providing the paths via the pipeline (stdin) is discussed in more detail in this related answer.
The resulting array can safely be passed to your scripts.
Also, enclose the entire -Command (-c) argument passed to pwsh.exe in \"...\" inside the "-c:..." argument.
Note: You may get away without doing this; however, this would result in whitespace normalization, which (however unlikely) would alter a file named, say, foo bar.txt to foo bar.txt (the run of multiple spaces was normalized to a single space).
Escaping " characters as \" is necessary for PowerShell's -Command (-c) CLI parameter to treat them verbatim, as part of the PowerShell code to execute that is seen after initial command-line parsing, during which any unescaped " characters are stripped.
Therefore, the first command stored in the registry should be (adapt the second one analogously; note that there must be no space between the echo $files and the subsequent |):
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -d:"" "-c:cmd /c echo $files| pwsh.exe -noprofile -c \"& 'C:\Tools\scripts\VSYSCopyPathToClipboard.ps1' -Path ($input -split '\\\"' -ne '')\"" "%1"
Note:
If you modified your scripts to accept the paths as individual arguments rather than as an array, a much simpler solution via the -File CLI parameter (rather than -Command (-c)) is possible. This could be as simple as decorating the $Path parameter declaration with [Parameter(ValueFromRemainingArguments)] and then invoking the script without naming the target parameter explicitly (-Path):
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -d:" " "-c:pwsh.exe -noprofile -File \"C:\Tools\scripts\VSYSCopyPathToClipboard.ps1\" $files" "%1"
Note the use of -d:" " to make SingleInstanceAccumulator.exe space-separate the (double-quoted by default) paths. Since -File passes the pass-through arguments verbatim, there is no concern about what characters the paths are composed of.
Self-contained PowerShell sample code:
The following code defines a Copy Paths to Clipboard shortcut-menu command for all file-system objects (except drives):
No separate .ps1 script is involved; instead, the code passed to -Command / -c directly performs the desired operation (copying the paths passed to the clipboard).
The following helps with troubleshooting:
The full command line with which PowerShell was invoked ([Environment]::CommandLine) is printed, as is the list of paths passed ($file)
-windowstyle hidden is omitted to keep the console window in which the PowerShell commands visible and -noexit is added so as to keep the window open after the command has finished executing.
Prerequisites:
Download and build the SingleInstanceAccumulator project using Visual Studio (using the .NET SDK is possible, but requires extra work).
Place the resulting SingleInstanceAccumulator.exe file in one of the directories listed in your $env:Path environment variable. Alternatively, specify the full path to the executable below.
Note:
reg.exe uses \ as its escape character, which means that \ characters that should become part of the string stored in the registry must be escaped, as \\.
The sad reality as of PowerShell 7.2 is that an extra, manual layer of \-escaping of embedded " characters is required in arguments passed to external programs. This may get fixed in a future version, which may require opt-in. See this answer for details. The code below does this by way of a -replace '"', '\"' operation, which can easily be removed if it should no longer be necessary in a future PowerShell version.
# RUN WITH ELEVATION (AS ADMIN).
# Determine the full path of SingleInstanceAccumulator.exe:
# Note: If it isn't in $env:PATH, specify its full path instead.
$singleInstanceAccumulatorExe = (Get-Command -ErrorAction Stop SingleInstanceAccumulator.exe).Path
# The name of the shortcut-menu command to create for all file-system objects.
$menuCommandName = 'Copy Paths To Clipboard'
# Create the menu command registry key.
$null = reg.exe add "HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\$menuCommandName" /f /v "MultiSelectModel" /d "Player"
if ($LASTEXITCODE) { throw }
# Define the command line for it.
# To use *Windows PowerShell* instead, replace "pwsh.exe" with "powershell.exe"
# SEE NOTES ABOVE.
$null = reg.exe add "HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\$menuCommandName\command" /f /ve /t REG_EXPAND_SZ /d (#"
"$singleInstanceAccumulatorExe" -d:"" "-c:cmd /c echo `$files| pwsh.exe -noexit -noprofile -c \\"[Environment]::CommandLine; `$paths = `$input -split [char] 34 -ne ''; `$paths; Set-Clipboard `$paths\\"" "%1"
"# -replace '"', '\"')
if ($LASTEXITCODE) { throw }
Write-Verbose -Verbose "Shortcut menu command '$menuCommandName' successfully set up."
Now you can right-click on multiple files/folders in File Explorer and select Copy Paths to Clipboard in order to copy the full paths of all selected items to the clipboard in a single operation.
[1] An alternative is to use the -f option instead, which causes SingleInstanceAccumulator.exe to write all file paths line by line to an auxiliary text file, and then expands $files to that file's full path. However, this requires the target scripts to be designed accordingly, and it is their responsibility to clean up the auxiliary text file.
I'm trying to have a few scripts that I can map to run from my keyboard for quickly changing the monitor/screen brightness. After some searching on the internet, I found this script which works when I enter it into Powershell.
$monitor=#(gwmi WmiMonitorBrightnessMethods -ns root/wmi)[0]
$monitor.WmiSetBrightness(50,0)
After I saved it as a .ps1 file and tried running it from the file, powershell tells me: The term "path of the file" is not recognized as the name of a cmdlet, function... and so on.
I'm not familiar with Powershell at all, can someone help with what I need to add in order for the script to run properly?
By default you can't run a PowerShell script that is in the current directory without putting .\ in front of the script name, or calling the full path of the script.
This is a security feature.
If you are in the directory that contains the script, run it by executing in a PowerShell window:
.\yourscript.ps1
Where yourscript is the name of your script.
See here for more information: https://ss64.com/ps/syntax-run.html
You may also see this error if your script has spaces in its name. If that is the case, enclose the path in quotes:
.\'your script.ps1'
Here's my configuration:
On the build log, I only see the output of the first two lines, and then "Process exited with code 0" as the last output of this build step.
I tried opening a terminal in the build server in the SYSTEM account (using PsTools), since Team City is configured to run under said account. Then, I created a Test.ps1 file with the same content and ran a command just like Team City's:
[Step 1/4] Starting: C:\Windows\system32\cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NonInteractive -Command - <C:\TeamCity\buildAgent\temp\buildTmp\powershell5129275380148486045.ps1 && exit /b %ERRORLEVEL%
(except for the path to the .ps1 file and the cmd.exe initial part, of course). I saw the output of the two first lines, and then the terminal disappeared all of a sudden!
Where did I mess up? I'm new to Powershell, by the way.
The stdin command option of Powershell has some weirdness around multiline commands like that.
You script in the following form would work:
write-host "test"
write-host "test2"
if("1" -eq "1"){write-host "test3 in if"} else {write-host "test4 in else"}
The ideal way would be to use the Script : File option in TeamCity which will will run the script you specify using the -File parameter to Powershell.
If you don't want to have a file and having VCS, in the current setup, change Script Execution Mode to Execute .ps1 file with -File argument.
I've had this problem with inline powershell scripts with TeamCity (right up until the current version of 7.1.3). I've found the problem to be the tab character rather than multi-line statements. Try replacing the tab characters with spaces (while still remaining multi-line) and the script should run fine.
You could try putting the brace opening the block on the same line as the If.
I.e.,
If ('1' -eq '1') {
...
}
Else {
...
}
That's the usual styling you see with Powershell, and obviously, putting the braces on the next line can cause problems.
I wanted to create some clickable PowerShell scripts, and I found this answer that I modified slightly to be:
;#Findstr -bv ;#F %0 | powershell -noprofile -command - & goto:eof
# PowerShell Code goes here.
I understand Findstr is passing all lines that don't begin with ;#F to the right-hand side of the pipe and the dash specifies where the input should go, but what is the dash character called and where is it documented?
I found an explanation of CMD's pipe operator on Microsoft's Using command redirection operators, but it doesn't mention anything about the dash character.
I presume you mean the - that precedes the &. It has nothing to do with the pipe operator, it is a directive for powershell.
Here is a description of the -Command option excerpted from powershell help (accessed by powershell /?)
-Command
Executes the specified commands (and any parameters) as though they were
typed at the Windows PowerShell command prompt, and then exits, unless
NoExit is specified. The value of Command can be "-", a string. or a
script block.
If the value of Command is "-", the command text is read from standard
input.
BTW - I did not realize FINDSTR accepted - as an option indicator until I saw your question. I've only seen and used /. Good info to know.
The - is to Powershell saying accept the command(s) from stdin rather than from arguments. This is not a feature in cmd / batch and piping. It would work with < as well.
Powershell version 2 adds a "Run with Powershell" right-click context menu item to run scripts . Here you'll find some enhanced shell extensions to run Powershell scripts with elevated privileges. However if you just want to run a Powershell script by double clicking a file, I recommend just calling the Powershell script from a batch script instead of trying to embed Powershell code in the batch script. In the batch script use this: powershell.exe -file "%~dp0MyScript.ps1" where %~dp0 expands to the current directory. This essentially creates a bootstrapper for your Powershell script that you can double click to launch your Powershell script.