SUBST functionality
;==========================================================================================================================================
; How to get SUBST.EXE functionality into WinBatch code?
;------------------------------------------------------------------------------------------------------------------------------------------
; - Substitute a local folderpath by a local drive letter and remove substitution.
; - Check if a local drive is a subst drive or not.
; - Create and remove normal subst drive.
; - Create and remove persistent subst drive.
; - List all local subst drives.
; - List all local persistent subst drives.
; - List all DOS devices.
;------------------------------------------------------------------------------------------------------------------------------------------
; Inspired by: archimede _archimede_@libero.it, Saturday, January 02, 2010 06:25 AM
; Inspired by: IFICantBYTE ificantbyte@yahoo.com.au, Sunday, January 03, 2010 10:11 PM.
; See also: http://en.wikipedia.org/wiki/Subst
; See also: http://technet.microsoft.com/en-us/library/bb491006.aspx
;
; (c) Detlev Dalitz.20100106.20111110.
;==========================================================================================================================================

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfQueryDosDevice ()
hdlK32 = DllLoad ("KERNEL32.DLL")
intBBSizeAdd = 4096
intBBSize = 0
While @TRUE
   intBBSize = intBBSize + intBBSizeAdd
   hdlBBTargetPath = BinaryAlloc (intBBSize)
   If !!DllCall (hdlK32, long : "QueryDosDeviceA", lpnull, lpbinary : hdlBBTargetPath, long : intBBSize) Then Break
   If DllLastError () != 122 Then Break ; ERROR_INSUFFICIENT_BUFFER = 122 ; The data area passed to a system call is too small (dderror).
   hdlBBTargetPath = BinaryFree (hdlBBTargetPath)
EndWhile
BinaryEodSet (hdlBBTargetPath, intBBSize)
intEod = BinaryIndexBin (hdlBBTargetPath, 0, "0000", @FWDSCAN, 1) ; Find trailing double zero byte.
BinaryEodSet (hdlBBTargetPath, intEod)
intCount = BinaryReplace (hdlBBTargetPath, "", @TAB, @TRUE)
strListDeviceNames = BinaryPeekStr (hdlBBTargetPath, 0, intEod)
hdlBBTargetPath = BinaryFree (hdlBBTargetPath)
hdlK32 = DllFree (hdlK32)
Return strListDeviceNames
;..........................................................................................................................................
; This UDF "udfQueryDosDevice" returns a tab delimited string list of all existing MS-DOS device names
; by utilizing the ANSI version of the KERNEL32.DLL function "QueryDosDevice".
;
; See also: http://msdn.microsoft.com/en-us/library/aa365461(VS.85).aspx
;
; (c) Detlev Dalitz.20100104.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfGetDriveMounted (intRequest)
strDelim1 = @TAB
strDelim2 = "|"
hdlK32 = DllLoad ("KERNEL32.DLL")
strLocalDrives = DiskScan (intRequest) ; Request numbers see below.
intDrives = ItemCount (strLocalDrives, @TAB)
strListDriveMounted = ""
For intDrive = 1 To intDrives
   strDrive = ItemExtract (intDrive, strLocalDrives, @TAB)
   intBBSizeAdd = 128
   intBBSize = 0
   While @TRUE
      intBBSize = intBBSize + intBBSizeAdd
      hdlBBTargetPath = BinaryAlloc (intBBSize) ; See note below.
      If !!DllCall (hdlK32, long : "QueryDosDeviceA", lpstr : strDrive, lpbinary : hdlBBTargetPath, long : intBBSize) Then Break
      If DllLastError () != 122 Then Break ; ERROR_INSUFFICIENT_BUFFER = 122 ; The data area passed to a system call is too small (dderror).
      hdlBBTargetPath = BinaryFree (hdlBBTargetPath)
   EndWhile
   BinaryEodSet (hdlBBTargetPath, intBBSize)
   intEod = BinaryIndexBin (hdlBBTargetPath, 0, "0000", @FWDSCAN, 1) ; Find trailing double zero byte.
   BinaryEodSet (hdlBBTargetPath, intEod)
   intCount = BinaryReplace (hdlBBTargetPath, "", @TAB, @TRUE)
   strList = BinaryPeekStr (hdlBBTargetPath, 0, intEod)
   strItem = ItemExtract (1, strList, @TAB)
   strListDriveMounted = ItemInsert (strDrive : strDelim2 : strItem, -1, strListDriveMounted, strDelim1)
   hdlBBTargetPath = BinaryFree (hdlBBTargetPath)
