Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0a65c04
Initial plan
Copilot Sep 2, 2025
5e6a457
Initial analysis - prepare to add XML documentation comments
Copilot Sep 2, 2025
27cdba8
Add XML documentation to core utility classes
Copilot Sep 2, 2025
713f016
Complete XML documentation for additional utility classes
Copilot Sep 2, 2025
8a9e1a7
Revert packages.lock.json changes - only add XML documentation
Copilot Sep 3, 2025
580ad58
Revert all packages.lock.json files to original state
Copilot Sep 3, 2025
24777f4
Revert global.json change and add comprehensive XML documentation to …
Copilot Sep 3, 2025
73e05d3
Add comprehensive XML documentation to all 700+ undocumented public m…
Copilot Sep 3, 2025
3f42f98
Fix XML documentation for core classes: DirectoryObject, NTHash, LMHa…
Copilot Sep 3, 2025
acafd44
Major improvement to XML documentation: replace 186 generic "implemen…
Copilot Sep 3, 2025
6fa9e4b
Improve XML documentation: Replace generic comments with meaningful d…
Copilot Sep 3, 2025
ba049a4
Continue improving XML documentation: Fix 10+ additional files with m…
Copilot Sep 3, 2025
113148d
Fix XML documentation in Replication.Model and Common.Data LAPS/Princ…
Copilot Sep 3, 2025
1ed0054
Fix XML documentation in more Common/Data classes, exceptions, and Se…
Copilot Sep 3, 2025
1d4d3e1
Fix XML documentation in Extensions, DataStore DirectoryAgent, and Po…
Copilot Sep 3, 2025
e12211f
Fix XML documentation across all PowerShell cmdlets to include dashes…
Copilot Sep 3, 2025
247cc59
Fix syntax error in ReplicaObjectCollection constructor
Copilot Sep 4, 2025
caadf64
Fix SecurityDescriptorRersolver typo throughout codebase
Copilot Sep 4, 2025
4dc0d0c
Improve XML documentation for PowerShell Utils, DataStore enums and c…
Copilot Sep 4, 2025
59924ad
Improve XML documentation for ReplicaAttributeCollection and Interop …
Copilot Sep 4, 2025
ccded49
Improve XML documentation for remaining Interop and DataStore Cryptog…
Copilot Sep 4, 2025
0dfc0b3
Fix remaining generic XML documentation comments across Common namespace
Copilot Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Src/DSInternals.Common/ADSI/AdsiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
using DSInternals.Common.Data;
using DSInternals.Common.Schema;

/// <summary>
/// Provides functionality for accessing Active Directory through ADSI (Active Directory Service Interfaces).
/// </summary>
public class AdsiClient : IDisposable
{
private const string ConfigurationContainerRDN = "CN=Configuration";
Expand Down Expand Up @@ -88,6 +91,9 @@ public string NetBIOSDomainName
private set;
}

/// <summary>
/// Gets account information from the data store.
/// </summary>
public IEnumerable<DSAccount> GetAccounts(AccountPropertySets propertySets = AccountPropertySets.All)
{
// Not all property sets work as secret attributes are never sent ove LDAP.
Expand Down Expand Up @@ -241,6 +247,9 @@ protected virtual void Dispose(bool disposing)
}

// This code added to correctly implement the disposable pattern.
/// <summary>
/// Releases all resources used by this instance.
/// </summary>
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Expand Down
34 changes: 34 additions & 0 deletions Src/DSInternals.Common/ADSI/AdsiObjectAdapter.cs
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method XML doc comments could still be improved.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
using System.Linq;
using System.Security.Principal;

/// <summary>
/// Provides an adapter for accessing Active Directory objects through ADSI SearchResult objects.
/// </summary>
public class AdsiObjectAdapter : DirectoryObject
{
protected SearchResult directoryEntry;
Expand Down Expand Up @@ -53,49 +56,80 @@ protected override bool HasBigEndianRid
}
}

/// <summary>
/// Determines whether the directory object has the specified attribute.
/// </summary>
/// <param name="name">The name of the attribute to check for.</param>
/// <returns>True if the attribute exists; otherwise, false.</returns>
public override bool HasAttribute(string name)
{
return this.directoryEntry.Properties.Contains(name);
}

