Jump to content

Get Junction Point path..the true path


Recommended Posts

Hello.

I know that it is easy to determine if a path is a junction point by using a dllcall to GetFileAttributes.

I know I could create an algorithm that could examine each directory in the path and find if a "reparse point" is present. I know I could use various tools, such as dir /al to find the true path of the junction point/symlink.

I don't get to work with dlls too much, so always like to learn a little more.

What I want to achieve is to pass a file path to GetFileInformationByHandle. This "should" expose a value "nNumberOfLinks" which would indicate the file is in a junction point/symlink path, but not entirely sure. While there is no problem performing actions upon a junction point/symlink path, I am logging certain things to the registry. The registry writes the junction point, and later when doing comparison work, that junction point path does not become valid.

As an example, I pass the path

"c:\users\me\my documents\desktop.ini"

operations all work fine, then writing to registry it goes in as

"c:\users\me\my documents\desktop.ini"

However, later when doing comparisons, a return value will see it as

"c:\users\me\documents\desktop.ini"

this of course is not good, as the registry value really needs the true path to ensure no errors.

So really I want to "find" the true path if the path contains a junction point. The easiest solution I think would be to pass a directory to GetFileAttributes, check for reparse point, and to pass a file to GetFileInformationByHandle to get the nNumberOfLinks.

After I know if there is a junction point, I have to "convert" the "linked path" to a "true path".

The call looks easy enough. The return value is boolean, the input is a handle to a file (I presume a str path?). The output is a structure, which looks to be simply all dwords. Like I said, I don't have a lot of experience in dllcalls. Here is what I am messing with right now, but the return value ($aRet[2]) I am unsure of how to process. I had read that passing a file to that dllcall would return a boolean for the nNumberOfLinks...

#include <array.au3>

$path = "C:\Users\<user name>\my Documents\desktop.ini"
$struc = DllStructCreate('dword dwFileAttributes;dword ftCreationTime;dword ftLastAccessTime; dword ftLastWriteTime;' & _
    'dword dwVolumeSerialNumber;dword nFileSizeHigh;dword nFileSizeLow;dword nNumberOfLinks;' & _
    'dword nFileIndexHigh;dword nFileIndexLow')

$aRet = DllCall('kernel32.dll','bool','GetFileInformationByHandle','handle',$path,'ptr',DllStructGetPtr($struc))
If @error Then MsgBox(0,'','there was error')

MsgBox(0,'aRet',$aRet)

_ArrayDisplay($aRet)

ConsoleWrite('dwFileAttributes' & ' :<>: ' & DllStructGetData($struc,'dwFileAttributes') & @CRLF)
ConsoleWrite('ftCreationTime' & ' :<>: ' & DllStructGetData($struc,'ftCreationTime') & @CRLF)
ConsoleWrite('ftLastAccessTime' & ' :<>: ' & DllStructGetData($struc,'ftLastAccessTime') & @CRLF)
ConsoleWrite('ftLastWriteTime' & ' :<>: ' & DllStructGetData($struc,'ftLastWriteTime') & @CRLF)
ConsoleWrite('dwVolumeSerialNumber' & ' :<>: ' & DllStructGetData($struc,'dwVolumeSerialNumber') & @CRLF)
ConsoleWrite('nFileSizeHigh' & ' :<>: ' & DllStructGetData($struc,'nFileSizeHigh') & @CRLF)
ConsoleWrite('nFileSizeLow' & ' :<>: ' & DllStructGetData($struc,'nFileSizeLow') & @CRLF)
ConsoleWrite('nNumberOfLinks' & ' :<>: ' & DllStructGetData($struc,'nNumberOfLinks') & @CRLF)
ConsoleWrite('nFileIndexHigh' & ' :<>: ' & DllStructGetData($struc,'nFileIndexHigh') & @CRLF)
ConsoleWrite('nFileIndexLow' & ' :<>: ' & DllStructGetData($struc,'nFileIndexLow') & @CRLF)

There might even be other methods to do this more easily, I don't know. After a lot of searching, I didn't find one. I was hoping there might even be a simple method to pass in a path and get back a path without junction points/symlinks, but again, I don't find one.

Can anyone enlighten me?

Sul.

Edited by sulfurious
Link to comment
Share on other sites

You should look into this:

http://msdn.microsoft.com/en-us/library/aa364962%28VS.85%29.aspx

