Scripting your way into the Knickers of Reparse-Point Rachel

A collection of especially useful xplorer² topics and ideas. New users may find it helpful to look here before searching the other forums for information. >>>>>> Please post new material in the relevant forum. (New stuff posted here will be removed.) Thanks. -fg-

Moderators: fgagnon, nikos

Post Reply
Kilmatead
Platinum Member
Platinum Member
Posts: 4569
Joined: 2008 Sep 30, 06:52
Location: Dublin

Scripting your way into the Knickers of Reparse-Point Rachel

Post by Kilmatead » 2013 Jul 13, 11:19

Were the historical purveyors of progress to be believed, the adolescent and disaffected young men of the world who took up coding (having failed at the guitar and all the images of rock-star dissatisfaction that ensues) could be seen as the dedicated pioneers of tomorrow, lionised by the great unwashed as intelligent individuals for whom it is considered socially acceptable to indulge their anti-social proclivities. And why? Because they tend to produce things that other people think are valuable.

The reality is a little different, of course - for these young men are victims of the same psychological frailties as form conspiracy-theorists, politicians, and girls with artificially ebullient personalities: Insecurity and Fear. In short, they live in a world which they are just beginning to understand is far beyond their capacity to understand or influence, where the whims of the few disturb the greater urges of youth in ways psychologists have yet to comprehend.

And then one day they discover that in the world of computers, everything is there for them to command and control - there are no errant and dishevelled lager-lout ruffians roaming the countryside terrorising the villages - there is only the opportunity to form a perfect world according to your own rules, where the satisfaction of accomplishment takes a backseat to getting the girl of their dreams in - well - the backseat. :wink:

To that end, the knickers of Reparse-Point Rachel and the mysteries of Jennifer's Junction-Point have a higher priority than the family-friendly tone the moderators of the world would prefer. So sayeth the post-adolescent mind of the mischievously-humorous and ever-curious control-freak coder, anyway. :D

"So what are you on about this time, is it yet another utility no one but hopeless anoraks and the emotionally damaged will ever find a use for?"

Well obviously! The day I produce anything practical and beneficial to humanity is the day I feed myself to the lions for the shame I would so feel. Except this time we will actually take a closer look at one particular function, created in a fit of blind frustration, which (if nothing else) the other emotionally damaged misfit coders of the world may appreciate.

Image

Download: TouchReparsePoint (source-code included for the curious and bored)

"So what's it do?" you ask.

Well - nothing, really, it's more of an elaborate proof-of-concept which allows us to see behind the frilly black lacing of Rachel's lingerie, to indulge our understanding of accessing the mysteries therein, and (more importantly) to see just how far we can stretch a smart-arse metaphor before it breaks and snaps us cruelly on the chin. :roll:

"Get to the point quickly, dude, my attention span isn't what it used to be."

In essence, if you've ever tried to manually change the timestamps on a reparse-point object, you'd find that your new timestamp would not actually be applied to the Symbolic Link or Junction Point you're looking at, but rather it would zip remotely through the wormhole and onto the target object itself. Normally at this point anyone else would just shrug and turn back to their cold oatmeal and soggy toast to look across the breakfast table and wonder if asking that sad and glintless woman to be your wife was really a good idea or not.

But not today! Today we will lift our sorrowful faces into the light to see what lies on the other side of that wormhole, and how it may transform our understanding of what a good breakfast is all about and why it really is the most important meal of the day.

"You're kidding, right? I mean, timestamps of junction points? Who gives a toss?"

<Sigh> Oh ye of little vision - look at the big picture, I beg of you. But first let's get the usual nonsense of the utility out of the way: Once you know what you're looking at, it should all be pretty self-explanatory...

Image

For simplicity this one has a couple of entry-points - you may either call it with command-line arguments via a user-command ('> TRP.exe $A') or just run the executable and Drag-&-Drop one or more symbolic links, junction points, or mount point objects directly into the window. It will happily identify the link-types, display the target objects (relative path viewing optional), and let you play with syncing their timestamp inheritance values. If a non-reparse object is used, it's just treated like any normal timestamp assignment task.

"Oh my god - you're actually serious - is that all it does?"

Yes, but once again, I ask you to look at...

"No! I am the voice of the ever-demanding Great Unwashed! I want to know if I've just wasted 20 minutes reading all this nonsense only to discover it's of no interest to me!"

Look, if you just let me explain...

"Bugger that! Get to the point already! Why should I be interested in this? I'm just a guy who writes the odd script to help him manage file and folder tasks - what good is this to me?"

