Merge branch 'develop' into kdb/msi-wip

This commit is contained in:
Kevin Bost
2025-09-15 21:08:21 -07:00
21 changed files with 703 additions and 87 deletions

View File

@@ -0,0 +1,14 @@
Copyright © 2015 NCode Group
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,60 @@
#region Copyright Preamble
//
// Copyright © 2015 NCode Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
namespace NCode.ReparsePoints
{
/// <summary>
/// Represents the type of reparse point such as a hard link, junction (aka
/// soft link), or symbolic link.
/// </summary>
[Serializable]
internal enum LinkType
{
/// <summary>
/// Represents an unknown reparse point type.
/// </summary>
Unknown = 0,
/// <summary>
/// Represents a file <c>hard link</c>.
/// </summary>
/// <remarks>
/// Technically not a reparse point.
/// </remarks>
HardLink,
/// <summary>
/// Represents a directory <c>junction</c> (aka soft link).
/// </summary>
Junction,
/// <summary>
/// Represents a <c>symbolic link</c> to either a file or folder.
/// </summary>
/// <remarks>
/// In order to create symbolic links, the current user must either be an
/// administrator running with elevated privileges or a non-admin user that
/// has the SeCreateSymbolicLinkPrivilege right in local security policy.
/// </remarks>
Symbolic
}
}

View File