*GERMAN* [note: you are not allowed to remove author / modified info from my UDFs]My UDFs:[_SetImageBinaryToCtrl] [_TaskDialog] [AutoItObject] [Animated GIF (GDI+)] [ClipPut for Image] [FreeImage] [GDI32 UDFs] [GDIPlus Progressbar] [Hotkey-Selector] [Multiline Inputbox] [MySQL without ODBC] [RichEdit UDFs] [SpeechAPI Example] [WinHTTP]UDFs included in AutoIt: FTP_Ex (as FTPEx), _WinAPI_SetLayeredWindowAttributes

Link to comment
Share on other sites

Thanks. I missed that at MSDN I guess. I got that to work, as well as the one I was trying. I wasn't paying attention that it needed a handle, I was trying to pass a string path.

Back to the drawing board as they say, because I am checking a string path, not a handle.

I am disappointed that GetFileAttributes only works if the endpoint is a junction. It does not "detect" if anywhere in the path is a junction/symlink.

Anyone got any ideas how I can examine a path and find if it has a junction/symlink in the path, and even better, how to fetch the actual path? - without having to test each directory in the path?

I hate to have to resort to other methods. A dllcall would be so much cleaner.

Sul.

Link to comment
Share on other sites

After a bit more digging, things aren't shaping up any better.

Passing the path ($CmdLine) to _winapi_createfile() I get a handle to a file, pass it to GetFinalPathNameByHandle() dllcall.

If the object to drop originates from windows explorer @ c:\users\me\documents, then it succeeds. However, if I followed the directory structure to c:\users\me\my documents, it does not work, returns a 0 value.

If I use FileExist(), it gives essentially the same results.

I find that I can have 3 types of paths given to $CmdLine ...

a fully "false" path (ie. "c:\users\me\my documents\my music")

a partially "false" path (ie. "c:\users\me\documents\my music")

a fully "true" path (ie. "c:\users\me\documents\music")

It will depend on where the item is passed from as to what $CmdLine/$CmdLineRaw recieves, and thus what FileExist() or GetFinalPathNameByHandle() will return.

Is there no way to pass a path to a function, and get back the "absolute path" with the jpoints/symlinks removed?

In order to store a path that is drag/dropped or obtained via context menu (binary.exe %1) in the registry, there must be some sort of test for this. Surely MS doesn't expect novice users to steer clear of junction points and always do operations from actual directories, do they?

I was thinking to do this the hard way, by using dir c:\ /aL /s > jpoints.txt to obtain all of the jpoints/symlinks, then parsing that into a registry structure. Problem with that is there is no "safe" way to actually test. I have tried permutations of commands to attrib.exe, dir.exe, where.exe and chdir/cd. Sometimes, depending how you structure it, you can get a "fail" back to test on, but then you use the same command flags for another object/container, and the results are confusingly different.

Does anyone have any ideas how to achieve this? I even looked up vbs/wsh/wmi infos, and found nothing there either. I am truly stumped as to how to resolve this issue.

Sul.

Link to comment
Share on other sites

You should really show your code to eliminate you as variable. That code from the first post is wrong in more ways than just the handle thing. If that's the case with other code then there is no sense in even discussing the issue.

Now now, no need to insult me like that. I clearly stated I don't mess with dllcall very much. That code was me messing with it. I subsequently dropped that call and got a few others to work. And I assume you mean to show you my code, you mean the project? Do you really really really want to parse through 2000+ lines (including comments)? No, I wouldn't, so I just displayed the dllcall I was futzing with.

How do you expect others to want to stay here if they are treated like that? I have been here since, what, 05? Honestly, I am suprised to be treated thus. LOL, so many times people are hammered on with "what is your code look like" and "we're not going to do all the work for you". I would have thought your reply would have been "dude, you got that struct all messed up, and the data type you want is..".

I am not very good at converting API to autoit. I can do it in VB to a degree. I can convert VBS/WMI to autoit. I can do lots of things, I don't really need to toot my horn. Suffice to say that I simply expected more... like actually discussing the issue rather than hurling insults :x

BTW, I would gladly take pointers on that GetFileInformationByHandle call, even if I have been semi-accused of being the problematic variable.

Sul.

Edited by sulfurious
Link to comment
Share on other sites

What are you talking about?

If you want help with your code post it. Even without seeing it, it's pretty much clear what your mistakes with the code are. Pointing them to you wouldn't help much if it's done without the code.