Look closer at the information displayed in the window. Say you've just written a script that recursively parses a folder structure searching for files (or whatever it does), and you're all very proud of yourself that your script works as expected. So you give it out to your friends with great pride thinking it's idiot-proof. And then the next day you start experimenting and discover that if it encounters a reparse-point object, your happy little recursion routine can accidentally end up only God-knows-where, and in a worse-case scenario, in an endless Ouroboros loop - and you think to yourself: If I only knew how to retrieve the target of a reparse-point before I entered it, I might have been able to avoid this mess, or at the very least I'd be better able to predict where/what my script was processing and how it got there.

"Well, Ok, I see what you mean, but I think you made that 'Ouroboros' word up. All I want to know is how to scripturally retrieve the targets of Symbolic Links and Junction Points. Are you saying you can tell me how to do this?"

Exactly! Welcome to my breakfast table, sit right down, pour yourself a coffee, light a cigarette, and we'll see how this works.

Like most things in life, there are actually two ways to do this: the easy way and the hard way. We'll look at the easy way first, just to get an idea of what's happening. I, of course, am using the AutoIt scripting language to do this, but as what we're doing is 99% Win32 API calls, it can be easily translated into any language, be that AHK, C, or the verbiage your grandmother used to mutter warblingly in her sleep. All you need to know is how the desired language interfaces with the stock kernel32.dll file when calling the functions.

I consider the first method the "Where's Wally?" approach as all it does is jump through the wormhole, then query the filesystem about where it ended up. Seems simple enough:

Code: Select all

$hFile = _WinAPI_CreateFileEx($sLink, $OPEN_EXISTING, 0, BitOR($FILE_SHARE_READ, $FILE_SHARE_WRITE, $FILE_SHARE_DELETE), $FILE_FLAG_BACKUP_SEMANTICS)
$Target = _WinAPI_GetFinalPathNameByHandleEx($hFile)
"Whoa there, that's not simple, that's gibberish!"

It's not, really. In Windows, files are opened using the CreateFileW function ('W' for "Wide character", meaning unicode) - despite its name, it is used for opening and reading objects too, hence the OPEN_EXISTING flag. One interesting thing to note here is that the third parameter normally contains either GENERIC_READ or GENERIC_WRITE constants to indicate the access-type - but in this case we're using zero ("permissions-free"), because that allows the attributes of certain files to be read (such as reparse-tags), without truly "opening" the object, thus not causing an access-denial error (this allows us to resolve system reparse-points as well as the more traditional per-user links without having to mess with their ACL's). FILE_FLAG_BACKUP_SEMANTICS is one of those wonderful things that actually tastes better than it sounds (like Pizza with Barbecue-Sauce or Mud Pie Ice-Cream) and basically is best thought of as a means of obtaining an open handle to a folder. (It is actually about verifying to the OS that the object is being opened for a backup or restore operation and requests that the OS deals with the necessary permissions accordingly - but at the end of the day, it's predominantly used to open Folder-objects without generating spurious access-errors.)

Once we have the object handle, we just call GetFinalPathNameByHandleW and it returns a full path to whatever we just opened.

"Now wait, didn't we just open the Reparse-Point by name, you mean we didn't actually open the thing we called?"

That's right - the way NTFS reparse-points work is that they are essentially normal file/folder objects with special user-defined "Tag-data" attached to them which (when found) is then immediately interpreted by the filesystem "filter" assigned to that type of tag before the user get can get his grubby hands on them - that's why when you click on a junction in a file manager it's the target you actually end up looking at, not the folder itself, or play the media-file the symlink points to, and not just end up looking at zero-size file content.

Now obviously anyone using a decent file-manager or shell extension (or even just those stuck using MKLINK.EXE) to create and manipulate Symlinks and Junctions know "how" the paradigm works, that's what makes the objects useful to us, but most don't know "why" it works the way it does - and most don't care. But for our purposes, it's useful background knowledge to have. :D

"Ok, so this method got us the target-string - that's all we wanted, wasn't it? What's wrong with it?"

A couple of drawbacks - for one, GetFinalPathNameByHandle can't be used on Windows XP as it doesn't exist in the API, so it's only good for Vista and above. Also, that function only works properly if the link target actually exists - if (for some reason) the reparse tag points to an object that has been moved or is otherwise unavailable (on a detached drive, for example), GetFinalPathNameByHandle is useless because the filter will have failed to resolve the destination upon the CreateFile call, and thus there is no object path to return. Also, symlink tags may have embedded "relative" paths, not absolute ones, and occasionally (especially in the case of being unable to resolve a path) it may be useful to allow the user to see this, so he can figure out what went wrong, and allow your script to do some better housekeeping.

