diff --git a/src/Cmdlets/NewMinIOObjectCmdlet.cs b/src/Cmdlets/NewMinIOObjectCmdlet.cs
index b8406fc..c761a9b 100644
--- a/src/Cmdlets/NewMinIOObjectCmdlet.cs
+++ b/src/Cmdlets/NewMinIOObjectCmdlet.cs
@@ -9,32 +9,88 @@ using PSMinIO.Utils;
namespace PSMinIO.Cmdlets
{
///
- /// Uploads objects to MinIO
+ /// Uploads files or directories to a MinIO bucket
///
[Cmdlet(VerbsCommon.New, "MinIOObject", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium)]
[OutputType(typeof(MinIOUploadResult))]
public class NewMinIOObjectCmdlet : MinIOBaseCmdlet
{
///
- /// Name of the bucket
+ /// Name of the bucket to upload to
///
- [Parameter(Position = 0, Mandatory = true)]
+ [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
+ [Alias("Bucket")]
public string BucketName { get; set; } = string.Empty;
///
- /// Name of the object (if not specified, uses filename)
+ /// Single file to upload
///
- [Parameter(Position = 1)]
+ [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, ParameterSetName = "SingleFile")]
+ [ValidateNotNull]
+ public FileInfo? File { get; set; }
+
+ ///
+ /// Array of FileInfo objects to upload
+ ///
+ [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, ParameterSetName = "Files")]
+ [ValidateNotNull]
+ [Alias("Files")]
+ public FileInfo[]? Path { get; set; }
+
+ ///
+ /// Directory to upload
+ ///
+ [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Directory")]
+ [ValidateNotNull]
+ [Alias("Dir")]
+ public DirectoryInfo? Directory { get; set; }
+
+ ///
+ /// Object name in the bucket (defaults to filename) - SingleFile parameter set only
+ ///
+ [Parameter(Position = 2, ParameterSetName = "SingleFile")]
[ValidateNotNullOrEmpty]
public string? ObjectName { get; set; }
///
- /// File to upload
+ /// Optional bucket directory path where files should be uploaded (Files parameter set only)
///
- [Parameter(Position = 2, Mandatory = true, ValueFromPipeline = true)]
- [ValidateNotNull]
- public FileInfo File { get; set; } = null!;
+ [Parameter(ParameterSetName = "Files")]
+ [ValidateNotNullOrEmpty]
+ [Alias("Prefix", "Folder")]
+ public string? BucketDirectory { get; set; }
+
+ ///
+ /// Upload directory contents recursively (Directory parameter set only)
+ ///
+ [Parameter(ParameterSetName = "Directory")]
+ public SwitchParameter Recursive { get; set; }
+
+ ///
+ /// Maximum depth for recursive directory upload (0 = unlimited)
+ ///
+ [Parameter(ParameterSetName = "Directory")]
+ [ValidateRange(0, int.MaxValue)]
+ public int MaxDepth { get; set; } = 0;
+
+ ///
+ /// Flatten directory structure (upload all files to bucket root)
+ ///
+ [Parameter(ParameterSetName = "Directory")]
+ public SwitchParameter Flatten { get; set; }
+
+ ///
+ /// Script block to filter files for inclusion
+ ///
+ [Parameter(ParameterSetName = "Directory")]
+ public ScriptBlock? InclusionFilter { get; set; }
+
+ ///
+ /// Script block to filter files for exclusion
+ ///
+ [Parameter(ParameterSetName = "Directory")]
+ public ScriptBlock? ExclusionFilter { get; set; }
///
/// Content type of the file (auto-detected if not specified)
@@ -50,12 +106,10 @@ namespace PSMinIO.Cmdlets
public Hashtable? Metadata { get; set; }
///
- /// Directory prefix in the bucket (creates nested structure)
+ /// Overwrite existing objects without prompting
///
[Parameter]
- [ValidateNotNullOrEmpty]
- [Alias("Folder", "Directory")]
- public string? BucketDirectory { get; set; }
+ public SwitchParameter Force { get; set; }
///
/// Force overwrite if object already exists
@@ -76,134 +130,19 @@ namespace PSMinIO.Cmdlets
{
ValidateBucketName(BucketName);
- // Validate file exists
- if (!File.Exists)
+ switch (ParameterSetName)
{
- var errorRecord = new ErrorRecord(
- new FileNotFoundException($"File not found: {File.FullName}"),
- "FileNotFound",
- ErrorCategory.ObjectNotFound,
- File);
- ThrowTerminatingError(errorRecord);
- }
-
- // Determine object name
- var finalObjectName = ObjectName ?? File?.Name ?? "unknown";
-
- // Add bucket directory prefix if specified
- if (!string.IsNullOrEmpty(BucketDirectory))
- {
- var cleanDirectory = BucketDirectory.Trim('/');
- if (!string.IsNullOrEmpty(cleanDirectory))
- {
- finalObjectName = $"{cleanDirectory}/{finalObjectName}";
- }
- }
-
- ValidateObjectName(finalObjectName);
-
- if (ShouldProcess($"{BucketName}/{finalObjectName}", "Upload file"))
- {
- var result = ExecuteOperation("UploadObject", () =>
- {
- WriteVerboseMessage("Uploading file '{0}' to bucket '{1}' as object '{2}'",
- File.FullName, BucketName, finalObjectName);
-
- // Create upload result
- var uploadResult = new MinIOUploadResult(BucketName, finalObjectName, string.Empty, File.Length)
- {
- SourceFilePath = File.FullName
- };
- uploadResult.MarkStarted();
-
- try
- {
- // Check if bucket exists
- if (!S3Client.BucketExists(BucketName))
- {
- throw new InvalidOperationException($"Bucket '{BucketName}' does not exist");
- }
-
- // Determine content type
- var fileContentType = ContentType ?? GetContentType(File.Extension);
- uploadResult.ContentType = fileContentType;
- WriteVerboseMessage("Using content type: {0}", fileContentType);
-
- // Convert metadata
- Dictionary? metadata = null;
- if (Metadata != null && Metadata.Count > 0)
- {
- metadata = new Dictionary();
- foreach (var key in Metadata.Keys)
- {
- if (key != null && Metadata[key] != null)
- {
- metadata[key.ToString()!] = Metadata[key]!.ToString()!;
- }
- }
- WriteVerboseMessage("Added {0} metadata entries", metadata.Count);
- }
-
- // Track progress
- var fileSize = File.Length;
- var startTime = DateTime.UtcNow;
-
- WriteVerboseMessage("Starting upload of {0}", SizeFormatter.FormatBytes(fileSize));
-
- // Upload the file
- string etag;
- using (var fileStream = File.OpenRead())
- {
- etag = S3Client.PutObject(
- BucketName,
- finalObjectName,
- fileStream,
- fileContentType,
- metadata,
- bytesTransferred =>
- {
- // Only update the result object - no PowerShell calls from background thread
- uploadResult.BytesTransferred = bytesTransferred;
- });
- }
-
- uploadResult.ETag = etag;
- uploadResult.MarkCompleted();
-
- var duration = uploadResult.Duration ?? TimeSpan.Zero;
- var averageSpeed = uploadResult.AverageSpeed ?? 0;
-
- // Report final progress from main thread
- var percentage = fileSize > 0 ? (double)uploadResult.BytesTransferred / fileSize * 100 : 100;
- WriteVerboseMessage("Upload progress: {0:F1}% ({1}/{2}) at {3}",
- percentage,
- SizeFormatter.FormatBytes(uploadResult.BytesTransferred),
- SizeFormatter.FormatBytes(fileSize),
- SizeFormatter.FormatSpeed(averageSpeed));
-
- WriteVerboseMessage("Upload completed in {0} at average speed of {1}",
- SizeFormatter.FormatDuration(duration),
- SizeFormatter.FormatSpeed(averageSpeed));
-
- return uploadResult;
- }
- catch (Exception ex)
- {
- uploadResult.MarkFailed(ex.Message);
- throw;
- }
-
- }, $"File: {File.FullName}, Bucket: {BucketName}, Object: {finalObjectName}");
-
- // Return result if requested
- if (PassThru.IsPresent)
- {
- WriteObject(result);
- }
- else
- {
- WriteVerboseMessage("Successfully uploaded object '{0}' to bucket '{1}'", finalObjectName, BucketName);
- }
+ case "SingleFile":
+ ProcessSingleFile();
+ break;
+ case "Files":
+ ProcessFiles();
+ break;
+ case "Directory":
+ ProcessDirectory();
+ break;
+ default:
+ throw new InvalidOperationException($"Unknown parameter set: {ParameterSetName}");
}
}
diff --git a/src/Utils/MinIOErrorHandler.cs b/src/Utils/MinIOErrorHandler.cs
index d7817e1..a7af95a 100644
--- a/src/Utils/MinIOErrorHandler.cs
+++ b/src/Utils/MinIOErrorHandler.cs
@@ -97,40 +97,19 @@ namespace PSMinIO.Utils
private static OrderedDictionary CreateDetailedErrorInfo(Exception exception, string operationName, string? operationDetails)
{
var errorDetails = new OrderedDictionary();
-
+
// Basic error information
errorDetails.Add("Operation", operationName);
errorDetails.Add("Message", exception.Message);
errorDetails.Add("ExceptionType", exception.GetType().FullName);
-
- // Operation details if provided
- if (!string.IsNullOrEmpty(operationDetails))
- {
- errorDetails.Add("OperationDetails", operationDetails);
- }
-
+
// Inner exception information
if (exception.InnerException != null)
{
errorDetails.Add("InnerExceptionType", exception.InnerException.GetType().FullName);
errorDetails.Add("InnerExceptionMessage", exception.InnerException.Message);
}
-
- // Stack trace information
- if (!string.IsNullOrEmpty(exception.StackTrace))
- {
- var stackLines = exception.StackTrace.Split('\n');
- if (stackLines.Length > 0)
- {
- errorDetails.Add("StackTraceTop", stackLines[0].Trim());
- }
- }
-
- // System information
- errorDetails.Add("Timestamp", DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss UTC"));
- errorDetails.Add("MachineName", Environment.MachineName);
- errorDetails.Add("ProcessId", Process.GetCurrentProcess().Id);
-
+
return errorDetails;
}
@@ -144,10 +123,7 @@ namespace PSMinIO.Utils
{
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
- // Log header
- cmdlet.WriteWarning($"{timestamp} - ERROR: PSMinIO Operation Failed");
-
- // Log each error detail
+ // Log each error detail (reduced set)
foreach (DictionaryEntry detail in errorDetails)
{
var key = detail.Key?.ToString() ?? "Unknown";
@@ -159,7 +135,7 @@ namespace PSMinIO.Utils
value = value.Substring(0, 197) + "...";
}
- cmdlet.WriteWarning($"{timestamp} - ERROR: {key}: {value}");
+ cmdlet.WriteWarning($"{timestamp} - {key}: {value}");
}
}
catch (InvalidOperationException)