@@ -0,0 +1,859 @@
#region Copyright Preamble
//
// Copyright © 2015 NCode Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
namespace NCode.ReparsePoints.Win32
{
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374896(v=vs.85).aspx
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374892(v=vs.85).aspx
/// </remarks>
[Flags]
[Serializable]
internal enum AccessRights : uint
{
None = 0x0,
/// <summary>
/// The MAXIMUM_ALLOWED access type is generally used with the AccessCheck(…)
/// function to determine whether a security descriptor grants a specified
/// set of access rights to the client identified by an access token.
/// Typically, server applications use this function to check access to a
/// private object. Note that MAXIMUM_ALLOWED cannot be used in an ACE (see
/// access control entries).
/// </summary>
/// <remarks>MAXIMUM_ALLOWED</remarks>
MaximumAllowed = 0x02000000,
#region Standard Access Rights
/// <summary>
/// The right to delete the object.
/// </summary>
/// <remarks>DELETE</remarks>
Delete = 0x00010000,
/// <summary>
/// The right to read the information in the object's security descriptor,
/// not including the information in the system access control list (SACL).
/// </summary>
/// <remarks>READ_CONTROL</remarks>
ReadControl = 0x00020000,
/// <summary>
/// The right to modify the discretionary access control list (DACL) in the
/// object's security descriptor.
/// </summary>
/// <remarks>WRITE_DAC</remarks>
WriteDac = 0x00040000,
/// <summary>
/// The right to change the owner in the object's security descriptor.
/// </summary>
/// <remarks>WRITE_OWNER</remarks>
WriteOwner = 0x00080000,
/// <summary>
/// The right to use the object for synchronization. This enables a thread
/// to wait until the object is in the signaled state. Some object types do not support this access right.
/// </summary>
/// <remarks>SYNCHRONIZE</remarks>
Synchronize = 0x00100000,
/// <summary>
/// Currently defined to equal READ_CONTROL.
/// </summary>
/// <remarks>STANDARD_RIGHTS_READ</remarks>
StandardRightsRead = 0x00020000,
/// <summary>
/// Currently defined to equal READ_CONTROL.
/// </summary>
/// <remarks>STANDARD_RIGHTS_WRITE</remarks>
StandardRightsWrite = 0x00020000,
/// <summary>
/// Currently defined to equal READ_CONTROL.
/// </summary>
/// <remarks>STANDARD_RIGHTS_EXECUTE</remarks>
StandardRightsExecute = 0x00020000,
/// <summary>
/// Combines DELETE, READ_CONTROL, WRITE_DAC, and WRITE_OWNER access.
/// </summary>
/// <remarks>STANDARD_RIGHTS_REQUIRED</remarks>
StandardRightsRequired = 0x000F0000,
/// <summary>
/// Combines DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, and SYNCHRONIZE access.
/// </summary>
/// <remarks>STANDARD_RIGHTS_ALL</remarks>
StandardRightsAll = 0x001F0000,
#endregion
#region Generic Access Rights
/// <summary>
/// Execute access.
/// </summary>
/// <remarks>GENERIC_EXECUTE</remarks>
GenericExecute = 0x20000000,
/// <summary>
/// Write access.
/// </summary>
/// <remarks>GENERIC_WRITE</remarks>
GenericWrite = 0x40000000,
/// <summary>
/// Read access.
/// </summary>
/// <remarks>GENERIC_READ</remarks>
GenericRead = 0x80000000,
/// <summary>
/// All possible access rights.
/// </summary>
/// <remarks>GENERIC_ALL</remarks>
GenericAll = 0x10000000,
#endregion
#region File Security and Access Rights
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364399(v=vs.85).aspx
/// <summary>
/// For a file object, the right to read the corresponding file data. For
/// a directory object, the right to read the corresponding directory data.
/// </summary>
/// <remarks>FILE_READ_DATA</remarks>
FileReadData = 0x0001,
/// <summary>
/// For a directory, the right to list the contents of the directory.
/// </summary>
/// <remarks>FILE_LIST_DIRECTORY</remarks>
FileListDirectory = 0x0001,
/// <summary>
/// For a file object, the right to write data to the file. For a directory
/// object, the right to create a file in the directory (FILE_ADD_FILE).
/// </summary>
/// <remarks>FILE_WRITE_DATA</remarks>
FileWriteData = 0x0002,
/// <summary>
/// For a directory, the right to create a file in the directory.
/// </summary>
/// <remarks>FILE_ADD_FILE</remarks>
FileAddFile = 0x0002,
/// <summary>
/// For a file object, the right to append data to the file. (For local
/// files, write operations will not overwrite existing data if this flag
/// is specified without FILE_WRITE_DATA.) For a directory object, the
/// right to create a subdirectory (FILE_ADD_SUBDIRECTORY).
/// </summary>
/// <remarks>FILE_APPEND_DATA</remarks>
FileAppendData = 0x0004,
/// <summary>
/// For a directory, the right to create a subdirectory.
/// </summary>
/// <remarks>FILE_ADD_SUBDIRECTORY</remarks>
FileAddSubdirectory = 0x0004,
/// <summary>
/// For a named pipe, the right to create a pipe.
/// </summary>
/// <remarks>FILE_CREATE_PIPE_INSTANCE</remarks>
FileCreatePipeInstance = 0x0004,
/// <summary>
/// The right to read extended file attributes.
/// </summary>
/// <remarks>FILE_READ_EA</remarks>
FileReadExtendedAttributes = 0x0008,
/// <summary>
/// The right to write extended file attributes.
/// </summary>
/// <remarks>FILE_WRITE_EA</remarks>
FileWriteExtendedAttributes = 0x0010,
/// <summary>
/// For a native code file, the right to execute the file. This access
/// right given to scripts may cause the script to be executable,
/// depending on the script interpreter.
/// </summary>
/// <remarks>FILE_EXECUTE</remarks>
FileExecute = 0x0020,
/// <summary>
/// For a directory, the right to traverse the directory. By default, users
/// are assigned the BYPASS_TRAVERSE_CHECKING privilege, which ignores the
/// FILE_TRAVERSE access right. See the remarks in File Security and Access
/// Rights for more information.
/// </summary>
/// <remarks>FILE_TRAVERSE</remarks>
FileTraverse = 0x0020,
/// <summary>
/// For a directory, the right to delete a directory and all the files it
/// contains, including read-only files.
/// </summary>
/// <remarks>FILE_DELETE_CHILD</remarks>
FileDeleteChild = 0x0040,
/// <summary>
/// The right to read file attributes.
/// </summary>
/// <remarks>FILE_READ_ATTRIBUTES</remarks>
FileReadAttributes = 0x0080,
/// <summary>
/// The right to write file attributes.
/// </summary>
/// <remarks>FILE_WRITE_ATTRIBUTES</remarks>
FileWriteAttributes = 0x0100,
#endregion
#region Thread Security and Access Rights
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686769(v=vs.85).aspx
/// <summary>
/// Required to terminate a thread using TerminateThread.
/// </summary>
/// <remarks>THREAD_TERMINATE</remarks>
ThreadTerminate = 0x0001,
/// <summary>
/// Required to suspend or resume a thread (see SuspendThread and ResumeThread).
/// </summary>
/// <remarks>THREAD_SUSPEND_RESUME</remarks>
ThreadSuspendResume = 0x0002,
/// <summary>
/// Required to read the context of a thread using GetThreadContext.
/// </summary>
/// <remarks>THREAD_GET_CONTEXT</remarks>
ThreadGetContext = 0x0008,
/// <summary>
/// Required to write the context of a thread using SetThreadContext.
/// </summary>
/// <remarks>THREAD_SET_CONTEXT</remarks>
ThreadSetContext = 0x0010,
/// <summary>
/// Required to set certain information in the thread object.
/// </summary>
/// <remarks>THREAD_SET_INFORMATION</remarks>
ThreadSetInformation = 0x0020,
/// <summary>
/// Required to read certain information from the thread object, such as the exit code (see GetExitCodeThread).
/// </summary>
/// <remarks>THREAD_QUERY_INFORMATION</remarks>
ThreadQueryInformation = 0x0040,
/// <summary>
/// Required to set the impersonation token for a thread using SetThreadToken.
/// </summary>
/// <remarks>THREAD_SET_THREAD_TOKEN</remarks>
ThreadSetThreadToken = 0x0080,
/// <summary>
/// Required to use a thread's security information directly without calling it by using a communication mechanism that provides impersonation services.
/// </summary>
/// <remarks>THREAD_IMPERSONATE</remarks>
ThreadImpersonate = 0x0100,
/// <summary>
/// Required for a server thread that impersonates a client.
/// </summary>
/// <remarks>THREAD_DIRECT_IMPERSONATION</remarks>
ThreadDirectImpersonation = 0x0200,
/// <summary>
/// All possible access rights for a thread object.
/// </summary>
/// <remarks>THREAD_ALL_ACCESS</remarks>
ThreadAllAccess = (StandardRightsRequired | Synchronize | 0x3FF),
#endregion
#region Process Security and Access Rights
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
/// <summary>
/// Required to terminate a process using TerminateProcess.
/// </summary>
/// <remarks>PROCESS_TERMINATE</remarks>
ProcessTerminate = 0x00000001,
/// <summary>
/// Required to create a thread.
/// </summary>
/// <remarks>PROCESS_CREATE_THREAD</remarks>
ProcessCreateThread = 0x00000002,
/// <summary>
/// Required to perform an operation on the address space of a process (see VirtualProtectEx and WriteProcessMemory).
/// </summary>
/// <remarks>PROCESS_VM_OPERATION</remarks>
ProcessVmOperation = 0x00000008,
/// <summary>
/// Required to read memory in a process using ReadProcessMemory.
/// </summary>
/// <remarks>PROCESS_VM_READ</remarks>
ProcessVmRead = 0x00000010,
/// <summary>
/// Required to write to memory in a process using WriteProcessMemory.
/// </summary>
/// <remarks>PROCESS_VM_WRITE</remarks>
ProcessVmWrite = 0x00000020,
/// <summary>
/// Required to duplicate a handle using DuplicateHandle.
/// </summary>
/// <remarks>PROCESS_DUP_HANDLE</remarks>
ProcessDupHandle = 0x00000040,
/// <summary>
/// Required to create a process.
/// </summary>
/// <remarks>PROCESS_CREATE_PROCESS</remarks>
ProcessCreateProcess = 0x00000080,
/// <summary>
/// Required to set memory limits using SetProcessWorkingSetSize.
/// </summary>
/// <remarks>PROCESS_SET_QUOTA</remarks>
ProcessSetQuota = 0x00000100,
/// <summary>
/// Required to set certain information about a process, such as its priority class (see SetPriorityClass).
/// </summary>
/// <remarks>PROCESS_SET_INFORMATION</remarks>
ProcessSetInformation = 0x00000200,
/// <summary>
/// Required to retrieve certain information about a process, such as its token, exit code, and priority class (see OpenProcessToken).
/// </summary>
/// <remarks>PROCESS_QUERY_INFORMATION</remarks>
ProcessQueryInformation = 0x00000400,
/// <summary>
/// All possible access rights for a process object.
/// </summary>
/// <remarks>PROCESS_ALL_ACCESS</remarks>
ProcessAllAccess = (StandardRightsRequired | Synchronize | 0xFFF),
#endregion
#region Token Security and Access Rights
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374905(v=vs.85).aspx
/// <summary>
/// Required to attach a primary token to a process. The SE_ASSIGNPRIMARYTOKEN_NAME privilege is also required to accomplish this task.
/// </summary>
/// <remarks>TOKEN_ASSIGN_PRIMARY</remarks>
TokenAssignPrimary = 0x0001,
/// <summary>
/// Required to duplicate an access token.
/// </summary>
/// <remarks>TOKEN_DUPLICATE</remarks>
TokenDuplicate = 0x0002,
/// <summary>
/// Required to attach an impersonation access token to a process.
/// </summary>
/// <remarks>TOKEN_IMPERSONATE</remarks>
TokenImpersonate = 0x0004,
/// <summary>
/// Required to query an access token.
/// </summary>
/// <remarks>TOKEN_QUERY</remarks>
TokenQuery = 0x0008,
/// <summary>
/// Required to query the source of an access token.
/// </summary>
/// <remarks>TOKEN_QUERY_SOURCE</remarks>
TokenQuerySource = 0x0010,
/// <summary>
/// Required to enable or disable the privileges in an access token.
/// </summary>
/// <remarks>TOKEN_ADJUST_PRIVILEGES</remarks>
TokenAdjustPrivileges = 0x0020,
/// <summary>
/// Required to adjust the attributes of the groups in an access token.
/// </summary>
/// <remarks>TOKEN_ADJUST_GROUPS</remarks>
TokenAdjustGroups = 0x0040,
/// <summary>
/// Required to change the default owner, primary group, or DACL of an access token.
/// </summary>
/// <remarks>TOKEN_ADJUST_DEFAULT</remarks>
TokenAdjustDefault = 0x0080,
/// <summary>
/// Required to adjust the session ID of an access token. The SE_TCB_NAME privilege is required.
/// </summary>
/// <remarks>TOKEN_ADJUST_SESSIONID</remarks>
TokenAdjustSessionid = 0x0100,
/// <summary>
/// Combines STANDARD_RIGHTS_READ and TOKEN_QUERY.
/// </summary>
/// <remarks>TOKEN_READ</remarks>
TokenRead = (StandardRightsRead | TokenQuery),
/// <summary>
/// Combines STANDARD_RIGHTS_WRITE, TOKEN_ADJUST_PRIVILEGES, TOKEN_ADJUST_GROUPS, and TOKEN_ADJUST_DEFAULT.
/// </summary>
/// <remarks>TOKEN_WRITE</remarks>
TokenWrite = (StandardRightsWrite | TokenAdjustPrivileges | TokenAdjustGroups | TokenAdjustDefault),
/// <summary>
/// Combines STANDARD_RIGHTS_EXECUTE and TOKEN_IMPERSONATE.
/// </summary>
/// <remarks>TOKEN_EXECUTE</remarks>
TokenExecute = (StandardRightsExecute | TokenImpersonate),
/// <summary>
/// Combines all possible access rights for a token.
/// </summary>
/// <remarks>TOKEN_ALL_ACCESS</remarks>
TokenAllAccess = (StandardRightsRequired | TokenAssignPrimary | TokenDuplicate | TokenImpersonate | TokenQuery | TokenQuerySource |
TokenAdjustPrivileges | TokenAdjustGroups | TokenAdjustDefault | TokenAdjustSessionid),
#endregion
}
[Flags]
[Serializable]
internal enum FileShareMode
{
/// <summary>
/// Prevents other processes from opening a file or device if they request
/// delete, read, or write access.
/// </summary>
None = 0x00000000,
/// <summary>
/// Enables subsequent open operations on a file or device to request read
/// access. Otherwise, other processes cannot open the file or device if
/// they request read access. If this flag is not specified, but the file
/// or device has been opened for read access, the function fails.
/// </summary>
/// <remarks>FILE_SHARE_READ</remarks>
FileShareRead = 0x00000001,
/// <summary>
/// Enables subsequent open operations on a file or device to request write
/// access. Otherwise, other processes cannot open the file or device if
/// they request write access. If this flag is not specified, but the file
/// or device has been opened for write access or has a file mapping with
/// write access, the function fails.
/// </summary>
/// <remarks>FILE_SHARE_WRITE</remarks>
FileShareWrite = 0x00000002,
/// <summary>
/// Enables subsequent open operations on a file or device to request delete
/// access. Otherwise, other processes cannot open the file or device if
/// they request delete access. If this flag is not specified, but the file
/// or device has been opened for delete access, the function fails. Note
/// Delete access allows both delete and rename operations.
/// </summary>
/// <remarks>FILE_SHARE_DELETE</remarks>
FileShareDelete = 0x00000004,
}
[Serializable]
internal enum FileCreationDisposition
{
/// <summary>
/// Creates a new file, only if it does not already exist. If the specified
/// file exists, the function fails and the last-error code is set to
/// ERROR_FILE_EXISTS (80). If the specified file does not exist and is a
/// valid path to a writable location, a new file is created.
/// </summary>
/// <remarks>CREATE_NEW</remarks>
CreateNew = 1,
/// <summary>
/// Creates a new file, always. If the specified file exists and is writable,
/// the function overwrites the file, the function succeeds, and last-error
/// code is set to ERROR_ALREADY_EXISTS (183). If the specified file does
/// not exist and is a valid path, a new file is created, the function
/// succeeds, and the last-error code is set to zero. For more information,
/// see the Remarks section of this topic.
/// </summary>
/// <remarks>CREATE_ALWAYS</remarks>
CreateAlways = 2,
/// <summary>
/// Opens a file or device, only if it exists. If the specified file or
/// device does not exist, the function fails and the last-error code is
/// set to ERROR_FILE_NOT_FOUND (2). For more information about devices,
/// see the Remarks section.
/// </summary>
/// <remarks>OPEN_EXISTING</remarks>
OpenExisting = 3,
/// <summary>
/// Opens a file, always. If the specified file exists, the function
/// succeeds and the last-error code is set to ERROR_ALREADY_EXISTS (183).
/// If the specified file does not exist and is a valid path to a writable
/// location, the function creates a file and the last-error code is set to
/// zero.
/// </summary>
/// <remarks>OPEN_ALWAYS</remarks>
OpenAlways = 4,
/// <summary>
/// Opens a file and truncates it so that its size is zero bytes, only if
/// it exists. If the specified file does not exist, the function fails and
/// the last-error code is set to ERROR_FILE_NOT_FOUND (2). The calling
/// process must open the file with the GENERIC_WRITE bit set as part of
/// the dwDesiredAccess parameter.
/// </summary>
/// <remarks>TRUNCATE_EXISTING</remarks>
TruncateExisting = 5,
}
[Flags]
[Serializable]
internal enum FileAttributeFlags : uint
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
#region File Attributes
// http://msdn.microsoft.com/en-us/library/windows/desktop/gg258117(v=vs.85).aspx
/// <summary>
/// A file that is read-only. Applications can read the file, but cannot
/// write to it or delete it. This attribute is not honored on directories.
/// For more information, see You cannot view or change the Read-only or
/// the System attributes of folders in Windows Server 2003, in Windows XP,
/// in Windows Vista or in Windows 7.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_READONLY</remarks>
FileAttributeReadonly = 0x1,
/// <summary>
/// The file or directory is hidden. It is not included in an ordinary
/// directory listing.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_HIDDEN</remarks>
FileAttributeHidden = 0x2,
/// <summary>
/// A file or directory that the operating system uses a part of, or uses
/// exclusively.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_SYSTEM</remarks>
FileAttributeSystem = 0x4,
/// <summary>
/// The handle that identifies a directory.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_DIRECTORY</remarks>
FileAttributeDirectory = 0x10,
/// <summary>
/// A file or directory that is an archive file or directory. Applications
/// typically use this attribute to mark files for backup or removal.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_ARCHIVE</remarks>
FileAttributeArchive = 0x20,
/// <summary>
/// This value is reserved for system use.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_DEVICE</remarks>
FileAttributeDevice = 0x40,
/// <summary>
/// A file that does not have other attributes set. This attribute is valid
/// only when used alone.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_NORMAL</remarks>
FileAttributeNormal = 0x80,
/// <summary>
/// A file that is being used for temporary storage. File systems avoid
/// writing data back to mass storage if sufficient cache memory is
/// available, because typically, an application deletes a temporary file
/// after the handle is closed. In that scenario, the system can entirely
/// avoid writing the data. Otherwise, the data is written after the handle
/// is closed.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_TEMPORARY</remarks>
FileAttributeTemporary = 0x100,
/// <summary>
/// A file that is a sparse file.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_SPARSE_FILE</remarks>
FileAttributeSparseFile = 0x200,
/// <summary>
/// A file or directory that has an associated reparse point, or a file
/// that is a symbolic link.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_REPARSE_POINT</remarks>
FileAttributeReparsePoint = 0x400,
/// <summary>
/// A file or directory that is compressed. For a file, all of the data in
/// the file is compressed. For a directory, compression is the default for
/// newly created files and subdirectories.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_COMPRESSED</remarks>
FileAttributeCompressed = 0x800,
/// <summary>
/// The data of a file is not immediately available. This attribute
/// indicates that file data is physically moved to offline storage.
/// This attribute is used by Remote Storage, the hierarchical storage
/// management software. Applications should not arbitrarily change this
/// attribute.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_OFFLINE</remarks>
FileAttributeOffline = 0x1000,
/// <summary>
/// The file or directory is not to be indexed by the content indexing
/// service.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_NOT_CONTENT_INDEXED</remarks>
FileAttributeNotContentIndexed = 0x2000,
/// <summary>
/// A file or directory that is encrypted. For a file, all data streams in
/// the file are encrypted. For a directory, encryption is the default for
/// newly created files and subdirectories.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_ENCRYPTED</remarks>
FileAttributeEncrypted = 0x4000,
/// <summary>
/// The directory or user data stream is configured with integrity (only
/// supported on ReFS volumes). It is not included in an ordinary directory
/// listing. The integrity setting persists with the file if it's renamed.
/// If a file is copied the destination file will have integrity set if
/// either the source file or destination directory have integrity set.
/// Windows Server 2008 R2, Windows 7, Windows Server 2008, Windows Vista,
/// Windows Server 2003, and Windows XP: This flag is not supported until
/// Windows Server 2012.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_INTEGRITY_STREAM</remarks>
FileAttributeIntegrityStream = 0x8000,
/// <summary>
/// This value is reserved for system use.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_VIRTUAL</remarks>
FileAttributeVirtual = 0x10000,
/// <summary>
/// The user data stream not to be read by the background data integrity
/// scanner (AKA scrubber). When set on a directory it only provides
/// inheritance. This flag is only supported on Storage Spaces and ReFS
/// volumes. It is not included in an ordinary directory listing. Windows
/// Server 2008 R2, Windows 7, Windows Server 2008, Windows Vista, Windows
/// Server 2003, and Windows XP: This flag is not supported until Windows
/// 8 and Windows Server 2012.
/// </summary>
/// <remarks>FILE_ATTRIBUTE_NO_SCRUB_DATA</remarks>
FileAttributeNoScrubData = 0x20000,
#endregion
#region File Flags
/// <summary>
/// If you attempt to create multiple instances of a pipe with this flag,
/// creation of the first instance succeeds, but creation of the next
/// instance fails with ERROR_ACCESS_DENIED. Windows 2000: This flag is
/// not supported until Windows 2000 SP2 and Windows XP.
/// </summary>
/// <remarks>FILE_FLAG_FIRST_PIPE_INSTANCE</remarks>
FileFlagFirstPipeInstance = 0x00080000,
/// <summary>
/// The file data is requested, but it should continue to be located in
/// remote storage. It should not be transported back to local storage.
/// This flag is for use by remote storage systems.
/// </summary>
/// <remarks>FILE_FLAG_OPEN_NO_RECALL</remarks>
FileFlagOpenNoRecall = 0x00100000,
/// <summary>
/// Access will occur according to POSIX rules. This includes allowing
/// multiple files with names, differing only in case, for file systems
/// that support that naming. Use care when using this option, because
/// files created with this flag may not be accessible by applications
/// that are written for MS-DOS or 16-bit Windows.
/// </summary>
/// <remarks>FILE_FLAG_POSIX_SEMANTICS</remarks>
FileFlagPosixSemantics = 0x00100000,
/// <summary>
/// Normal reparse point processing will not occur; CreateFile will attempt
/// to open the reparse point. When a file is opened, a file handle is
/// returned, whether or not the filter that controls the reparse point is
/// operational. This flag cannot be used with the CREATE_ALWAYS flag. If
/// the file is not a reparse point, then this flag is ignored. For more
/// information, see the Remarks section.
/// </summary>
/// <remarks>FILE_FLAG_OPEN_REPARSE_POINT</remarks>
FileFlagOpenReparsePoint = 0x00200000,
/// <summary>
/// The file or device is being opened with session awareness. If this flag
/// is not specified, then per-session devices (such as a redirected USB
/// device) cannot be opened by processes running in session 0. This flag
/// has no effect for callers not in session 0. This flag is supported only
/// on server editions of Windows. Windows Server 2008 R2, Windows Server
/// 2008, and Windows Server 2003: This flag is not supported before Windows
/// Server 2012.
/// </summary>
/// <remarks>FILE_FLAG_SESSION_AWARE</remarks>
FileFlagSessionAware = 0x00800000,
/// <summary>
/// The file is being opened or created for a backup or restore operation.
/// The system ensures that the calling process overrides file security
/// checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME
/// privileges. For more information, see Changing Privileges in a Token.
/// You must set this flag to obtain a handle to a directory. A directory
/// handle can be passed to some functions instead of a file handle. For
/// more information, see the Remarks section.
/// </summary>
/// <remarks>FILE_FLAG_BACKUP_SEMANTICS</remarks>
FileFlagBackupSemantics = 0x02000000,
/// <summary>
/// The file is to be deleted immediately after all of its handles are
/// closed, which includes the specified handle and any other open or
/// duplicated handles. If there are existing open handles to a file, the
/// call fails unless they were all opened with the FILE_SHARE_DELETE share
/// mode. Subsequent open requests for the file fail, unless the
/// FILE_SHARE_DELETE share mode is specified.
/// </summary>
/// <remarks>FILE_FLAG_DELETE_ON_CLOSE</remarks>
FileFlagDeleteOnClose = 0x04000000,
/// <summary>
/// Access is intended to be sequential from beginning to end. The system
/// can use this as a hint to optimize file caching. This flag should not
/// be used if read-behind (that is, reverse scans) will be used. This flag
/// has no effect if the file system does not support cached I/O and
/// FILE_FLAG_NO_BUFFERING. For more information, see the Caching Behavior
/// section of this topic.
/// </summary>
/// <remarks>FILE_FLAG_SEQUENTIAL_SCAN</remarks>
FileFlagSequentialScan = 0x08000000,
/// <summary>
/// Access is intended to be random. The system can use this as a hint to
/// optimize file caching. This flag has no effect if the file system does
/// not support cached I/O and FILE_FLAG_NO_BUFFERING. For more information,
/// see the Caching Behavior section of this topic.
/// </summary>
/// <remarks>FILE_FLAG_RANDOM_ACCESS</remarks>
FileFlagRandomAccess = 0x10000000,
/// <summary>
/// The file or device is being opened with no system caching for data
/// reads and writes. This flag does not affect hard disk caching or memory
/// mapped files. There are strict requirements for successfully working
/// with files opened with CreateFile using the FILE_FLAG_NO_BUFFERING
/// flag, for details see File Buffering.
/// </summary>
/// <remarks>FILE_FLAG_NO_BUFFERING</remarks>
FileFlagNoBuffering = 0x20000000,
/// <summary>
/// The file or device is being opened or created for asynchronous I/O.
/// When subsequent I/O operations are completed on this handle, the event
/// specified in the OVERLAPPED structure will be set to the signaled state.
/// If this flag is specified, the file can be used for simultaneous read
/// and write operations. If this flag is not specified, then I/O operations
/// are serialized, even if the calls to the read and write functions
/// specify an OVERLAPPED structure. For information about considerations
/// when using a file handle created with this flag, see the Synchronous
/// and Asynchronous I/O Handles section of this topic.
/// </summary>
/// <remarks>FILE_FLAG_OVERLAPPED</remarks>
FileFlagOverlapped = 0x40000000,
/// <summary>
/// Write operations will not go through any intermediate cache, they will
/// go directly to disk. For additional information, see the Caching
/// Behavior section of this topic.
/// </summary>
/// <remarks>FILE_FLAG_WRITE_THROUGH</remarks>
FileFlagWriteThrough = unchecked(0x80000000),
#endregion
}
[Flags]
[Serializable]
internal enum AllocFlags : uint
{
//LMEM_FIXED
Fixed = 0x00,
//LMEM_MOVEABLE
Moveable = 0x02,
//LMEM_ZEROINIT
ZeroInit = 0x40,
}
[Serializable]
internal enum SymbolicLinkFlag : uint
{
File = 0,
Directory = 1,
}
}

