Major improvements: MinIO 4.0.7 compatibility, file handle fixes, clean logging, session management

Key Achievements:
- Downgraded to MinIO 4.0.7 for PowerShell compatibility (eliminated async/await issues)
- Fixed file handle leaks in upload operations (using explicit FileStream management)
- Implemented clean logging (timestamps without redundant prefixes)
- Fixed automatic session variable management (Connect-MinIO stores, cmdlets retrieve)
- Removed duplicate certificate parameters (kept only SkipCertificateValidation)
- Fixed all 'Folder' alias conflicts across cmdlets
- Added correct System.Runtime.CompilerServices.Unsafe.dll version (4.5.3)

 Working Features:
- Connection management (automatic + explicit override)
- Bucket operations (list, create, remove)
- File upload/download (with proper handle release)
- Progress tracking and speed reporting
- Clean verbose logging with timestamps

 Known Issue:
- Chunked operations have threading violations (PowerShell cmdlet methods called from background threads)
- Regular operations work perfectly, chunked operations need threading architecture fix

 Test Results:
- 3.71GB Windows install.wim file tested
- Regular upload/download:  Working
- File handles:  No leaks, immediate deletion possible
- Session management:  Automatic connection storage/retrieval
- Chunked operations:  Threading violations need fix
This commit is contained in:
PSMinIO Developer
2025-07-10 20:55:14 -04:00
parent 51cd2d4133
commit 2974ead76e
63 changed files with 3463 additions and 29 deletions
+3 -14
View File
@@ -57,11 +57,11 @@ namespace PSMinIO.Cmdlets
public SwitchParameter TestConnection { get; set; }
/// <summary>
/// Store the connection in a session variable for reuse
/// Store the connection in a session variable for reuse (default: MinIOConnection)
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
public string? SessionVariable { get; set; }
public string SessionVariable { get; set; } = "MinIOConnection";
/// <summary>
/// Skip SSL certificate validation (use with caution)
@@ -69,18 +69,6 @@ namespace PSMinIO.Cmdlets
[Parameter]
public SwitchParameter SkipCertificateValidation { get; set; }
/// <summary>
/// Accept self-signed certificates
/// </summary>
[Parameter]
public SwitchParameter AcceptSelfSignedCertificates { get; set; }
/// <summary>
/// Accept certificates with hostname mismatches
/// </summary>
[Parameter]
public SwitchParameter AcceptHostnameMismatch { get; set; }
/// <summary>
/// Processes the cmdlet
/// </summary>
@@ -147,6 +135,7 @@ namespace PSMinIO.Cmdlets
// Store in session variable if requested
if (!string.IsNullOrWhiteSpace(SessionVariable))
{
// Set variable in session state
SessionState.PSVariable.Set(SessionVariable, connection);
MinIOLogger.WriteVerbose(this, "Connection stored in session variable: {0}", SessionVariable!);
}
+4 -1
View File
@@ -175,7 +175,10 @@ namespace PSMinIO.Cmdlets
try
{
var testFile = Path.Combine(FilePath.DirectoryName ?? ".", $".psminiotest_{Guid.NewGuid():N}");
File.WriteAllText(testFile, "test");
using (var testStream = File.Create(testFile))
{
testStream.WriteByte(0);
}
File.Delete(testFile);
}
catch (Exception ex)
+2 -2
View File
@@ -36,7 +36,7 @@ namespace PSMinIO.Cmdlets
/// </summary>
[Parameter(ParameterSetName = "Files")]
[ValidateNotNullOrEmpty]
[Alias("Folder", "Prefix")]
[Alias("Prefix")]
public string? BucketDirectory { get; set; }
/// <summary>
@@ -44,7 +44,7 @@ namespace PSMinIO.Cmdlets
/// </summary>
[Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Directory")]
[ValidateNotNull]
[Alias("Dir", "Folder")]
[Alias("Dir")]
public DirectoryInfo? Directory { get; set; }
/// <summary>
+2 -2
View File
@@ -36,7 +36,7 @@ namespace PSMinIO.Cmdlets
/// </summary>
[Parameter(ParameterSetName = "Files")]
[ValidateNotNullOrEmpty]
[Alias("Folder", "Prefix")]
[Alias("Prefix")]
public string? BucketDirectory { get; set; }
/// <summary>
@@ -44,7 +44,7 @@ namespace PSMinIO.Cmdlets
/// </summary>
[Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Directory")]
[ValidateNotNull]
[Alias("Dir", "Folder")]
[Alias("Dir")]
public DirectoryInfo? Directory { get; set; }
/// <summary>
+7 -1
View File
@@ -237,7 +237,13 @@ namespace PSMinIO.Models
/// </summary>
public override int GetHashCode()
{
return HashCode.Combine(Name, BucketName?.ToLowerInvariant());
unchecked
{
int hash = 17;
hash = hash * 23 + (Name?.GetHashCode() ?? 0);
hash = hash * 23 + (BucketName?.ToLowerInvariant()?.GetHashCode() ?? 0);
return hash;
}
}
}
}
+10 -6
View File
@@ -383,19 +383,23 @@ namespace PSMinIO.Utils
contentType = GetContentType(filePath);
}
// Use explicit file stream management to ensure proper handle release
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
var args = new PutObjectArgs()
.WithBucket(bucketName)
.WithObject(objectName)
.WithFileName(filePath)
.WithStreamData(fileStream)
.WithObjectSize(fileSize)
.WithContentType(contentType);
// Progress tracking not available in MinIO 5.0.0
// Progress tracking not available in MinIO 4.0.7
// progressCallback is ignored for now
var result = Task.Run(async () =>
Task.Run(async () =>
await _client.PutObjectAsync(args, CancellationToken)).GetAwaiter().GetResult();
return result.Etag ?? string.Empty;
return string.Empty; // MinIO 4.0.7 PutObjectAsync returns void
}
catch (Exception ex)
{
@@ -481,10 +485,10 @@ namespace PSMinIO.Utils
// Progress tracking not available in MinIO 5.0.0
// progressCallback is ignored for now
var result = Task.Run(async () =>
Task.Run(async () =>
await _client.PutObjectAsync(args, CancellationToken)).GetAwaiter().GetResult();
return result.Etag ?? string.Empty;
return string.Empty; // MinIO 4.0.7 PutObjectAsync returns void
}
catch (Exception ex)
{
+1 -2
View File
@@ -178,13 +178,12 @@ namespace PSMinIO.Utils
private static string FormatLogMessage(LogLevel level, string message, params object[] args)
{
var timestamp = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff");
var levelString = level.ToString().ToUpperInvariant();
// Process arguments to format byte sizes intelligently
var processedArgs = ProcessLogArguments(args);
var formattedMessage = processedArgs.Length > 0 ? string.Format(message, processedArgs) : message;
return $"{timestamp} - [{levelString}] - {formattedMessage}";
return $"{timestamp} - {formattedMessage}";
}
/// <summary>