Obviously, to anyone used to reparse-points, there are many ways for the user himself to "see" the destination path, even if it doesn't exist (shell columns, shell extensions, FSUTIL.EXE, etc) - but it's not so simple for your poor lonesome script to "see" this information itself, in a practical sense.

"So this is where it gets interesting then, yeah? When do we get to the part where I get to raise Rachel's skirt?"

Hold your horses, slow and steady wins the day - if you just show how desperate you really are and jump the gun the girl will never acquiesce to your charms... and before you know it, you're rejected, cold, alone, and you've gone and signed up for the Légion étrangère like many a broken-hearted youth before ye. So, before that happens, let's see how this courtship thing works first, shall we? As the saying goes, you can climb any tree once it's down - it's just a matter of getting it down with as little fuss as possible first. :wink:

Now, about those Tags. There are actually many types of ReparseTag-Identifiers available under NTFS, but we're mainly just interested in two of them: IO_REPARSE_TAG_MOUNT_POINT (which gives us access to Junctions and actual Volume Mount Points themselves), and IO_REPARSE_TAG_SYMLINK which references both relative and absolute paths related to Symlinks.

When building the function to retrieve reparse targets, the best place to start is the API function FindFirstFileW.

Code: Select all

$tFindData = DllStructCreate($tagWIN32_FIND_DATA)
$hFile = _WinAPI_FindFirstFile($sLink, DllStructGetPtr($tFindData))
Note that this is very different from AutoIt's "built-in" FindFirstFile function - that only gets us a filename which is useless by itself - using the API we get back a FIND_DATA structure which contains many items, amongst them the file's attributes and (if reparse) the Tag identifier itself. This kills three birds with one stone - we check to see if the requested file actually exists, we check if its attributes contain FILE_ATTRIBUTE_REPARSE_POINT, and we get the tag which will tell us whether we are looking to reserve a SymbolicLinkReparseBuffer or a MountpointReparseBuffer

"Huh? What the heck are those things?"

Reparse buffers are the things that actually contain the information we want - there are actually 3 different kinds (Symbolic, Mount, and Generic) but we only need concern ourselves with the first two. The "official" C-language syntax structure would look something like this:

Code: Select all

typedef struct _REPARSE_DATA_BUFFER {
  ULONG  ReparseTag;
  USHORT ReparseDataLength;
  USHORT Reserved;
  union {
    struct {
      USHORT SubstituteNameOffset;
      USHORT SubstituteNameLength;
      USHORT PrintNameOffset;
      USHORT PrintNameLength;
      ULONG  Flags;
      WCHAR  PathBuffer[1];
    } SymbolicLinkReparseBuffer;
    struct {
      USHORT SubstituteNameOffset;
      USHORT SubstituteNameLength;
      USHORT PrintNameOffset;
      USHORT PrintNameLength;
      WCHAR  PathBuffer[1];
    } MountPointReparseBuffer;
    struct {
      UCHAR DataBuffer[1];
    } GenericReparseBuffer;
  };
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
Since structural "unions" (commonplace in "real" programming languages like C) are a little more difficult to define when scripting due to data-type size drifting between declarations, we'll just define the type we want in one go (since they're so similar anyway). If you look closely, the only real difference between them is the extra 4-byte "ulong [dword] Flags" element which tells us whether a symbolic link contains a relative path or an absolute one. (Exactly why MS requires 4 bytes to identify what is essentially a boolean element is beyond me - their motto must be "Never a wasted opportunity for bloat!")

Of the other elements, we're only really interested in the "SubstituteName" offset values, as they will allow us to dissect the DataBuffer itself, which is where our "target" resides.

"So how do we suck the marrow out of the bones of the Reparse Point and stuff it in our carefully defined structure?"

Well, we go back to the API...

Code: Select all

$hFile = _WinAPI_CreateFileEx($sLink, $OPEN_EXISTING, 0, BitOR($FILE_SHARE_READ, $FILE_SHARE_WRITE, $FILE_SHARE_DELETE), BitOR($FILE_FLAG_BACKUP_SEMANTICS, $FILE_FLAG_OPEN_REPARSE_POINT))
WinAPI_DeviceIoControl($hFile, $FSCTL_GET_REPARSE_POINT, 0, 0, DllStructGetPtr($RGDB), DllStructGetSize($RGDB))
"Ok, now you're just taking the piss - WTF is all this?"

If you remember in the "Where's Wally?" approach we had to open the reparse point first, and we have to do it now as well, except this time instead of allowing the NTFS reparse filter to transfer us to the target automatically (which would obviously defeat the purpose) we have to open the actual reparse point itself, by using (unsurprisingly) FILE_FLAG_OPEN_REPARSE_POINT.