View File

@@ -0,0 +1,122 @@
#region Copyright Preamble
//
// Copyright <20> 2015 NCode Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
#pragma warning disable SYSLIB0004
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using Microsoft.Win32.SafeHandles;
namespace NCode.ReparsePoints.Win32
{
[SecurityCritical]
[SuppressUnmanagedCodeSecurity]
internal static class NativeMethods
{
private const string Kernel32 = "kernel32.dll";
[DllImport(Kernel32, SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFindHandle FindFirstFile(
[In]
string lpFileName,
[Out]
out Win32FindData lpFindFileData);
[DllImport(Kernel32, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FindClose(
[In]
IntPtr hFindFile);
[DllImport(Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeviceIoControl(
[In]
SafeFileHandle hDevice,
[In]
uint dwIoControlCode,
[In]
SafeLocalAllocHandle lpInBuffer,
[In]
int nInBufferSize,
[In]
SafeLocalAllocHandle lpOutBuffer,
[In]
int nOutBufferSize,
[Out]
out int lpBytesReturned,
[In]
IntPtr lpOverlapped);
[DllImport(Kernel32, SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle CreateFile(
[In]
string lpFileName,
[In]
AccessRights dwDesiredAccess,
[In]
FileShareMode dwShareMode,
[In]
IntPtr lpSecurityAttributes,
[In]
FileCreationDisposition dwCreationDisposition,
[In]
FileAttributeFlags dwFlagsAndAttributes,
[In]
IntPtr hTemplateFile);
[DllImport(Kernel32, SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CreateHardLink(
[In]
string lpFileName,
[In]
string lpExistingFileName,
[In]
IntPtr lpSecurityAttributes);
[DllImport(Kernel32, SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool CreateSymbolicLink(
[In]
string lpSymlinkFileName,
[In]
string lpTargetFileName,
[In]
SymbolicLinkFlag dwFlags);
[DllImport(Kernel32, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern SafeLocalAllocHandle LocalAlloc(
[In]
AllocFlags flags,
[In]
IntPtr cb);
[DllImport(Kernel32, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
public static extern IntPtr LocalFree(
[In]
IntPtr handle);
}
}

View File

@@ -0,0 +1,68 @@
#region Copyright Preamble
//
// Copyright <20> 2015 NCode Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
using System.Runtime.InteropServices;
namespace NCode.ReparsePoints.Win32
{
[Serializable]
[StructLayout(LayoutKind.Sequential)]
internal struct ReparseHeader
{
public uint ReparseTag;
public ushort ReparseDataLength;
public ushort Reserved;
// next in memory:
// ReparseData
// SubstituteName
// PrintName
}
internal interface IReparseData
{
ushort SubstituteNameOffset { get; }
ushort SubstituteNameLength { get; }
ushort PrintNameOffset { get; }
ushort PrintNameLength { get; }
}
[Serializable]
[StructLayout(LayoutKind.Sequential)]
internal struct JunctionData : IReparseData
{
public ushort SubstituteNameOffset { get; set; }
public ushort SubstituteNameLength { get; set; }
public ushort PrintNameOffset { get; set; }
public ushort PrintNameLength { get; set; }
}
[Serializable]
[StructLayout(LayoutKind.Sequential)]
internal struct SymbolicData : IReparseData
{
public ushort SubstituteNameOffset { get; set; }
public ushort SubstituteNameLength { get; set; }
public ushort PrintNameOffset { get; set; }
public ushort PrintNameLength { get; set; }
public uint Flags { get; set; }
}
}

View File

@@ -0,0 +1,48 @@
#region Copyright Preamble
//
// Copyright <20> 2015 NCode Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
using System.Security;
using Microsoft.Win32.SafeHandles;
namespace NCode.ReparsePoints.Win32
{
[SecurityCritical]
internal class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeFindHandle()
: base(true)
{
// do not delete this ctor
// it is required for pinvoke
}
public SafeFindHandle(IntPtr handle)
: base(true)
{
SetHandle(handle);
}
protected override bool ReleaseHandle()
{
return NativeMethods.FindClose(handle);
}
}
}

View File

@@ -0,0 +1,107 @@
#region Copyright Preamble
//
// Copyright © 2015 NCode Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Microsoft.Win32.SafeHandles;
namespace NCode.ReparsePoints.Win32
{
[SecurityCritical]
internal class SafeLocalAllocHandle : SafeHandleZeroOrMinusOneIsInvalid
{
#region Static Members
public static SafeLocalAllocHandle InvalidHandle {
get { return new SafeLocalAllocHandle(IntPtr.Zero); }
}
public static SafeLocalAllocHandle Allocate(int cb)
{
return Allocate(new IntPtr(cb));
}
public static SafeLocalAllocHandle Allocate(IntPtr cb)
{
return NativeMethods.LocalAlloc(AllocFlags.Fixed, cb);
}
#endregion
protected SafeLocalAllocHandle()
: base(true)
{
// do not delete this ctor
// it is required for pinvoke
}
public SafeLocalAllocHandle(IntPtr handle)
: base(true)
{
SetHandle(handle);
}
public virtual int Write(int position, byte[] buffer, int offset, int count)
{
Marshal.Copy(buffer, offset, handle + position, count);
return count;
}
public virtual int Write<T>(int position, T value) where T : notnull
{
var count = Marshal.SizeOf(value);
Marshal.StructureToPtr(value, handle + position, false);
return count;
}
public virtual T Read<
#if NET7_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
#endif
T>(
int position) where T : notnull
{
var value = Marshal.PtrToStructure<T>(handle + position);
return value!;
}
public virtual void Read(int position, byte[] buffer, int offset, int count)
{
Marshal.Copy(handle + position, buffer, offset, count);
}
public virtual string ReadString(int position, int byteCount, Encoding encoding)
{
var bytes = new byte[byteCount];
Read(position, bytes, 0, byteCount);
var str = encoding.GetString(bytes);
return str;
}
[SecurityCritical]
protected override bool ReleaseHandle()
{
return NativeMethods.LocalFree(handle) == IntPtr.Zero;
}
}
}

View File

@@ -0,0 +1,44 @@
#region Copyright Preamble
//
// Copyright © 2015 NCode Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
namespace NCode.ReparsePoints.Win32
{
internal static class Win32Constants
{
public const int MaxPath = 260;
public const int ERROR_INSUFFICIENT_BUFFER = 122;
public const int ERROR_NOT_A_REPARSE_POINT = 4390;
public const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;
public const uint IO_REPARSE_TAG_SYMLINK = 0xA000000C;
public const uint FSCTL_SET_REPARSE_POINT = 0x000900A4;
public const uint FSCTL_GET_REPARSE_POINT = 0x000900A8;
public const string NonInterpretedPathPrefix = "\\??\\";
public static readonly string[] DosDevicePrefixes = {
"\\??\\",
"\\DosDevices\\",
"\\Global??\\"
};
}
}

View File

@@ -0,0 +1,45 @@
#region Copyright Preamble
//
// Copyright <20> 2015 NCode Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System.IO;
using System.Runtime.InteropServices;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
namespace NCode.ReparsePoints.Win32
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct Win32FindData
{
public FileAttributes FileAttributes;
public FILETIME CreationTime;
public FILETIME LastAccessTime;
public FILETIME LastWriteTime;
public int FileSizeHigh;
public int FileSizeLow;
public uint Reserved0;
public uint Reserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Win32Constants.MaxPath)]
public string FileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string AlternateFileName;
}
}

View File

@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,49 @@
#region Copyright Preamble
//
// Copyright <20> 2015 NCode Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System.IO;
namespace NCode.ReparsePoints
{
/// <summary>
/// Contains the information about a reparse point.
/// </summary>
internal struct ReparseLink
{
/// <summary>
/// Contains the <see cref="FileAttributes"/> of a reparse point.
/// </summary>
public FileAttributes Attributes { get; set; }
/// <summary>
/// Contains the <see cref="LinkType"/> of a reparse point.
/// </summary>
public LinkType Type { get; set; }
/// <summary>
/// Contains the target of a reparse point.
/// </summary>
/// <remarks>
/// The target for a hard link cannot be determined so this member will
/// always be <c>null</c> for hard links.
/// </remarks>
public string Target { get; set; }
}
}

View File

@@ -0,0 +1,282 @@
#region Copyright Preamble
//
// Copyright © 2015 NCode Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
using NCode.ReparsePoints.Win32;
namespace NCode.ReparsePoints
{
internal class ReparsePointProvider
{
/// <summary>
/// Given a path, determines the type of reparse point.
/// </summary>
/// <param name="path">The path to inspect.</param>
/// <returns>A <see cref="LinkType"/> enumeration.</returns>
public virtual LinkType GetLinkType(string path)
{
Win32FindData data;
using (var handle = NativeMethods.FindFirstFile(path, out data)) {
if (handle.IsInvalid)
return LinkType.Unknown;
if (!data.FileAttributes.HasFlag(FileAttributes.ReparsePoint)) {
return data.FileAttributes.HasFlag(FileAttributes.Directory)
? LinkType.Unknown
: LinkType.HardLink;
}
switch (data.Reserved0) {
case Win32Constants.IO_REPARSE_TAG_SYMLINK:
return LinkType.Symbolic;
case Win32Constants.IO_REPARSE_TAG_MOUNT_POINT:
return LinkType.Junction;
}
}
return LinkType.Unknown;
}
/// <summary>
/// Given a path, returns the information about a reparse point.
/// </summary>
/// <param name="path">The path to inspect.</param>
/// <returns>A <see cref="ReparseLink"/> that contains the information
/// about a reparse point.</returns>
public virtual ReparseLink GetLink(string path)
{
FileAttributes attributes;
try {
attributes = File.GetAttributes(path);
} catch (DirectoryNotFoundException) {
return new ReparseLink();
} catch (FileNotFoundException) {
return new ReparseLink();
}
var link = new ReparseLink {
Attributes = attributes
};
if (!attributes.HasFlag(FileAttributes.ReparsePoint)) {
link.Type = attributes.HasFlag(FileAttributes.Directory)
? LinkType.Unknown
: LinkType.HardLink;
return link;
}
var encoding = Encoding.Unicode;
var reparseHeaderSize = Marshal.SizeOf<ReparseHeader>();
var bufferLength = reparseHeaderSize + 2048;
using (var hReparsePoint = OpenReparsePoint(path, AccessRights.GenericRead)) {
int error;
do {
using (var buffer = SafeLocalAllocHandle.Allocate(bufferLength)) {
int bytesReturned;
var b = NativeMethods.DeviceIoControl(
hReparsePoint,
Win32Constants.FSCTL_GET_REPARSE_POINT,
SafeLocalAllocHandle.InvalidHandle,
0,
buffer,
bufferLength,
out bytesReturned,
IntPtr.Zero);
error = Marshal.GetLastWin32Error();
if (b) {
var reparseHeader = buffer.Read<ReparseHeader>(0);
IReparseData data;
switch (reparseHeader.ReparseTag) {
case Win32Constants.IO_REPARSE_TAG_MOUNT_POINT:
data = buffer.Read<JunctionData>(reparseHeaderSize);
link.Type = LinkType.Junction;
break;
case Win32Constants.IO_REPARSE_TAG_SYMLINK:
data = buffer.Read<SymbolicData>(reparseHeaderSize);
link.Type = LinkType.Symbolic;
break;
default:
throw new InvalidOperationException(
String.Format(
"An unknown reparse tag {0:X} was encountered.",
reparseHeader.ReparseTag));
}
var offset = Marshal.SizeOf(data) + reparseHeaderSize;
var target = buffer.ReadString(offset + data.SubstituteNameOffset, data.SubstituteNameLength, encoding);
link.Target = ParseDosDevicePath(target);
return link;
}
if (error == Win32Constants.ERROR_INSUFFICIENT_BUFFER) {
var reparseHeader = buffer.Read<ReparseHeader>(0);
bufferLength = reparseHeader.ReparseDataLength;
} else {
throw new Win32Exception(error);
}
}
} while (error == Win32Constants.ERROR_INSUFFICIENT_BUFFER);
}
return link;
}
public virtual void CreateHardLink(string file, string target)
{
if (!NativeMethods.CreateHardLink(file, target, IntPtr.Zero))
throw new Win32Exception();
}
public virtual void CreateSymbolicLink(string path, string target, bool isDirectory)
{
var flags = isDirectory
? SymbolicLinkFlag.Directory
: SymbolicLinkFlag.File;
if (!NativeMethods.CreateSymbolicLink(path, target, flags))
throw new Win32Exception();
}
/// <summary>
/// Helper method to create a junction.
/// </summary>
/// <param name="path">The path of the junction to create.</param>
/// <param name="target">The target for the junction.</param>
public virtual void CreateJunction(string path, string target)
{
path = Path.GetFullPath(path);
target = Path.GetFullPath(target);
Win32FindData data;
using (var handle = NativeMethods.FindFirstFile(path, out data)) {
if (!handle.IsInvalid)
throw new InvalidOperationException("A file or folder already exists with the same name as the junction.");
}
Directory.CreateDirectory(path);
var encoding = Encoding.Unicode;
var nullChar = new byte[] { 0, 0 };
var printName = ParseDosDevicePath(target);
var printNameBytes = encoding.GetBytes(printName);
var printNameLength = printNameBytes.Length;
var substituteName = FormatDosDevicePath(printName, false);
var substituteNameBytes = encoding.GetBytes(substituteName);
var substituteNameLength = substituteNameBytes.Length;
var junction = new JunctionData {
SubstituteNameOffset = 0,
SubstituteNameLength = checked((ushort) substituteNameLength),
PrintNameOffset = checked((ushort) (substituteNameLength + nullChar.Length)),
PrintNameLength = checked((ushort) printNameLength)
};
var junctionLength = Marshal.SizeOf(junction) + nullChar.Length * 2;
var reparseLength = junctionLength + junction.SubstituteNameLength + junction.PrintNameLength;
var reparse = new ReparseHeader {
ReparseTag = Win32Constants.IO_REPARSE_TAG_MOUNT_POINT,
ReparseDataLength = checked((ushort) (reparseLength)),
Reserved = 0,
};
var bufferLength = Marshal.SizeOf(reparse) + reparse.ReparseDataLength;
using (var hReparsePoint = OpenReparsePoint(path, AccessRights.GenericWrite))
using (var buffer = SafeLocalAllocHandle.Allocate(bufferLength)) {
var offset = buffer.Write(0, reparse);
offset += buffer.Write(offset, junction);
offset += buffer.Write(offset, substituteNameBytes, 0, substituteNameBytes.Length);
offset += buffer.Write(offset, nullChar, 0, nullChar.Length);
offset += buffer.Write(offset, printNameBytes, 0, printNameBytes.Length);
offset += buffer.Write(offset, nullChar, 0, nullChar.Length);
Debug.Assert(offset == bufferLength);
int bytesReturned;
var b = NativeMethods.DeviceIoControl(
hReparsePoint,
Win32Constants.FSCTL_SET_REPARSE_POINT,
buffer,
bufferLength,
SafeLocalAllocHandle.InvalidHandle,
0,
out bytesReturned,
IntPtr.Zero);
if (!b) throw new Win32Exception();
}
}
private static string FormatDosDevicePath(string path, bool sanitize)
{
if (sanitize)
path = ParseDosDevicePath(path);
return Win32Constants.NonInterpretedPathPrefix + path + "\\";
}
private static string ParseDosDevicePath(string path)
{
var result = Win32Constants
.DosDevicePrefixes
.Where(prefix => path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
.Aggregate(path, (current, prefix) => current.Remove(0, prefix.Length));
while (result.EndsWith("\\"))
result = result.Remove(result.Length - 1);
return result;
}
private static SafeFileHandle OpenReparsePoint(string reparsePoint, AccessRights accessRights)
{
var hFile = NativeMethods.CreateFile(
reparsePoint,
accessRights,
FileShareMode.FileShareRead | FileShareMode.FileShareWrite,
IntPtr.Zero,
FileCreationDisposition.OpenExisting,
FileAttributeFlags.FileFlagBackupSemantics | FileAttributeFlags.FileFlagOpenReparsePoint,
IntPtr.Zero);
if (hFile.IsInvalid)
throw new Win32Exception();
return hFile;
}
}
}

View File

@@ -1,8 +1,8 @@
using System.ComponentModel;
using System.IO.Compression;
using NCode.ReparsePoints;
using System.IO.Compression;
using System.Runtime.InteropServices;
using Velopack.Logging;
using Velopack.Util;
using NCode.ReparsePoints;
namespace Velopack.Tests;
@@ -270,4 +270,356 @@ public class SymbolicLinkTests
Assert.True(File.Exists(currentSym));
Assert.Equal("A/", File.ReadAllText(currentSym));
}
// ===== New comprehensive tests =====
[Fact]
public void Create_SymlinkToNonExistentTarget_ShouldWork()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var target = Path.Combine(tempFolder, "NonExistent");
var link = Path.Combine(tempFolder, "Link");
// Should be able to create symlink to non-existent target
File.WriteAllText(target, "test");
SymbolicLink.Create(link, target);
Assert.True(SymbolicLink.Exists(link));
Assert.Equal(target, SymbolicLink.GetTarget(link));
// Clean up
SymbolicLink.Delete(link);
}
[Fact]
public void Create_MultipleLevelsOfSymlinks()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var file = Path.Combine(tempFolder, "Original.txt");
var link1 = Path.Combine(tempFolder, "Link1.txt");
var link2 = Path.Combine(tempFolder, "Link2.txt");
var link3 = Path.Combine(tempFolder, "Link3.txt");
File.WriteAllText(file, "Hello");
// Create chain: link3 -> link2 -> link1 -> file
SymbolicLink.Create(link1, file);
SymbolicLink.Create(link2, link1);
SymbolicLink.Create(link3, link2);
// All should resolve to the same content
Assert.Equal("Hello", File.ReadAllText(link1));
Assert.Equal("Hello", File.ReadAllText(link2));
Assert.Equal("Hello", File.ReadAllText(link3));
// Each should point to their immediate target
Assert.Equal(file, SymbolicLink.GetTarget(link1));
Assert.Equal(link1, SymbolicLink.GetTarget(link2));
Assert.Equal(link2, SymbolicLink.GetTarget(link3));
}
[Fact]
public void GetTarget_WithTrailingSlash_ShouldWork()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var target = Path.Combine(tempFolder, "Target");
var link = Path.Combine(tempFolder, "Link");
Directory.CreateDirectory(target);
SymbolicLink.Create(link, target);
// Should work with and without trailing slash
Assert.Equal(target, SymbolicLink.GetTarget(link));
Assert.Equal(target, SymbolicLink.GetTarget(link + Path.DirectorySeparatorChar));
}
[Fact]
public void Create_WithSpecialCharactersInPath()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var target = Path.Combine(tempFolder, "Target With Spaces & Special-Chars");
var link = Path.Combine(tempFolder, "Link With Spaces & Special-Chars");
Directory.CreateDirectory(target);
File.WriteAllText(Path.Combine(target, "file.txt"), "content");
SymbolicLink.Create(link, target);
Assert.True(SymbolicLink.Exists(link));
Assert.Equal(target, SymbolicLink.GetTarget(link));
Assert.Equal("content", File.ReadAllText(Path.Combine(link, "file.txt")));
}
[Fact]
public void Create_AbsoluteVsRelativeComparison()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var subdir = Directory.CreateDirectory(Path.Combine(tempFolder, "subdir")).FullName;
var target = Path.Combine(tempFolder, "target.txt");
var linkAbs = Path.Combine(subdir, "link_abs.txt");
var linkRel = Path.Combine(subdir, "link_rel.txt");
File.WriteAllText(target, "test");
// Create absolute and relative links
SymbolicLink.Create(linkAbs, target, relative: false);
SymbolicLink.Create(linkRel, target, relative: true);
// Both should work
Assert.Equal("test", File.ReadAllText(linkAbs));
Assert.Equal("test", File.ReadAllText(linkRel));
// Check targets
Assert.Equal(target, SymbolicLink.GetTarget(linkAbs));
Assert.Equal(target, SymbolicLink.GetTarget(linkRel));
// Relative target should be different when requested
var relTarget = SymbolicLink.GetTarget(linkRel, relative: true);
Assert.Contains("..", relTarget);
}
[Fact]
public void FileSymlink_AllImplementationsAgree()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var target = Path.Combine(tempFolder, "target.txt");
var link = Path.Combine(tempFolder, "link.txt");
File.WriteAllText(target, "test content");
// Create with our implementation
SymbolicLink.Create(link, target);
// Verify all implementations agree
var ourTarget = SymbolicLink.GetTarget(link);
Assert.Equal(target, ourTarget);
// Compare with NCode.ReparsePoints (Windows only)
if (VelopackRuntimeInfo.IsWindows) {
var provider = new ReparsePointProvider();
var linkInfo = provider.GetLink(link);
Assert.Equal(LinkType.Symbolic, linkInfo.Type);
Assert.Equal(target, linkInfo.Target);
}
#if NET6_0_OR_GREATER
var fileInfo = new FileInfo(link);
Assert.NotNull(fileInfo.LinkTarget);
Assert.Equal(target, Path.GetFullPath(fileInfo.LinkTarget));
// Test interoperability: create with framework, read with ours
var link2 = Path.Combine(tempFolder, "link2.txt");
File.CreateSymbolicLink(link2, target);
Assert.Equal(target, SymbolicLink.GetTarget(link2));
#endif
}
[Fact]
public void DirectorySymlink_AllImplementationsAgree()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var target = Path.Combine(tempFolder, "targetDir");
var link = Path.Combine(tempFolder, "linkDir");
Directory.CreateDirectory(target);
File.WriteAllText(Path.Combine(target, "file.txt"), "content");
// Create with our implementation
SymbolicLink.Create(link, target);
// Verify all implementations agree
var ourTarget = SymbolicLink.GetTarget(link);
Assert.Equal(target, ourTarget);
// Compare with NCode.ReparsePoints (Windows only)
if (VelopackRuntimeInfo.IsWindows) {
var provider = new ReparsePointProvider();
var linkInfo = provider.GetLink(link);
// Directory symlinks on Windows are actually junctions
Assert.True(linkInfo.Type == LinkType.Junction || linkInfo.Type == LinkType.Symbolic);
Assert.Equal(target, linkInfo.Target);
}
#if NET6_0_OR_GREATER
var dirInfo = new DirectoryInfo(link);
Assert.NotNull(dirInfo.LinkTarget);
Assert.Equal(target, Path.GetFullPath(dirInfo.LinkTarget));
// Test interoperability: create with framework, read with ours
var link2 = Path.Combine(tempFolder, "linkDir2");
Directory.CreateSymbolicLink(link2, target);
Assert.Equal(target, SymbolicLink.GetTarget(link2));
#endif
}
[Fact]
public void RelativeSymlink_AllImplementationsAgree()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var subdir = Directory.CreateDirectory(Path.Combine(tempFolder, "subdir")).FullName;
var target = Path.Combine(tempFolder, "target.txt");
var link = Path.Combine(subdir, "link.txt");
File.WriteAllText(target, "test");
// Create relative symlink with our implementation
SymbolicLink.Create(link, target, relative: true);
// Verify our implementation handles relative vs absolute correctly
var ourAbsoluteTarget = SymbolicLink.GetTarget(link);
var ourRelativeTarget = SymbolicLink.GetTarget(link, relative: true);
Assert.Equal(target, ourAbsoluteTarget);
Assert.Contains("..", ourRelativeTarget);
// Compare with NCode.ReparsePoints (Windows only)
if (VelopackRuntimeInfo.IsWindows) {
var provider = new ReparsePointProvider();
var linkInfo = provider.GetLink(link);
Assert.Equal(LinkType.Symbolic, linkInfo.Type);
// NCode returns the raw target path as stored in the symlink
// For relative symlinks, this will be the relative path, not absolute
Assert.Equal(ourRelativeTarget, linkInfo.Target);
}
#if NET6_0_OR_GREATER
var fileInfo = new FileInfo(link);
Assert.NotNull(fileInfo.LinkTarget);
// Framework returns the relative path for relative symlinks
Assert.Contains("..", fileInfo.LinkTarget);
Assert.Equal(fileInfo.LinkTarget, ourRelativeTarget);
#endif
}
[Fact]
public void MultipleLevelsOfSymlinks_AllImplementationsAgree()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var file = Path.Combine(tempFolder, "original.txt");
var link1 = Path.Combine(tempFolder, "link1.txt");
var link2 = Path.Combine(tempFolder, "link2.txt");
File.WriteAllText(file, "content");
// Create chain: link2 -> link1 -> file
SymbolicLink.Create(link1, file);
SymbolicLink.Create(link2, link1);
// Verify our implementation
Assert.Equal(file, SymbolicLink.GetTarget(link1));
Assert.Equal(link1, SymbolicLink.GetTarget(link2));
// Verify content access works through the chain
Assert.Equal("content", File.ReadAllText(link1));
Assert.Equal("content", File.ReadAllText(link2));
// Compare with NCode.ReparsePoints (Windows only)
if (VelopackRuntimeInfo.IsWindows) {
var provider = new ReparsePointProvider();
var link1Info = provider.GetLink(link1);
var link2Info = provider.GetLink(link2);
Assert.Equal(LinkType.Symbolic, link1Info.Type);
Assert.Equal(LinkType.Symbolic, link2Info.Type);
Assert.Equal(file, link1Info.Target);
Assert.Equal(link1, link2Info.Target);
}
#if NET6_0_OR_GREATER
var fileInfo1 = new FileInfo(link1);
var fileInfo2 = new FileInfo(link2);
Assert.NotNull(fileInfo1.LinkTarget);
Assert.NotNull(fileInfo2.LinkTarget);
Assert.Equal(file, Path.GetFullPath(fileInfo1.LinkTarget));
Assert.Equal(link1, Path.GetFullPath(fileInfo2.LinkTarget));
// Test ResolveLinkTarget for final resolution
var finalTarget = fileInfo2.ResolveLinkTarget(true);
Assert.Equal(file, finalTarget?.FullName);
#endif
}
[Fact]
public void Delete_OnlyDeletesLinkNotTarget()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var target = Path.Combine(tempFolder, "target.txt");
var link = Path.Combine(tempFolder, "link.txt");
File.WriteAllText(target, "important data");
SymbolicLink.Create(link, target);
// Delete the link
SymbolicLink.Delete(link);
// Link should be gone, but target should remain
Assert.False(File.Exists(link));
Assert.False(SymbolicLink.Exists(link));
Assert.True(File.Exists(target));
Assert.Equal("important data", File.ReadAllText(target));
}
[SkippableFact]
public void Exists_ReturnsFalseForHardLink()
{
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var target = Path.Combine(tempFolder, "target.txt");
var hardLink = Path.Combine(tempFolder, "hardlink.txt");
File.WriteAllText(target, "test");
// Create hard link using P/Invoke
if (!CreateHardLink(hardLink, target, IntPtr.Zero)) {
// Skip test if hard link creation fails (may need elevation)
return;
}
// Hard link should exist as a file but not as a symbolic link
Assert.True(File.Exists(hardLink));
Assert.False(SymbolicLink.Exists(hardLink));
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
[Fact]
public void GetTarget_ErrorMessages_AreDescriptive()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var regularFile = Path.Combine(tempFolder, "regular.txt");
var regularDir = Path.Combine(tempFolder, "regularDir");
var nonExistent = Path.Combine(tempFolder, "nonExistent");
File.WriteAllText(regularFile, "test");
Directory.CreateDirectory(regularDir);
// Test various error conditions
var ex1 = Assert.Throws<IOException>(() => SymbolicLink.GetTarget(regularFile));
Assert.Contains("junction", ex1.Message.ToLower());
var ex2 = Assert.Throws<IOException>(() => SymbolicLink.GetTarget(regularDir));
Assert.Contains("junction", ex2.Message.ToLower());
var ex3 = Assert.Throws<IOException>(() => SymbolicLink.GetTarget(nonExistent));
Assert.Contains("junction", ex2.Message.ToLower());
}
[Fact]
public void Create_OverwriteExistingSymlink()
{
using var _1 = TempUtil.GetTempDirectory(out var tempFolder);
var target1 = Path.Combine(tempFolder, "target1.txt");
var target2 = Path.Combine(tempFolder, "target2.txt");
var link = Path.Combine(tempFolder, "link.txt");
File.WriteAllText(target1, "content1");
File.WriteAllText(target2, "content2");
// Create initial symlink
SymbolicLink.Create(link, target1);
Assert.Equal("content1", File.ReadAllText(link));
// Overwrite with new target
SymbolicLink.Create(link, target2, overwrite: true);
Assert.Equal("content2", File.ReadAllText(link));
Assert.Equal(target2, SymbolicLink.GetTarget(link));
}
}