If you are unable to write a little snippet that would demonstrate the issue then there is something really wrong. 2000+ lines of code is certainly not something that needs be posted. Or you think otherwise?

Deductive reasoning - learn it.

If you want serious help you (will) know how to get it.

Edited by trancexx
Link to comment
Share on other sites

What are you talking about?

If you want help with your code post it. Even without seeing it, it's pretty much clear what your mistakes with the code are. Pointing them to you wouldn't help much if it's done without the code.

If you are unable to write a little snippet that would demonstrate the issue then there is something really wrong. 2000+ lines of code is certainly not something that needs be posted. Or you think otherwise?

Deductive reasoning - learn it.

If you want serious help you (will) know how to get it.

What example am I needing to provide? I suppose this will work if that is what is needed.

If FileExists($CmdLineRaw) Then
    MsgBox(0,'Success','The path ' & $CmdLineRaw & ' exists')
Else
    MsgBox(0,'Failure','The path ' & $CmdLineRaw & ' does not exist')
EndIf

Exi

Compile that and drag/drop onto it using explorer:

1. c:\users\<user name>\documents\desktop.ini (success)

2. c:\users\<user name>\my documents\desktop.ini (failure)

If you collect each $CmdLineRaw value and log them, they will be different, yet the data lives in the same place. Most users aren't going to know the difference, and Vista/7 offer so many ways to get to certain directories, it would be a hassle. So the issue is, before you log the $CmdLineRaw value for future use, it should first be checked for an "absolute path". It must check both objects and containers, because the issue happens to both. If you don't get an absolute path, then you cannot do a simple string comparison later, or for that matter, even a FileExist comparison, or who knows what else one might want to do with it.

Now, you state "it's pretty much clear what your mistakes with the code are". I will have to assume you mean the code I posted with the dllcall, which I already knew didn't work, which is why I came here. Pointing them out is the whole reason to use this forum, isn't it? All the code is there, all that matters. I believe the goal was to get that dll call to work or learn of an alternate method.

I will assume you thought there was much more code needed to demonstrate what is going on to cause such a terse reply. I am not out to get a free ride. I do know a thing or two (or three). I did not just now fall of the turnip wagon. This is not my first rodeo. And lastly, I don't think I am as dumb as a box of rocks, but that might be debatable :x

Sul.

Link to comment
Share on other sites

Now, you state "it's pretty much clear what your mistakes with the code are". I will have to assume you mean the code I posted with the dllcall, which I already knew didn't work, which is why I came here.

No, I meant this:

If FileExists(StringReplace($CmdLineRaw, '"', '')) Then
    MsgBox(0,'Success','The path ' & $CmdLineRaw & ' exists')
Else
    MsgBox(0,'Failure','The path ' & $CmdLineRaw & ' does not exist')
EndIf

Was I right?

Link to comment
Share on other sites

I don't know exactly. I don't honestly know how supplying a little bit of code that display a strange quirk of the OS/file structure makes a difference in the topic.

The path you get from a drag/drop event can be either absolute or not. I thought that was the issue, and how to get an absolute, not whether any code produced errors. Well, other than that snippet I posted on the dllcall. Like I said, I don't do many of them, especially ones that need structs. I assumed that dllcall snippet had everything it needed to see if it would help find if a path had a junction point. I also assumed someone would call me a bozo and help me understand where I went wrong.

After digging on this issue for quite some time, I am not really finding any answer to the issue. If you pass a junction point as an argument, the OS is happy to pass it AS the junction point. I am developing a little tool that modifies objects/containers IntegrityLevels and ACL using things such as icacls. I am modifying the registry. All of these things, when thinking of John Q Noob, need to be able to "reset and restore" and not cause issues. If I am changing an ACE, I don't want to have a hiccup. So I found when going through my "restore" routines, that the registry value I had logged (I checked the original IL and logged it using path and IL value) was valid, but that depending on how the user would choose to restore, it might not match. This is due to the use of a context menu that of course passes the first parameter %1 as the path. If the path was from a junction point, and the logged path was also from a junction point, no problem. But the project also calls for a script file which the user is allowed to define fully qualified paths, and also later for a GUI which will use a FileOpen dialogue. All of these amount to not being able to guarantee where the user might start the actions from, either a junction point or an absolute path.