/// <summary>
/// Reads the value of the specified attribute.
/// </summary>
public override void ReadAttribute(string name, out byte[] value)
{
value = this.ReadAttributeSingle<byte[]>(name);
}

/// <summary>
/// Reads the value of the specified attribute.
/// </summary>
public override void ReadAttribute(string name, out byte[][] value)
{
value = this.ReadAttributeMulti<byte[]>(name);
}

/// <summary>
/// Reads the value of the specified attribute.
/// </summary>
public override void ReadAttribute(string name, out int? value)
{
value = this.ReadAttributeSingle<int?>(name);
}

/// <summary>
/// Reads the value of the specified attribute.
/// </summary>
public override void ReadAttribute(string name, out long? value)
{
value = this.ReadAttributeSingle<long?>(name);
}

/// <summary>
/// Reads the value of the specified attribute.
/// </summary>
public override void ReadAttribute(string name, out string value, bool unicode = true)
{
// Unicode vs. IA5 strings are handled by ADSI itself.
value = this.ReadAttributeSingle<string>(name);
}

/// <summary>
/// Reads the value of the specified attribute.
/// </summary>
public override void ReadAttribute(string name, out string[] values, bool unicode = true)
{
// Unicode vs. IA5 strings are handled by ADSI itself.
values = this.ReadAttributeMulti<string>(name);
}

/// <summary>
/// Reads the value of the specified attribute.
/// </summary>
public override void ReadAttribute(string name, out DistinguishedName value)
{
string dnString = this.ReadAttributeSingle<string>(name);
value = new DistinguishedName(dnString);
}

