How to check in command-line if a given file or directory is locked (used by any process)? - command-line

I need to know that before any attempt to do anything with such file.

Not sure about locked directories (does Windows have that?)
But detecting if a file is being written to by another process is not difficult.
#echo off
2>nul (
>>test.txt echo off
) && (echo file is not locked) || (echo file is locked)
I use the following test script from another window to place a lock on the file.
(
>&2 pause
) >> test.txt
When I run the 2nd script from one window and then run the 1st script from a second window, I get my "locked" message. Once I press <Enter> in the 1st window, I get the "unlocked" message if I rerun the 1st script.
Explanation
Whenever the output of a command is redirected to a file, the file of course must be opened for write access. The Windows CMD session will attempt to open the file, even if the command does not produce any output.
The >> redirection operator opens the file in append mode.
So >>test.txt echo off will attempt to open the file, it writes nothing to the file (assuming echo is already off), and then it closes the file. The file is not modified in any way.
Most processes lock a file whenever they open a file for write access. (There are OS system calls that allow opening a file for writing in a shared mode, but that is not the default). So if another process already has "test.txt" locked for writing, then the redirection will fail with the following error message sent to stderr - "The process cannot access the file because it is being used by another process.". Also an error code will be generated upon redirection failure. If the command and the redirection succeed, then a success code is returned.
Simply adding 2>nul to the command will not prevent the error message because it redirects the error output for the command, not the redirection. That is why I enclose the command in parentheses and then redirect the error output to nul outside of the parens.
So the error message is effectively hidden, but the error code is still propagated outside of the parens. The standard Windows && and || operators are used to detect whether the command inside the parens was successful or failed. Presumably echo off will never fail, so the only possible reason for failure would be the redirection failed. Most likely it fails because of a locking issue, though technically there could be other reasons for failure.
It is a curious "feature" that Windows does not set the %ERRORLEVEL% dynamic variable to an error upon redirection failure unless the || operator is used. (See File redirection in Windows and %errorlevel%). So the || operator must read the returned error code at some low level, not via the %ERRORLEVEL% variable.
Using these techniques to detect redirection failure can be very useful in a batch context. It can be used to establish locks that allow serialization of multiple events in parallel processes. For example, it can enable multiple processes to safely write to the same log file at the "same" time. How do you have shared log files under Windows?
EDIT
Regarding locked folders. I'm not sure how Windows implements this, perhaps with a lock. But if a process has an active directory involving the folder, then the folder cannot be renamed. That can easily be detected using
2>nul ren folderName folderName && echo Folder is NOT locked || echo folder is LOCKED
EDIT
I have since learned that (call ) (with a space) is a very fast command without side effects that is guaranteed to succeed with ERRORLEVEL set to 0. And (call) (without a space) is a fast command without side effects that is guaranteed to fail with ERRORLEVEL 1.
So I now use the following to check if a file is locked:
2>nul (
>>test.txt (call )
) && (echo file is not locked) || (echo file is locked)

In addition to great answer from dbenham, the following form finally help me understand used technique:
( type nul >> file.txt ) 2>nul || echo File is locked!
type nul command gives an empty output and does not affect the current echo setting like echo off command in orginal.
If you want to use if–then–else condition remember of correct order - success statement (&&) is going first and alternate statement (||) is going second:
command && (echo Command is successful) || (echo Command has failed)

If you download and install the Windows Server 2003 Resource Kit Tools there is a utility called oh.exe that will list open file handles for a given file:
http://www.microsoft.com/en-us/download/details.aspx?id=17657
Once you install it, reboot your machine and you'll be able to use the utility. You can see all the options in the Help and Support center as well as by typing oh /? in the command prompt.
(Info from : http://windowsxp.mvps.org/processlock.htm )

Note, the writing of a message stating the file status was less helpful than a batch command that set a return code. For example, return code 1 if file is locked.
#echo off
2>nul (
>>test.tmp echo off
) && (EXIT /B 0) || (EXIT /B 1)

Other answers resulted in side-effects for me. For instance, the following from this answer will cause file watchers to trigger:
COPY /B app.exe+NUL app.exe
And the following from the top answer here would overwrite any changes made to the target file:
2>nul (
>>test.txt (call )
) && (echo file is not locked) || (echo file is locked)
On modern version of Windows, you can call into Powershell to accomplish this task with zero side-effects:
powershell -Command "$FileStream = [System.IO.File]::Open('%FILE%', 'Open', 'Write'); $FileStream.Close(); $FileStream.Dispose()" && (echo File is not locked) || (echo File is locked)
This will not modify the file or its metadata at all, even when it isn't locked.
Example usage
I use this method in my custom git mergetool script for merging Excel files. The way a git mergetool works is that it waits for the script shell to exit, then checks if the target file was modified, prompting with "XX.yyy seems unchanged. Was the merge successful [y/n]?" if it wasn't. However, Excel (at least the version I'm using) does not spawn a new process for each file it opens. So if Excel is already open, the script will exit immediately, and git will detect no changes to the file, resulting in that prompt.
So I devised the method above, and I use it like below:
REM block until MERGED is closed
:loop
powershell -Command "$FileStream = [System.IO.File]::Open('%MERGED%', 'Open', 'Write'); $FileStream.Close(); $FileStream.Dispose()" >NUL 2>NUL || (goto :loop)