I am going ahead and doing a bunch of string parsing to handle the issue. I don't really like that method, but it might be the only way, to do a string comparison on each path to see if it contains a junction point path, and then replacing it if it does. It is always so much more enjoyable though to find a new way to do things that amounts to one small function and then implementing it in other projects. Like I said, the more I get to use API, the better, because most of what I do is either already a UDF or doesn't need it.

Sul.

Link to comment
Share on other sites

Hmm. To make matters even worse there is a problem dumping a dir command to text file.

If you use, from command prompt

dir c:\ /aL /s > c:\jpoints.txt

it will traverse all subdirectories looking for junctions and place the output in the file declared. One can then parse that file and do something with it.

If you use this

Run(@ComSpec & " /c dir c:\ /aL > c:\jpoints.txt",@SystemDir)

I operates correctly, but when you do this

Run(@ComSpec & " /c dir c:\ /aL /s > c:\jpoints.txt",@SystemDir)

and watch the console window, it starts following junction points beyond the first level, for many levels deep.Such as

C:\ProgramData\Application Data\Application Data\Application Data\Application Data\Application Data\Application Data

I haven't seen that happen before, but have not tried that command before with autoit either.

I tried changing the working directory to root, sysdir and leaving it blank (default), none of them resolve it. I have already done the code to parse the text and get it into a usable form and was about to automate the whole process and examine it. I don't want to call a batch file to do this, it seems comspec should be fine, but apparently not.

Sul.

Link to comment
Share on other sites

You were right, in a manner of speaking. I didn't notice that StringReplace() you put in there at first, but that wasn't the issue. The problem stems, it seems, from using an alternate explorer. I use Q-dir in win7 rather than windows explorer. It passes the literal string path rather than the referenced junction path.

Also, is a $CmdLine[] argument needing a StringReplace for double quotes as in your example? I have a possible 4 arguments, one of which can be a path, but I have never had to strip double quotes. The example using $CmdLineRaw was just something to show how a passed argument might have either an absolute path or a junction point.

It is also interesting that if you use Q-dir to execute an .au3 file, which start scite, then use Run() with that dir c:\ /aL /s command, you get the peculiar behaviour I described. But if you use window explorer to execute the .au3 and open it in scite, that error no longer happens. Must be a problem with Q-dir then.

It resolves that issue, but still leaves me with the problem of how to efficiently manage a user supplied FQP when they use "c:\users\name\my documents" rather than the actual FQP of "c:\users\name\documents". If the user supplies a command line parameter with a junction point path, and the program is capable of finding/verifying/logging that path, then later, from a GUI or context menu option the non junction point path is given, the logged (junction) path cannot be accurately compared to the given (absolute) path.

All this is to say I can build a string parsing routine, but is there no method avialable (API etc) that will return an absolute path from a junction point path without StringInStr(), StringReplace() and multiple stepping that would be needed? I know it works, but we always strive to compact the code and optimize it :x

Sul.

Edited by sulfurious
Link to comment
Share on other sites

I found no method to return an absolute path. My original questions came about from testing my program in vmWare using default installs of Vist Ultimate x32/x64 and Win7 Ultimate x32/x64. The issues were found on win7 x64, using a windows context menu to pass the %1 parameter to my program, by a small test GUI that allowed drag/drop to get the command line argument (essentially %1), by using command prompt on the binary with correctly structured paramaters and finally by the use of a type of INI file that allows bulk commands.

The original problem was that a given "path" would be valid and actions performed upon it, but when that path was logged to the registry as a REG_SZ value, it would not always be the same the next time around. For example, if I gave a path to a file with one of the input methods listed above, assigned a Deny Execute ACE to the object and then logged the path to the registry, when the file was passed again with a "remove deny execute" request, depending on from where and what method the path was passed, the path might be either in junction point form or absolute form. Factors in my program need to compare the incoming path to an existing logged path, and depending upon the logic, either continue to run or abort.

Thus, the need to guarantee that the absolute path is always found prior to any file manipulation is important, else unexpected events happen. I was hoping to find an API or other "fancy" method, mainly because I am weak in that area and it would have been a good exercise.

In the end I resorted to string manipulation. Nothing wrong with it at all. I could probably use a regular expression, but that is another area I don't have uses for that often and am weak in. Here is the code that I used. I commented out some internal stuff.