We then need to use DeviceIoControl to read the contents of the tag... again, unsurprisingly, FSCTL_GET_REPARSE_POINT is the file-system control-code (FSCTL) to do this - the attentive reader will note that at this point if we were to open the reparse object using GENERIC_WRITE instead of GENERIC_READ, and substitute FSCTL_SET_REPARSE_POINT (or even FSCTL_DELETE_REPARSE_POINT), we could really stretch our knickers metaphor to the breaking point. You get the idea - courtship is really just a roadmap - what happens after the lights go out and the skirts go up is entirely up to the consenting individuals involved. :D

"For Odin's sake my good man, are we done yet? I've been reading this rubbish for hours - if you stayed on topic we might get there faster!"

As it happens, we are done - the DeviceIoControl call will deposit the SubstituteName and/or the PrintName strings in the Buffer so all we have to do is read the one we want.

"What's the difference between a SubstituteName and a PrintName? Should I care?"

Yes, you should care - for one thing, Junctions/Mount Points will only return a SubstituteName string, while Symbolic Links will return both - effectively SubstituteName strings will contain the full UNC path of the target objects (or the relative path, if need be), while PrintNames are the "cleaned up" versions of those - but since Junctions have no PrintNames, just use SubstituteName.

The "offset" and "length" values pertaining to SubstituteName are calculated via the size of the wchar type itself (wchar = unicode = 2 bytes), so just extract the string position in the Buffer by dividing everything by 2.

And after a little string clean-up, and/or providing a more accurate Link-type identifier to the user (is the target a Junction or a Mount Point?, if Symlink does it have a relative or absolute path?) we're done. For simplicity I've allowed for the automatic conversion of relative paths into absolute based on the link container itself, so it's easier for the rest of the user's script to deal with, but that's all normal stuff.

While the full script of the utility (as a usage-example) and the _GetReparseTarget() function source-code is included in the download itself, I'll add the function code here just for those "curious" but "not curious enough" to wonder what material Rachel's knickers were made of. :wink:

I hope you've enjoyed this little explanation of reparse-points - if you found reading it all too difficult, imagine what it was like trying to figure this stuff out by piecing together stuff from the web to get a cohesive function that works on both XP and Vista+, is compatible with UNC pathways, and was reliable enough for a confident public release. Believe it or not, there is no rule-book for dealing with reparse-points - it's every man for himself when it comes to deciphering the MSDN references.

I thank the users fgagnon and RightPaddock for their invaluable beta-testing and bug detection, and nikos for pointing me back in the direction of FILE_FLAG_BACKUP_SEMANTICS which I had dismissed as irrelevant upon first glance, but which ultimately lead to everything that came afterward.

Code: Select all

