Batch files: First Free Drive

From The Uniform Server Wiki
Jump to navigation Jump to search

Batch Files:    Snippets 1 | First Free Drive

MPG UniCenter

MPG UniCenter

Batch file code snippets.

These code snippets are my thoughts on automatic available drive detection; final code is destined for the mini-servers to prevent conflicts.

Batch file to detect first free drive letter

Every drive has a hidden nul file associated with it, this can be used to determine a drives existence.

For example if exist h:\nul echo. Disk in use -- If the nul file exists on drive "h" output disk in use.

:: Test folder exists
cls
@echo off
set w=s

if exist e:\nul echo. Disk in use

for %%p in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do if exist %%p:\nul echo. %%p Disk in use
for %%p in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do if not exist %%p:\nul echo. %%p Disk is available
for %%p in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do if not exist %%p:\nul set w=%%p

echo.
echo Drive to use %w%
PAUSE

The above code has been on the back burner for some time. It suffers from the fact unless a drive contains some media it is not registered as being in use.

Drive A and B are rightly detected as being free however I would consider it undesirable to assigned these using SUBST. Bound to cause conflicts on older machines that have these drives installed. (BIOS also assumes these are still used) Wonder what these legacy drives will be used for!

Real problem with the above code is lack of CD detection. Default for a CD (DVD) is drive D however more than one device may be installed even worst CD’s may be remapped to a different drive letters.

I found some horrific code to detect CD drives hence the back burner. The above code is useful, remove letters a, b and any CD drives that exist, use the “for” loop to scan for free drive letter (reverse letters to find first free drive). Backwards-compatible OS 95.

Top

Disk Start.vbs automatic drive detection

I noticed on the forum Marshy had found a bug in “Disk Start.vbs after applying his correction to get the database start stop correctly working I thought why not hack this code some more.

I spent two evenings at several sites learning some Vbscript, hacked the code to automatically detect the first free drive letter.

Proposed script:
' Name: disk_start.vbs
' Location: Uniform Server
' Created By: The Uniform Server Development Team
' Edited Last By: Olajide Olaolorun (empirex)
' Comment: Re-Arranged everything to look nicely.
' To Developers: Added the MySQL start option.
' MPG: Added auto drive detect and command prompt select

Dim WSHShell, dir, fso, letter, str1, str2, str3, str4

Set fso = CreateObject("Scripting.FileSystemObject")
Set WSHShell = WScript.CreateObject("WScript.Shell")

'Locate next available drive letter -- Added MPG

letter=Asc("c")
while fso.DriveExists(Chr(letter)+":")
letter=letter+1
wend

' Build string to display
str1 = "Use free drive found click OK or" & vbCR
str2 = "Specify a Disk Drive letter then click OK" & vbCR
str3 = "Note: To abort click Cancel"
str4 = str1 & str2 & str3

s=InputBox( str4 ,"Server Disk",UCASE(Chr(letter)))

If s = "" Then
WScript.Quit
End If
s=mid(s,1,1)

t=MsgBox("Start the MySQL Database Server?", vbYesNo
 + vbQuestion, "Database Support")
If t = vbNo Then
m=" nomysql"
Else
m=" mysql"
End If

c=MsgBox("Start a Command Prompt for MySQL commands?",
 vbYesNo + vbQuestion, "MySQL Command-line Support")
If c = vbNo Then
cl=""
Else
cl=" console"
End If

WSHShell.run "Server_Start.bat "&s&m&cl,0,0
WScript.sleep(1000)

Variable letter is assigned a numeric value starting with "c" drive.

Note 1: I have not included A or B drives because I believe these will cause problems as mentioned above.

Within the while loop the variable letter is incremented to the next drive letter.

This new value is checked the while loop repeats until a drive does not exist.

Note 2: DriveExists requires a drive letter with a colon hence the need to build a string.

While loop exit, variable letter contains first free drive letter.

Drive numeric is converted to a character which in turn is converted to upper case using: UCASE(Chr(letter))

Note 3: I think lower case drive letters are safe to use however I have always used uppercase.

What really is neat about this code it requires no additional instructions to detect CD drives.

Note 4: This script passes incorrect variables to Server_Start.bat in addition %2 is not checked hence will not work.

Check this page for a solution: Known bugs

Top

Summary 1

After two evenings work I looked at that elegant code and it really annoyed me! I could not achieve that using a batch file.

My next quest how do you communicate between these two scripts; an easy answer is to use an intermediary write and read to and from a file. The bat would run vb, which creates a file and write data to it, bat would read this file’s data and then delete the file.