Func _parseJunctionPoints()
    Local $sJbase,$j1,$sJextended,$sAbsolute,$rK
    Dim $arJ
    If Not FileExists('c:\jpoints.txt') Then
        RunWait(@ComSpec & " /c dir c:\ /aL /S > c:\jpoints.txt",@SystemDir,@SW_HIDE)
    EndIf
    If Not FileExists('c:\jpoints.txt') Then
        ; error - cannot find jpoints.txt
        ;_errLog(766,'area: junction points','c:\jpoints.txt','command: output jpoints to file','error: file not found after command')
        ;If $DEBUG Then _outDebug(767,'dir c:\ /aL /s > c:\jpoints.txt - failed to create the file')
        ;SplashOff()
        ;MsgBox(4096,'Error','Could not create the file c:\jpoints.txt' & @CRLF & _
        ;           'The information in this file is needed to proceed' & @CRLF & _
        ;           'You may try to create this file manually by opening' & @CRLF & _
        ;           'command prompt and issuing this command:' & @CRLF & @CRLF & _
        ;           'dir c:\ /aL /s > c:\jpoints.txt' & @CRLF & @CRLF & _
        ;           'Verify c:\jpoints.txt exists and proceed again')
        Exit
    EndIf
    _FileReadToArray('c:\jpoints.txt',$arJ)
    ;If $DEBUG Then _ArrayDisplay($arJ,'Raw Junction Points')
    RegWrite('HKCU\Software\JPoints','','REG_SZ','jpoints')     ;... create a default value to search on
    For $x = 1 To $arJ[0]
        If StringInStr($arJ[$x],'directory of') Then $sJbase = StringStripWS(StringReplace($arJ[$x],'Directory of',''),3)
        If StringInStr($arJ[$x],'<junction>') Then
            $j1 = StringTrimLeft($arJ[$x],StringInStr($arJ[$x],'N>')+2)
            $sJextended = StringLeft($j1,StringInStr($j1,'[')-1)
            $sJextended = StringStripWS($sJextended,3)
            $sAbsolute = StringTrimLeft($j1,StringInStr($j1,'[')-1)
            $sAbsolute = StringStripWS(StringReplace(StringReplace($sAbsolute,'[',''),']',''),3)
            $rK = StringReplace($sJbase & '\' & $sJextended,'\\','\')
    ;~      ConsoleWrite($rk & ' == ' & $sAbsolute & @CRLF)
            RegWrite('HKCU\Software\JPoints',$rK,'REG_SZ',$sAbsolute)
        EndIf
    Next

EndFunc

All this was needed then was to step through an array of those registry values, comparing the incoming path to see if it contained a junction path, and if it did replacing it with the absolute path from the array.

Hope this helps someone else out someday.

Sul.

EDIT: found something that needed to be appended. When the jpoints are parsed out, they include the path "c:\users\<name>\documents\my music" which has an absolute path of "c:\users\<name>\music". I found that it will be common still for a user to perhaps enter a path like "c:\users\stv\my documents\my music", which is a valid jpoint path. In order to handle any of these "my" entries I added a little piece to search for them and append "My " in front of "Documents" so as to catch those as well.

For $x = 1 To $arJ[0]
        If StringInStr($arJ[$x],'directory of') Then $sJbase = StringStripWS(StringReplace($arJ[$x],'Directory of',''),3)
        If StringInStr($arJ[$x],'<junction>') Then
            $j1 = StringTrimLeft($arJ[$x],StringInStr($arJ[$x],'N>')+2)
            $sJextended = StringLeft($j1,StringInStr($j1,'[')-1)
            $sJextended = StringStripWS($sJextended,3)
            $sAbsolute = StringTrimLeft($j1,StringInStr($j1,'[')-1)
            $sAbsolute = StringStripWS(StringReplace(StringReplace($sAbsolute,'[',''),']',''),3)
            $rK = StringReplace($sJbase & '\' & $sJextended,'\\','\')
            [b]If StringInStr($rk,'\Documents\My ') Then
                $j1 = StringReplace($rk,'\Documents\My ','\My Documents\My ')
                ConsoleWrite('** ' & $j1 & ' == ' & $sAbsolute & @CRLF)
            EndIf[/b]
            ConsoleWrite($rk & ' == ' & $sAbsolute & @CRLF)

;~          RegWrite('HKCU\Software\JPoints',$rK,'REG_SZ',$sAbsolute)
        EndIf
    Next
Edited by sulfurious
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...