; #FUNCTION# ====================================================================================================================
; Name...........: _GetReparseTarget
; Description....: Resolves a Reparse-Point (Junction, Symbolic Link or Mount Point) to its target and returns that destination path
; Syntax.........: _GetReparseTarget ( $sLink[, $AbsPath = True] )
; Parameters.....: $sLink - Full path to a Reparse-Point object
;							$AbsPath - Return an absolute path when link ID type is 2 (embedded relative path Symbolic Link)
;
; Return values..: Success - The path/filename of the target location
;
;									@extended returns the ID/type of the Reparse-Point itself:
;									0 - Unknown/Unresolved
;									1 - Symbolic Link (embedded Absolute-Path)
;									2 - Symbolic Link (embedded Relative-Path) - primary return value will be an absolute path via the $sLink container (set $AbsPath = False to return the relative path)
;									3 - Junction Point
;									4 - Mount Point - primary return value will be the Globally Unique Identifier (GUID) as \\?\Volume{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\
;
;							Failure - Empty string ("") and sets the @error flag:
;									1 - $sLink Not Found
;									2 - Unable to Open $sLink
;									3 - $sLink is not a Reparse-Point
;									4 - Unresolveable (Corrupted Tag / No Target details)
;
; Author.........: Kilmatead
; Modified.......:
; Remarks........: @Extended may still contain a valid ID even if the link itself failed resolution
;
;						The $AbsPath parameter has no effect beyond relative path Symbolic Links (ID 2)
;
;						No check is made to see if the resolved target folder or file actually exists, as even though the target-destination may have been renamed/removed or is temporarily
;						unavailable, that doesn't invalidate the data integrity of the reparse-tag itself, especially when it may contain relative-path references
;
;						Permission-Free access is used to open the link so as to resolve even System Links (as found in Vista+) - this can be misleading, as it does not indicate that using
;						$sLink directly in the script outside of this function will likely fail as System Links have ACL's which deny access to everyone
;
; Related........:
; Link...........:
; Example........:
; ===============================================================================================================================

#include <APIConstants.au3>
#include <File.au3>
#include <WinAPIEx.au3>

Func _GetReparseTarget($sLink, $AbsPath = True)
	Local Enum $ID_UNKNOWN, $ID_SYMLINK, $ID_SYMLINK_RELATIVE, $ID_JUNCTION, $ID_MOUNT_POINT
	Local Enum $NOTFOUND = 1, $ACCESSDENIED, $NOTREPARSE, $NOTRESOLVED

	Local $tFindData = DllStructCreate($tagWIN32_FIND_DATA)
	Local $hFile = _WinAPI_FindFirstFile($sLink, DllStructGetPtr($tFindData)) ; Retrieve the attributes / verify existence / obtain the ReparseTag identifier
	If @error Then Return SetError($NOTFOUND, $ID_UNKNOWN, "")
	_WinAPI_FindClose($hFile)

	If BitAND(DllStructGetData($tFindData, "dwFileAttributes"), $FILE_ATTRIBUTE_REPARSE_POINT) Then
		Local Const $IO_REPARSE_TAG_SYMLINK = 0xA000000C
		Local Const $IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003

		Local $Ret = "", $TypeID = $ID_UNKNOWN
		Local $Tag = _WinAPI_LoWord(DllStructGetData($tFindData, "dwReserved0"))

		Local $tREPARSE_GUID_DATA_BUFFER = _
				"dword ReparseTag;" & _
				"word ReparseDataLength;" & _
				"word Reserved; " & _
				"word SubstituteNameOffset;" & _
				"word SubstituteNameLength;" & _
				"word PrintNameOffset;" & _
				"word PrintNameLength;"

		Select
			Case BitAND($Tag, $IO_REPARSE_TAG_SYMLINK)
				$TypeID = $ID_SYMLINK
				$tREPARSE_GUID_DATA_BUFFER &= "dword Flags;" ; Convert (default) struct MountPointReparseBuffer to struct SymbolicLinkReparseBuffer

			Case BitAND($Tag, $IO_REPARSE_TAG_MOUNT_POINT)
				$TypeID = $ID_JUNCTION

			Case Else
				Return SetError($NOTRESOLVED, $ID_UNKNOWN, "")
		EndSelect

		$hFile = _WinAPI_CreateFileEx($sLink, $OPEN_EXISTING, 0, BitOR($FILE_SHARE_READ, $FILE_SHARE_WRITE, $FILE_SHARE_DELETE), _ ; dwDesiredAccess 0 (permission-free)
				BitOR($FILE_FLAG_BACKUP_SEMANTICS, $FILE_FLAG_OPEN_REPARSE_POINT))
		If @error Then Return SetError($ACCESSDENIED, $TypeID, "")

		Local $RGDB = DllStructCreate($tREPARSE_GUID_DATA_BUFFER & "wchar PathBuffer[4096]")
		_WinAPI_DeviceIoControl($hFile, $FSCTL_GET_REPARSE_POINT, 0, 0, DllStructGetPtr($RGDB), DllStructGetSize($RGDB))

		If Not @error Then
			Local Const $SYMLINK_FLAG_RELATIVE = 0x00000001
			Local Const $SIZEOF_WCHAR = 2

			Local $sBuffer = DllStructGetData($RGDB, "PathBuffer") ; Buffer "may" contain multiple strings "in any order" [MSDN]...
			Local $iOffset = DllStructGetData($RGDB, "SubstituteNameOffset") / $SIZEOF_WCHAR
			Local $iLength = DllStructGetData($RGDB, "SubstituteNameLength") / $SIZEOF_WCHAR

			$Ret = StringMid($sBuffer, 1 + $iOffset, $iLength) ; ...so always extract SubstituteName (despite its moniker) as the path-proper

			If StringLeft($Ret, 2) = "\?" Then $Ret = "\\" & StringMid($Ret, 3) ; DeviceIoControl loves substituting \??\ for more common \\?\, so we substitute it right back

			If $TypeID = $ID_SYMLINK And DllStructGetData($RGDB, "Flags") = $SYMLINK_FLAG_RELATIVE Then
				$TypeID = $ID_SYMLINK_RELATIVE
				If $Ret <> "" And $AbsPath Then $Ret = _PathFull($Ret, StringLeft($sLink, StringInStr($sLink, "\", 0, -1))) ; Convert to absolute path based from $sLink container
			EndIf

			Select ; Regulate possible mapped/unmapped UNC prefix genera or verify Mounted Volume ID by format
				Case StringRegExp($Ret, "(?i)\\Volume\{[a-f\d]{8}-([a-f\d]{4}-){3}[a-f\d]{12}\}\\$") ; "\Volume{GUID}\"
					$TypeID = $ID_MOUNT_POINT
				Case StringLeft($Ret, 8) = "\\?\UNC\"
					$Ret = StringReplace($Ret, "?\UNC\", "", 1) ; "\\?\UNC\server\share" -> "\\server\share"
				Case StringLeft($Ret, 4) = "\\?\" And StringMid($Ret, 6, 1) = ":"
					$Ret = StringTrimLeft($Ret, 4) ; "\\?\C:\FolderObject" -> "C:\FolderObject"
			EndSelect
		EndIf

		_WinAPI_CloseHandle($hFile)
		$RGDB = 0

		If $Ret = "" Then Return SetError($NOTRESOLVED, $TypeID, "")

		Return SetExtended($TypeID, $Ret)
	EndIf

	Return SetError($NOTREPARSE, $ID_UNKNOWN, "")
EndFunc
Last edited by Kilmatead on 2017 Mar 15, 20:05, edited 1 time in total.

Kilmatead
Platinum Member
Platinum Member
Posts: 4569
Joined: 2008 Sep 30, 06:52
Location: Dublin

Re: Scripting your way into the Knickers of Reparse-Point Ra

Post by Kilmatead » 2013 Dec 27, 22:26

Following on from some interesting experiments pertaining to the behaviour of system reparse-points, the utility outlined above (TouchReparsePoint) and the attending resolution function have been updated (links in the original post) with the ability to resolve (and indeed sync) the targets and relevant timestamps of even system reparse-points.

Why is this of note, you ask? Well, when the original resolution function was written I was aware that the system reparse-points (the ones everyone knows from their user-folder) had their Access Control Lists set to deny read access to anyone other than SYSTEM permissions, and thus they could not be resolved by the function. I wasn't particularly bothered by this, considering that it made sense as MS wouldn't want plebeian users messing with these legacy-compatibility links - and, besides, the function worked flawlessly on all other types of links (the ones users create themselves), as was originally intended.

Turns out I missed a little something:
CreateFile API (MSDN) wrote:[Regarding dwDesiredAccess:] If this parameter is zero, the application can query certain metadata such as file, directory, or device attributes without accessing that file or device, even if GENERIC_READ access would have been denied.
Which is where it gets curious, because I had used the GENERIC_READ flag on the (reasonable) assumption that to open a reparse-point for content-retrieval, one had to "read" the file. So, fair enough, replacing GENERIC_READ with zero did the trick, allowing us to resolve any link we wanted, even protected system ones (given that reparse-tags are part of the NTFS filter-driver, it makes sense, in hindsight) - except it didn't stop there.

Having written, just for the fun of it, a subsequent function for removing reparse-links (using FSCTL_DELETE_REPARSE_POINT)...

Code: Select all

; #FUNCTION# ====================================================================================================================
; Name...........: _DeleteReparsePoint
; Description....: Neutralises a Reparse Point and removes the lefover generic object
; Syntax.........: _DeleteReparsePoint ( $sLink[, $LeaveFinalObject = False] )
; Parameters.....: $sLink - Full path to a Reparse-Point object
;							$LeaveFinalObject - Neutralising the Reparse tag leaves an empty file or folder in its place, ordinarily this would be removed as well, but may optionally be left extant
;
; Return values..: Success - 1
;
;							Failure - 0 and sets the @error flag:
;									1 - $sLink Not Found
;									2 - Unable to Open $sLink (Access Denied)
;									3 - $sLink is not a Reparse-Point
;									4 - Unable to remove the Reparse Tag
;									5 - Unable to remove the Final Object
;
; Author.........: Kilmatead
; Modified.......:
; Remarks........:
; Related........:
; Link...........:
; Example........:
; ===============================================================================================================================

#include <APIConstants.au3>
#include <WinAPIEx.au3>

Func _DeleteReparsePoint($sLink, $LeaveFinalObject = False)
	Local Enum $NOTFOUND = 1, $ACCESSDENIED, $NOTREPARSE, $TAGNOTDELETED, $FINALOBJECTDELETEFAILURE

	Local $tFindData = DllStructCreate($tagWIN32_FIND_DATA)
	Local $hFile = _WinAPI_FindFirstFile($sLink, DllStructGetPtr($tFindData))
	If @error Then Return SetError($NOTFOUND, 0, 0)
	_WinAPI_FindClose($hFile)

	If BitAND(DllStructGetData($tFindData, "dwFileAttributes"), $FILE_ATTRIBUTE_REPARSE_POINT) Then
		Local $tREPARSE_GUID_DATA_BUFFER = "dword ReparseTag; word ReparseDataLength; word Reserved; byte ReparseGuid[16];"

		$hFile = _WinAPI_CreateFileEx($sLink, $OPEN_EXISTING, $GENERIC_WRITE, BitOR($FILE_SHARE_READ, $FILE_SHARE_WRITE, $FILE_SHARE_DELETE), _
				BitOR($FILE_FLAG_BACKUP_SEMANTICS, $FILE_FLAG_OPEN_REPARSE_POINT))
		If @error Then Return SetError($ACCESSDENIED, 0, 0)

		Local $RGDB = DllStructCreate($tREPARSE_GUID_DATA_BUFFER) ; REPARSE_GUID_DATA_BUFFER_HEADER_SIZE = 24

		DllStructSetData($RGDB, "ReparseTag", DllStructGetData($tFindData, "dwReserved0"))

		_WinAPI_DeviceIoControl($hFile, $FSCTL_DELETE_REPARSE_POINT, DllStructGetPtr($RGDB), DllStructGetSize($RGDB)) ; Destroy the Tag, generic object remains
		Local $Ret = @error

		_WinAPI_CloseHandle($hFile)
		$RGDB = 0

		If $Ret = 0 Then
			If $LeaveFinalObject Then Return 1

			If BitAND(DllStructGetData($tFindData, "dwFileAttributes"), $FILE_ATTRIBUTE_DIRECTORY) Then
				If DirRemove($sLink) Then Return 1
			Else
				If FileDelete($sLink) Then Return 1
			EndIf

			Return SetError($FINALOBJECTDELETEFAILURE, 0, 0)
		EndIf

		Return SetError($TAGNOTDELETED, 0, 0)
	EndIf

	Return SetError($NOTREPARSE, 0, 0)
EndFunc
...which necessitated the GENERIC_WRITE access flag, I was rather surprised when (in a pique fit of wild abandon) I applied it to a system link, and - much to my surprise - it worked, successfully neutralising the object's reparse tag. I dare say I don't have any explanation as to why MS would specifically deny GENERIC_READ access (by ACL), but it would allow GENERIC_WRITE. :shrug:. Anyway, deciding to run with the concept, I plugged the necessary changes into TouchReparsePoint (which utilises the API to read and manipulate the timestamps properly), and the result is the ability to bend, fold, mutilate, and otherwise disgrace system reparse-point timestamps to our heart's content. :D Thus this small but significant update (to 1.0.0.2).

One last entertaining observation... the above _DeleteReparsePoint() function is actually capable of removing the locked reparse-point "C:\Documents and Settings", which even x2 can't seem to do. :shock: Exactly why anyone would ever want to do this, I can't say - but given that the only true freedom we have left as adults in this world is the ability to hurt ourselves just to prove a point about our dignity as a species in the face of the civilised depravity this century represents, I'll always support the little-guy's protest against the tides of history. No matter how foolish it may seem to the mundane postwar naïvety the middle-classes have confused with sanity. :twisted:

Enjoy.

User avatar
nikos
Site Admin
Site Admin
Posts: 14355
Joined: 2002 Feb 07, 15:57
Location: UK
Contact:

Re: Scripting your way into the Knickers of Reparse-Point Ra

Post by nikos » 2013 Dec 28, 07:52

those little bitmap buttons with clocks etc on your GUI, are these made available by the scripting or you made them yourself?

Kilmatead
Platinum Member
Platinum Member
Posts: 4569
Joined: 2008 Sep 30, 06:52
Location: Dublin

Re: Scripting your way into the Knickers of Reparse-Point Ra

Post by Kilmatead » 2013 Dec 28, 09:44

That, Jolene, that's what us overly-anal types call "attention to detail", baby - because, if you notice, the clock-icon actually changes as per whatever the current-time is when the control itself is drawn, for those are an overly stylised version of your muggle-like "Now" buttons, so they really act the part. :D

In other words, go to your start-menu, type in "character" and dropdown "Wingdings"! :mrgreen:

Image

Code: Select all

GUICtrlCreateButton(_NowSilly(), ($Col + 390) * $DPI, ($Line + 2 + ($i * 25)) * $DPI, 20 * $DPI, 20 * $DPI)
GUICtrlSetFont(-1, 12, 400, 0, "WingDings")

Func _NowSilly()
	Local $iHour = @HOUR

	If $iHour > 11 Then $iHour -= 12
	If $iHour Then $iHour = Abs($iHour - 12)

	Return Chr(0xC2 - $iHour)
EndFunc
(The single greatest accomplishment/obsession of this clever monkey's intellectual life, 900+ views, no responses, and all you care about are the icons? My mother warned me about girls like you. Or something like that. :D And you! Of all people! The man who was convicted of the single-worst crime in GUI history for his Actions -> Export Settings modal!)

Oh, the tragedy of it all. I weep! I weep for humanity! :cry: (And I weep for the poor neglected Wingydingies, too :wink:)

User avatar
nikos
Site Admin
Site Admin
Posts: 14355
Joined: 2002 Feb 07, 15:57
Location: UK
Contact:

Re: Scripting your way into the Knickers of Reparse-Point Ra

Post by nikos » 2013 Dec 28, 12:31

neat! and what about that combined date+time control with a drop-down stylish button? surely that isn't from windings

Kilmatead
Platinum Member
Platinum Member
Posts: 4569
Joined: 2008 Sep 30, 06:52
Location: Dublin

Re: Scripting your way into the Knickers of Reparse-Point Ra

Post by Kilmatead » 2013 Dec 28, 13:07

Yo, Rumpelstiltskin, did you fall asleep in XP and never wake up when the Windows API DTP control chimed midnight (DTM_SETFORMATW)?

If you really want to see a spooky thing, click the help icon and play with the command-line prototyping I designed for the sheer exuberance of decadent capitalist madness! True, it's a bit wasted for a small utility like this, but the core idea is sound enough for any size project and makes the creation of x2 user-command strings so easy we weep hot buttery tears every morning in our porridge! :D

Image

Hot buttery tears, man!

Hot!

Buttery!

Tears!

Sexy shite, that nonsense is. :wink:

And if you really want to lose your cookies, hover the mouse over the DTP control, and check out the funk-show-brother tooltip pièce-de-résistance...

Image
Last edited by Kilmatead on 2013 Dec 28, 13:18, edited 1 time in total.

User avatar
nikos
Site Admin
Site Admin
Posts: 14355
Joined: 2002 Feb 07, 15:57
Location: UK
Contact:

Re: Scripting your way into the Knickers of Reparse-Point Ra

Post by nikos » 2013 Dec 28, 13:16

ok sally sassalot, there are a few date pickers in x2 (e.g. change attributes) but the drop down button looks nothing like yours
:shrug:
minutiae

Kilmatead
Platinum Member
Platinum Member
Posts: 4569
Joined: 2008 Sep 30, 06:52
Location: Dublin

Re: Scripting your way into the Knickers of Reparse-Point Ra

Post by Kilmatead » 2013 Dec 28, 13:27

One man's minutia is another man's devil in the details.

Simply don't use the style flag DTS_UPDOWN next time. :shrug:

I keep telling you that users like shiny crap, so you gotta give 'em shiny crap! What do you think sells more cupcakes at Vanessa's lemonade stand? Icing, or flour? Yes, flour is functional, but it's nonsense to sprinkle on top and call it sugar!

Shiny crap sells cupcakes, cupcake! :D
DTM_SETFORMATW wrote:It is acceptable to include extra characters within the format string to produce a more rich display. However, any nonformat characters must be enclosed within single quotes. For example, the format string "'Today is: 'hh':'m':'s ddddMMMdd', 'yyy" would produce output like "Today is: 04:22:31 Tuesday Mar 23, 1996".
Last edited by Kilmatead on 2014 Jan 04, 16:20, edited 1 time in total.

Tuxman
Platinum Member
Platinum Member
Posts: 1483
Joined: 2009 Aug 19, 07:49

Re: Scripting your way into the Knickers of Reparse-Point Ra

Post by Tuxman » 2013 Dec 28, 15:55

1996? I might assume those docs are rather antique.
Tux. ; tuxproject.de ; Windows 10 x64
registered xplorer² pro user since Oct 2009, ultimated in Mar 2012

Kilmatead
Platinum Member
Platinum Member
Posts: 4569
Joined: 2008 Sep 30, 06:52
Location: Dublin

Re: Scripting your way into the Knickers of Reparse-Point Ra

Post by Kilmatead » 2014 Apr 12, 20:09

A small update (link in original post) to 1.0.0.3 concerning Mount-Points resolving to user-friendly drive-letter roots (directly from within the _GetReparseTarget() function itself) instead of always using the literal (and not very friendly) GUID paths; plus a few issues with relative path arguments were fixed.

Post Reply