Next
hdlK32 = DllFree (hdlK32)
Return strListDriveMounted
;..........................................................................................................................................
; This UDF "udfGetDriveMounted" returns a tab delimited string list of all mounted drives
; based on the list of local fixed hard drives detected by WinBatch function DiskScan (2).
;
; Each list item is build from a sub list of two items separated by "|" pipe symbol.
; Examples:
;  ListItem = "C:|\Device\HarddiskVolume1"
;  ListItem = "Z:|\??\F:\TEMP\"
;..........................................................................................................................................
; Note:
; Retrieve information about the particular MS-DOS device specified by lpDeviceName, e. g. lpstr:strDrive.
; The first null-terminated string stored into the buffer is the current mapping for the device.
; The other null-terminated strings, if any, represent undeleted prior mappings for the device.
;..........................................................................................................................................;
; The request number is a bitmask, so adding the values together (except for 0) returns all drive types specified;
; e. g. the request number 3 returns removeable drives plus local hard drives.
; Request
;    0   List of unused disk IDs
;    1   List of removable drives (floppies, zip, Jaz, memory sticks etc.)
;    2   List of local fixed (hard) drives
;    4   List of remote (network) drives
;    8   CD-ROM (32 bit)
;   16   RamDisk (32 bit)
;   32   List of persistent non-connected drives
;   64   List USB bus disk drives. (Windows 2000 and later only)
;..........................................................................................................................................;
; (c) Detlev Dalitz.20100104.20111110.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfGetDriveSubst (strListDriveMounted)
strDelim1 = @TAB
strDelim2 = "|"
intDrives = ItemCount (strListDriveMounted, strDelim1)
strListDriveSubst = ""
For intDrive = 1 To intDrives
   strItem = ItemExtract (intDrive, strListDriveMounted, strDelim1)
   strItem2 = ItemExtract (2, strItem, strDelim2)
   If StrIndex (strItem2, "\??\", 1, @FWDSCAN) == 1
      strItem2 = StrSub (strItem2, 5, -1)
      strItem2 = strItem2 : StrSub ("\", StrSub (strItem2, StrLen (strItem2), 1) != "\", 1); AddBackslash.
      strItem = ItemExtract (1, strItem, strDelim2) : strDelim2 : "\ ==> " : strDelim2 : strItem2
      strListDriveSubst = ItemInsert (strItem, -1, strListDriveSubst, strDelim1)
   EndIf
Next
Return strListDriveSubst
;..........................................................................................................................................
; This UDF "udfGetDriveSubst" returns a tab delimited string list of all drives, which currently substitute a folder path.
;
; Each list item is build from a sub list of three items separated by "|" pipe symbol.
; Example:
;  ListItem = "Z:|\ ==> |F:\TEMP\"
;   SubItem 1: Drive letter with trailing colon, e.g. "Z:"
;   SubItem 2: Filler "\ ==> ", just for displaying purpose.
;   SubItem 3: Folder with trailing backslash, e. g. "F:\TEMP\"
;
; (c) Detlev Dalitz.20100104.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfGetDriveSubstPersistent ()
strDelim1 = @TAB
strDelim2 = "|"
strRegKey = "SYSTEM\CurrentControlSet\Control\Session Manager\DOS Devices"
If !RegExistKey (@REGMACHINE, strRegKey) Then Return ""
strListDriveSubst = ""
hdlRegKey = RegOpenKey (@REGMACHINE, strRegKey)
strListDevices = RegQueryItem (hdlRegKey, "")
intDevices = ItemCount (strListDevices, @TAB)
For intDevice = 1 To intDevices
   strDevice = ItemExtract (intDevice, strListDevices, @TAB)
   If RegExistValue (hdlRegKey, "[" : strDevice : "]")
      strValue = RegQueryValue (hdlRegKey, "[" : strDevice : "]")
      If StrIndex (strValue, "\??\", 1, @FWDSCAN) == 1
         strValue = StrSub (strValue, 5, -1)
         strValue = strValue : StrSub ("\", StrSub (strValue, StrLen (strValue), 1) != "\", 1); AddBackslash.
         strItem = strDevice : strDelim2 : "\ ==> " : strDelim2 : strValue
         strListDriveSubst = ItemInsert (strItem, -1, strListDriveSubst, strDelim1)
      EndIf
   EndIf
Next
blnResult = RegCloseKey (hdlRegKey)
Return strListDriveSubst
;..........................................................................................................................................
; This UDF "udfGetDriveSubstPersistent" returns a tab delimited string list of all drives, which substitute a folder path persistently.
;
; Each list item is build from a sub list of three items separated by "|" pipe symbol.
; Example:
;  ListItem = "Z:|\ ==> |F:\TEMP\"
;   SubItem 1: Drive letter with trailing colon, e.g. "Z:"
;   SubItem 2: Filler "\ ==> ", just for displaying purpose.
;   SubItem 3: Folder with trailing backslash, e. g. "F:\TEMP\"
;
; A persistent subst drive will be set up automatically by the operating system while boot process.
;
; (c) Detlev Dalitz.20100105.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfDriveSubstCreate (strDriveLetter, strFolderPath, intMode)
blnResult = @TRUE
Switch intMode
Case 2
Case 1
   strRegKey = "SYSTEM\CurrentControlSet\Control\Session Manager\DOS Devices"
   If !RegExistKey (@REGMACHINE, strRegKey) Then Return @FALSE
   hdlRegKey = RegOpenKey (@REGMACHINE, strRegKey)
   blnResult = RegSetValue (hdlRegKey, "[" : strDriveLetter : "]", "\??\" : strFolderPath) && RegCloseKey (hdlRegKey)
   If !blnresult Then Return @FALSE
   If intMode == 2 Then Return blnResult
Case 0
   Return blnResult && !!DllCall ("KERNEL32.DLL", long : "DefineDosDeviceA", long : 0, lpstr : strDriveLetter, lpstr : strFolderPath)
EndSwitch
;..........................................................................................................................................
; This UDF "udfDriveSubstCreate () substitutes a local folder by a local drive letter.
; The device name string must not have a colon as the last character, unless a drive letter is being defined, redefined, or deleted.
; For example, drive C would be the string "C:". In no case is a trailing backslash ("\") allowed.
;
; Parameter intMode:
; 0 = Create normal subst drive.
; 1 = Create normal drive together with persistent subst drive.
; 2 = Create only persistent subst drive to be activated during the next boot.
;
; Return value is @TRUE (1) for success or @FALSE (0) for failure.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfDriveSubstRemove (strDriveLetter, intMode)
blnResult = @TRUE
Switch intMode
Case 2
Case 1
   strRegKey = "SYSTEM\CurrentControlSet\Control\Session Manager\DOS Devices"
   If !RegExistKey (@REGMACHINE, strRegKey) Then Return @FALSE
   hdlRegKey = RegOpenKey (@REGMACHINE, strRegKey)
   blnResult = RegDelValue (hdlRegKey, "[" : strDriveLetter : "]") && RegCloseKey (hdlRegKey)
   If !blnresult Then Return @FALSE
   If intMode == 2 Then Return blnResult
Case 0
   Return blnResult && !!DllCall ("KERNEL32.DLL", long : "DefineDosDeviceA", long : 2, lpstr : strDriveLetter, lpnull)
EndSwitch
;..........................................................................................................................................
; This UDF "udfDriveSubstRemove" removes the substitution made by a previous call of udfDriveSubstCreate (strDriveLetter, strFolderpath, intMode).
;
; Parameter intMode:
; 0 = Remove normal subst drive.
; 1 = Remove normal drive together with persistent subst drive.
; 2 = Remove only persistent drive.
;
; Return value is @TRUE (1) for success or @FALSE (0) for failure.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfPathRoot (strString)
strPathRoot = ""
arrPattern = Arrayize ("?:\" : @LF : "\\*\*\" : @LF : "\", @LF)
For intI = 0 To 2
   If 1 == StrIndexWild (strString, arrPattern [intI], 1)
      strPathRoot = StrSubWild (strString, arrPattern [intI], 1)
      If intI == 1 Then strPathRoot = StrSub (strPathRoot, 1, StrLen (strPathRoot) - 1)
      Break
   EndIf
Next
Return strPathRoot
;..........................................................................................................................................
; This UDf udfPathRoot works like SHLWAPI.DLL "PathStripToRootA".
;
; Detlev Dalitz.20090630.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfAddBackSlash (strString)
Return strString : StrSub ("\", StrSub (strString, StrLen (strString), 1) != "\", 1)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfMsgCreate (strDriveMount, srFolderMount, intResult)
Message ("Create Subst Drive", "Set" : @LF : strDriveMount : @LF : "To" : @LF : srFolderMount : @LF : @LF : ItemExtract (1 + !!intResult, "Failure.|Success.", "|"))
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfMsgRemove (strDriveMount, intResult)
Message ("Remove Subst Drive", "Remove" : @LF : strDriveMount : @LF : @LF : ItemExtract (1 + !!intResult, "Failure.|Success.", "|"))
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfMsgDriveCheck (strRootFolder, intResult)
Message ("Drive Check " : strRootFolder, "Drive " : ItemExtract (1 + intResult, "virtual.|normal.", "|"))
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfGetPathRoot (strFolderPath)
Return udfAddBackSlash (udfPathRoot (udfAddBackSlash (strFolderPath)))
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------



; Main.

:Part1
Pause ("Subst Example, Part 1", "Substitute folder by drive, then check if drive is a subst drive or not.")

; Choose a folder, e. g. user temp folder.
strFolderTest = ShortCutDir ("Local Settings", 0, 1) : "Temp" ; Any test folder, without trailing backslash.

; Get an unused drive letter from the end of the unused drives list.
strDriveNew = ItemExtract (-1, DiskScan (0), @TAB)
If strDriveNew == "" Then Goto Part4 ; No drive letter available, try to free one.

; Set drive letter to folder.
intResult = udfDriveSubstCreate (strDriveNew, strFolderTest, 0)
udfMsgCreate (strDriveNew, strFolderTest, intResult)

; Check drive (DOS DIR trick).
strFolderPath = udfGetPathRoot (strFolderTest)
intResult = RunShell (Environment ("COMSPEC"), '/C DIR ' : strFolderPath : ' /W | FIND "[.]"', "", @HIDDEN, @GETEXITCODE)
udfMsgDriveCheck (strFolderPath, intResult)

; Check drive (DOS DIR trick).
strFolderPath = udfGetPathRoot (strDriveNew)
intResult = RunShell (Environment ("COMSPEC"), '/C DIR ' : strFolderPath : ' /W | FIND "[.]"', "", @HIDDEN, @GETEXITCODE)
udfMsgDriveCheck (strFolderPath, intResult)

; Release drive letter.
intResult = udfDriveSubstRemove (strDriveNew, 0)
udfMsgRemove (strDriveNew, intResult)


;------------------------------------------------------------------------------------------------------------------------------------------


:Part2
Pause ("Subst Example, Part 2", "Substitute folder by drive, then substitute the substituted drive by another drive.")

; Choose a folder, e. g. user temp folder.
strFolderTest1 = ShortCutDir ("Local Settings", 0, 1) : "Temp" ; Any test folder, without trailing backslash.

; Get an unused drive letter from the end of the unused drives list.
strDriveNew1 = ItemExtract (-1, DiskScan (0), @TAB)
If strDriveNew1 == "" Then Goto Part4 ; No drive letter available, try to free one.

; Set drive letter to folder.
intResult = udfDriveSubstCreate (strDriveNew1, strFolderTest1, 0)
udfMsgCreate (strDriveNew1, strFolderTest1, intResult)

; Choose a folder.
strFolderTest2 = strDriveNew1 ; We use the new drive from just before as mountpoint.

; Get an unused drive letter from the end of the unused drives list.
strDriveNew2 = ItemExtract (-1, DiskScan (0), @TAB)

; Set drive letter to folder.
intResult = udfDriveSubstCreate (strDriveNew2, strFolderTest2, 0)
udfMsgCreate (strDriveNew2, strFolderTest2, intResult)

; Get list of subst drives.
intRequest = 2 ; List of local fixed (hard) drives.
strListDrivesSubst = udfGetDriveSubst (udfGetDriveMounted (intRequest))
Message ("Subst drives", StrReplace (StrReplace (strListDrivesSubst, "|", ""), @TAB, @LF))


;------------------------------------------------------------------------------------------------------------------------------------------


:Part3
Pause ("Subst Example, Part 3", "Substitute folder by drive, normal and persistent.")

; Choose a folder, e. g. user temp folder.
strFolderTest1 = ShortCutDir ("Local Settings", 0, 1) : "Temp" ; Any test folder, without trailing backslash.

; Get an unused drive letter from the end of the unused drives list.
strDriveNew1 = ItemExtract (-1, DiskScan (0), @TAB)
If strDriveNew1 == "" Then Goto Part4 ; No drive letter available, try to free one.

; Set drive letter to folder.
intResult = udfDriveSubstCreate (strDriveNew1, strFolderTest1, 0)
udfMsgCreate (strDriveNew1, strFolderTest1, intResult)

; Get an unused drive letter from the end of the unused drives list.
strDriveNew1 = ItemExtract (-1, DiskScan (0), @TAB)
If strDriveNew1 == "" Then Goto Part4 ; No drive letter available, try to free one.

; Set drive letter to folder persistently.
intResult = udfDriveSubstCreate (strDriveNew1, strFolderTest1, 1)
udfMsgCreate (strDriveNew1, strFolderTest1, intResult)

; Get list of subst drives.
intRequest = 2 ; List of local fixed (hard) drives.
strListDrivesSubst = udfGetDriveSubst (udfGetDriveMounted (intRequest))
Message ("Subst drives", StrReplace (StrReplace (strListDrivesSubst, "|", ""), @TAB, @LF))

; Get list of persistent subst drives.
strListDrivesSubst = udfGetDriveSubstPersistent ()
Message ("Persistent subst drives", StrReplace (StrReplace (strListDrivesSubst, "|", ""), @TAB, @LF))


;------------------------------------------------------------------------------------------------------------------------------------------


:Part4
Pause ("Subst Example, Part 4", "Create and remove only persistent subst drives.")

; Choose a folder, e. g. user temp folder.
strFolderTest = ShortCutDir ("Local Settings", 0, 1) : "Temp" ; Any test folder, without trailing backslash.

; Get an unused drive letter from the end of the unused drives list.
strDriveNew = ItemExtract (-1, DiskScan (0), @TAB)
If strDriveNew == "" Then Goto Part5 ; No drive letter available, try to free one.

; Set drive letter to folder.
intResult = udfDriveSubstCreate (strDriveNew, strFolderTest, 2)
udfMsgCreate (strDriveNew, strFolderTest, intResult)

; Get list of persistent subst drives.
strListDrivesSubst = udfGetDriveSubstPersistent ()
Message ("Persistent subst drives", StrReplace (StrReplace (strListDrivesSubst, "|", ""), @TAB, @LF))

; Release drive letter.
intResult = udfDriveSubstRemove (strDriveNew, 2)
udfMsgRemove (strDriveNew, intResult)

; Get list of persistent subst drives.
strListDrivesSubst = udfGetDriveSubstPersistent ()
Message ("Persistent subst drives", StrReplace (StrReplace (strListDrivesSubst, "|", ""), @TAB, @LF))


;------------------------------------------------------------------------------------------------------------------------------------------


:Part5
Pause ("Subst Example, Part 5", "Remove all normal and persistent subst drives.")

; Remove all subst drives.
intRequest = 2 ; List of local fixed (hard) drives.
strListDrivesSubst = udfGetDriveSubst (udfGetDriveMounted (intRequest))
Message ("Subst drives", StrReplace (StrReplace (strListDrivesSubst, "|", ""), @TAB, @LF))

intDrivesSubst = ItemCount (strListDrivesSubst, @TAB)
For intDrive = 1 To intDrivesSubst
   strItem = ItemExtract (intDrive, strListDrivesSubst, @TAB)
   strDriveSubst = ItemExtract (1, strItem, "|")
   intResult = udfDriveSubstRemove (strDriveSubst, 0)
   udfMsgRemove (strDriveSubst, intResult)
Next

; Remove all persistent subst drives.
strListDrivesSubst = udfGetDriveSubstPersistent ()
Message ("Persistent subst drives", StrReplace (StrReplace (strListDrivesSubst, "|", ""), @TAB, @LF))

intDrivesSubst = ItemCount (strListDrivesSubst, @TAB)
For intDrive = 1 To intDrivesSubst
   strItem = ItemExtract (intDrive, strListDrivesSubst, @TAB)
   strDriveSubst = ItemExtract (1, strItem, "|")
   intResult = udfDriveSubstRemove (strDriveSubst, 1)
   udfMsgRemove (strDriveSubst, intResult)
Next


;------------------------------------------------------------------------------------------------------------------------------------------


:Part6
Pause ("Subst Example, Part 6", "Get QueryDosDevice List.")

; Get a list of all Dos Devices.
strFileOut = ShortCutDir ("Local Settings", 0, 1) : "Temp\DosDevAll.txt"
intBytesWritten = FilePut (strFileOut, StrReplace (ItemSort (udfQueryDosDevice (), @TAB), @TAB, @CRLF))
Run (strFileOut, "")


;------------------------------------------------------------------------------------------------------------------------------------------
:CANCEL
Display (2, "Subst Example", "Good Bye!")
Exit
;==========================================================================================================================================