PowerShell in batch : unexpected directory creation using a FOR loop and Tmp variable :-O

45 views Asked by At

I have to upgrade an existing batch script, for a new need. This one is launching PowerShell subscripts. I found an unexpected behaviour, that I can reproduce on a smaller use case.

Create a "test" directory Inside, create 2 directories "1" and "2" In directory "1", create 2 empty files, "1.txt" and "2.txt" In directory "2", create 2 empty files, "3.txt" and "4.txt" In "test", create text.bat with the following code:

@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

When running the batch, files.txt and files2.txt are created into "1" and "2", as expected. The files of "1" are printed out as expected. BUT right after the second Powershell script, a folder, named "files.txt" is created into folder "2". I made some test with other names for the Tmp variable, and oddly the pb does not appear with a variable named differently ("Tmp2" for example)

Can someone explain this strange behaviour ?

I'm on Windows 10, with PSVersion 5.1.19041.4046 PSEdition Desktop PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...} BuildVersion 10.0.19041.4046 CLRVersion 4.0.30319.42000 WSManStackVersion 3.0 PSRemotingProtocolVersion 2.3 SerializationVersion 1.1.0.1

1

There are 1 answers

0
Mofi On

The user environment variables TEMP and TMP are defined by Windows default with the path of the directory for temporary files. Open a command prompt window and run set TEMP and set TMP to see them with their values. Run just set to see all environment variables defined by default for the current user account and see also Windows environment variables.

Run in the command prompt window reg QUERY HKCU\Environment and their is output most likely at least:

HKEY_CURRENT_USER\Environment
    TEMP    REG_EXPAND_SZ    %USERPROFILE%\AppData\Local\Temp
    TMP    REG_EXPAND_SZ    %USERPROFILE%\AppData\Local\Temp

The command line set Tmp=%%b in the batch file redefines in the FOR loop the local environment variable TMP with the name of the current file read from the file files.txt which is on last loop run the string files.txt.

That is the source of the problem. The redefinition of the environment variable TMP holding per definition the full path to the directory for temporary files with the string files.txt is no good idea.

There can be viewed with the free Windows Sysinternals (Microsoft) tool Process Monitor all the registry and file system accesses done by powershell.exe on being run by cmd.exe a second time.

I recommend to change the command line

PowerShell "Get-ChildItem -Recurse | Select-Object -ExpandProperty Name | Out-File -FilePath files2.txt -Encoding ASCII"

in the batch file to just

PowerShell "echo hello"

That makes it easier to see in log of Process Monitor what happens in the background.

Or there is used following single line batch file for reproducing the issue:

@setlocal & set "TMP=files.txt" & %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe "echo hello" & endlocal

Recommended filters set in Process Monitor in addition to the defaults:

Column Relation Value Action
Process Name is cmd.exe Include
Process Name is powershell.exe Include

PowerShell wants to create a small script file __PSScriptPolicyTest_*.*.ps1 in the directory for temporary files of which directory path is assigned to the environment variable TMP. That should be the directory files.txt in the current working directory after the last redefinition of the environment variable TMP in the FOR loop respectively as second command in the single line batch file. The directory files.txt does currently not exist in the current directory. But that is no problem for PowerShell as it creates now successfully the directory files.txt.

Then the script file __PSScriptPolicyTest_*.*.ps1 is created in the temporary files directory files.txt in the current working directory.

It can be seen with Process Monitor that each execution of PowerShell causes lots of registry and file system accesses before even a simple PS command like echo hello is executed.

I recommend reading also What is the reason for "X is not recognized as an internal or external command, operable program or batch file"? The environment variable is here TMP and not PATH but cmd.exe calls the Windows kernel library function CreateProcess for execution of powershell.exe with a null pointer for the function parameter lpEnvironment. The Windows kernel function CreateProcess makes therefore a copy of the current environment variables list of process cmd.exe with the redefined TMP variable for process powershell.exe and standard actions of PowerShell are done now with wrong temporary files directory.