A drive letter by any stretch of the imagination is not a large amount of data and does not warrant an intermediary file. So how do you pass variables? How compatible with older kit would this be? Hey! Just had a thought how compatible will it be with newer kit.

An interesting dilemma, VBscripts fire up security warnings, “Malicious script detected” well that rattles my cage thought I wrote better code than that!

Top

An intermediary file

These two alternative batch files produce identical results however they suffer from being Windows 2000 and XP specific. They both use extended batch commands (FOR /F and SET /P).

test1.bat
: Name: test1.bat

@echo off
: run vb script
test1.vbs

: read file content note: uses extended batch commands
FOR /F %%i in (test1.txt) do echo %%i


pause
test1b.bat
: Name: test1b.bat

@echo off
: run vb script
test1.vbs

: read file content Note: uses extended batch commands
SET /P driveLetter=<test1.txt
echo %driveLetter%

pause
test1.vbs
' Name: test1.vbs

Dim fso, oft, driveLetter
'Some code to get drive letter
driveLetter="W"

'Write drive letter to a file
Set fso = CreateObject("Scripting.FileSystemObject")  
Set oft = fso.CreateTextFile("test1.txt", True)
oft.WriteLine(driveLetter)
oft.Close

'Exit code 0 ok
WScript.Quit(0)

This script creates and writes to a file. As expected fires up security,
“Malicious script detected”

Top

Error code - Alternative

It is possible to pass numeric values using the errorlevel variable.

test2.bat
: Name: test2.bat

@echo off
: run vb script
test2.vbs
: pick up the error value
set driveLetter=%errorlevel%
echo %driveLetter%
pause
test2.vbs
' Name: test2.vbs

'Some code to get drive as numeric value
DIM driveLetter
driveLetter = 45
'Exit with numeric error value
Wscript.Quit(driveLetter)


The above looks promising, uses standard batch file code and the vb scripts does not change any system stuff hence no security alert.

Top

cscript.exe and for loop alternative

I am interested in passing a text string and not a numeric this solution achieves that.

The following file cscript.exe (or WScript.exe included with XP) can read and execute scripts the output is directed to either command window or stdout. Running vb script with cscript.exe any values echoed by the script can be read within the batch file using a for loop and f switch.

test3.bat
: Name: test3.bat
@echo off
: run vb script
For /F "delims=#" %%i in ('cscript /nologo test3.vbs') do echo %%i
pause


test3.vbs
' Name: test3.vbs
'Some code to get drive letter
DIM driveLetter
driveLetter = "Z"
WScript.Echo driveLetter
'Exit error code 0 ok
WScript.Quit(0)

It uses extended batch command (For /F) however no security alerts produced by vbs because it outputs to stdio.

Ref: http://www.tomshardware.com/ucg/commands/cscript-13412.html (cscript - Windows 98/ME, 2000, XP)

Top

Summary 2

Of the solutions only one looks backward compatible with older OS’s I suppose the real question is when was vb script introduced.

It looks as if VBScript is usable on 98! The For /F is Windows 2000 and XP specific. Using a modified version of 4) means the script will be backwards compatible to 98. What will Vista do with it?

Top

Proposed solution - 1

It’s a long-winded approach suppose how far back you need to go.

test4.bat
: Name: test4.bat
: Runs vbs script to obtain first free drive letter index

@echo off
: run vb script
test4.vbs

if errorlevel 65 set driveLetter=A
if errorlevel 66 set driveLetter=B
if errorlevel 67 set driveLetter=C
if errorlevel 68 set driveLetter=D
if errorlevel 69 set driveLetter=E
if errorlevel 70 set driveLetter=F
if errorlevel 71 set driveLetter=G
if errorlevel 72 set driveLetter=H

etc

if errorlevel 86 set driveLetter=V
if errorlevel 87 set driveLetter=W
if errorlevel 88 set driveLetter=X
if errorlevel 89 set driveLetter=Y
if errorlevel 90 set driveLetter=Z

echo %driveLetter%
pause
test4.vbs
' Name: test4.vbs
' Detects first free drive letter
' Return ASCII numeric using error variable
' Bat uses this to select a drive letter
' MPG:

DIM fso, driveLetter, driveNumeric
Set fso = CreateObject("Scripting.FileSystemObject")

'Locate next available drive letter
'Start with drive c
driveNumeric=Asc("c")
while fso.DriveExists(Chr(driveNumeric)+":")
driveNumeric=driveNumeric+1
wend

'Convert result to upper-case
driveLetter=UCASE(Chr(driveNumeric))

