long path support
Posted: 21 Apr 2018, 07:16
- In response to:
Test build - Obj.Count(), OnError(), long paths, experimental switch-case - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 68#p213868
TERMINOLOGY
- I will distinguish between '259-' and '260+' files, files of 259 characters or fewer, and files of 260 characters or more.
- Note: 'short', 'long', and 'full' have special meanings, hence why I'm avoiding those terms. See the MSDN pages for: GetShortPathName, GetLongPathName, GetFullPathName.
INTRO
- I'm thankful for the new functionality. Also, the summary of long path findings, in the link, has been most interesting.
- In a file loop, the A_LoopXXX variables present you with info for each file in a folder. Previously for a file loop, any '260+' files were ignored, they are now included, without any changes needing to be made to existing scripts. [EDIT: Some '260+' files will appear without changing existing loops. But to ensure that *all* '260+' files are listed (and that all '260+' folders are recused into) use the '\\?\' prefix.]
- Apart from 'PROBLEM 1 OF 2: FOLDERS', all of the statements about functions, and the example code relate to v1.1.28.02 (the version before the test build).
PROBLEM 1 OF 2: FOLDERS
- The file loop currently handles '260+' files in full, but not folders. It will list '260+' folders, but it will not recurse into them, and it will not accept '260+' folders as the starting directory. [EDIT: It depends on whether '\\?\' is used, see 'EDIT' above.]
- In this script I present methods for listing every file on a drive, including '260+' files and folders.
259-char path limit workarounds - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=26170
SOURCE CODE
- FileCopy(Dir) and FileMove(Dir) have not been changed at present. Here is some info from the source code re. which Winapi functions they use:
ACT_FILECOPY: Util_CopyFile (uses CopyFile)
ACT_FILEMOVE: Util_CopyFile (uses MoveFile)
ACT_FILECOPYDIR: Util_CopyDir (uses SHFileOperation)
ACT_FILEMOVEDIR: MoveFile
- Curiously it seems that MoveFile can handle files *and* folders, but that CopyFile can only handle files.
PROBLEM 2 OF 2: MOVE/COPY
- I did some tests with MoveFile and CopyFile (with DllCall) on Windows 7. Unless I'm missing something, it seems that the functions can handle '260+' files as input files, but not as output files. So if you want to copy a '260+' file, 'C:\...\MyFile.txt' to E:, I'm not sure how you're supposed to do that. [EDIT: They *can* handle '260+' files as output files, what I was missing was that each substring has a limit of 255 characters (the MaximumComponentLength) e.g. C:\substring2\substring3\substring4.]
- I have a workaround at present, although it's not great. CreateDirectory appeared to be able to create '260+' folders, and FileOpen was able to create '260+' files. So, you could open an existing file in FileOpen, and create a new file via FileOpen, and copy the data over. I would be interested in the most efficient FileOpen script to do this, even if a better method becomes apparent. [EDIT: Workaround not needed, MoveFile/CopyFile *can* be used.]
GOOD NEWS
- Some functions can already handle long files. FileExist, DirExist (AHK v2) and FileOpen. Just prepend the path with '\\?\'.
- Sometimes you can use the short-form of a path, with existing commands e.g. FileDelete. Use GetShortPathName (with DllCall) to get the short-form path.
- Quick replacements for certain functionality just need a one-line DllCall to CopyFile/CreateDirectory/DeleteFile/MoveFile.
FILE OBJECTS: GET PATH/DELETE FILE
- Unless I've missed something, it has surprised me that for FileOpen there are no methods to delete the file or get the path. However, while the file is open, on Vista/newer OSes, you can use the handle (hFile) with GetFinalPathNameByHandle to get the path of the file, including for '260+' files.
- Link:
Obtaining a File Name From a File Handle (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
TEST CODE
- Here is a collection of test code for v1.1.28.02 (the version before the test build). Cheers.
CONVERTING BETWEEN SHORT/LONG FORMS
- I have found being able to convert to and from short-form useful for different reasons, it's part of the functionality that I've added here:
file get part (SplitPath alternative/short-form/long-form/correct case) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=47709
NOTES ON FUNCTIONALITY RELATED TO FILE LOOPS/LONG PATHS
- A file loop with * currently checks the short-form name as well as the long-form name. [I don't particularly mind if this behaviour is kept.]
- Since various bits of file-related functionality are touched by 'long' path issues, I thought it would be useful to collect ideas from my wish list that overlap with affected code.
- A_LoopFileAttribNum (or A_LoopFileAttribValue).
- A_LoopFileNameNoExt. (Useful in it's own right, plus it would be consistent with A_AhkNameNoExt and A_ScriptNameNoExt which would be very useful with #Include.)
- A_LoopFileTimeCreatedUTC/A_LoopFileTimeModifiedUTC (and A_LoopFileTimeAccessedUTC).
- A_Recent (or A_RecentItems) (Recent folder).
- FileInstall function for AHK v1.
Test build - Obj.Count(), OnError(), long paths, experimental switch-case - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 68#p213868
TERMINOLOGY
- I will distinguish between '259-' and '260+' files, files of 259 characters or fewer, and files of 260 characters or more.
- Note: 'short', 'long', and 'full' have special meanings, hence why I'm avoiding those terms. See the MSDN pages for: GetShortPathName, GetLongPathName, GetFullPathName.
INTRO
- I'm thankful for the new functionality. Also, the summary of long path findings, in the link, has been most interesting.
- In a file loop, the A_LoopXXX variables present you with info for each file in a folder. Previously for a file loop, any '260+' files were ignored, they are now included, without any changes needing to be made to existing scripts. [EDIT: Some '260+' files will appear without changing existing loops. But to ensure that *all* '260+' files are listed (and that all '260+' folders are recused into) use the '\\?\' prefix.]
- Apart from 'PROBLEM 1 OF 2: FOLDERS', all of the statements about functions, and the example code relate to v1.1.28.02 (the version before the test build).
PROBLEM 1 OF 2: FOLDERS
- The file loop currently handles '260+' files in full, but not folders. It will list '260+' folders, but it will not recurse into them, and it will not accept '260+' folders as the starting directory. [EDIT: It depends on whether '\\?\' is used, see 'EDIT' above.]
- In this script I present methods for listing every file on a drive, including '260+' files and folders.
259-char path limit workarounds - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=26170
SOURCE CODE
- FileCopy(Dir) and FileMove(Dir) have not been changed at present. Here is some info from the source code re. which Winapi functions they use:
ACT_FILECOPY: Util_CopyFile (uses CopyFile)
ACT_FILEMOVE: Util_CopyFile (uses MoveFile)
ACT_FILECOPYDIR: Util_CopyDir (uses SHFileOperation)
ACT_FILEMOVEDIR: MoveFile
- Curiously it seems that MoveFile can handle files *and* folders, but that CopyFile can only handle files.
PROBLEM 2 OF 2: MOVE/COPY
- I did some tests with MoveFile and CopyFile (with DllCall) on Windows 7. Unless I'm missing something, it seems that the functions can handle '260+' files as input files, but not as output files. So if you want to copy a '260+' file, 'C:\...\MyFile.txt' to E:, I'm not sure how you're supposed to do that. [EDIT: They *can* handle '260+' files as output files, what I was missing was that each substring has a limit of 255 characters (the MaximumComponentLength) e.g. C:\substring2\substring3\substring4.]
- I have a workaround at present, although it's not great. CreateDirectory appeared to be able to create '260+' folders, and FileOpen was able to create '260+' files. So, you could open an existing file in FileOpen, and create a new file via FileOpen, and copy the data over. I would be interested in the most efficient FileOpen script to do this, even if a better method becomes apparent. [EDIT: Workaround not needed, MoveFile/CopyFile *can* be used.]
GOOD NEWS
- Some functions can already handle long files. FileExist, DirExist (AHK v2) and FileOpen. Just prepend the path with '\\?\'.
- Sometimes you can use the short-form of a path, with existing commands e.g. FileDelete. Use GetShortPathName (with DllCall) to get the short-form path.
- Quick replacements for certain functionality just need a one-line DllCall to CopyFile/CreateDirectory/DeleteFile/MoveFile.
FILE OBJECTS: GET PATH/DELETE FILE
- Unless I've missed something, it has surprised me that for FileOpen there are no methods to delete the file or get the path. However, while the file is open, on Vista/newer OSes, you can use the handle (hFile) with GetFinalPathNameByHandle to get the path of the file, including for '260+' files.
- Link:
Obtaining a File Name From a File Handle (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
TEST CODE
- Here is a collection of test code for v1.1.28.02 (the version before the test build). Cheers.
Code: Select all
;note: create a folder called 'long path' on the Desktop
return
;q:: ;260+ files - create
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxy.txt"
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy.txt" ;259
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxya.txt" ;260
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy.txt" ;284
;FileAppend, % "hello world`r`n", % "*" "\\?\" vPath
oFile := FileOpen("\\?\" vPath, "w")
;note: GetFinalPathNameByHandle doesn't work on Windows XP
vChars := DllCall("kernel32\GetFinalPathNameByHandle", Int,oFile.__Handle, Ptr,0, UInt,0, UInt,0, UInt)
VarSetCapacity(vPathLong, (vChars+1)*(A_IsUnicode?2:1), 0)
DllCall("kernel32\GetFinalPathNameByHandle", Int,oFile.__Handle, Str,vPathLong, UInt,vChars, UInt,0, UInt)
if (SubStr(vPathLong, 1, 4) = "\\?\")
vPathLong := SubStr(vPathLong, 5)
MsgBox, % vPathLong
oFile.Write("hello world`r`n")
oFile.Close()
vChars := DllCall("kernel32\GetShortPathName", Str,"\\?\" vPathLong, Ptr,0, UInt,0, UInt)
VarSetCapacity(vPathShort, (vChars+1)*(A_IsUnicode?2:1), 0)
DllCall("kernel32\GetShortPathName", Str,"\\?\" vPathLong, Str,vPathShort, UInt,vChars, UInt)
if (SubStr(vPathShort, 1, 4) = "\\?\")
vPathShort := SubStr(vPathShort, 5)
MsgBox, % vPathShort
return
;w:: ;260+ files - check if exists/delete
;260+ files - send to recycle bin (didn't work)
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxy.txt"
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy.txt" ;259
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxya.txt" ;260
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy.txt" ;284
vPathLong := vPath
vChars := DllCall("kernel32\GetShortPathName", Str,"\\?\" vPathLong, Ptr,0, UInt,0, UInt)
VarSetCapacity(vPathShort, (vChars+1)*(A_IsUnicode?2:1), 0)
DllCall("kernel32\GetShortPathName", Str,"\\?\" vPathLong, Str,vPathShort, UInt,vChars, UInt)
if (SubStr(vPathShort, 1, 4) = "\\?\")
vPathShort := SubStr(vPathShort, 5)
MsgBox, % vChars "`r`n" vPathShort
;where '260+' is a file with 260 characters or more
;FileRecycle, % vPathShort ;didn't work on 260+ files
;FileDelete, % vPathShort ;did work on 260+ files
;FileDelete, % "\\?\" vPathLong ;didn't work on 260+ files
;MsgBox, % FileExist(vPathShort) ;did work on 260+ files
;MsgBox, % FileExist("\\?\" vPathLong) ;did work on 260+ files
;DllCall("kernel32\DeleteFile", Str,vPathShort) ;did work on 260+ files
DllCall("kernel32\DeleteFile", Str,"\\?\" vPathLong) ;did work on 260+ files
return
;e:: ;260+ files - list (note: extensions are omitted sometimes)
vDir1 := A_Desktop "\long path"
oShell := ComObjCreate("Shell.Application")
vCount := oShell.Namespace(vDir1).Items.Count
MsgBox, % vCount
VarSetCapacity(vOutput, vCount*261 << !!A_IsUnicode)
vDoWarn := 0
for oItem in oShell.Namespace(vDir1).Items
{
if !(oItem.Path = "")
vOutput .= oItem.Path "`r`n" ;oItem.Path sometimes gave blanks
else
vDoWarn := 1, vOutput .= vDir1 "\" oItem.Name "`r`n" ;literal name so extensions may be omitted
}
if vDoWarn
vOutput := "[warning: somes paths may lack file extensions]`r`n" vOutput
oShell := oItem := ""
Clipboard := vOutput
MsgBox, % vOutput
return
;r:: ;260+ files - copy/rename file (260+ to 259-)
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy.txt" ;284
vPathLong := vPath
vChars := DllCall("kernel32\GetShortPathName", Str,"\\?\" vPathLong, Ptr,0, UInt,0, UInt)
VarSetCapacity(vPathShort, (vChars+1)*(A_IsUnicode?2:1), 0)
DllCall("kernel32\GetShortPathName", Str,"\\?\" vPathLong, Str,vPathShort, UInt,vChars, UInt)
if (SubStr(vPathShort, 1, 4) = "\\?\")
vPathShort := SubStr(vPathShort, 5)
MsgBox, % vChars "`r`n" vPathShort
vPathLongNew := A_Desktop "\long path\ZCcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy.txt" ;284
vPathLongNew := A_Desktop "\long path\ZC.txt"
DllCall("kernel32\CopyFile", Str,"\\?\" vPathLong, Str,"\\?\" vPathLongNew, Int,1)
vPathLongNew := A_Desktop "\long path\ZMcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy.txt" ;284
vPathLongNew := A_Desktop "\long path\ZM.txt"
DllCall("kernel32\MoveFile", Str,"\\?\" vPathLong, Str,"\\?\" vPathLongNew)
return
;t:: ;260+ files - workarounds to copy/rename file (260+ to 260+)
;create file
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy.txt" ;284
oFile := FileOpen("\\?\" vPath, "w")
oFile.Write("hello world`r`n")
oFile.Close()
;copy file
vPathNew := A_Desktop "\long path\ZCcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy.txt" ;284
oFile := FileOpen("\\?\" vPath, "r")
oFile.Pos := 0
oFile.RawRead(vData, vSize := oFile.Length)
oFile.Close()
if !oFile := FileOpen("\\?\" vPathNew, "w") ;empties file if it already exists
return
oFile.RawWrite(&vData, vSize) ;appends data, advances pointer
oFile.Close()
;rename file (by deleting and creating)
vPathNew := A_Desktop "\long path\ZMcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy.txt" ;284
oFile := FileOpen("\\?\" vPath, "r")
oFile.Pos := 0
oFile.RawRead(vData, vSize := oFile.Length)
oFile.Close()
if !oFile := FileOpen("\\?\" vPathNew, "w") ;empties file if it already exists
return
oFile.RawWrite(&vData, vSize) ;appends data, advances pointer
oFile.Close()
DllCall("kernel32\DeleteFile", Str,"\\?\" vPath) ;did work on 260+ files
return
;y:: ;create '260+' dir and a file within it
;note: example below uses MoveFileEx to rename folder, the folder can then be opened, and the file found inside
vPath := A_Desktop "\long path\abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy\hello.txt" ;290
;vPath := A_Desktop "\long path\zz\hello.txt"
SplitPath, vPath,, vDir
;create dir
DllCall("kernel32\CreateDirectory", Str,"\\?\" vDir, Ptr,0)
;create file
oFile := FileOpen("\\?\" vPath, "w")
oFile.Write("hello world`r`n")
oFile.Close()
return
;i:: ;move '260+' dir
vDir := A_Desktop "\long path\abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy" ;280
vDirNew := A_Desktop "\long path\zz"
;both MoveFile and MoveFileEx worked
DllCall("kernel32\MoveFile", Str,"\\?\" vDir, Str,vDirNew)
;DllCall("kernel32\MoveFileEx", Str,"\\?\" vDir, Str,vDirNew, UInt,0)
return
;not done, AHK uses SHFileOperation
;u:: ;copy '260+' dir
return
- I have found being able to convert to and from short-form useful for different reasons, it's part of the functionality that I've added here:
file get part (SplitPath alternative/short-form/long-form/correct case) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=47709
NOTES ON FUNCTIONALITY RELATED TO FILE LOOPS/LONG PATHS
- A file loop with * currently checks the short-form name as well as the long-form name. [I don't particularly mind if this behaviour is kept.]
- Since various bits of file-related functionality are touched by 'long' path issues, I thought it would be useful to collect ideas from my wish list that overlap with affected code.
- A_LoopFileAttribNum (or A_LoopFileAttribValue).
- A_LoopFileNameNoExt. (Useful in it's own right, plus it would be consistent with A_AhkNameNoExt and A_ScriptNameNoExt which would be very useful with #Include.)
- A_LoopFileTimeCreatedUTC/A_LoopFileTimeModifiedUTC (and A_LoopFileTimeAccessedUTC).
- A_Recent (or A_RecentItems) (Recent folder).
- FileInstall function for AHK v1.