Incidentally, dbenham's solution also seems to be an effective way to find out if a process is running. It was the best solution I found for the following application:
start /b "job1.exe >> job1.out"
start /b /wait "job2.exe >> job2.out"
::wait for job1 to finish using dbenham's code to check if job1.out is in use
comparejobs.exe

Just i want to share with you an example of my script based on #dbenham's trick
Description of this script : Check_Locked_Files.bat :
This script can scan and check for locked files on a set of folders that can be modified into the script; for example, i have chosen those set of folders to be scanned :
Set Folders=^
^ "%ProgramData%\Microsoft\Windows\Start Menu\Programs\Startup"^
^ "%UserProfile%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"^
^ "%ProgramFiles%\Internet Explorer"^
^ "%ProgramFiles%\Skype"^
^ "%ProgramFiles%\TeamViewer"^
^ "%WinDir%\system32\drivers"^
^ "%Temp%"
The output result is in HTML format for more readability.
If the file is locked we show it in red color otherwise we show it in green color.
And the whole script is : Check_Locked_Files.bat
#echo off
Rem This source is inspired from here
Rem hxxps://stackoverflow.com/questions/
Rem 10518151/how-to-check-in-command-line-if-a-given-file-or-directory-is-locked-used-by-any?answertab=active#tab-top
Rem Thanks for dbenham for this nice trick ;)
Mode con cols=90 lines=5 & color 9E
Title Scan and Check for Locked Files by Hackoo 2017
set "LogFile=%~dp0%~n0.html"
(
echo ^<html^>
echo ^<title^> Scan and Check for locked files by Hackoo 2017^</title^>
echo ^<body bgcolor^=#ffdfb7^>
echo ^<center^>^<b^>Log Started on %Date% # %Time% by the user : "%username%" on the computer : "%ComputerName%"^</b^>^</center^>
)> "%LogFile%"
echo(
echo --------------------------------------------------------------------------
echo Please Wait a while ....... Scanning for locked files is in progress
echo --------------------------------------------------------------------------
Rem We Play radio just for fun and in order to let the user be patient until the scan ended
Call :Play_DJ_Buzz_Radio
Timeout /T 3 /nobreak>nul
cls
Set Folders=^
^ "%ProgramData%\Microsoft\Windows\Start Menu\Programs\Startup"^
^ "%UserProfile%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"^
^ "%ProgramFiles%\Internet Explorer"^
^ "%ProgramFiles%\Skype"^
^ "%ProgramFiles%\TeamViewer"^
^ "%WinDir%\system32\drivers"^
^ "%Temp%"
#For %%a in (%Folders%) Do (
( echo ^<hr^>^<font color^=DarkOrange^>^<B^>Folder : %%a^</B^>^</font^>^<hr^>) >> "%LogFile%"
#for /f "delims=" %%b in ('Dir /A-D /s /b "%%~a\*.*"') do (
Call :Scanning "%%~nxb"
Call:Check_Locked_File "%%~b" "%LogFile%"
)
)
(
echo ^<hr^>
echo ^<center^>^<b^>Log ended on %Date% # %Time% on the computer : "%ComputerName%"^</b^>^</center^>
echo ^</body^>
echo ^</html^>
)>> "%LogFile%"
Start "" "%LogFile%" & Call :Stop_Radio & exit
::***********************************************************************************
:Check_Locked_File <File> <LogFile>
(
2>nul (
>>%1 (call )
) && ( #echo ^<font color^=green^>file "%~1"^</font^>^<br^>
) || (
#echo ^<font color^=red^>file "%~1" is locked and is in use^</font^>^<br^>
)
)>>%2 2>nul
exit /b
::***********************************************************************************
:Scanning <file>
cls
echo(
echo --------------------------------------------------------------------------
echo Please Wait a while... Scanning for %1
echo --------------------------------------------------------------------------
exit /b
::***********************************************************************************
:Play_DJ_Buzz_Radio
Taskkill /IM "wscript.exe" /F >nul 2>&1
Set "vbsfile=%temp%\DJBuzzRadio.vbs"
Set "URL=http://www.chocradios.ch/djbuzzradio_windows.mp3.asx"
Call:Play "%URL%" "%vbsfile%"
Start "" "%vbsfile%"
Exit /b
::**************************************************************
:Play
(
echo Play "%~1"
echo Sub Play(URL^)
echo Dim Sound
echo Set Sound = CreateObject("WMPlayer.OCX"^)
echo Sound.URL = URL
echo Sound.settings.volume = 100
echo Sound.Controls.play
echo do while Sound.currentmedia.duration = 0
echo wscript.sleep 100
echo loop
echo wscript.sleep (int(Sound.currentmedia.duration^)+1^)*1000
echo End Sub
)>%~2
exit /b
::**************************************************************
:Stop_Radio
Taskkill /IM "wscript.exe" /F >nul 2>&1
If Exist "%vbsfile%" Del "%vbsfile%"
::**************************************************************

:: Create the file Running.tmp
ECHO %DATE% > Running.tmp
ECHO %TIME% >> Running.tmp
:: block it and do the work
(
>&2 CALL :Work 30
) >> Running.tmp
:: when the work is finished, delete the file
DEL Running.tmp
GOTO EOF
:: put here the work to be done by the batch file
:Work
ping 127.0.0.1 -n 2 -w 1000 > NUL
ping 127.0.0.1 -n %1 -w 1000 > NUL
:: when the process finishes, the execution go back
:: to the line after the CALL

In case you want to use this in a Cygwin Bash, here are the one-liners:
# To lock a file: (in a different window)
cmd.exe /C "( >&2 pause ) >> test.txt"
#Press any key to continue . . .
# To test if a file is locked (with text)
cmd.exe /C "2>nul ( >>test.txt (call ) ) && (echo ok) || (echo locked)"
#locked
# To test if a file is locked (with POSIX exit code)
cmd.exe /C "2>nul ( >>test.txt (call ) ) && (exit /b 0) || (exit /b 1)"
echo $?
#1

In case of windows network share you can try powershell command:
Get-SmbOpenFile
For example execute on file server command as administrator:
Get-SmbOpenFile | Where-Object -Property Path -match "file.txt"

Related

Run batch-file always with administration rights, link to a program and point to a non-specific filet that should be opened with the program

Starting my batch-file looks like this:
start "" "%PROGRAMFILES%\program.exe"
If i am putting there a %1 at the end:
start "" "%PROGRAMFILES%\program.exe" %1
, i am able to open a non-specifif file,
if i change "always open with" and link this to my batch-file.
But there is a problem - i would like to run my program as an administrator.
If i create a link to my programm, give this link admin-rights and define that my filetype-ending should be opend with the "link to the program" it completely ignores the admin-rights.
Therefore i found an other solution:
:: BatchGotAdmin
:-------------------------------------
REM --> Check for permissions
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
REM --> If error flag set, we do not have admin.
if '%errorlevel%' NEQ '0' (
echo Requesting administrative privileges...
goto UACPrompt
) else ( goto gotAdmin )
:UACPrompt
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
set params = %*:"=""
echo UAC.ShellExecute "%PROGRAMFILES%\program.exe", "/c %~s0
%params%", "", "runas", 1 >> "%temp%\getadmin.vbs"
"%temp%\getadmin.vbs"
del "%temp%\getadmin.vbs"
exit /B
:gotAdmin
pushd "%CD%"
CD /D "%~dp0"
With the help of this code my batch-file runs as administrator and the program starts, but the file doesn´t open in window. If tried several things with adding a "%1", but it doesn´t work.
Any idea?
Thanks for Help!

according to choice specific line should run in cmd.exe using batch file

I wanted to give this choice
#echo off
echo what operation you wanted to perform
echo a:creating a new web server?
echo or
echo b:Edit root url for already existed web server
set /P var = "What is your option a or b"
if "%var%" == "A" (
cd C:\Program Files\Infor\Mongoose\Tools
infordbcl.exe addwebserver -name:sample -product:Mongoose -rooturl:https://uscovwmongoose3
)
if "%var%" == "B" (
infordbcl.exe addwebserver -name:sample -product:Mongoose -rooturl:https://mongoose.com -
mode:edit
)
I ran this batch file using powershell.
After entering the choice A or B, the commands specified in if blocks are not getting executed in cmd.exe
Is their any syntax errors in it, please let me know
This is how Choice could be used here:
#echo off
Choice /N /C ce /M " <?> (C)reate new Server <?> (E)dit root url for existing web server"
If Errorlevel 2 (
REM If the Change directory command is also meant to execute in this block, remove and place CD command prior to choice command.
infordbcl.exe addwebserver -name:sample -product:Mongoose -rooturl:https://mongoose.com -mode:edit
) Else (
REM confirm if lack of url extension is correct
cd "C:\Program Files\Infor\Mongoose\Tools"
infordbcl.exe addwebserver -name:sample -product:Mongoose -rooturl:https://uscovwmongoose3
)
Explanation:
/N hides the default prompt (whatever keys are defined using /C or the default Y/N if /C is not used)
/C allows definition of keys within the range 0-9 and A-Z
errorlevel for each key is set according to occurance after the /C
switch
for /C ce, c will return Errorlevel 1, e will return errorlevel 2
and so on
/M "string" allows a custom string to be defined to explain the Choice. Encase the string within doublequotes.
When you test the returned Errorlevel
You must test from Highest to Lowest.
This is because the command intepreter reads If Errorlevel n as If Errorlevel n or higher
For more information and usage examples, type choice /? in cmd.exe

How to launch html report from cake (build)

I'm using cake in my projects to build, run unit tests, check code coverage and then generate an HTML report (using ReportGenerator). This is all working correctly, and I can open the generated report in my browser.
However, when I was previously using a dos batch file to do this, it would also launch my default browser and load the report after it was generated, but I can't find a way to do that with cake.
Here are the contents of the batch file I've been using:
#ECHO OFF
SET SearchDirectory=%~dp0Grapevine.Tests\bin\Debug
SET DllContainingTests=%~dp0Grapevine.Tests\bin\Debug\Grapevine.Tests.dll
for /R "%~dp0packages" %%a in (*) do if /I "%%~nxa"=="xunit.console.exe" SET TestRunnerExe=%%~dpnxa
for /R "%~dp0packages" %%a in (*) do if /I "%%~nxa"=="OpenCover.Console.exe" SET OpenCoverExe=%%~dpnxa
for /R "%~dp0packages" %%a in (*) do if /I "%%~nxa"=="ReportGenerator.exe" SET ReportGeneratorExe=%%~dpnxa
if not exist "%~dp0GeneratedReports" mkdir "%~dp0GeneratedReports"
call :RunOpenCoverUnitTestMetrics
if %errorlevel% equ 0 (
call :RunReportGeneratorOutput
)
if %errorlevel% equ 0 (
call :RunLaunchReport
)
exit /b %errorlevel%
:RunOpenCoverUnitTestMetrics
"%OpenCoverExe%" ^
-target:"%TestRunnerExe%" ^
-targetargs:"\"%DllContainingTests%\"" ^
-filter:"+[*]* -[*.Tests*]* -[*]*.*Config -[xunit*]* -[*]Grapevine.Interfaces.*" ^
-mergebyhash ^
-skipautoprops ^
-register:user ^
-output:"%~dp0GeneratedReports\CoverageReport.xml"^
-searchdirs:"%SearchDirectory%"
exit /b %errorlevel%
:RunReportGeneratorOutput
"%ReportGeneratorExe%" ^
-reports:"%~dp0\GeneratedReports\CoverageReport.xml" ^
-targetdir:"%~dp0\GeneratedReports\ReportGeneratorOutput"
exit /b %errorlevel%
:RunLaunchReport
start "report" "%~dp0\GeneratedReports\ReportGeneratorOutput\index.htm"
exit /b %errorlevel%
I have tried using the following:
StartProcess(new FilePath("./GeneratedReports/ReportGeneratorOutput/index.htm"));
To which I receive the following error:
An error occured when executing task 'generate-report'.
Error: The specified executable is not a valid application for this OS platform.
I have verified that the path is correct and the file exists, and that copy/pasting the file path on the command line indeed opens the file in my default browser.
I couldn't figure out a way to do this with just Cake, so I resorted to calling CMD with StartProcess:
if (IsRunningOnWindows()) {
StartProcess("cmd", new ProcessSettings {
Arguments = $"/C start \"\" {testCoverageReportPath}index.htm"
});
}
This works great for my needs.
You can do this using the StartProcess alias example:
FilePath reportpath = File("./GeneratedReports/ReportGeneratorOutput/index.htm");
StartProcess(reportpath);
What finally worked for me was this:
if (IsRunningOnWindows())
{
StartProcess("explorer.exe", reportPath);
}
Obviously, this won't work on non-windows environments, but that's outside the scope of my needs. Everything else I tried produced an error either that the file could not be found or that the executable was invalid for the OS.

How to stop counting of timeout automatically if command finishes earlier?

In my script I'm using timeout to set a maximum timelimit for executing a command.
start 7z.exe t %%f
if errorlevel 2 (DEL %%f & echo File broken. Reload File!)
timeout /t 3600
If the execution lasts longer than an hour the timeout should break the execution. That's working without any problems.
But how can I ignore the timeout counting and proceed my script automatically if the command finishes before the timestamp?
As curretly written, start command without /W switch does not affect/change errorlevel! Proof:
==> hfhgf
'hfhgf' is not recognized as an internal or external command,
operable program or batch file.
==> echo %errorlevel%
9009
==> start "" ping.exe localhost
==> echo %errorlevel%
9009
==> start "" /W ping.exe localhost
==> echo %errorlevel%
0
Use /W or /WAIT switch (start application and wait for it to terminate) and remove all that timeout line as follows. I guess that your code snippet could be do part of a for loop:
for %%f in (*.zip) do (
(call )
start "" /W 7z.exe t "%%f"
if errorlevel 2 (
echo File broken. Reload File %%f
DEL "%%f"
)
)
On (call ) explanation, see Dave Benham's reply to setting ERRORLEVEL to 0 question:
If you want to force the errorlevel to 0, then you can use this
totally non-intuitive, but very effective syntax: (call ). The space
after call is critical. If you want to set the errorlevel to 1, you
can use (call). It is critical that there not be any space after
call.

BATCH/CMD: Print each line of a .txt into a variable

Goal: Have every dll file in a computer passed into regsvr32.exe
Accomplished:
cd\
::REM Exports every file and directory (/s) into a file in root of c: called dir.txt with only file names (/b)
dir /s /b>>dir.txt
::REM Now, this will contain every extension type. We only want dll's, so we findstr it into dll.txt:
findstr ".dll$" dir.txt>>dll.txt
The Kink:
Now, if I want to regsvr32.exe "file" every file that is now in dll.txt, I somehow need to get out every individual filename that is individually on each line. I was wondering if there is a third party command line tool that can export each line of the file into a variable. This way, I could:
==========
::REM assume this tool had switches /l for the line number, and /v:"" for variable to use, and used "file" at the end:
set line=1
:loop
set dll=
tool.exe /l %line% /v:"dll" "dll.txt"
::REM We if defined here because if the line doesn't exist in dll.txt, the tool would return nothing to %dll%
if not defined %dll% exit
::REM With the variable defined, we can continue
regsvr32.exe %dll%
set /a line=%line%+1
goto loop
=======================
Then the tool would process each path of each line of the file until it exits automatically, because there would be no more lines. Notice right after loop I set dll to nothing so that 'if not defined' will work each time.
If this type of third-party tool cannot be done, is there a way to do that with for??
I honestly never learned for, and tried to but could never figure it out.
Any help is greatly appreciated!
Sorry if this has already been answered.
EDIT/UPDATE:
I have discovered how I will make this work.
Thanks to: http://pyrocam.com/re-register-all-dlls-to-fix-no-such-interface-supported-error-in-windows-7-after-installing-ie7-standalone/
and : Read a txt line by line in a batch file
The first link shows manually replacing the beginning with regsvr32.exe
The second shows how to use for in this case {also thanks to craig65535 FOR his help :)}
Code:
#echo off
color 1f
title Register .dll
echo.
echo Exporting file list . . .
echo.
cd /d c:
cd\
if exist dll.txt del dll.txt
if exist dir.txt del dir.txt
if exist dll.bat del dll.bat
echo Part 1 of 3 . . .
echo.
dir /s /b>>dir.txt
echo Part 2 of 3 . . .
echo.
findstr ".dll$" dir.txt>>dll.txt
del dir.txt
echo Part 3 of 3 . . .
echo.
for /f "delims=" %%i IN ('type dll.txt') do echo regsvr32.exe /s "%%i">>dll.bat
del dll.txt
echo Ready to begin regsvr32.exe . . .
echo.
pause
echo.
echo Beginning registration . . .
echo *This will take time, close any popups that occur
echo.
call dll.bat
echo.
echo Deleting registration file . . .
if exist dll.bat del dll.bat
echo.
echo DONE.
echo.
pause >nul
The command you want is for /f.
for /f %%f in ('type dll.txt') do regsvr32.exe %%f
That takes the output of type dll.txt and puts one line at a time into %%f. You can then use %%f as an argument for some other command.
If you want to do more than regsvr32.exe %%f, you can write another batch file and call that:
for /f %%f in ('type dll.txt') do call process.bat %%f
process.bat would then receive the filename as %1.