#Region " Option Statements "
Option Explicit On
Option Strict On
Option Infer Off
#End Region
#Region " Imports "
Imports System.Collections.Specialized
Imports System.ComponentModel
Imports System.Configuration
Imports System.IO
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.Security
Imports System.Security.AccessControl
Imports System.Security.Cryptography
Imports System.Security.Principal
Imports System.Text
#End Region
#Region " FlexibleSettingsProvider "
''' <summary>
''' A settings provider that allows to store the application's user configuration file
''' in a user-defined directory path and file name, ensuring the configuration location remains
''' predictable across application updates.
''' </summary>
'''
''' <example> This is a code example.
''' <code language="VB">
''' '------------------------------------------------------------------------------
''' ' <auto-generated>
''' ' This code was generated by a tool.
''' ' Runtime Version:4.0.30319.42000
''' '
''' ' Changes to this file may cause incorrect behavior and will be lost if
''' ' the code is regenerated.
''' ' </auto-generated>
''' '------------------------------------------------------------------------------
''' Namespace My
'''
''' <Global.System.Runtime.CompilerServices.CompilerGeneratedAttribute(), _
''' Global.System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0"), _
''' Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
''' Partial Friend NotInheritable Class MySettings
''' Inherits Global.System.Configuration.ApplicationSettingsBase
'''
''' ' ...
''' End Class
''' End Namespace
'''
''' ' ⛔ DO NOT MODIFY THE AUTO-GENERATED DESIGNER FILE ABOVE.
''' ' INSTEAD, PLACE THE FOLLOWING NAMESPACE IN A SEPARATE PART OF YOUR SOURCE CODE:
'''
''' Namespace My
'''
''' <Global.System.Configuration.SettingsProvider(GetType(FlexibleSettingsProvider))>
''' Partial Friend NotInheritable Class MySettings
'''
''' Public Sub New()
''' FlexibleSettingsProvider.BaseDirectoryPath = ".\"
''' FlexibleSettingsProvider.DirectoryName = ""
''' FlexibleSettingsProvider.DirectoryNameFlags = SettingsDirectoryNameFlags.None
''' FlexibleSettingsProvider.FileName = "user.config"
'''
''' Debug.WriteLine($"Effective config file path: {FlexibleSettingsProvider.EffectiveConfigFilePath}")
''' End Sub
'''
''' End Class
''' End Namespace
''' </code>
''' </example>
'''
''' <example> This is a code example.
''' <code language="CSharp">
'''
''' //------------------------------------------------------------------------------
''' // <auto-generated>
''' // This code was generated by a tool.
''' // Runtime Version:4.0.30319.42000
''' //
''' // Changes to this file may cause incorrect behavior and will be lost if
''' // the code is regenerated.
''' // </auto-generated>
''' //------------------------------------------------------------------------------
'''
''' namespace WindowsFormsApp1.Properties {
'''
''' [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
''' [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
''' internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
''' // ...
''' }
''' }
'''
''' // ⛔ DO NOT MODIFY THE AUTO-GENERATED DESIGNER FILE ABOVE.
''' // INSTEAD, PLACE THE FOLLOWING NAMESPACE IN A SEPARATE PART OF YOUR SOURCE CODE:
'''
''' namespace WindowsFormsApp1.Properties
''' {
''' [SettingsProvider(typeof(FlexibleSettingsProvider))]
''' internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
''' {
''' public Settings()
''' {
''' FlexibleSettingsProvider.BaseDirectoryPath = @".\";
''' FlexibleSettingsProvider.DirectoryName = string.Empty;
''' FlexibleSettingsProvider.DirectoryNameFlags = SettingsDirectoryNameFlags.None;
''' FlexibleSettingsProvider.FileName = "user.config";
'''
''' Debug.WriteLine($"Effective config file path: {FlexibleSettingsProvider.EffectiveConfigFilePath}");
''' }
''' }
''' }
''' </code>
''' </example>
Public Class FlexibleSettingsProvider : Inherits SettingsProvider
#Region " Private Fields "
''' <summary>
''' The default base directory path to use when the path specified by
''' <see cref="FlexibleSettingsProvider.BaseDirectoryPath"/> is null or cannot be accessed.
''' </summary>
Private Shared ReadOnly DefaultBaseDirectoryPath As String =
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ' Note: THIS VALUE CANNOT BE NULL.
''' <summary>
''' The default configuration file name to use when the name specified by
''' <see cref="FlexibleSettingsProvider.FileName"/> is null.
''' </summary>
Private Shared ReadOnly DefaultFileName As String = "user.config" ' Note: THIS VALUE CANNOT BE NULL.
#End Region
#Region " Public Properties "
''' <summary>
''' Gets or sets the base directory path where the settings storage folder specified by
''' <see cref="FlexibleSettingsProvider.DirectoryName"/> property will be created.
''' </summary>
'''
''' <remarks>
''' This can be a relative path, for example <b>".\"</b>, which refers to the current application's base directory.
''' <para></para>
''' If this value is null or empty, <see cref="Environment.SpecialFolder.LocalApplicationData"/> directory path will be used.
''' <para></para>
''' Default value is <b>".\"</b>.
''' </remarks>
Public Shared Property BaseDirectoryPath As String = ".\"
''' <summary>
''' Gets or sets the name of the settings storage folder that will be created under the
''' base directory path specified by <see cref="FlexibleSettingsProvider.BaseDirectoryPath"/> property;
''' For example, <b>"My Application"</b>.
''' </summary>
'''
''' <remarks>
''' This value can be null, in which case this folder will not be created at all.
''' <para></para>
''' Default value is null.
''' </remarks>
Public Shared Property DirectoryName As String = Nothing
''' <summary>
''' Gets or sets additional flags that allows to automatically append extra information to the
''' settings storage folder name specified by <see cref="FlexibleSettingsProvider.DirectoryName"/> property.
''' <para></para>
''' Default value is <see cref="SettingsDirectoryNameFlags.None"/>.
''' </summary>
Public Shared Property DirectoryNameFlags As SettingsDirectoryNameFlags = SettingsDirectoryNameFlags.None
''' <summary>
''' Gets or sets the name of the user configuration file to create inside the
''' settings storage folder specified by <see cref="FlexibleSettingsProvider.DirectoryName"/> property.
''' <para></para>
''' If this value is null or empty, <b>"user.config"</b> is used.
''' <para></para>
''' Default value is <b>"user.config"</b>.
''' </summary>
Public Shared Property FileName As String = FlexibleSettingsProvider.DefaultFileName
''' <summary>
''' Gets or sets the type of <see cref="HashAlgorithm"/> to use for appending the hash suffix to the
''' settings storage folder name specified by <see cref="FlexibleSettingsProvider.DirectoryName"/> when
''' <see cref="FlexibleSettingsProvider.DirectoryNameFlags"/> contains <see cref="SettingsDirectoryNameFlags.Hash"/> flag.
''' <para></para>
''' Default value is <see cref="MD5"/>.
''' </summary>
Public Shared Property HashAlgorithmType As Type = GetType(MD5)
''' <summary>
''' Gets or sets the maximum character length to use for appending the hash suffix to the
''' settings storage folder name specified by <see cref="FlexibleSettingsProvider.DirectoryName"/> when
''' <see cref="FlexibleSettingsProvider.DirectoryNameFlags"/> contains <see cref="SettingsDirectoryNameFlags.Hash"/> flag.
''' <see cref="SettingsDirectoryNameFlags.Hash"/> flag.
''' <para></para>
''' Default value is <b>8</b>.
''' </summary>
'''
''' <remarks>
''' Note: If the specified length exceeds the maximum length supported by the hash algorithm specified by
''' <see cref="FlexibleSettingsProvider.HashAlgorithmType"/> property,
''' the value is automatically truncated to the maximum allowed.
''' </remarks>
Public Shared Property HashLength As Integer = 8
''' <summary>
''' Gets the effective full path to the user configuration file
''' using the current rules specified by
''' <see cref="FlexibleSettingsProvider.BaseDirectoryPath"/>,
''' <see cref="FlexibleSettingsProvider.DirectoryName"/> ,
''' <see cref="FlexibleSettingsProvider.DirectoryNameFlags"/>,
''' <see cref="FlexibleSettingsProvider.FileName"/>,
''' <see cref="FlexibleSettingsProvider.HashAlgorithmType"/> and
''' <see cref="FlexibleSettingsProvider.HashLength"/> properties;
''' For example, <b>"C:\Users\{USERNAME}\AppData\Local\My Application\user.config"</b>
''' </summary>
Public Shared ReadOnly Property EffectiveConfigFilePath As String
<DebuggerStepThrough>
Get
Return FlexibleSettingsProvider.GetEffectiveConfigFilePath()
End Get
End Property
''' <summary>
''' Gets the name of the currently running application
''' using the current rules specified by
''' <see cref="FlexibleSettingsProvider.DirectoryName"/> ,
''' <see cref="FlexibleSettingsProvider.DirectoryNameFlags"/>,
''' <see cref="FlexibleSettingsProvider.HashAlgorithmType"/> and
''' <see cref="FlexibleSettingsProvider.HashLength"/> properties;
''' For example, <b>"My Application"</b>.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Overrides Property ApplicationName As String
<DebuggerStepThrough>
Get
Return FlexibleSettingsProvider.GetEffectiveDirectoryName()
End Get
<DebuggerStepThrough>
Set(value As String)
' Intentionally ignored, and required.
End Set
End Property
''' <summary>
''' Gets a brief, friendly description of this <see cref="SettingsProvider"/>,
''' suitable for display in administrative tools or other user interfaces (UIs).
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Overrides ReadOnly Property Description As String
<DebuggerStepThrough>
Get
Return If(Not String.IsNullOrEmpty(Me._Description), Me._Description, Me.Name)
End Get
End Property
''' <summary>
''' ( Backing field of <see cref="Description"/> property.)
''' <para></para>
''' A brief, friendly description of this <see cref="SettingsProvider"/>,
''' suitable for display in administrative tools or other user interfaces (UIs).
''' </summary>
Private ReadOnly _Description As String =
"A settings provider that allows to store the application's user configuration file in a user-defined directory path and file name."
#End Region
#Region " Constructors "
''' <summary>
''' Initializes a new instance of the <see cref="FlexibleSettingsProvider"/> class.
''' </summary>
<DebuggerNonUserCode>
Public Sub New()
End Sub
#End Region
#Region " Public Methods "
''' <summary>
''' Initializes the configuration builder.
''' </summary>
'''
''' <param name="name">
''' The friendly name of the provider.
''' </param>
'''
''' <param name="config">
''' A collection of the name/value pairs representing the provider-specific attributes
''' specified in the configuration for this provider.
''' </param>
<DebuggerStepperBoundary>
Public Overrides Sub Initialize(name As String, config As NameValueCollection)
If String.IsNullOrEmpty(name) Then
name = NameOf(FlexibleSettingsProvider)
End If
MyBase.Initialize(name, config)
End Sub
''' <summary>
''' Returns the collection of settings property values for the specified application instance and settings property group.
''' </summary>
'''
''' <param name="context">
''' A <see cref="SettingsContext"/> describing the current application use.
''' </param>
'''
''' <param name="properties">
''' A <see cref="SettingsPropertyCollection"/> containing the settings property group whose values are to be retrieved.
''' </param>
''' <returns>
''' A <see cref="SettingsPropertyValueCollection"/> containing the values for the specified settings property group.
''' </returns>
<DebuggerStepperBoundary>
Public Overrides Function GetPropertyValues(context As SettingsContext, properties As SettingsPropertyCollection) As SettingsPropertyValueCollection
Dim values As New SettingsPropertyValueCollection()
Dim doc As XDocument = Nothing
Dim effectiveConfigFilePath As String = FlexibleSettingsProvider.EffectiveConfigFilePath()
If File.
Exists(effectiveConfigFilePath
) Then Try
Using fs As New FileStream(effectiveConfigFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)
doc = XDocument.Load(fs)
End Using
Catch ex As Exception
' If file is corrupt / unreadable, recreate a fresh doc.
doc = New XDocument(New XElement("settings"))
End Try
Else
doc = New XDocument(New XElement("settings"))
End If
' Ensure root exists.
If doc.Root Is Nothing Then
doc = New XDocument(New XElement("settings"))
End If
For Each prop As SettingsProperty In properties
Dim el As XElement = doc.Root.Element(prop.Name)
Dim value As Object = If(el IsNot Nothing, el.Value, prop.DefaultValue)
Dim spv As New SettingsPropertyValue(prop) With {
.SerializedValue = value
}
values.Add(spv)
Next
Return values
End Function
''' <summary>
''' Sets the values of the specified group of property settings.
''' </summary>
'''
''' <param name="context">
''' A <see cref="SettingsContext"/> describing the current application use.
''' </param>
'''
''' <param name="values">
''' A <see cref="SettingsPropertyValueCollection"/> representing the group of property settings to set.
''' </param>
<DebuggerStepperBoundary>
Public Overrides Sub SetPropertyValues(context As SettingsContext, values As SettingsPropertyValueCollection)
Dim effectiveConfigFilePath As String = FlexibleSettingsProvider.EffectiveConfigFilePath()
Dim directoryPath As String = Path.GetDirectoryName(effectiveConfigFilePath)
If Not Directory.Exists(directoryPath) Then
Directory.CreateDirectory(directoryPath)
End If
Dim root As New XElement("settings")
For Each val As SettingsPropertyValue In values
Dim nodeName As String = If(val.Property IsNot Nothing AndAlso Not String.IsNullOrEmpty(val.Property.Name),
val.Property.Name,
"unknown")
Dim nodeValue As String = If(val.SerializedValue Is Nothing, "", val.SerializedValue.ToString())
root.Add(New XElement(nodeName, nodeValue))
Next
Dim doc As New XDocument(root)
Using fs As New FileStream(effectiveConfigFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)
doc.Save(fs)
End Using
End Sub
#End Region
#Region " Private Methods "
''' <summary>
''' Resolves and returns the effective base directory path where the settings storage folder specified by
''' <see cref="FlexibleSettingsProvider.DirectoryName"/> property will be created.
''' </summary>
'''
''' <remarks>
''' This function determines the proper directory path by first using the value specified in
''' <see cref="FlexibleSettingsProvider.BaseDirectoryPath"/> property.
''' <para></para>
''' If that value is null, empty, whitespace, or the directory cannot be created, the path specified by
''' <see cref="FlexibleSettingsProvider.DefaultBaseDirectoryPath"/> property is used instead.
''' </remarks>
'''
''' <returns>
''' A string representing the effective base directory path.
''' </returns>
'''
''' <exception cref="InvalidOperationException">
''' Thrown when the provier is unable to resolve a base directory path that
''' exists and can grant read/write access to the current application.
''' <para></para>
''' This exception indicates that neither the directory specified by <see cref="FlexibleSettingsProvider.DirectoryName"/> property
''' nor the fallback specified by <see cref="FlexibleSettingsProvider.DefaultBaseDirectoryPath"/> property
''' can be used to read from and write to the location.
''' </exception>
<DebuggerStepThrough>
Private Shared Function GetEffectiveBaseDirectoryPath() As String
Dim currentBaseDirectoryPath As String = FlexibleSettingsProvider.BaseDirectoryPath
' Expand to full path.
If Not String.IsNullOrWhiteSpace(currentBaseDirectoryPath) Then
currentBaseDirectoryPath = Path.GetFullPath(currentBaseDirectoryPath)
End If
' Ensure the directory path is set.
If String.IsNullOrWhiteSpace(currentBaseDirectoryPath) Then
currentBaseDirectoryPath = FlexibleSettingsProvider.DefaultBaseDirectoryPath
End If
' Try creating the directory.
Try
Directory.CreateDirectory(currentBaseDirectoryPath)
Catch ' If failed, fallback to LocalAppData
currentBaseDirectoryPath = FlexibleSettingsProvider.DefaultBaseDirectoryPath
Try
Directory.CreateDirectory(currentBaseDirectoryPath)
Catch
' Ignore: write check will catch this later.
End Try
End Try
' Verify that we can read from and write to the directory path.
If Not FlexibleSettingsProvider.CanReadAndWriteToDirectory(currentBaseDirectoryPath) Then
Dim previousDirectoryPath As String = currentBaseDirectoryPath
' Switch to default directory path if not already using it.
If currentBaseDirectoryPath <> FlexibleSettingsProvider.DefaultBaseDirectoryPath Then
currentBaseDirectoryPath = FlexibleSettingsProvider.DefaultBaseDirectoryPath
End If
If currentBaseDirectoryPath <> previousDirectoryPath Then
' Throw if directory still not writable.
If Not FlexibleSettingsProvider.CanReadAndWriteToDirectory(currentBaseDirectoryPath) Then
Throw New InvalidOperationException(
$"Cannot read from or write the user configuration file in directory: {currentBaseDirectoryPath}. Check user permissions.")
End If
End If
End If
Return currentBaseDirectoryPath
End Function
''' <summary>
''' Resolves and returns the effective name of the settings storage folder that will be created under the
''' base directory path specified by <see cref="FlexibleSettingsProvider.BaseDirectoryPath"/> property,
''' applying the rules specified by <see cref="FlexibleSettingsProvider.DirectoryNameFlags"/>.
''' </summary>
'''
''' <returns>
''' A string representing the fully constructed directory name after applying all configured naming rules;
''' For example, <b>"My Application"</b>.
''' </returns>
<DebuggerStepThrough>
Private Shared Function GetEffectiveDirectoryName() As String
Dim appendApplicationName As Boolean =
FlexibleSettingsProvider.DirectoryNameFlags.HasFlag(SettingsDirectoryNameFlags.ApplicationName)
Dim appendAssemblyName As Boolean =
FlexibleSettingsProvider.DirectoryNameFlags.HasFlag(SettingsDirectoryNameFlags.AssemblyName)
Dim appendVersion As Boolean =
FlexibleSettingsProvider.DirectoryNameFlags.HasFlag(SettingsDirectoryNameFlags.Version)
Dim appendHash As Boolean =
FlexibleSettingsProvider.DirectoryNameFlags.HasFlag(SettingsDirectoryNameFlags.Hash)
Dim appendUserName As Boolean =
FlexibleSettingsProvider.DirectoryNameFlags.HasFlag(SettingsDirectoryNameFlags.UserName)
Dim name As String = FlexibleSettingsProvider.DirectoryName
Dim sb As New StringBuilder(Math.Max(16, If(String.IsNullOrEmpty(name), 0, name.Length)))
If Not String.IsNullOrWhiteSpace(name) Then
sb.Append(name)
End If
If appendApplicationName Then
Dim applicationName As String = My.Application.Info.ProductName
If Not String.IsNullOrWhiteSpace(applicationName) Then
sb.Append($"{If(sb.Length <> 0, "_", "")}{applicationName}")
End If
End If
If appendAssemblyName Then
Dim assemblyName As String = My.Application.Info.AssemblyName
If Not String.IsNullOrWhiteSpace(assemblyName) Then
sb.Append($"{If(sb.Length <> 0, "_", "")}{assemblyName}")
End If
End If
If appendVersion Then
Dim version As Version = My.Application.Info.Version
If version IsNot Nothing Then
sb.Append($"{If(sb.Length <> 0, "_", "")}{version}")
End If
End If
If appendHash Then
' Derive a deterministic unique ID from the current assembly GUID.
Dim asm As Assembly = If(Assembly.GetEntryAssembly(), Assembly.GetExecutingAssembly())
If asm IsNot Nothing Then
Dim guidAttr As GuidAttribute = asm.GetCustomAttribute(Of GuidAttribute)()
Dim guid As Guid =
If(guidAttr IsNot Nothing,
New Guid(guidAttr.Value),
asm.ManifestModule.ModuleVersionId ' Fallback: Use the manifest module for the GUID extraction value.
)
Dim hashSeed As String =
If(guid <> Guid.Empty,
guid.ToString("N"),
GetType(FlexibleSettingsProvider).FullName ' Fallback: Use the current type full name.
)
Using hasher As HashAlgorithm = HashAlgorithm.Create(FlexibleSettingsProvider.HashAlgorithmType.Name)
Dim hashLength As Integer = Math.Min(FlexibleSettingsProvider.HashLength, (hasher.HashSize \ 4))
Dim hashString As String = FlexibleSettingsProvider.ComputeDeterministicHashOfString(hasher, hashSeed, hashLength)
sb.Append($"{If(sb.Length <> 0, "_", "")}{hashString}")
End Using
End If
End If
If appendUserName Then
Dim userName As String = Environment.UserName
If Not String.IsNullOrWhiteSpace(userName) Then
sb.Append($"{If(sb.Length <> 0, "_", "")}{userName}")
End If
End If
Return sb.ToString()
End Function
''' <summary>
''' Resolves and returns the effective file name used for the user settings configuration file.
''' </summary>
'''
''' <returns>
''' A string representing the effective file name; For example, <b>"user.config"</b>.
''' </returns>
<DebuggerStepThrough>
Private Shared Function GetEffectiveFileName() As String
Return If(Not String.IsNullOrWhiteSpace(FlexibleSettingsProvider.FileName),
FlexibleSettingsProvider.FileName,
FlexibleSettingsProvider.DefaultFileName)
End Function
''' <summary>
''' Resolves and returns the effective full path to the user configuration file.
''' </summary>
'''
''' <returns>
''' A string representing the full path to the user configuration file;
''' For example, <b>"C:\Users\{USERNAME}\AppData\Local\My Application\user.config"</b>.
''' </returns>
<DebuggerStepThrough>
Private Shared Function GetEffectiveConfigFilePath() As String
Dim baseDirectoryPath As String = FlexibleSettingsProvider.GetEffectiveBaseDirectoryPath()
Dim directoryName As String = FlexibleSettingsProvider.GetEffectiveDirectoryName()
Dim fileName As String = FlexibleSettingsProvider.GetEffectiveFileName()
Return Path.Combine(baseDirectoryPath, directoryName, fileName)
End Function
''' <summary>
''' Checks whether the application has read and write permissions in the specified directory.
''' </summary>
'''
''' <param name="directoryPath">
''' The directory path to check for read and write access.
''' </param>
'''
''' <returns>
''' <see langword="True"/> if the application has read and write permissions in the directory;
''' otherwise <see langword="False"/>.
''' </returns>
<DebuggerStepThrough>
Private Shared Function CanReadAndWriteToDirectory(directoryPath As String) As Boolean
If String.IsNullOrWhiteSpace(directoryPath) Then
Throw New ArgumentNullException(NameOf(directoryPath))
End If
If Not Directory.Exists(directoryPath) Then
Throw New DirectoryNotFoundException($"Directory not found: {directoryPath}")
End If
Try
Dim directoryInfo As New DirectoryInfo(directoryPath)
Dim acl As DirectorySecurity = directoryInfo.GetAccessControl()
Dim rules As AuthorizationRuleCollection =
acl.GetAccessRules(includeExplicit:=True, includeInherited:=True, targetType:=GetType(SecurityIdentifier))
Dim identity As WindowsIdentity = WindowsIdentity.GetCurrent()
If identity Is Nothing Then
Return False
End If
' Collect SIDs for current user and groups.
Dim sids As New HashSet(Of SecurityIdentifier)()
If identity.User IsNot Nothing Then
sids.Add(identity.User)
End If
For Each grp As IdentityReference In identity.Groups
Dim sid As SecurityIdentifier = TryCast(grp, SecurityIdentifier)
If sid IsNot Nothing Then
sids.Add(sid)
End If
Next
' Define the specific bits we require for read and write.
' Note: We intentionally DO NOT include Delete/DeleteSubdirectoriesAndFiles here,
' because a deny on Delete should not block basic read/write operations.
Dim requiredRead As FileSystemRights = FileSystemRights.ReadData Or FileSystemRights.ListDirectory Or FileSystemRights.Read
Dim requiredWrite As FileSystemRights = FileSystemRights.WriteData Or FileSystemRights.AppendData Or FileSystemRights.Write
' Accumulate allow and deny masks for relevant SIDs.
Dim accumulatedAllow As FileSystemRights = 0
Dim accumulatedDeny As FileSystemRights = 0
For Each ruleObj As AuthorizationRule In rules
Dim rule As FileSystemAccessRule = TryCast(ruleObj, FileSystemAccessRule)
If rule Is Nothing Then
Continue For
End If
Dim sid As SecurityIdentifier = TryCast(rule.IdentityReference, SecurityIdentifier)
If sid Is Nothing OrElse Not sids.Contains(sid) Then
Continue For
End If
Dim rights As FileSystemRights = rule.FileSystemRights
If rule.AccessControlType = AccessControlType.Deny Then
accumulatedDeny = accumulatedDeny Or rights
ElseIf rule.AccessControlType = AccessControlType.Allow Then
accumulatedAllow = accumulatedAllow Or rights
End If
Next
' If any required read/write bit is explicitly denied, cannot read/write.
If (accumulatedDeny And (requiredRead Or requiredWrite)) <> 0 Then
Return False
End If
' Check that all required read bits are allowed.
If (accumulatedAllow And requiredRead) <> requiredRead Then
Return False
End If
' Check that all required write bits are allowed.
Return (accumulatedAllow And requiredWrite) = requiredWrite
Catch ex As UnauthorizedAccessException
' Explicitly cannot access the directory.
Return False
Catch ex As SecurityException
' Security policy prevents access.
Return False
Catch ex As Exception
' Unexpected error.
Return False
End Try
End Function
''' <summary>
''' Computes a deterministic hash of the given input string using the specified hash algorithm type.
''' </summary>
'''
''' <param name="algorithm">
''' The hash algorithm instance to use (e.g., <see cref="MD5"/>, <see cref="SHA256"/>).
''' </param>
'''
''' <param name="value">
''' The input string to compute the hash from.
''' </param>
'''
''' <param name="length">
''' The desired total length of the resulting hexadecimal string.
''' <para></para>
''' If the computed hash is shorter than this length, the result is padded with '0' characters.
''' <para></para>
''' If the length is not a multiple of two, the final nibble of the next byte is used for the extra character.
''' </param>
'''
''' <returns>
''' A string of exactly <paramref name="length"/> hexadecimal characters representing the hash of the input string.
''' <para></para>
''' This is deterministic: the same input and algorithm always produce the same output.
''' </returns>
<DebuggerStepThrough>
Private Shared Function ComputeDeterministicHashOfString(algorithm As HashAlgorithm,
value As String,
length As Integer) As String
Dim bytes() As Byte = Encoding.UTF8.GetBytes(value)
Dim hash() As Byte = algorithm.ComputeHash(bytes)
Dim sb As New StringBuilder(length)
' Convert full bytes to hex, up to requested length.
For i As Integer = 0 To Math.Min((length \ 2) - 1, hash.Length - 1)
sb.Append(hash(i).ToString("X2"))
Next
' If length is odd, append the high nibble of the next byte.
If length Mod 2 = 1 AndAlso hash.Length > (length \ 2) Then
sb.Append((hash(length \ 2) >> 4).ToString("X"))
End If
' Pad with zeros if the hash is shorter than requested length.
Dim remaining As Integer = length - sb.Length
If remaining > 0 Then
sb.Append(New String("0"c, remaining))
End If
Return sb.ToString()
End Function
#End Region
End Class
#End Region
#Region " Enumerations "
''' <summary>
''' Specifies flags that allows to automatically append extra information to the
''' settings storage folder name specified by <see cref="FlexibleSettingsProvider.DirectoryName"/> property.
''' </summary>
<Flags>
Public Enum SettingsDirectoryNameFlags
''' <summary>
''' No additional information is appended to the directory name.
''' </summary>
None = 0
''' <summary>
''' Appends the current application name to the directory name.
''' </summary>
ApplicationName = 1 << 0
''' <summary>
''' Appends the current assembly name to the directory name.
''' </summary>
AssemblyName = 1 << 1
''' <summary>
''' Appends the current application version to the directory name.
''' </summary>
Version = 1 << 2
''' <summary>
''' Appends a deterministic hash to the directory name.
''' </summary>
Hash = 1 << 3
''' <summary>
''' Appends the current user name to the directory name.
''' </summary>
UserName = 1 << 4
End Enum
#End Region