I was looking for an easy way to get the system volumes info in GB (batch) so I got this:
for /f "tokens=1-3" %a in ('WMIC LOGICALDISK GET FreeSpace^,Name^,Size ^|FINDSTR /I /V "Name"') do @echo wsh.echo "%b" ^& " free=" ^& FormatNumber^(cdbl^(%a^)/1024/1024/1024, 2^)^& " GiB"^& " size=" ^& FormatNumber^(cdbl^(%c^)/1024/1024/1024, 2^)^& " GiB" > %temp%\tmp.vbs & @if not "%c"=="" @echo(& @cscript //nologo %temp%\tmp.vbs >> c:\test\test2.txt | type C:\test\test2.txt
It works fine if I just copy and paste it to the CMD but if I run it from a .bat file it just don't do anything.
What could I be doing wrong?
PD: I can't use PowerShell, I need it in batch.
There are the following issues in your code:
- in batch files,
for
meta-variables must be preceded by two %
-signs in contrast to one in command prompt (cmd
), hence change %a
, etc., to %%a
, etc.; otherwise, a syntax error arises;
- you could avoid filtering out the headline of the output of
wmic
by findstr /V
when using the skip=1
option of for /F
;
- instead of the property
Name
I recommend to use DeviceID
, according to this Super User thread: What is the difference between properties Name
, Caption
and DeviceID
(when executing wmic LogicalDisk
)?;
- not sure what drives you want to query, but perhaps you want local disks only; if so, add the clause
where "DriveType=3"
; refer to this Microsoft article: Win32_LogicalDisk class;
- creation of the temporary VBScript script should happen inside of the
if
condition where it is called as well in order to avoid pointless file write activities;
- you are appending to the text file in every loop iteration, but you never initialise an empty file at the beginning; perhaps this is the intentional behaviour, but I do not think so; anyway, you could do the redirection once for the whole
for /F
loop, either (over-)writing (>
) or appending (>>
), as you prefer;
- the pipe into the
type
command is useless, because type
does not accept any incoming data anyway; I guess the |
symbol should have been replaced by &
; nevertheless, the type
command does not make sense in the loop, I think it should be executed after the loop instead to display the complete collected data once;
- the
echo(
could be removed as it just writes a blank line to the console;
- at the end the temporary VBScript file should be cleaned up;
Correcting all these things lead to a batch script like this (I do not write it all in a single line for readability):
> "C:\test\test2.txt" (
for /F "skip=1 tokens=1-3" %%a in ('
wmic LogicalDisk where "DriveType=3" get DeviceID^,FreeSpace^,Size
') do @(
if not "%%c"=="" (
> "%TEMP%\tmp.vbs" echo WScript.Echo "%%a" ^& " free=" ^& FormatNumber^(CDbl^(%%b^) / 1024 / 1024 / 1024, 2^) ^& " GiB" ^& " size=" ^& FormatNumber^(CDbl^(%%c^) / 1024 / 1024 / 1024, 2^) ^& " GiB"
CScript //NoLogo "%TEMP%\tmp.vbs"
)
)
)
type "C:\test\test2.txt"
del "%TEMP%\tmp.vbs"
The whole approach could be improved though:
- the temporary VBScript script could be written once rather than per each
for /F
loop iteration; for this to work you need to pass the variable values as command line arguments; consult this thread to learn how it works: Using command line arguments in VBscript;
- using
echo
to write the temporary VBScript file requires lots of escaping, like ^&
, for example; to avoid this, we could place the VBScript code in a block that is prefixed by ::::
, which is seen as an invalid jump label by the batch file interpreter, and they could even be skipped by placing an exit /B
just before them; this block can easily be extracted by findstr
, and the colons can be removed by for /F
;
So this is what I mean:
> "%TEMP%\tmp.vbs" (for /F "tokens=* delims=:" %%z in ('findstr /B "::::" "%~f0"') do @echo/%%z)
> "C:\test\test2.txt" (
for /F "skip=1 tokens=1-3" %%a in ('
wmic LogicalDisk where "DriveType=3" get DeviceID^,FreeSpace^,Size
') do @(
if not "%%c"=="" CScript //NoLogo "%TEMP%\tmp.vbs" "%%a" "%%b" "%%c"
)
)
type "C:\test\test2.txt"
del "%TEMP%\tmp.vbs"
exit /B
::::If WScript.Arguments.Count < 3 Then WScript.Quit 1
::::WScript.Echo WScript.Arguments.Item(0) & _
:::: " free=" & FormatNumber(CDbl(WScript.Arguments.Item(1)) / 1024 / 1024 / 1024, 2) & " GiB" & _
:::: " size=" & FormatNumber(CDbl(WScript.Arguments.Item(2)) / 1024 / 1024 / 1024, 2) & " GiB"
You could even avoid a temporary file holding the VBScript code when applying the technique demonstrated in this thread: Is it possible to embed and execute VBScript within a batch file without using a temporary file?
(Refer also to these Microsoft articles: Using Windows Script Files (.wsf), and How Come You Guys Don’t Use .WSF Files?.)
<!-- :Batch script section
> "C:\test\test2.txt" (
for /F "skip=1 tokens=1-3" %%a in ('
wmic LogicalDisk where "DriveType=3" get DeviceID^,FreeSpace^,Size
') do @(
if not "%%c"=="" CScript //NoLogo "%~f0?.wsf" "%%a" "%%b" "%%c"
)
)
type "C:\test\test2.txt"
exit /B
---- WSF script section -->
<job><script language="VBScript">
If WScript.Arguments.Count < 3 Then WScript.Quit 1
WScript.Echo WScript.Arguments.Item(0) & _
" free=" & FormatNumber(CDbl(WScript.Arguments.Item(1)) / 1024 / 1024 / 1024, 2) & " GiB" & _
" size=" & FormatNumber(CDbl(WScript.Arguments.Item(2)) / 1024 / 1024 / 1024, 2) & " GiB"
</script></job>
And here is yet another approach that does not use a temporary VBScript file, applying the method illustrated in this thread: HTA & Batch Hybrid, passing variables from BATCH section. The disadvantage of this is brief flickers of HTA windows appearing and disappearing.
(Refer also to this Microsoft article: HTML Applications (HTAs).)
<!-- ::Batch script section ----
> "C:\test\test2.txt" (
for /F "skip=1 tokens=1-3" %%a in ('
wmic LogicalDisk where "DriveType=3" get DeviceID^,FreeSpace^,Size
') do @(
if not "%%c"=="" set "DeviceID=%%a" & set "FreeSpace=%%b" & set "Size=%%c" & MSHTA "%~f0"
)
)
type "C:\test\test2.txt"
exit /B
---- ::HTA script section -->
<script language="VBScript">
Set Env = CreateObject("WScript.Shell").Environment("Process")
Set StdOut = CreateObject("Scripting.FileSystemObject").GetStandardStream(1)
If Not Env("DeviceID") = "" Then
StdOut.WriteLine(Env("DeviceID") & _
" free=" & FormatNumber(CDbl(Env("FreeSpace")) / 1024 / 1024 / 1024, 2) & " GiB" & _
" size=" & FormatNumber(CDbl(Env("Size")) / 1024 / 1024 / 1024, 2) & " GiB")
End If
Set StdOut = Nothing
Set Env = Nothing
Close()
</script>