'Convert back to ASCII
driveNumeric=Asc(driveLetter)

'Exit with numeric error value
Wscript.Quit(driveNumeric)


Top

Proposed solution - 2

Even more long-winded however it does go back to year dot. Interestingly it has one advantage it picks up a CD or DV drive without the need for media being inserted as in Batch file to detect first free drive letter

test5.bat
: Name: test5.bat
: Uses basic batch commands compatible with very old OS

@echo off

CD d:
if errorlevel 1 set driveLetter=D
if errorlevel 1 Goto EndDrive
CD e:
if errorlevel 1 set driveLetter=E
if errorlevel 1 Goto EndDrive
CD f:
if errorlevel 1 set driveLetter=F
if errorlevel 1 Goto EndDrive
CD g:
if errorlevel 1 set driveLetter=G
if errorlevel 1 Goto EndDrive
CD h:
if errorlevel 1 set driveLetter=H
if errorlevel 1 Goto EndDrive
CD i:
if errorlevel 1 set driveLetter=I
if errorlevel 1 Goto EndDrive
CD j:
if errorlevel 1 set driveLetter=J
if errorlevel 1 Goto EndDrive
CD k:
if errorlevel 1 set driveLetter=K
if errorlevel 1 Goto EndDrive
CD l:
if errorlevel 1 set driveLetter=L
if errorlevel 1 Goto EndDrive
CD m:
if errorlevel 1 set driveLetter=M
if errorlevel 1 Goto EndDrive
CD n:
if errorlevel 1 set driveLetter=N
if errorlevel 1 Goto EndDrive
CD o:
if errorlevel 1 set driveLetter=O
if errorlevel 1 Goto EndDrive
CD p:
if errorlevel 1 set driveLetter=P
if errorlevel 1 Goto EndDrive
CD q:
if errorlevel 1 set driveLetter=Q
if errorlevel 1 Goto EndDrive
CD r:
if errorlevel 1 set driveLetter=R
if errorlevel 1 Goto EndDrive
CD s:
if errorlevel 1 set driveLetter=S
if errorlevel 1 Goto EndDrive
CD t:
if errorlevel 1 set driveLetter=T
if errorlevel 1 Goto EndDrive
CD u:
if errorlevel 1 set driveLetter=U
if errorlevel 1 Goto EndDrive
CD v:
if errorlevel 1 set driveLetter=V
if errorlevel 1 Goto EndDrive
CD w:
if errorlevel 1 set driveLetter=W
if errorlevel 1 Goto EndDrive
CD x:
if errorlevel 1 set driveLetter=X
if errorlevel 1 Goto EndDrive
CD y:
if errorlevel 1 set driveLetter=Y
if errorlevel 1 Goto EndDrive
CD z:
if errorlevel 1 set driveLetter=Z
if errorlevel 1 Goto EndDrive

:EndDrive
cls
echo %driveLetter%

pause

Top Top

Summary 3

After completing (16-4-2008) the above write-up again left it on the back burner because the proposed solutions were far from elegant. Three months later discovered a real solution was staring me in the face take the very first piece of code and combine it with Proposed solution - 2.

Top

Final Solution

The for loop selects each drive letter in turn, CD (change drive) attempts to force a drive to be currently selected. An error is produced if the drive is unavailable (free) and that letter is assigned to the variable freedrive. Drives are scanned in reverse order hence the last drive assigned becomes the first available.

@echo off
for %%a in (Z Y X W V U T S R Q P O N M L K J I H G F E D C) do CD %%a: 1>> nul 2>&1 & if errorlevel 1 set freedrive=%%a:
echo %freedrive%
pause

The solution to this problem of auto-drive detection was always destined for a new range of mini-servers now published. To my horror discovered the above failed on XP but worked fine on Vista. The failure mode was interesting, drive letter was allocated and servers worked fine plug in a USB stick, its detected with no problem however the drive letter allocated was the same as auto-detection. Servers continued to work USB stick was not accessible. After stopping the servers USB drive letter registered and drive was accessible.

Strictly speaking not a failure from the server’s point of view just inconvenient to a user. Solution implemented on the mini-servers was to reverse drive letter detection hence first allocated if free is Z.

@echo off
for %%a in (C D E F G H I J K L M N O P Q R S T U V W X Y Z) do CD %%a: 1>> nul 2>&1 & if errorlevel 1 set freedrive=%%a:
echo %freedrive%
pause

Conclusion

Looks like several solutions are possible it’s a bit of pick and mix, suppose it depends on the requirements.

Top


Ric