/// <summary>
/// Reads linked attribute values that contain DN with binary data.
/// </summary>
/// <param name="attributeName">The name of the linked attribute to read.</param>
/// <param name="values">When this method returns, contains the binary values from the linked attribute, or null if the attribute is not present.</param>
public override void ReadLinkedValues(string attributeName, out byte[][] values)
{
// Parse the DN with binary value
Expand Down
37 changes: 37 additions & 0 deletions Src/DSInternals.Common/AzureAD/AzureADClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

namespace DSInternals.Common.AzureAD
{
/// <summary>
/// Client for interacting with Azure Active Directory Graph API.
/// </summary>
public class AzureADClient : IDisposable
{
private const string DefaultTenantId = "myorganization";
Expand All @@ -27,6 +30,10 @@ public class AzureADClient : IDisposable
private const string UsersUrlFormat = "https://graph.windows.net/{0}/users/{1}?";
private const string JsonContentType = "application/json";
private const string KeyCredentialAttributeName = "searchableDeviceKey";

/// <summary>
/// The maximum number of users that can be retrieved in a single batch request.
/// </summary>
public const int MaxBatchSize = 999;
private static readonly MediaTypeWithQualityHeaderValue s_odataContentType = MediaTypeWithQualityHeaderValue.Parse("application/json;odata=nometadata;streaming=false");
private string _tenantId;
Expand All @@ -46,6 +53,12 @@ public AzureADClient(string accessToken, Guid? tenantId = null, int batchSize =
_httpClient.DefaultRequestHeaders.Accept.Add(s_odataContentType);
}

/// <summary>
/// Retrieves a user from Azure AD by user principal name.
/// </summary>
/// <param name="userPrincipalName">The user principal name of the user to retrieve.</param>
/// <returns>The Azure AD user if found.</returns>
/// <exception cref="ArgumentNullException">Thrown when userPrincipalName is null or empty.</exception>
public async Task<AzureADUser> GetUserAsync(string userPrincipalName)
{
// Vaidate the input
Expand All @@ -55,6 +68,11 @@ public async Task<AzureADUser> GetUserAsync(string userPrincipalName)
return await GetUserAsync(filter, userPrincipalName).ConfigureAwait(false);
}

/// <summary>
/// Retrieves a user from Azure AD by object ID.
/// </summary>
/// <param name="objectId">The object ID of the user to retrieve.</param>
/// <returns>The Azure AD user if found.</returns>
public async Task<AzureADUser> GetUserAsync(Guid objectId)
{
var filter = string.Format(CultureInfo.InvariantCulture, IdFilterParameterFormat, objectId);
Expand All @@ -80,6 +98,11 @@ private async Task<AzureADUser> GetUserAsync(string filterParameter, object user
return result.Items[0];
}

/// <summary>
/// Retrieves a paged list of users from Azure AD.
/// </summary>
/// <param name="nextLink">Optional link to retrieve the next page of results.</param>
/// <returns>A paged response containing Azure AD users.</returns>
public async Task<OdataPagedResponse<AzureADUser>> GetUsersAsync(string nextLink = null)
{
var url = new StringBuilder(nextLink);
Expand Down Expand Up @@ -112,6 +135,12 @@ public async Task<OdataPagedResponse<AzureADUser>> GetUsersAsync(string nextLink
}
}

/// <summary>
/// Updates a user's key credentials by user principal name.
/// </summary>
/// <param name="userPrincipalName">The user principal name of the user to update.</param>
/// <param name="keyCredentials">The key credentials to set for the user.</param>
/// <exception cref="ArgumentNullException">Thrown when userPrincipalName is null or empty.</exception>
public async Task SetUserAsync(string userPrincipalName, KeyCredential[] keyCredentials)
{
// Vaidate the input
Expand All @@ -121,6 +150,11 @@ public async Task SetUserAsync(string userPrincipalName, KeyCredential[] keyCred
await SetUserAsync(userPrincipalName, properties).ConfigureAwait(false);
}

/// <summary>
/// Updates a user's key credentials by object ID.
/// </summary>
/// <param name="objectId">The object ID of the user to update.</param>
/// <param name="keyCredentials">The key credentials to set for the user.</param>
public async Task SetUserAsync(Guid objectId, KeyCredential[] keyCredentials)
{
var properties = new Dictionary<string, object> { { KeyCredentialAttributeName, keyCredentials } };
Expand Down Expand Up @@ -191,6 +225,9 @@ private async Task<T> SendODataRequest<T>(HttpRequestMessage request)
}

#region IDisposable Support
/// <summary>
/// Releases all resources used by the AzureADClient.
/// </summary>
public virtual void Dispose()
{
_httpClient.Dispose();
Expand Down
3 changes: 3 additions & 0 deletions Src/DSInternals.Common/AzureAD/AzureADUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

namespace DSInternals.Common.AzureAD
{
/// <summary>
/// Represents an Azure Active Directory user object.
/// </summary>
public class AzureADUser
{
[JsonPropertyName("objectId")]
Expand Down
20 changes: 20 additions & 0 deletions Src/DSInternals.Common/AzureAD/GraphApiException.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
namespace DSInternals.Common.AzureAD
{
/// <summary>
/// Exception thrown when Azure AD Graph API operations fail.
/// </summary>
public class GraphApiException : Exception
{
/// <summary>
/// Gets the Azure AD error code associated with this exception.
/// </summary>
public string ErrorCode
{
get;
protected set;
}

/// <summary>
/// Initializes a new instance of the GraphApiException class with a specified error message and optional error code.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="errorCode">The Azure AD error code, if available.</param>
public GraphApiException(string message, string errorCode = null) : base(message)
{
this.ErrorCode = errorCode;
}

/// <summary>
/// Initializes a new instance of the GraphApiException class with a specified error message and a reference to the inner exception.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public GraphApiException(string message, Exception innerException) : base(message, innerException)
{
}

/// <summary>
/// Initializes a new instance of the GraphApiException class from an OData error.
/// </summary>
/// <param name="error">The OData error containing error details.</param>
public GraphApiException(ODataError error) : base(error.Message.Value)
{
this.ErrorCode = error.Code;
Expand Down
3 changes: 3 additions & 0 deletions Src/DSInternals.Common/AzureAD/ODataError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace DSInternals.Common.AzureAD
{
/// <summary>
/// Represents an OData error from Azure AD Graph API responses.
/// </summary>
public class ODataError
{
[JsonPropertyName("code")]
Expand Down
3 changes: 3 additions & 0 deletions Src/DSInternals.Common/AzureAD/ODataErrorMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace DSInternals.Common.AzureAD
{
/// <summary>
/// Represents an OData error message with language and value information.
/// </summary>
public class ODataErrorMessage
{
[JsonPropertyName("lang")]
Expand Down
7 changes: 7 additions & 0 deletions Src/DSInternals.Common/AzureAD/OdataErrorResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

namespace DSInternals.Common.AzureAD
{
/// <summary>
/// Represents an OData error response container from Azure AD Graph API.
/// </summary>
public class OdataErrorResponse
{
[JsonPropertyName("odata.error")]
Expand All @@ -13,6 +16,10 @@ public ODataError Error
private set;
}

/// <summary>
/// Creates an exception from this error response.
/// </summary>
/// <returns>A GraphApiException containing the error details.</returns>
public Exception GetException()
{
return new GraphApiException(this.Error);
Expand Down
4 changes: 4 additions & 0 deletions Src/DSInternals.Common/AzureAD/OdataPagedResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

namespace DSInternals.Common.AzureAD
{
/// <summary>
/// Represents a paged response from OData-compliant Azure AD Graph API endpoints.
/// </summary>
/// <typeparam name="T">The type of items contained in the response.</typeparam>
public class OdataPagedResponse<T>
{
[JsonPropertyName("value")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public class CngProtectedDataBlob
public ReadOnlyMemory<byte> Nonce { get; private set; }


/// <summary>
/// Decrypts the DPAPI-NG protected data using the Windows CNG API.
/// </summary>
/// <returns>A read-only span containing the decrypted data.</returns>
public ReadOnlySpan<byte> Decrypt()
{
if (this.RawData.Length == 0)
Expand All @@ -34,6 +38,11 @@ public ReadOnlySpan<byte> Decrypt()
return decryptedData;
}

/// <summary>
/// Attempts to decrypt the DPAPI-NG protected data without throwing exceptions on failure.
/// </summary>
/// <param name="cleartext">When this method returns, contains the decrypted data if successful, or an empty span if decryption fails.</param>
/// <returns>true if decryption was successful; otherwise, false.</returns>
public bool TryDecrypt(out ReadOnlySpan<byte> cleartext)
{
if (this.RawData.Length == 0)
Expand All @@ -49,6 +58,11 @@ public bool TryDecrypt(out ReadOnlySpan<byte> cleartext)
return resultCode == Win32ErrorCode.Success;
}

/// <summary>
/// Decodes a binary blob containing DPAPI-NG protected data in CMS enveloped format.
/// </summary>
/// <param name="blob">The binary data to decode.</param>
/// <returns>A CngProtectedDataBlob object containing the parsed protection metadata and encrypted content.</returns>
public static CngProtectedDataBlob Decode(ReadOnlyMemory<byte> blob)
{
var cms = Cryptography.Asn1.Pkcs7.ContentInfo.Decode(blob);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

namespace DSInternals.Common.Cryptography.Asn1.DpapiNg
{
/// <summary>
/// Represents a DPAPI-NG protection key descriptor that contains information about how data is protected, including security identifiers and protection methods.
/// </summary>
public struct ProtectionKeyDescriptor
{
private const string ProtectionInfoOid = "1.3.6.1.4.1.311.74.1";
Expand All @@ -23,6 +26,11 @@ public SecurityIdentifier Sid
private set;
}

/// <summary>
/// Decodes a protection key descriptor from the specified ASN.1 encoded binary data.
/// </summary>
/// <param name="encoded">The ASN.1 encoded binary data containing the protection key descriptor.</param>
/// <returns>A decoded ProtectionKeyDescriptor structure.</returns>
public static ProtectionKeyDescriptor Decode(ReadOnlyMemory<byte> encoded)
{
var reader = new AsnReader(encoded, AsnEncodingRules.DER);
Expand All @@ -31,6 +39,11 @@ public static ProtectionKeyDescriptor Decode(ReadOnlyMemory<byte> encoded)
return decoded;
}

/// <summary>
/// Decodes a protection key descriptor from the specified ASN.1 reader.
/// </summary>
/// <param name="reader">The ASN.1 reader positioned at the protection key descriptor data.</param>
/// <returns>A decoded ProtectionKeyDescriptor structure.</returns>
public static ProtectionKeyDescriptor Decode(AsnReader reader)
{
/*
Expand Down
Loading