這是一個看似復雜的問題,但我會盡力解釋這個問題。
我有一個簡單的包裝腳本,如下所示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只是我可用的一個功能,可將路徑/檔案名復制到剪貼板。這與問題無關,只是假設它按照它所說的去做。
呼叫包裝器的方式是通過 Windows 右鍵單擊??背景關系選單。這涉及在此處創建一個密鑰:HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\。
我的看起來像這樣:

命令如下:
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -Path $files" "%1"
與“復制為檔案名”類似:
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -FilenamesOnly -Path $files" "%1"
我在這里使用一個名為
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.
uj5u.com熱心網友回復:
作為存盤在注冊表中的命令列的逐字部分,使用
-q:\\\"代替-q:'[1],這意味著SingleInstanceAccumulator.exe將逐字\"用作分隔符,PowerShell 最終將其視為"(見下文)。- 使用
"作為最終有效定界符確保了包含甚至路徑'被正確傳遞原樣。
- 使用
此外,將傳遞給in的整個
-Command(-c) 引數括在引數內。pwsh.exe\"...\""-c:..."- 注意:如果不這樣做,您可能會逃脫;然而,這將導致空白規范化,它(但不太可能)會改變一個名為,比如說檔案,
foo bar.txt以foo bar.txt(多個空格的跑步被歸為一個空格)。
- 注意:如果不這樣做,您可能會逃脫;然而,這將導致空白規范化,它(但不太可能)會改變一個名為,比如說檔案,
這會導致SingleInstanceAccumulator.exe將其占位符$files擴展到的單個檔案路徑包含在\"...\"傳遞給的命令列中pwsh.exe。
PowerShell 的( ) CLI引數需要轉義"字符以逐字處理它們,作為要執行的 PowerShell 代碼的一部分,在初始命令列決議后會看到,在此期間任何未轉義的字符都將被剝離。\"-Command-c "
因此,存盤在注冊表中的第一個命令應該是(類似地調整第二個):
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:\\\" "-c:pwsh -noprofile -windowstyle hidden -Command \"& 'C:\Tools\scripts\VSYSCopyPathToClipboard.ps1' -Path $files\"" "%1"
自包含的 PowerShell 示例代碼:
以下代碼Copy Paths to Clipboard為所有檔案系統物件定義了一個快捷選單命令:
不
.ps1涉及單獨的腳本;相反,傳遞給-Command/的代碼-c直接執行所需的操作(復制傳遞到剪貼板的路徑)。以下有助于排除故障:
[Environment]::CommandLine列印呼叫 PowerShell 的完整命令列 ( ),以及傳遞的路徑串列 ($file)-windowstyle hidden省略以保持 PowerShell 命令可見的控制臺視窗,并-noexit添加以在命令執行完成后保持視窗打開。
先決條件:
使用 Visual Studio下載并構建
SingleInstanceAccumulator專案(使用 .NET SDK 是可能的,但需要額外的作業)。將生成的
SingleInstanceAccumulator.exe檔案放在$env:Path環境變數中列出的目錄之一中。或者,指定下面可執行檔案的完整路徑。
筆記:
reg.exe用途\為轉義字符,這意味著\應該成為存盤在注冊表字串的一部分字符必須被轉義,如\\。PowerShell 7.2 的可悲現實是,在傳遞給外部程式的引數中需要額外的手動
\轉義嵌入"字符層。這可能會在未來版本中得到修復,這可能需要選擇加入。有關詳細資訊,請參閱此答案。下面的代碼通過一個操作來做到這一點,如果在未來的 PowerShell 版本中不再需要它,可以很容易地將其洗掉。-replace '"', '\"'
# 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" -q:\\\\\\" "-c:pwsh.exe -noexit -c \\"[Environment]::CommandLine; `$files; Set-Clipboard `$files; pause\\"" "%1"
"@ -replace '"', '\"')
if ($LASTEXITCODE) { throw }
Write-Verbose -Verbose "Shortcut menu command '$menuCommandName' successfully set up."
現在,您可以在檔案資源管理器中右鍵單擊多個檔案/檔案夾并進行選擇Copy Paths to Clipboard,以便在一次操作中將所有選定專案的完整路徑復制到剪貼板。
[1] 另一種方法是使用該-f選項,這會導致SingleInstanceAccumulator.exe將所有檔案路徑逐行寫入輔助文本檔案,然后擴展$files為該檔案的完整路徑。但是,這需要相應地設計目標腳本,清理輔助文本檔案是他們的責任。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/383451.html
標籤:powershell batch-file cmd command-line escaping
上一篇:批處理檔案注冊查詢
