我必须升级现有的批处理脚本以满足新的需求。这个正在启动 PowerShell 下标。我发现了一个意想不到的行为,我可以在较小的用例上重现它。
创建一个“测试”目录 在里面,创建2个目录“1”和“2” 在目录“1”中,创建2个空文件“1.txt”和“2.txt” 在目录“2”中,创建2个空文件“3.txt”和“4.txt” 在“test”中,使用以下代码创建text.bat:
@echo OFF
SETLOCAL EnableDelayedExpansion
REM go to directory "1" and get file names into files.txt
chdir 1
PowerShell "Get-ChildItem -Recurse | Select-Object -ExpandProperty Name | Out-File -FilePath files.txt -Encoding ASCII"
REM loop over file names and print them
for /F "usebackq tokens=* " %%b in ("files.txt") do (
REM set a variable to each file name and print it
set Tmp=%%b
echo !Tmp!
)
REM go to directory "2" and get file names into files.txt
chdir ..
chdir 2
pause
REM At this stage, nothing strange in folder "2"
PowerShell "Get-ChildItem -Recurse | Select-Object -ExpandProperty Name | Out-File -FilePath files2.txt -Encoding ASCII"
REM At this stage, in directory "2", files2.txt is created correctly. BUT a unexpected DIRECTORY has been created, which name if the one of the last looped file in directory "1" (in that case, "files.txt")
pause
运行批处理时,files.txt 和 files2.txt 按预期创建为“1”和“2”。 “1”的文件按预期打印出来。 但在第二个 Powershell 脚本之后,会在文件夹“2”中创建一个名为“files.txt”的文件夹。 我用 Tmp 变量的其他名称进行了一些测试,奇怪的是 pb 没有出现具有不同名称的变量(例如“Tmp2”)
有人可以解释这种奇怪的行为吗?
我使用的是 Windows 10, PS版本5.1.19041.4046 PS版桌面版 PS兼容版本 {1.0、2.0、3.0、4.0...} 构建版本 10.0.19041.4046 CLR版本 4.0.30319.42000 WSManStack版本3.0 PSRemoting协议版本2.3 序列化版本1.1.0.1
user环境变量
TEMP
和TMP
由Windows默认定义为临时文件目录的路径。打开 命令提示符 窗口并运行 set TEMP
和 set TMP
以查看它们及其值。只需运行 set
即可查看当前用户帐户默认定义的所有环境变量,另请参阅 Windows 环境变量。
在命令提示符窗口中运行
reg QUERY HKCU\Environment
,它们的输出最有可能至少是:
HKEY_CURRENT_USER\Environment
TEMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp
TMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp
批处理文件中的命令行
set Tmp=%%b
在FOR循环中重新定义local环境变量TMP
,其中包含从文件files.txt
读取的当前文件的名称,该文件是在最后一个循环运行字符串 files.txt
。
这就是问题的根源。重新定义环境变量
TMP
,根据定义保存带有字符串 files.txt
的临时文件目录的完整路径,这并不是一个好主意。
可以使用免费的 Windows Sysinternals (Microsoft) 工具 Process Monitor 查看由
powershell.exe
第二次运行时 cmd.exe
完成的所有注册表和文件系统访问。
我建议更改命令行
PowerShell "Get-ChildItem -Recurse | Select-Object -ExpandProperty Name | Out-File -FilePath files2.txt -Encoding ASCII"
在批处理文件中
PowerShell "echo hello"
这使得您可以更轻松地在 Process Monitor 的日志中查看后台发生的情况。
或者使用以下单行批处理文件来重现问题:
@setlocal & set "TMP=files.txt" & %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe "echo hello" & endlocal
除了默认值之外,在Process Monitor中设置的推荐过滤器:
专栏 | 关系 | 价值 | 行动 |
---|---|---|---|
进程名称 | 是 | cmd.exe | 包括 |
进程名称 | 是 | powershell.exe | 包括 |
PowerShell想要在临时文件目录中创建一个小脚本文件
__PSScriptPolicyTest_*.*.ps1
,并将其目录路径分配给环境变量TMP
。这应该是在 FOR循环中最后一次重新定义环境变量
files.txt
后当前工作目录中的目录 TMP
分别作为单行批处理文件中的第二个命令。当前目录中当前不存在目录files.txt
。但这对于 PowerShell 来说不是问题,因为它现在成功创建了目录 files.txt
。
然后在当前工作目录的临时文件目录
__PSScriptPolicyTest_*.*.ps1
中创建脚本文件files.txt
。
通过Process Monitor可以看到,每次执行PowerShell都会导致大量的注册表和文件系统访问,甚至在执行像
echo hello
这样的简单PS命令之前。
我还建议阅读 “X 不被识别为内部或外部命令、可操作程序或批处理文件”的原因是什么? 环境变量在这里
TMP
而不是 PATH
但 cmd.exe
调用Windows 内核库函数 CreateProcess 用于执行 powershell.exe
,函数参数 lpEnvironment
为空指针。因此,Windows 内核函数 CreateProcess
使用进程 cmd.exe
重新定义的 TMP
变量复制进程 powershell.exe
的当前环境变量列表,并且现在使用错误的临时文件目录完成 PowerShell 的标准操作。