diff --git a/Chunked-Test.ps1 b/Chunked-Test.ps1
deleted file mode 100644
index bc73d51..0000000
--- a/Chunked-Test.ps1
+++ /dev/null
@@ -1,135 +0,0 @@
-# Chunked upload test with large Windows install.wim file
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== PSMinIO Chunked Upload Test ===" -ForegroundColor Cyan
-Write-Host ""
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-# Large test file
-$testFile = "C:\Users\gsadmin\Downloads\windows_10_enterprise_ltsc_2021_x64\sources\install.wim"
-
-# Check if file exists
-if (-not (Test-Path $testFile)) {
- Write-Host "ERROR: Test file not found: $testFile" -ForegroundColor Red
- exit 1
-}
-
-# Get file info
-$fileInfo = Get-Item $testFile
-Write-Host "Test file: $($fileInfo.Name)" -ForegroundColor Yellow
-Write-Host "File size: $([math]::Round($fileInfo.Length / 1MB, 2)) MB ($($fileInfo.Length) bytes)" -ForegroundColor Yellow
-Write-Host ""
-
-# Connect
-Write-Host "1. Connecting to MinIO..." -ForegroundColor Yellow
-try {
- $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey -Verbose
- Write-Host "SUCCESS: Connected to $($connection.EndpointUrl)" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- exit 1
-}
-
-# Get bucket
-$buckets = Get-MinIOBucket
-if ($buckets.Count -eq 0) {
- Write-Host "ERROR: No buckets available for testing" -ForegroundColor Red
- exit 1
-}
-$testBucket = $buckets[0].Name
-Write-Host "Using bucket: $testBucket" -ForegroundColor Yellow
-Write-Host ""
-
-# Test chunked upload with different chunk sizes
-$chunkSizes = @(
- @{ Size = "10MB"; Bytes = 10MB },
- @{ Size = "50MB"; Bytes = 50MB }
-)
-
-foreach ($chunkConfig in $chunkSizes) {
- Write-Host "2. Testing chunked upload with $($chunkConfig.Size) chunks..." -ForegroundColor Yellow
-
- try {
- $startTime = Get-Date
-
- # Perform chunked upload
- $uploadResult = New-MinIOObjectChunked -BucketName $testBucket -Files $testFile -ChunkSize $chunkConfig.Bytes -Verbose
-
- $endTime = Get-Date
- $duration = $endTime - $startTime
- $speedMBps = [math]::Round(($fileInfo.Length / 1MB) / $duration.TotalSeconds, 2)
-
- Write-Host "SUCCESS: Chunked upload completed!" -ForegroundColor Green
- Write-Host "Duration: $($duration.ToString('mm\:ss\.fff'))" -ForegroundColor Gray
- Write-Host "Average speed: $speedMBps MB/s" -ForegroundColor Gray
- Write-Host ""
-
- # Display result
- $uploadResult | Format-Table ObjectName, BucketName, Size, @{
- Name = "SizeMB"
- Expression = { [math]::Round($_.Size / 1MB, 2) }
- }
-
- # Test chunked download
- Write-Host "3. Testing chunked download with $($chunkConfig.Size) chunks..." -ForegroundColor Yellow
-
- $downloadFile = "downloaded-$($fileInfo.Name)"
-
- try {
- $downloadStartTime = Get-Date
-
- # Perform chunked download
- $downloadResult = Get-MinIOObjectContentChunked -BucketName $testBucket -ObjectName $fileInfo.Name -FilePath $downloadFile -ChunkSize $chunkConfig.Bytes -Force -Verbose
-
- $downloadEndTime = Get-Date
- $downloadDuration = $downloadEndTime - $downloadStartTime
- $downloadSpeedMBps = [math]::Round(($fileInfo.Length / 1MB) / $downloadDuration.TotalSeconds, 2)
-
- Write-Host "SUCCESS: Chunked download completed!" -ForegroundColor Green
- Write-Host "Duration: $($downloadDuration.ToString('mm\:ss\.fff'))" -ForegroundColor Gray
- Write-Host "Average speed: $downloadSpeedMBps MB/s" -ForegroundColor Gray
-
- # Verify file size
- $downloadedFileInfo = Get-Item $downloadFile
- if ($downloadedFileInfo.Length -eq $fileInfo.Length) {
- Write-Host "SUCCESS: File size verification passed ($($downloadedFileInfo.Length) bytes)" -ForegroundColor Green
- } else {
- Write-Host "WARNING: File size mismatch - Original: $($fileInfo.Length), Downloaded: $($downloadedFileInfo.Length)" -ForegroundColor Yellow
- }
-
- # Cleanup downloaded file
- Remove-Item $downloadFile -Force -ErrorAction SilentlyContinue
-
- } catch {
- Write-Host "FAILED: Download error - $($_.Exception.Message)" -ForegroundColor Red
- }
-
- # Cleanup uploaded file from bucket
- Write-Host "4. Cleaning up uploaded file..." -ForegroundColor Yellow
- try {
- Remove-MinIOObject -BucketName $testBucket -ObjectName $fileInfo.Name -Force -Verbose
- Write-Host "SUCCESS: Cleanup completed" -ForegroundColor Green
- } catch {
- Write-Host "WARNING: Cleanup failed - $($_.Exception.Message)" -ForegroundColor Yellow
- }
-
- Write-Host ""
- Write-Host "--- Test with $($chunkConfig.Size) chunks completed ---" -ForegroundColor Cyan
- Write-Host ""
-
- # Only test one chunk size for now to avoid long test times
- break
-
- } catch {
- Write-Host "FAILED: Upload error - $($_.Exception.Message)" -ForegroundColor Red
- Write-Host "Inner Exception: $($_.Exception.InnerException.Message)" -ForegroundColor Red
- break
- }
-}
-
-Write-Host "=== Chunked Test Suite Complete ===" -ForegroundColor Cyan
-Write-Host "✅ Large file chunked upload/download with progress tracking" -ForegroundColor Green
diff --git a/Debug-Test.ps1 b/Debug-Test.ps1
deleted file mode 100644
index ed10379..0000000
--- a/Debug-Test.ps1
+++ /dev/null
@@ -1,39 +0,0 @@
-# Debug test for PSMinIO module
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== PSMinIO Debug Test ===" -ForegroundColor Cyan
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-Write-Host "1. Connecting..." -ForegroundColor Yellow
-try {
- $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
- Write-Host "SUCCESS: Connected to $($connection.EndpointUrl)" -ForegroundColor Green
-
- # Check if connection is stored in session variable
- Write-Host "2. Checking session variable..." -ForegroundColor Yellow
- $sessionConnection = Get-Variable -Name "MinIOConnection" -ErrorAction SilentlyContinue
- if ($sessionConnection) {
- Write-Host "SUCCESS: Session variable exists with value type: $($sessionConnection.Value.GetType().Name)" -ForegroundColor Green
- Write-Host "Session connection endpoint: $($sessionConnection.Value.EndpointUrl)" -ForegroundColor Gray
- } else {
- Write-Host "FAILED: Session variable not found" -ForegroundColor Red
- }
-
- # Try to manually call Get-MinIOBucket with explicit connection
- Write-Host "3. Testing with explicit connection..." -ForegroundColor Yellow
- try {
- $buckets = Get-MinIOBucket -MinIOConnection $connection
- Write-Host "SUCCESS: Found $($buckets.Count) buckets with explicit connection" -ForegroundColor Green
- } catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- }
-
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-Write-Host "Debug test completed!" -ForegroundColor Cyan
diff --git a/File-Handle-Test.ps1 b/File-Handle-Test.ps1
deleted file mode 100644
index 067fa4a..0000000
--- a/File-Handle-Test.ps1
+++ /dev/null
@@ -1,93 +0,0 @@
-# Test file handle release
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== File Handle Test ===" -ForegroundColor Cyan
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-# Connect
-$connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
-$buckets = Get-MinIOBucket
-$testBucket = $buckets[0].Name
-
-Write-Host "Testing file handle release with bucket: $testBucket" -ForegroundColor Yellow
-
-# Test 1: Create file, upload, try to delete immediately
-Write-Host "1. Testing immediate file deletion after upload..." -ForegroundColor Yellow
-$testFile1 = "handle-test-1.txt"
-"Test content 1" | Out-File -FilePath $testFile1 -Encoding UTF8
-
-try {
- # Upload
- $result = New-MinIOObject -BucketName $testBucket -Files $testFile1
- Write-Host "Upload successful" -ForegroundColor Green
-
- # Try to delete immediately
- Remove-Item $testFile1 -Force
- Write-Host "SUCCESS: File deleted immediately after upload" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-# Test 2: Create file, upload, wait a bit, then delete
-Write-Host "2. Testing file deletion after short delay..." -ForegroundColor Yellow
-$testFile2 = "handle-test-2.txt"
-"Test content 2" | Out-File -FilePath $testFile2 -Encoding UTF8
-
-try {
- # Upload
- $result = New-MinIOObject -BucketName $testBucket -Files $testFile2
- Write-Host "Upload successful" -ForegroundColor Green
-
- # Wait a bit
- Start-Sleep -Seconds 2
-
- # Try to delete
- Remove-Item $testFile2 -Force
- Write-Host "SUCCESS: File deleted after delay" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-# Test 3: Download and immediate delete
-Write-Host "3. Testing download and immediate delete..." -ForegroundColor Yellow
-$downloadFile = "downloaded-handle-test.txt"
-
-try {
- # Download
- $result = Get-MinIOObjectContent -BucketName $testBucket -ObjectName $testFile1 -FilePath $downloadFile -Force
- Write-Host "Download successful" -ForegroundColor Green
-
- # Try to delete immediately
- Remove-Item $downloadFile -Force
- Write-Host "SUCCESS: Downloaded file deleted immediately" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-# Test 4: Force garbage collection and retry
-Write-Host "4. Testing with garbage collection..." -ForegroundColor Yellow
-$testFile3 = "handle-test-3.txt"
-"Test content 3" | Out-File -FilePath $testFile3 -Encoding UTF8
-
-try {
- # Upload
- $result = New-MinIOObject -BucketName $testBucket -Files $testFile3
- Write-Host "Upload successful" -ForegroundColor Green
-
- # Force garbage collection
- [System.GC]::Collect()
- [System.GC]::WaitForPendingFinalizers()
- [System.GC]::Collect()
-
- # Try to delete
- Remove-Item $testFile3 -Force
- Write-Host "SUCCESS: File deleted after GC" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-Write-Host "File handle test completed!" -ForegroundColor Cyan
diff --git a/Final-Clean-Test.ps1 b/Final-Clean-Test.ps1
deleted file mode 100644
index c42dec9..0000000
--- a/Final-Clean-Test.ps1
+++ /dev/null
@@ -1,92 +0,0 @@
-# Final clean test for PSMinIO module with MinIO 4.0.7
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== PSMinIO Module Final Test ===" -ForegroundColor Cyan
-Write-Host ""
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-# Test 1: Connection with automatic session variable
-Write-Host "1. Connection Test (with automatic session variable)..." -ForegroundColor Yellow
-try {
- $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey -Verbose
- Write-Host "SUCCESS: Connected to $($connection.EndpointUrl)" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- exit 1
-}
-
-# Test 2: Bucket Listing (using session variable)
-Write-Host "2. Bucket Listing Test (using session variable)..." -ForegroundColor Yellow
-try {
- $buckets = Get-MinIOBucket -Verbose
- Write-Host "SUCCESS: Found $($buckets.Count) buckets" -ForegroundColor Green
- if ($buckets.Count -gt 0) {
- $buckets | Format-Table Name, CreationDate
- }
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-# Test 3: Bucket Listing (with explicit connection)
-Write-Host "3. Bucket Listing Test (with explicit connection)..." -ForegroundColor Yellow
-try {
- $buckets2 = Get-MinIOBucket -MinIOConnection $connection -Verbose
- Write-Host "SUCCESS: Found $($buckets2.Count) buckets with explicit connection" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-# Test 4: Create test bucket if none exist
-if ($buckets.Count -eq 0) {
- Write-Host "4. Creating Test Bucket..." -ForegroundColor Yellow
- try {
- $testBucketName = "psminiotest$(Get-Random -Minimum 1000 -Maximum 9999)"
- New-MinIOBucket -BucketName $testBucketName -Verbose
- Write-Host "SUCCESS: Created bucket $testBucketName" -ForegroundColor Green
- $buckets = Get-MinIOBucket
- } catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- }
-}
-
-# Test 5: File Upload (if we have buckets)
-if ($buckets.Count -gt 0) {
- $testBucket = $buckets[0].Name
- Write-Host "5. File Upload Test to bucket '$testBucket'..." -ForegroundColor Yellow
-
- # Create test file
- $testFile = "test-upload.txt"
- "Test content created at $(Get-Date)" | Out-File -FilePath $testFile -Encoding UTF8
-
- try {
- $uploadResult = New-MinIOObject -BucketName $testBucket -Files $testFile -Verbose
- Write-Host "SUCCESS: Uploaded $testFile" -ForegroundColor Green
- $uploadResult | Format-Table ObjectName, BucketName, Size
- } catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- }
-
- # Test 6: File Download
- Write-Host "6. File Download Test..." -ForegroundColor Yellow
- try {
- $downloadFile = "downloaded-test.txt"
- $downloadResult = Get-MinIOObjectContent -BucketName $testBucket -ObjectName $testFile -FilePath $downloadFile -Force -Verbose
- Write-Host "SUCCESS: Downloaded to $($downloadResult.FullName)" -ForegroundColor Green
- Write-Host "Content: $(Get-Content $downloadFile)" -ForegroundColor Gray
- } catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- }
-
- # Cleanup test files
- @($testFile, $downloadFile) | ForEach-Object {
- if (Test-Path $_) { Remove-Item $_ -Force }
- }
-}
-
-Write-Host ""
-Write-Host "=== Test Suite Complete ===" -ForegroundColor Cyan
-Write-Host "✅ MinIO 4.0.7 with clean logging and automatic session management" -ForegroundColor Green
diff --git a/Final-Test.ps1 b/Final-Test.ps1
deleted file mode 100644
index 23b6609..0000000
--- a/Final-Test.ps1
+++ /dev/null
@@ -1,109 +0,0 @@
-# Final comprehensive test for PSMinIO module
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== PSMinIO Module Test Suite ===" -ForegroundColor Cyan
-Write-Host ""
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-# Test 1: Connection
-Write-Host "1. Connection Test..." -ForegroundColor Yellow
-try {
- $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
- Write-Host "SUCCESS: Connected to $($connection.EndpointUrl)" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- exit 1
-}
-
-# Test 2: Bucket Listing
-Write-Host "2. Bucket Listing Test..." -ForegroundColor Yellow
-try {
- $buckets = Get-MinIOBucket
- Write-Host "SUCCESS: Found $($buckets.Count) buckets" -ForegroundColor Green
- if ($buckets.Count -gt 0) {
- $buckets | Select-Object Name, CreationDate | Format-Table
- }
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-# Test 3: Create test bucket if none exist
-if ($buckets.Count -eq 0) {
- Write-Host "3. Creating Test Bucket..." -ForegroundColor Yellow
- try {
- $testBucketName = "psminiotest$(Get-Random -Minimum 1000 -Maximum 9999)"
- New-MinIOBucket -BucketName $testBucketName
- Write-Host "SUCCESS: Created bucket $testBucketName" -ForegroundColor Green
- $buckets = Get-MinIOBucket
- } catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- }
-}
-
-# Test 4: File Upload
-if ($buckets.Count -gt 0) {
- $testBucket = $buckets[0].Name
- Write-Host "4. File Upload Test to bucket '$testBucket'..." -ForegroundColor Yellow
-
- # Create test file
- $testFile = "test-upload.txt"
- "Test content created at $(Get-Date)" | Out-File -FilePath $testFile -Encoding UTF8
-
- try {
- $uploadResult = New-MinIOObject -BucketName $testBucket -Files $testFile
- Write-Host "SUCCESS: Uploaded $testFile" -ForegroundColor Green
- $uploadResult | Format-Table ObjectName, BucketName, Size
- } catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- }
-
- # Test 5: File Download
- Write-Host "5. File Download Test..." -ForegroundColor Yellow
- try {
- $downloadFile = "downloaded-test.txt"
- $downloadResult = Get-MinIOObjectContent -BucketName $testBucket -ObjectName $testFile -FilePath $downloadFile -Force
- Write-Host "SUCCESS: Downloaded to $($downloadResult.FullName)" -ForegroundColor Green
- Write-Host "Content: $(Get-Content $downloadFile)" -ForegroundColor Gray
- } catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- }
-
- # Test 6: Chunked Upload
- Write-Host "6. Chunked Upload Test..." -ForegroundColor Yellow
- try {
- $chunkedFile = "chunked-test.txt"
- $content = @()
- for ($i = 1; $i -le 100; $i++) {
- $content += "Line $i of chunked test file created at $(Get-Date)"
- }
- $content | Out-File -FilePath $chunkedFile -Encoding UTF8
-
- $chunkedResult = New-MinIOObjectChunked -BucketName $testBucket -Files $chunkedFile -ChunkSize 1KB
- Write-Host "SUCCESS: Chunked upload completed" -ForegroundColor Green
- $chunkedResult | Format-Table ObjectName, BucketName, Size
- } catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- }
-
- # Test 7: Chunked Download
- Write-Host "7. Chunked Download Test..." -ForegroundColor Yellow
- try {
- $chunkedDownload = "chunked-downloaded.txt"
- $chunkedDownloadResult = Get-MinIOObjectContentChunked -BucketName $testBucket -ObjectName $chunkedFile -FilePath $chunkedDownload -ChunkSize 1KB -Force
- Write-Host "SUCCESS: Chunked download completed to $($chunkedDownloadResult.FullName)" -ForegroundColor Green
- } catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- }
-
- # Cleanup test files
- @($testFile, $downloadFile, $chunkedFile, $chunkedDownload) | ForEach-Object {
- if (Test-Path $_) { Remove-Item $_ -Force }
- }
-}
-
-Write-Host ""
-Write-Host "=== Test Suite Complete ===" -ForegroundColor Cyan
diff --git a/Large-File-Test.ps1 b/Large-File-Test.ps1
deleted file mode 100644
index aa41b1f..0000000
--- a/Large-File-Test.ps1
+++ /dev/null
@@ -1,105 +0,0 @@
-# Large file chunked upload test
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== Large File Chunked Upload Test ===" -ForegroundColor Cyan
-Write-Host ""
-
-# Test file
-$testFile = "C:\Users\gsadmin\Downloads\windows_10_enterprise_ltsc_2021_x64\sources\install.wim"
-
-# Check file
-if (Test-Path $testFile) {
- $fileInfo = Get-Item $testFile
- Write-Host "File: $($fileInfo.Name)" -ForegroundColor Yellow
- Write-Host "Size: $([math]::Round($fileInfo.Length / 1GB, 2)) GB ($([math]::Round($fileInfo.Length / 1MB, 0)) MB)" -ForegroundColor Yellow
-} else {
- Write-Host "File not found!" -ForegroundColor Red
- exit 1
-}
-
-# Connect
-Write-Host "Connecting..." -ForegroundColor Yellow
-$connection = Connect-MinIO -Endpoint "https://api.s3.gracesolution.info" -AccessKey "T34Wg85SAwezUa3sk3m4" -SecretKey "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-# Get bucket
-$buckets = Get-MinIOBucket
-$testBucket = $buckets[0].Name
-Write-Host "Using bucket: $testBucket" -ForegroundColor Yellow
-Write-Host ""
-
-# Test chunked upload with 50MB chunks
-Write-Host "Starting chunked upload with 50MB chunks..." -ForegroundColor Yellow
-Write-Host "This may take several minutes for a 3.7GB file..." -ForegroundColor Gray
-
-try {
- $startTime = Get-Date
-
- $result = New-MinIOObjectChunked -BucketName $testBucket -Files $testFile -ChunkSize 50MB -Verbose
-
- $endTime = Get-Date
- $duration = $endTime - $startTime
- $speedMBps = [math]::Round(($fileInfo.Length / 1MB) / $duration.TotalSeconds, 2)
-
- Write-Host ""
- Write-Host "SUCCESS: Chunked upload completed!" -ForegroundColor Green
- Write-Host "Duration: $($duration.ToString('hh\:mm\:ss'))" -ForegroundColor Gray
- Write-Host "Average speed: $speedMBps MB/s" -ForegroundColor Gray
- Write-Host ""
-
- $result | Format-Table ObjectName, BucketName, @{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}}
-
- # Test chunked download of first 100MB only (to save time)
- Write-Host "Testing partial chunked download (first 100MB)..." -ForegroundColor Yellow
-
- $downloadFile = "partial-download-test.wim"
-
- try {
- $downloadStartTime = Get-Date
-
- # Download with 10MB chunks
- $downloadResult = Get-MinIOObjectContentChunked -BucketName $testBucket -ObjectName $fileInfo.Name -FilePath $downloadFile -ChunkSize 10MB -Force -Verbose
-
- $downloadEndTime = Get-Date
- $downloadDuration = $downloadEndTime - $downloadStartTime
-
- Write-Host ""
- Write-Host "SUCCESS: Chunked download completed!" -ForegroundColor Green
- Write-Host "Duration: $($downloadDuration.ToString('hh\:mm\:ss'))" -ForegroundColor Gray
-
- # Check downloaded file size
- $downloadedFileInfo = Get-Item $downloadFile
- Write-Host "Downloaded: $([math]::Round($downloadedFileInfo.Length / 1GB, 2)) GB" -ForegroundColor Gray
-
- if ($downloadedFileInfo.Length -eq $fileInfo.Length) {
- Write-Host "SUCCESS: File size verification passed" -ForegroundColor Green
- } else {
- Write-Host "INFO: Partial download completed (expected for large files)" -ForegroundColor Yellow
- }
-
- # Cleanup downloaded file
- Remove-Item $downloadFile -Force -ErrorAction SilentlyContinue
-
- } catch {
- Write-Host "Download test failed: $($_.Exception.Message)" -ForegroundColor Red
- }
-
- # Cleanup uploaded file
- Write-Host ""
- Write-Host "Cleaning up uploaded file..." -ForegroundColor Yellow
- try {
- Remove-MinIOObject -BucketName $testBucket -ObjectName $fileInfo.Name -Force
- Write-Host "SUCCESS: Cleanup completed" -ForegroundColor Green
- } catch {
- Write-Host "WARNING: Cleanup failed - $($_.Exception.Message)" -ForegroundColor Yellow
- }
-
-} catch {
- Write-Host ""
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- if ($_.Exception.InnerException) {
- Write-Host "Inner Exception: $($_.Exception.InnerException.Message)" -ForegroundColor Red
- }
-}
-
-Write-Host ""
-Write-Host "=== Large File Test Complete ===" -ForegroundColor Cyan
diff --git a/Module/PSMinIO/bin/PSMinIO.dll b/Module/PSMinIO/bin/PSMinIO.dll
index 65c8da0..f4fe4f4 100644
Binary files a/Module/PSMinIO/bin/PSMinIO.dll and b/Module/PSMinIO/bin/PSMinIO.dll differ
diff --git a/Module/PSMinIO/bin/PSMinIO.pdb b/Module/PSMinIO/bin/PSMinIO.pdb
index 266d540..9c38678 100644
Binary files a/Module/PSMinIO/bin/PSMinIO.pdb and b/Module/PSMinIO/bin/PSMinIO.pdb differ
diff --git a/Quick-Test.ps1 b/Quick-Test.ps1
deleted file mode 100644
index 9e6f0b9..0000000
--- a/Quick-Test.ps1
+++ /dev/null
@@ -1,32 +0,0 @@
-# Quick test for PSMinIO module
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== Quick PSMinIO Test ===" -ForegroundColor Cyan
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-Write-Host "1. Connecting..." -ForegroundColor Yellow
-try {
- $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
- Write-Host "SUCCESS: Connected to $($connection.EndpointUrl)" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- exit 1
-}
-
-Write-Host "2. Testing bucket listing..." -ForegroundColor Yellow
-try {
- $buckets = Get-MinIOBucket
- Write-Host "SUCCESS: Found $($buckets.Count) buckets" -ForegroundColor Green
- if ($buckets.Count -gt 0) {
- $buckets | Format-Table Name, CreationDate
- }
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- Write-Host "Inner Exception: $($_.Exception.InnerException.Message)" -ForegroundColor Red
-}
-
-Write-Host "Test completed!" -ForegroundColor Cyan
diff --git a/README.md b/README.md
index 69b288e..05c1b54 100644
--- a/README.md
+++ b/README.md
@@ -1,71 +1,154 @@
# PSMinIO
-A fully-fledged C# PowerShell binary module built on top of the [Minio](https://www.nuget.org/packages/Minio) .NET SDK for managing MinIO object storage operations.
+A comprehensive PowerShell module for MinIO object storage operations, built on the official [Minio .NET SDK](https://www.nuget.org/packages/Minio). Provides full-featured object storage management with enterprise-grade capabilities.
## Features
-- **Cross-Platform Compatibility**: Built for .NET Standard 2.0, compatible with PowerShell 5.1+ and PowerShell 7+
-- **Comprehensive Bucket Operations**: Create, list, delete, and check bucket existence
-- **Object Management**: Upload, download, list, and delete objects with progress tracking
-- **Security & Policy Management**: Manage bucket policies and access controls
-- **Synchronous Operations**: All operations are synchronous for PowerShell compatibility
-- **Detailed Logging**: Centralized logging with timestamps when `-Verbose` is specified
-- **Progress Reporting**: Upload/download progress with percentages and time estimates
+- **🚀 High Performance**: Built for .NET Standard 2.0, compatible with PowerShell 5.1+ and PowerShell 7+
+- **📦 Complete Object Management**: Upload, download, list, and delete objects with advanced filtering and sorting
+- **🗂️ Flexible Directory Support**: Create nested folder structures with automatic directory creation
+- **⚡ Chunked Operations**: Large file uploads and downloads with resume capability and progress tracking
+- **🔒 Security & Policy Management**: Comprehensive bucket policy and access control management
+- **📊 Advanced Object Listing**: Filter by prefix, sort by multiple criteria, limit results, and exclude directories
+- **⏱️ Timing & Performance Metrics**: Detailed timing information and transfer speed reporting
+- **🔄 Progress Reporting**: Multi-layer progress tracking for file collections and chunked operations
+- **📝 Professional Logging**: Clean, timestamped logging with configurable verbosity levels
+- **🛡️ Robust Error Handling**: Graceful handling of network issues and edge cases
## Installation
+### From Source
```powershell
+# Clone the repository
+git clone https://github.com/yourusername/PSMinIO.git
+cd PSMinIO
+
+# Build the module
+dotnet build PSMinIO.csproj --configuration Release
+
# Import the module
-Import-Module .\PSMinIO.psd1
+Import-Module .\Module\PSMinIO\PSMinIO.psd1
+```
+
+### Direct Import
+```powershell
+# Import from local path
+Import-Module .\Module\PSMinIO\PSMinIO.psd1
```
## Quick Start
```powershell
-# Configure connection
-Set-MinIOConfig -Endpoint 'https://minio.myorg.com' -AccessKey 'AKIA...' -SecretKey 'abc123' -UseSSL
+# Connect to MinIO server
+$connection = Connect-MinIO -Endpoint "https://minio.example.com" -AccessKey "your-access-key" -SecretKey "your-secret-key"
# Create a bucket
-New-MinIOBucket -BucketName 'my-bucket' -Verbose
+New-MinIOBucket -BucketName 'my-data-bucket' -Verbose
-# Upload a file
-New-MinIOObject -BucketName 'my-bucket' -ObjectName 'data.txt' -FilePath 'C:\data.txt'
+# Upload files with automatic directory creation
+New-MinIOObject -BucketName 'my-data-bucket' -Files "document.pdf" -BucketDirectory "documents/2025/january"
-# List objects
-Get-MinIOObject -BucketName 'my-bucket' -Prefix '2025/'
+# List objects with advanced filtering
+Get-MinIOObject -BucketName 'my-data-bucket' -Prefix "documents/" -SortBy "Size" -Descending -MaxObjects 10
-# Download an object
-Get-MinIOObjectContent -BucketName 'my-bucket' -ObjectName 'data.txt' -FilePath 'C:\downloaded-data.txt'
+# Download with timing information
+Get-MinIOObjectContent -BucketName 'my-data-bucket' -ObjectName 'documents/2025/january/document.pdf' -FilePath 'C:\Downloads\document.pdf'
+
+# Chunked upload for large files
+New-MinIOObjectChunked -BucketName 'my-data-bucket' -Files "large-video.mp4" -ChunkSize 10MB -BucketDirectory "media/videos"
```
-## Cmdlets
+## Available Cmdlets
+
+### Connection Management
+- **`Connect-MinIO`** - Establish connection to MinIO server with SSL support and certificate validation options
### Bucket Operations
-- `Get-MinIOBucket` - Lists all buckets
-- `New-MinIOBucket` - Creates a new bucket
-- `Remove-MinIOBucket` - Deletes a bucket
-- `Test-MinIOBucketExists` - Checks if a bucket exists
+- **`Get-MinIOBucket`** - List all buckets with optional statistics
+- **`New-MinIOBucket`** - Create new buckets with region support
+- **`Remove-MinIOBucket`** - Delete buckets with safety confirmations
+- **`Test-MinIOBucketExists`** - Check bucket existence
### Object Operations
-- `Get-MinIOObject` - Lists objects in a bucket
-- `New-MinIOObject` - Uploads a file to a bucket
-- `Get-MinIOObjectContent` - Downloads an object
-- `Remove-MinIOObject` - Deletes an object
+- **`Get-MinIOObject`** - Advanced object listing with filtering, sorting, and pagination
+ - Filter by prefix or exact object name
+ - Sort by Name, Size, LastModified, or ETag (ascending/descending)
+ - Limit results with MaxObjects
+ - Exclude directories with ObjectsOnly
+- **`New-MinIOObject`** - Upload files with directory support
+ - Single file or multiple file uploads
+ - Automatic nested directory creation
+ - Progress tracking and timing information
+- **`New-MinIOObjectChunked`** - Chunked uploads for large files
+ - Configurable chunk sizes (1MB minimum)
+ - Resume capability and multi-layer progress tracking
+ - Automatic directory creation
+- **`Get-MinIOObjectContent`** - Download objects with progress tracking
+- **`Get-MinIOObjectContentChunked`** - Chunked downloads for large files
+- **`Remove-MinIOObject`** - Delete objects with confirmation prompts
+- **`New-MinIOFolder`** - Create folder structures in buckets
-### Security & Policy
-- `Get-MinIOBucketPolicy` - Retrieves bucket policy
-- `Set-MinIOBucketPolicy` - Sets bucket policy
+### Security & Policy Management
+- **`Get-MinIOBucketPolicy`** - Retrieve bucket access policies
+- **`Set-MinIOBucketPolicy`** - Configure bucket access policies
-### Utility
-- `Get-MinIOConfig` - Shows current configuration
-- `Set-MinIOConfig` - Sets connection configuration
-- `Get-MinIOStats` - Displays statistics and metrics
+### Monitoring & Statistics
+- **`Get-MinIOStats`** - Comprehensive server and bucket statistics with object counting limits
+
+## Key Features in Detail
+
+### 🗂️ Advanced Directory Support
+- **Nested Folder Creation**: Automatically create multi-level directory structures (e.g., `documents/2025/january/reports`)
+- **BucketDirectory Parameter**: Specify target directories for uploads without manual folder creation
+- **Clean Directory Handling**: Non-critical directory creation attempts with graceful fallback
+
+### ⚡ Chunked Operations
+- **Large File Support**: Handle files of any size with configurable chunk sizes
+- **Resume Capability**: Interrupted transfers can be resumed (future enhancement)
+- **Multi-Layer Progress**: Track collection progress, file progress, and chunk progress simultaneously
+- **Performance Optimization**: Optimal chunk sizes for different network conditions
+
+### 📊 Enhanced Object Listing
+```powershell
+# Advanced filtering and sorting examples
+Get-MinIOObject -BucketName "data" -Prefix "logs/" -SortBy "LastModified" -Descending -MaxObjects 50
+Get-MinIOObject -BucketName "media" -ObjectsOnly -SortBy "Size" -Descending
+Get-MinIOObject -BucketName "docs" -ObjectName "specific-file.pdf"
+```
+
+### ⏱️ Performance Metrics
+All operations provide detailed timing information:
+- **Duration**: Precise operation timing
+- **Transfer Speed**: Formatted speed reporting (B/s, KB/s, MB/s, GB/s, TB/s)
+- **Progress Tracking**: Real-time progress updates during transfers
+
+## Examples
+
+See the [examples](./examples/) directory for comprehensive usage examples:
+- **Basic Operations**: Connection, bucket management, simple uploads/downloads
+- **Advanced Scenarios**: Chunked transfers, directory management, bulk operations
+- **Enterprise Patterns**: Policy management, monitoring, and automation scripts
## Requirements
-- PowerShell 5.1+ or PowerShell 7+
-- .NET Framework 4.7.2+ (for PowerShell 5.1) or .NET Core/.NET 5+ (for PowerShell 7+)
+- **PowerShell**: 5.1+ or PowerShell 7+
+- **.NET Framework**: 4.7.2+ (for PowerShell 5.1) or .NET Core/.NET 5+ (for PowerShell 7+)
+- **MinIO Server**: Compatible with MinIO and Amazon S3 APIs
+
+## Contributing
+
+1. Fork the repository
+2. Create a feature branch (`git checkout -b feature/amazing-feature`)
+3. Commit your changes (`git commit -m 'Add amazing feature'`)
+4. Push to the branch (`git push origin feature/amazing-feature`)
+5. Open a Pull Request
## License
-This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
\ No newline at end of file
+This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
+
+## Support
+
+- 📖 **Documentation**: See [docs/USAGE.md](docs/USAGE.md) for detailed usage instructions
+- 🐛 **Issues**: Report bugs and request features via GitHub Issues
+- 💬 **Discussions**: Join the community discussions for questions and tips
\ No newline at end of file
diff --git a/Session-Debug.ps1 b/Session-Debug.ps1
deleted file mode 100644
index 54fb022..0000000
--- a/Session-Debug.ps1
+++ /dev/null
@@ -1,40 +0,0 @@
-# Debug session variable issue
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== Session Variable Debug ===" -ForegroundColor Cyan
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-Write-Host "1. Before connection - checking variables..." -ForegroundColor Yellow
-Get-Variable -Name "*MinIO*" -Scope Global -ErrorAction SilentlyContinue | ForEach-Object {
- Write-Host "Found: $($_.Name) = $($_.Value)" -ForegroundColor Gray
-}
-
-Write-Host "2. Connecting..." -ForegroundColor Yellow
-$connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
-
-Write-Host "3. After connection - checking variables..." -ForegroundColor Yellow
-Get-Variable -Name "*MinIO*" -Scope Global -ErrorAction SilentlyContinue | ForEach-Object {
- Write-Host "Found: $($_.Name) = $($_.Value)" -ForegroundColor Gray
-}
-
-Write-Host "4. Checking specific variable..." -ForegroundColor Yellow
-try {
- $var = Get-Variable -Name "MinIOConnection" -Scope Global -ErrorAction Stop
- Write-Host "SUCCESS: Variable exists with value: $($var.Value)" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: Variable not found: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-Write-Host "5. Testing Get-MinIOBucket with verbose..." -ForegroundColor Yellow
-try {
- $buckets = Get-MinIOBucket -Verbose
- Write-Host "SUCCESS: Found $($buckets.Count) buckets" -ForegroundColor Green
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-Write-Host "Debug completed!" -ForegroundColor Cyan
diff --git a/Session-Test.ps1 b/Session-Test.ps1
deleted file mode 100644
index 7c64386..0000000
--- a/Session-Test.ps1
+++ /dev/null
@@ -1,48 +0,0 @@
-# Session variable test for PSMinIO module
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== Session Variable Test ===" -ForegroundColor Cyan
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-Write-Host "1. Before connection - checking existing variables..." -ForegroundColor Yellow
-Get-Variable -Name "*MinIO*" -ErrorAction SilentlyContinue | ForEach-Object {
- Write-Host "Found variable: $($_.Name) = $($_.Value)" -ForegroundColor Gray
-}
-
-Write-Host "2. Connecting with verbose output..." -ForegroundColor Yellow
-try {
- $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey -Verbose
- Write-Host "SUCCESS: Connected" -ForegroundColor Green
-
- Write-Host "3. After connection - checking variables..." -ForegroundColor Yellow
- Get-Variable -Name "*MinIO*" -ErrorAction SilentlyContinue | ForEach-Object {
- Write-Host "Found variable: $($_.Name) = $($_.Value)" -ForegroundColor Gray
- }
-
- # Try to manually set the session variable
- Write-Host "4. Manually setting session variable..." -ForegroundColor Yellow
- Set-Variable -Name "MinIOConnection" -Value $connection -Scope Global
-
- Write-Host "5. After manual set - checking variables..." -ForegroundColor Yellow
- Get-Variable -Name "*MinIO*" -ErrorAction SilentlyContinue | ForEach-Object {
- Write-Host "Found variable: $($_.Name) = $($_.Value)" -ForegroundColor Gray
- }
-
- # Now try Get-MinIOBucket without explicit connection
- Write-Host "6. Testing Get-MinIOBucket without explicit connection..." -ForegroundColor Yellow
- try {
- $buckets = Get-MinIOBucket -Verbose
- Write-Host "SUCCESS: Found $($buckets.Count) buckets" -ForegroundColor Green
- } catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- }
-
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-Write-Host "Session test completed!" -ForegroundColor Cyan
diff --git a/Simple-Chunked-Test.ps1 b/Simple-Chunked-Test.ps1
deleted file mode 100644
index 29fa95b..0000000
--- a/Simple-Chunked-Test.ps1
+++ /dev/null
@@ -1,48 +0,0 @@
-# Simple chunked upload test
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== Simple Chunked Test ===" -ForegroundColor Cyan
-
-# Test file
-$testFile = "C:\Users\gsadmin\Downloads\windows_10_enterprise_ltsc_2021_x64\sources\install.wim"
-
-# Check file
-if (Test-Path $testFile) {
- $fileInfo = Get-Item $testFile
- Write-Host "File found: $($fileInfo.Name)" -ForegroundColor Green
- Write-Host "Size: $([math]::Round($fileInfo.Length / 1GB, 2)) GB" -ForegroundColor Yellow
-} else {
- Write-Host "File not found!" -ForegroundColor Red
- exit 1
-}
-
-# Connect
-Write-Host "Connecting..." -ForegroundColor Yellow
-$connection = Connect-MinIO -Endpoint "https://api.s3.gracesolution.info" -AccessKey "T34Wg85SAwezUa3sk3m4" -SecretKey "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-# Get bucket
-$buckets = Get-MinIOBucket
-$testBucket = $buckets[0].Name
-Write-Host "Using bucket: $testBucket" -ForegroundColor Yellow
-
-# Test chunked upload with 50MB chunks
-Write-Host "Starting chunked upload with 50MB chunks..." -ForegroundColor Yellow
-try {
- $startTime = Get-Date
- $result = New-MinIOObjectChunked -BucketName $testBucket -Files $testFile -ChunkSize 50MB -Verbose
- $endTime = Get-Date
-
- $duration = $endTime - $startTime
- $speedMBps = [math]::Round(($fileInfo.Length / 1MB) / $duration.TotalSeconds, 2)
-
- Write-Host "SUCCESS!" -ForegroundColor Green
- Write-Host "Duration: $($duration.ToString('hh\:mm\:ss'))" -ForegroundColor Gray
- Write-Host "Speed: $speedMBps MB/s" -ForegroundColor Gray
-
- $result | Format-Table ObjectName, BucketName, @{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}}
-
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-Write-Host "Test completed!" -ForegroundColor Cyan
diff --git a/Simple-Test.ps1 b/Simple-Test.ps1
deleted file mode 100644
index 3851e97..0000000
--- a/Simple-Test.ps1
+++ /dev/null
@@ -1,34 +0,0 @@
-# Simple test script for PSMinIO module
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== Testing PSMinIO Module ===" -ForegroundColor Cyan
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-# Test 1: Connection
-Write-Host "1. Testing Connection..." -ForegroundColor Yellow
-try {
- $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
- Write-Host "✓ Connection successful" -ForegroundColor Green
- Write-Host " Endpoint: $($connection.EndpointUrl)" -ForegroundColor Gray
-} catch {
- Write-Host "✗ Connection failed: $($_.Exception.Message)" -ForegroundColor Red
- exit 1
-}
-
-# Test 2: Bucket Listing
-Write-Host "2. Testing Bucket Listing..." -ForegroundColor Yellow
-try {
- $buckets = Get-MinIOBucket
- Write-Host "✓ Found $($buckets.Count) buckets" -ForegroundColor Green
- if ($buckets.Count -gt 0) {
- $buckets | Select-Object -First 3 | Format-Table Name, CreationDate
- }
-} catch {
- Write-Host "✗ Bucket listing failed: $($_.Exception.Message)" -ForegroundColor Red
-}
-
-Write-Host "Tests completed!" -ForegroundColor Cyan
diff --git a/Test-PSMinIO.ps1 b/Test-PSMinIO.ps1
deleted file mode 100644
index acf92d7..0000000
--- a/Test-PSMinIO.ps1
+++ /dev/null
@@ -1,107 +0,0 @@
-# Test script for PSMinIO module
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== Testing PSMinIO Module ===" -ForegroundColor Cyan
-Write-Host ""
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-# Test 1: Connection
-Write-Host "1. Testing Connection..." -ForegroundColor Yellow
-try {
- $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey -TestConnection
- Write-Host "✓ Connection successful" -ForegroundColor Green
- Write-Host " Endpoint: $($connection.EndpointUrl)" -ForegroundColor Gray
-} catch {
- Write-Host "✗ Connection failed: $($_.Exception.Message)" -ForegroundColor Red
- exit 1
-}
-Write-Host ""
-
-# Test 2: Bucket Listing
-Write-Host "2. Testing Bucket Listing..." -ForegroundColor Yellow
-try {
- $buckets = Get-MinIOBucket
- Write-Host "✓ Found $($buckets.Count) buckets" -ForegroundColor Green
- if ($buckets.Count -gt 0) {
- $buckets | Select-Object -First 3 | Format-Table Name, CreationDate, @{Name="Size";Expression={$_.SizeFormatted}}
- }
-} catch {
- Write-Host "✗ Bucket listing failed: $($_.Exception.Message)" -ForegroundColor Red
-}
-Write-Host ""
-
-# Test 3: Create test file for upload
-Write-Host "3. Creating test file..." -ForegroundColor Yellow
-$testFile = "test-upload.txt"
-$testContent = "This is a test file created at $(Get-Date) for PSMinIO testing."
-Set-Content -Path $testFile -Value $testContent
-Write-Host "✓ Created test file: $testFile" -ForegroundColor Green
-Write-Host ""
-
-# Test 4: File Upload (if we have buckets)
-if ($buckets -and $buckets.Count -gt 0) {
- $testBucket = $buckets[0].Name
- Write-Host "4. Testing File Upload to bucket '$testBucket'..." -ForegroundColor Yellow
- try {
- $uploadResult = New-MinIOObject -BucketName $testBucket -Files $testFile
- Write-Host "✓ File upload successful" -ForegroundColor Green
- $uploadResult | Format-Table ObjectName, BucketName, Size
- } catch {
- Write-Host "✗ File upload failed: $($_.Exception.Message)" -ForegroundColor Red
- }
- Write-Host ""
-
- # Test 5: File Download
- Write-Host "5. Testing File Download..." -ForegroundColor Yellow
- $downloadFile = "downloaded-test.txt"
- try {
- $downloadResult = Get-MinIOObjectContent -BucketName $testBucket -ObjectName $testFile -FilePath $downloadFile -Force
- Write-Host "✓ File download successful" -ForegroundColor Green
- Write-Host " Downloaded to: $($downloadResult.FullName)" -ForegroundColor Gray
- Write-Host " Content: $(Get-Content $downloadFile)" -ForegroundColor Gray
- } catch {
- Write-Host "✗ File download failed: $($_.Exception.Message)" -ForegroundColor Red
- }
- Write-Host ""
-
- # Test 6: Chunked Upload
- Write-Host "6. Testing Chunked Upload..." -ForegroundColor Yellow
- $chunkedFile = "chunked-test.txt"
- $chunkedContent = ("This is a chunked upload test file created at $(Get-Date).`n") * 100
- Set-Content -Path $chunkedFile -Value $chunkedContent
- try {
- $chunkedResult = New-MinIOObjectChunked -BucketName $testBucket -Files $chunkedFile -ChunkSize 1KB
- Write-Host "✓ Chunked upload successful" -ForegroundColor Green
- $chunkedResult | Format-Table ObjectName, BucketName, Size
- } catch {
- Write-Host "✗ Chunked upload failed: $($_.Exception.Message)" -ForegroundColor Red
- }
- Write-Host ""
-
- # Test 7: Chunked Download
- Write-Host "7. Testing Chunked Download..." -ForegroundColor Yellow
- $chunkedDownload = "chunked-downloaded.txt"
- try {
- $chunkedDownloadResult = Get-MinIOObjectContentChunked -BucketName $testBucket -ObjectName $chunkedFile -FilePath $chunkedDownload -ChunkSize 1KB -Force
- Write-Host "✓ Chunked download successful" -ForegroundColor Green
- Write-Host " Downloaded to: $($chunkedDownloadResult.FullName)" -ForegroundColor Gray
- } catch {
- Write-Host "✗ Chunked download failed: $($_.Exception.Message)" -ForegroundColor Red
- }
-} else {
- Write-Host "4-7. Skipping upload/download tests - no buckets available" -ForegroundColor Yellow
-}
-
-Write-Host ""
-Write-Host "=== Test Summary ===" -ForegroundColor Cyan
-Write-Host "All tests completed. Check results above." -ForegroundColor White
-
-# Cleanup
-if (Test-Path $testFile) { Remove-Item $testFile -Force }
-if (Test-Path $downloadFile) { Remove-Item $downloadFile -Force }
-if (Test-Path $chunkedFile) { Remove-Item $chunkedFile -Force }
-if (Test-Path $chunkedDownload) { Remove-Item $chunkedDownload -Force }
diff --git a/Verbose-Test.ps1 b/Verbose-Test.ps1
deleted file mode 100644
index 6a91896..0000000
--- a/Verbose-Test.ps1
+++ /dev/null
@@ -1,27 +0,0 @@
-# Verbose test for PSMinIO module
-Import-Module ./Module/PSMinIO/PSMinIO.psd1 -Force
-
-Write-Host "=== Verbose PSMinIO Test ===" -ForegroundColor Cyan
-
-# Test credentials
-$endpoint = "https://api.s3.gracesolution.info"
-$accessKey = "T34Wg85SAwezUa3sk3m4"
-$secretKey = "PxEmnbQoQJTJsDocSEV6mSSscDpJMiJCayPv93xe"
-
-Write-Host "1. Connecting with verbose..." -ForegroundColor Yellow
-try {
- $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey -Verbose
- Write-Host "SUCCESS: Connected to $($connection.EndpointUrl)" -ForegroundColor Green
-
- Write-Host "2. Testing bucket listing with verbose..." -ForegroundColor Yellow
- $buckets = Get-MinIOBucket -Verbose
- Write-Host "SUCCESS: Found $($buckets.Count) buckets" -ForegroundColor Green
- if ($buckets.Count -gt 0) {
- $buckets | Format-Table Name, CreationDate
- }
-} catch {
- Write-Host "FAILED: $($_.Exception.Message)" -ForegroundColor Red
- Write-Host "Inner Exception: $($_.Exception.InnerException.Message)" -ForegroundColor Red
-}
-
-Write-Host "Test completed!" -ForegroundColor Cyan
diff --git a/docs/USAGE.md b/docs/USAGE.md
index 4570462..fd17178 100644
--- a/docs/USAGE.md
+++ b/docs/USAGE.md
@@ -1,16 +1,19 @@
# PSMinIO Usage Guide
-This guide provides comprehensive examples and usage patterns for the PSMinIO PowerShell module.
+This comprehensive guide covers all aspects of using the PSMinIO PowerShell module for MinIO object storage operations.
## Table of Contents
- [Installation](#installation)
-- [Configuration](#configuration)
+- [Connection Management](#connection-management)
- [Bucket Operations](#bucket-operations)
- [Object Operations](#object-operations)
+- [Advanced Object Listing](#advanced-object-listing)
+- [Directory and Folder Management](#directory-and-folder-management)
+- [Chunked Operations](#chunked-operations)
- [Security and Policies](#security-and-policies)
-- [Statistics and Monitoring](#statistics-and-monitoring)
-- [Advanced Usage](#advanced-usage)
+- [Performance and Monitoring](#performance-and-monitoring)
+- [Advanced Usage Patterns](#advanced-usage-patterns)
- [Troubleshooting](#troubleshooting)
## Installation
@@ -23,29 +26,39 @@ This guide provides comprehensive examples and usage patterns for the PSMinIO Po
### Import the Module
```powershell
-# Import the module
-Import-Module .\PSMinIO.psd1
+# Import the module from the Module directory
+Import-Module .\Module\PSMinIO\PSMinIO.psd1
-# Verify the module is loaded
+# Verify the module is loaded and check available cmdlets
Get-Module PSMinIO
+Get-Command -Module PSMinIO
```
-## Configuration
+## Connection Management
-### Basic Configuration
+### Establishing Connections
+
+PSMinIO uses a modern connection-based approach with the `Connect-MinIO` cmdlet:
```powershell
-# Set up MinIO connection
-Set-MinIOConfig -Endpoint "minio.example.com:9000" `
- -AccessKey "your-access-key" `
- -SecretKey "your-secret-key" `
- -UseSSL
+# Connect to MinIO server with SSL
+$connection = Connect-MinIO -Endpoint "https://minio.example.com" -AccessKey "your-access-key" -SecretKey "your-secret-key"
-# For local development (no SSL)
-Set-MinIOConfig -Endpoint "localhost:9000" `
- -AccessKey "minioadmin" `
- -SecretKey "minioadmin" `
- -NoSSL
+# Connect to local MinIO (no SSL)
+$connection = Connect-MinIO -Endpoint "http://localhost:9000" -AccessKey "minioadmin" -SecretKey "minioadmin"
+
+# Connect with custom port
+$connection = Connect-MinIO -Endpoint "https://minio.example.com:9443" -AccessKey "your-access-key" -SecretKey "your-secret-key"
+```
+
+### Connection Options
+
+```powershell
+# Skip SSL certificate validation (for self-signed certificates)
+$connection = Connect-MinIO -Endpoint "https://minio.internal.com" -AccessKey "key" -SecretKey "secret" -SkipCertificateValidation
+
+# Connection with region specification
+$connection = Connect-MinIO -Endpoint "https://s3.amazonaws.com" -AccessKey "key" -SecretKey "secret" -Region "us-west-2"
```
### Advanced Configuration
@@ -220,6 +233,158 @@ Remove-MinIOObject -BucketName "cache" `
-Force
```
+## Advanced Object Listing
+
+The `Get-MinIOObject` cmdlet provides powerful filtering, sorting, and pagination capabilities:
+
+### Basic Object Listing
+
+```powershell
+# List all objects in a bucket
+Get-MinIOObject -BucketName "my-bucket"
+
+# List objects with a specific prefix
+Get-MinIOObject -BucketName "my-bucket" -Prefix "documents/"
+
+# List objects recursively (default behavior)
+Get-MinIOObject -BucketName "my-bucket" -Prefix "logs/" -Recursive
+
+# List objects non-recursively (current level only)
+Get-MinIOObject -BucketName "my-bucket" -Prefix "logs/" -Recursive:$false
+```
+
+### Advanced Filtering
+
+```powershell
+# Get a specific object by exact name
+Get-MinIOObject -BucketName "my-bucket" -ObjectName "documents/report.pdf"
+
+# List only files (exclude directory markers)
+Get-MinIOObject -BucketName "my-bucket" -ObjectsOnly
+
+# Include object versions (for versioned buckets)
+Get-MinIOObject -BucketName "my-bucket" -IncludeVersions
+
+# Limit the number of results
+Get-MinIOObject -BucketName "my-bucket" -MaxObjects 50
+```
+
+### Sorting Options
+
+```powershell
+# Sort by name (ascending - default)
+Get-MinIOObject -BucketName "my-bucket" -SortBy "Name"
+
+# Sort by name (descending)
+Get-MinIOObject -BucketName "my-bucket" -SortBy "Name" -Descending
+
+# Sort by file size (largest first)
+Get-MinIOObject -BucketName "my-bucket" -SortBy "Size" -Descending
+
+# Sort by last modified date (newest first)
+Get-MinIOObject -BucketName "my-bucket" -SortBy "LastModified" -Descending
+
+# Sort by ETag
+Get-MinIOObject -BucketName "my-bucket" -SortBy "ETag"
+```
+
+### Complex Queries
+
+```powershell
+# Find large files in a specific directory
+Get-MinIOObject -BucketName "media" -Prefix "videos/" -SortBy "Size" -Descending -MaxObjects 10
+
+# Get recent files only
+$recentFiles = Get-MinIOObject -BucketName "logs" -SortBy "LastModified" -Descending -MaxObjects 20
+
+# Find files by pattern using PowerShell filtering
+Get-MinIOObject -BucketName "documents" | Where-Object { $_.Name -like "*.pdf" -and $_.Size -gt 1MB }
+
+# Get directory structure overview
+Get-MinIOObject -BucketName "my-bucket" | Group-Object { ($_.Name -split '/')[0] } | Format-Table Name, Count
+```
+
+## Directory and Folder Management
+
+### Creating Directory Structures
+
+```powershell
+# Create explicit folder structures
+New-MinIOFolder -BucketName "my-bucket" -FolderName "projects/web-app/src"
+New-MinIOFolder -BucketName "my-bucket" -FolderName "projects/web-app/docs"
+
+# Create multiple folder levels at once
+New-MinIOFolder -BucketName "my-bucket" -FolderName "company/departments/engineering/teams/backend"
+```
+
+### Automatic Directory Creation
+
+```powershell
+# Upload files with automatic directory creation using BucketDirectory
+New-MinIOObject -BucketName "my-bucket" -Files "report.pdf" -BucketDirectory "documents/2025/january"
+
+# Create nested directory structures automatically
+New-MinIOObject -BucketName "my-bucket" -Files "config.json" -BucketDirectory "projects/web-app/config/production"
+```
+
+### Directory-Based Operations
+
+```powershell
+# List all directories (folder markers)
+Get-MinIOObject -BucketName "my-bucket" | Where-Object { $_.IsDirectory }
+
+# List files in a specific directory
+Get-MinIOObject -BucketName "my-bucket" -Prefix "documents/2025/" -ObjectsOnly
+
+# Organize files by date-based directories
+$today = Get-Date -Format "yyyy/MM/dd"
+New-MinIOObject -BucketName "logs" -Files "app.log" -BucketDirectory "daily-logs/$today"
+```
+
+## Chunked Operations
+
+### Chunked Uploads
+
+```powershell
+# Upload large files with chunked transfer
+New-MinIOObjectChunked -BucketName "media" -Files "large-video.mp4" -ChunkSize 10MB
+
+# Upload with custom chunk size and directory
+New-MinIOObjectChunked -BucketName "backups" -Files "database-backup.sql" -ChunkSize 5MB -BucketDirectory "daily-backups/$(Get-Date -Format 'yyyy-MM-dd')"
+
+# Upload multiple large files
+New-MinIOObjectChunked -BucketName "media" -Files @("video1.mp4", "video2.mp4") -ChunkSize 10MB -BucketDirectory "videos/uploads"
+```
+
+### Chunked Downloads
+
+```powershell
+# Download large files with chunked transfer
+Get-MinIOObjectContentChunked -BucketName "media" -ObjectName "large-video.mp4" -FilePath "C:\Downloads\video.mp4" -ChunkSize 10MB
+
+# Download with progress tracking
+Get-MinIOObjectContentChunked -BucketName "backups" -ObjectName "large-backup.zip" -FilePath "C:\Restore\backup.zip" -ChunkSize 5MB
+```
+
+### Chunk Size Guidelines
+
+```powershell
+# Recommended chunk sizes based on file size:
+# Files < 10MB: Use regular upload/download
+# Files 10MB - 100MB: Use 1-5MB chunks
+# Files 100MB - 1GB: Use 5-10MB chunks
+# Files > 1GB: Use 10-50MB chunks
+
+# Example with optimal chunk size selection
+$fileSize = (Get-Item "large-file.zip").Length
+$chunkSize = if ($fileSize -lt 10MB) { 1MB }
+ elseif ($fileSize -lt 100MB) { 5MB }
+ elseif ($fileSize -lt 1GB) { 10MB }
+ else { 25MB }
+
+New-MinIOObjectChunked -BucketName "uploads" -Files "large-file.zip" -ChunkSize $chunkSize
+```
+
## Security and Policies
### Get Bucket Policies
diff --git a/examples/01-Basic-Operations.ps1 b/examples/01-Basic-Operations.ps1
new file mode 100644
index 0000000..0953e36
--- /dev/null
+++ b/examples/01-Basic-Operations.ps1
@@ -0,0 +1,71 @@
+# PSMinIO Basic Operations Example
+# This script demonstrates fundamental MinIO operations using PSMinIO
+
+# Import the module
+Import-Module ..\Module\PSMinIO\PSMinIO.psd1
+
+# Example connection details (replace with your actual values)
+$endpoint = "https://minio.example.com"
+$accessKey = "your-access-key"
+$secretKey = "your-secret-key"
+
+try {
+ # Connect to MinIO server
+ $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
+ "Connected to MinIO server: $endpoint"
+
+ # List all buckets
+ "Listing all buckets..."
+ $buckets = Get-MinIOBucket
+ $buckets | Format-Table Name, CreationDate, @{Name="Objects";Expression={"N/A"}}
+
+ # Create a new bucket
+ $bucketName = "example-bucket-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ "Creating bucket: $bucketName"
+ New-MinIOBucket -BucketName $bucketName
+
+ # Verify bucket creation
+ if (Test-MinIOBucketExists -BucketName $bucketName) {
+ "✅ Bucket '$bucketName' created successfully"
+ }
+
+ # Create a sample file for upload
+ $sampleFile = "sample-document.txt"
+ "This is a sample document created on $(Get-Date)" | Out-File -FilePath $sampleFile -Encoding UTF8
+
+ # Upload the file
+ "Uploading file: $sampleFile"
+ $uploadResult = New-MinIOObject -BucketName $bucketName -Files $sampleFile
+ $uploadResult | Format-Table Name, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+
+ # List objects in the bucket
+ "Listing objects in bucket '$bucketName':"
+ $objects = Get-MinIOObject -BucketName $bucketName
+ $objects | Format-Table Name, @{Name="Size";Expression={"$($_.Size) bytes"}}, LastModified
+
+ # Download the file
+ $downloadPath = "downloaded-$sampleFile"
+ "Downloading file to: $downloadPath"
+ $downloadResult = Get-MinIOObjectContent -BucketName $bucketName -ObjectName $sampleFile -FilePath $downloadPath
+ $downloadResult | Format-Table @{Name="LocalFile";Expression={$_.FilePath.Name}}, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+
+ # Verify download
+ if (Test-Path $downloadPath) {
+ "✅ File downloaded successfully"
+ "Content: $(Get-Content $downloadPath)"
+ }
+
+ # Clean up
+ "Cleaning up..."
+ Remove-MinIOObject -BucketName $bucketName -ObjectName $sampleFile -Force
+ Remove-MinIOBucket -BucketName $bucketName -Force
+ Remove-Item $sampleFile, $downloadPath -Force -ErrorAction SilentlyContinue
+
+ "✅ Basic operations completed successfully"
+
+} catch {
+ "❌ Error: $($_.Exception.Message)"
+} finally {
+ # Clean up any remaining files
+ Remove-Item "sample-document.txt", "downloaded-sample-document.txt" -Force -ErrorAction SilentlyContinue
+}
diff --git a/examples/02-Advanced-Object-Listing.ps1 b/examples/02-Advanced-Object-Listing.ps1
new file mode 100644
index 0000000..a86aac3
--- /dev/null
+++ b/examples/02-Advanced-Object-Listing.ps1
@@ -0,0 +1,108 @@
+# PSMinIO Advanced Object Listing Example
+# Demonstrates the powerful Get-MinIOObject cmdlet with filtering, sorting, and pagination
+
+# Import the module
+Import-Module ..\Module\PSMinIO\PSMinIO.psd1
+
+# Connection details (replace with your actual values)
+$endpoint = "https://minio.example.com"
+$accessKey = "your-access-key"
+$secretKey = "your-secret-key"
+
+try {
+ # Connect to MinIO
+ $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
+ "Connected to MinIO server for advanced object listing demo"
+
+ # Create a demo bucket
+ $bucketName = "demo-listing-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ New-MinIOBucket -BucketName $bucketName
+ "Created demo bucket: $bucketName"
+
+ # Create sample files with different sizes and organize them in directories
+ "Creating sample files..."
+
+ # Documents directory
+ "Small document content" | Out-File -FilePath "small-doc.txt" -Encoding UTF8
+ "Medium document content " * 50 | Out-File -FilePath "medium-doc.txt" -Encoding UTF8
+ "Large document content " * 200 | Out-File -FilePath "large-doc.txt" -Encoding UTF8
+
+ # Images directory (simulated)
+ "Image data " * 100 | Out-File -FilePath "image1.jpg" -Encoding UTF8
+ "Image data " * 300 | Out-File -FilePath "image2.png" -Encoding UTF8
+
+ # Logs directory
+ "Log entry " * 20 | Out-File -FilePath "app.log" -Encoding UTF8
+ "Error log " * 80 | Out-File -FilePath "error.log" -Encoding UTF8
+
+ # Upload files to different directories
+ New-MinIOObject -BucketName $bucketName -Files "small-doc.txt" -BucketDirectory "documents/2025"
+ New-MinIOObject -BucketName $bucketName -Files "medium-doc.txt" -BucketDirectory "documents/2025"
+ New-MinIOObject -BucketName $bucketName -Files "large-doc.txt" -BucketDirectory "documents/archive"
+ New-MinIOObject -BucketName $bucketName -Files "image1.jpg" -BucketDirectory "media/images"
+ New-MinIOObject -BucketName $bucketName -Files "image2.png" -BucketDirectory "media/images"
+ New-MinIOObject -BucketName $bucketName -Files "app.log" -BucketDirectory "logs/application"
+ New-MinIOObject -BucketName $bucketName -Files "error.log" -BucketDirectory "logs/system"
+
+ "Sample files uploaded to various directories"
+ ""
+
+ # Demonstrate different listing capabilities
+ "=== 1. List All Objects ==="
+ $allObjects = Get-MinIOObject -BucketName $bucketName
+ $allObjects | Format-Table Name, @{Name="Size";Expression={"$($_.Size) B"}}, LastModified -AutoSize
+ ""
+
+ "=== 2. Filter by Prefix ==="
+ $documentsObjects = Get-MinIOObject -BucketName $bucketName -Prefix "documents/"
+ $documentsObjects | Format-Table Name, @{Name="Size";Expression={"$($_.Size) B"}}, LastModified -AutoSize
+ ""
+
+ "=== 3. Sort by Size (Descending) ==="
+ $sortedBySize = Get-MinIOObject -BucketName $bucketName -SortBy "Size" -Descending
+ $sortedBySize | Format-Table Name, @{Name="Size";Expression={"$($_.Size) B"}}, LastModified -AutoSize
+ ""
+
+ "=== 4. Sort by Name (Ascending) ==="
+ $sortedByName = Get-MinIOObject -BucketName $bucketName -SortBy "Name"
+ $sortedByName | Format-Table Name, @{Name="Size";Expression={"$($_.Size) B"}}, LastModified -AutoSize
+ ""
+
+ "=== 5. Limit Results ==="
+ $limitedResults = Get-MinIOObject -BucketName $bucketName -MaxObjects 3 -SortBy "Size" -Descending
+ $limitedResults | Format-Table Name, @{Name="Size";Expression={"$($_.Size) B"}}, LastModified -AutoSize
+ ""
+
+ "=== 6. Files Only (Exclude Directories) ==="
+ $filesOnly = Get-MinIOObject -BucketName $bucketName -ObjectsOnly
+ $filesOnly | Format-Table Name, @{Name="Size";Expression={"$($_.Size) B"}}, LastModified -AutoSize
+ ""
+
+ "=== 7. Specific Object Lookup ==="
+ $specificObject = Get-MinIOObject -BucketName $bucketName -ObjectName "documents/2025/small-doc.txt"
+ if ($specificObject) {
+ $specificObject | Format-Table Name, @{Name="Size";Expression={"$($_.Size) B"}}, LastModified -AutoSize
+ } else {
+ "Object not found"
+ }
+ ""
+
+ "=== 8. Complex Filtering Example ==="
+ "Large files in media directory:"
+ $largeMediaFiles = Get-MinIOObject -BucketName $bucketName -Prefix "media/" -SortBy "Size" -Descending | Where-Object { $_.Size -gt 1000 }
+ $largeMediaFiles | Format-Table Name, @{Name="Size";Expression={"$($_.Size) B"}}, LastModified -AutoSize
+
+ # Clean up
+ "Cleaning up demo files..."
+ $allObjects = Get-MinIOObject -BucketName $bucketName
+ foreach ($obj in $allObjects) {
+ Remove-MinIOObject -BucketName $bucketName -ObjectName $obj.Name -Force
+ }
+ Remove-MinIOBucket -BucketName $bucketName -Force
+ Remove-Item "small-doc.txt", "medium-doc.txt", "large-doc.txt", "image1.jpg", "image2.png", "app.log", "error.log" -Force -ErrorAction SilentlyContinue
+
+ "✅ Advanced object listing demo completed"
+
+} catch {
+ "❌ Error: $($_.Exception.Message)"
+}
diff --git a/examples/03-Directory-Management.ps1 b/examples/03-Directory-Management.ps1
new file mode 100644
index 0000000..34bd147
--- /dev/null
+++ b/examples/03-Directory-Management.ps1
@@ -0,0 +1,135 @@
+# PSMinIO Directory and Folder Management Example
+# Demonstrates creating nested directory structures and organizing files
+
+# Import the module
+Import-Module ..\Module\PSMinIO\PSMinIO.psd1
+
+# Connection details (replace with your actual values)
+$endpoint = "https://minio.example.com"
+$accessKey = "your-access-key"
+$secretKey = "your-secret-key"
+
+try {
+ # Connect to MinIO
+ $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
+ "Connected to MinIO server for directory management demo"
+
+ # Create a demo bucket
+ $bucketName = "directory-demo-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ New-MinIOBucket -BucketName $bucketName
+ "Created demo bucket: $bucketName"
+
+ # Create sample files for different scenarios
+ "Creating sample files..."
+ "Project documentation" | Out-File -FilePath "README.md" -Encoding UTF8
+ "Configuration settings" | Out-File -FilePath "config.json" -Encoding UTF8
+ "Application source code" | Out-File -FilePath "app.py" -Encoding UTF8
+ "Test data" | Out-File -FilePath "test-data.csv" -Encoding UTF8
+ "Build output" | Out-File -FilePath "app.exe" -Encoding UTF8
+
+ "=== 1. Creating Explicit Folder Structures ==="
+ # Create explicit folder structures using New-MinIOFolder
+ New-MinIOFolder -BucketName $bucketName -FolderName "projects/web-app/src"
+ New-MinIOFolder -BucketName $bucketName -FolderName "projects/web-app/tests"
+ New-MinIOFolder -BucketName $bucketName -FolderName "projects/web-app/docs"
+ New-MinIOFolder -BucketName $bucketName -FolderName "projects/api/v1"
+ New-MinIOFolder -BucketName $bucketName -FolderName "projects/api/v2"
+ "Created explicit folder structures"
+ ""
+
+ "=== 2. Automatic Directory Creation with BucketDirectory ==="
+ # Upload files with automatic directory creation
+ New-MinIOObject -BucketName $bucketName -Files "README.md" -BucketDirectory "projects/web-app/docs"
+ New-MinIOObject -BucketName $bucketName -Files "config.json" -BucketDirectory "projects/web-app/config"
+ New-MinIOObject -BucketName $bucketName -Files "app.py" -BucketDirectory "projects/web-app/src"
+ New-MinIOObject -BucketName $bucketName -Files "test-data.csv" -BucketDirectory "projects/web-app/tests/data"
+ New-MinIOObject -BucketName $bucketName -Files "app.exe" -BucketDirectory "releases/v1.0/binaries"
+ "Uploaded files with automatic directory creation"
+ ""
+
+ "=== 3. Multi-Level Directory Structure ==="
+ # Create a complex directory structure
+ $complexStructure = @(
+ "company/departments/engineering/teams/backend",
+ "company/departments/engineering/teams/frontend",
+ "company/departments/engineering/teams/devops",
+ "company/departments/marketing/campaigns/2025",
+ "company/departments/hr/policies/remote-work"
+ )
+
+ foreach ($folder in $complexStructure) {
+ New-MinIOFolder -BucketName $bucketName -FolderName $folder
+ }
+ "Created complex multi-level directory structure"
+ ""
+
+ "=== 4. Viewing Directory Structure ==="
+ # List all objects to see the directory structure
+ $allObjects = Get-MinIOObject -BucketName $bucketName
+ "Complete directory and file structure:"
+ $allObjects | Sort-Object Name | Format-Table Name, @{Name="Type";Expression={if($_.IsDirectory){"Directory"}else{"File"}}}, @{Name="Size";Expression={if($_.IsDirectory){"-"}else{"$($_.Size) B"}}} -AutoSize
+ ""
+
+ "=== 5. Filtering by Directory ==="
+ # Show objects in specific directories
+ "Files in projects/web-app/:"
+ $webAppFiles = Get-MinIOObject -BucketName $bucketName -Prefix "projects/web-app/"
+ $webAppFiles | Format-Table Name, @{Name="Type";Expression={if($_.IsDirectory){"Directory"}else{"File"}}}, @{Name="Size";Expression={if($_.IsDirectory){"-"}else{"$($_.Size) B"}}} -AutoSize
+ ""
+
+ "Engineering team directories:"
+ $engineeringDirs = Get-MinIOObject -BucketName $bucketName -Prefix "company/departments/engineering/teams/"
+ $engineeringDirs | Format-Table Name, @{Name="Type";Expression={if($_.IsDirectory){"Directory"}else{"File"}}} -AutoSize
+ ""
+
+ "=== 6. Directory-Only Listing ==="
+ # Show only directories (folders)
+ $directoriesOnly = Get-MinIOObject -BucketName $bucketName | Where-Object { $_.IsDirectory }
+ "Directories only:"
+ $directoriesOnly | Sort-Object Name | Format-Table Name -AutoSize
+ ""
+
+ "=== 7. Files-Only Listing ==="
+ # Show only files (excluding directories)
+ $filesOnly = Get-MinIOObject -BucketName $bucketName -ObjectsOnly
+ "Files only:"
+ $filesOnly | Format-Table Name, @{Name="Size";Expression={"$($_.Size) B"}}, LastModified -AutoSize
+ ""
+
+ "=== 8. Organizing Files by Date ==="
+ # Create date-based directory structure
+ $currentDate = Get-Date
+ $yearMonth = $currentDate.ToString("yyyy/MM")
+ $dailyFolder = $currentDate.ToString("yyyy/MM/dd")
+
+ # Upload files to date-based directories
+ "Sample log entry" | Out-File -FilePath "daily.log" -Encoding UTF8
+ "Backup data" | Out-File -FilePath "backup.zip" -Encoding UTF8
+
+ New-MinIOObject -BucketName $bucketName -Files "daily.log" -BucketDirectory "logs/$dailyFolder"
+ New-MinIOObject -BucketName $bucketName -Files "backup.zip" -BucketDirectory "backups/$yearMonth"
+
+ "Created date-based directory structure and uploaded files"
+ ""
+
+ # Show the final structure
+ "=== Final Directory Structure ==="
+ $finalStructure = Get-MinIOObject -BucketName $bucketName | Sort-Object Name
+ $finalStructure | Format-Table Name, @{Name="Type";Expression={if($_.IsDirectory){"Directory"}else{"File"}}}, @{Name="Size";Expression={if($_.IsDirectory){"-"}else{"$($_.Size) B"}}} -AutoSize
+
+ # Clean up
+ "Cleaning up demo files..."
+ $allObjects = Get-MinIOObject -BucketName $bucketName
+ foreach ($obj in $allObjects) {
+ if (-not $obj.IsDirectory) {
+ Remove-MinIOObject -BucketName $bucketName -ObjectName $obj.Name -Force
+ }
+ }
+ Remove-MinIOBucket -BucketName $bucketName -Force
+ Remove-Item "README.md", "config.json", "app.py", "test-data.csv", "app.exe", "daily.log", "backup.zip" -Force -ErrorAction SilentlyContinue
+
+ "✅ Directory management demo completed"
+
+} catch {
+ "❌ Error: $($_.Exception.Message)"
+}
diff --git a/examples/04-Chunked-Operations.ps1 b/examples/04-Chunked-Operations.ps1
new file mode 100644
index 0000000..5cd607b
--- /dev/null
+++ b/examples/04-Chunked-Operations.ps1
@@ -0,0 +1,153 @@
+# PSMinIO Chunked Operations Example
+# Demonstrates chunked uploads and downloads for large files with progress tracking
+
+# Import the module
+Import-Module ..\Module\PSMinIO\PSMinIO.psd1
+
+# Connection details (replace with your actual values)
+$endpoint = "https://minio.example.com"
+$accessKey = "your-access-key"
+$secretKey = "your-secret-key"
+
+try {
+ # Connect to MinIO
+ $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
+ "Connected to MinIO server for chunked operations demo"
+
+ # Create a demo bucket
+ $bucketName = "chunked-demo-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ New-MinIOBucket -BucketName $bucketName
+ "Created demo bucket: $bucketName"
+
+ "=== 1. Creating Large Test Files ==="
+ # Create files of different sizes for testing
+
+ # Small file (under chunk size)
+ $smallContent = "Small file content " * 100 # ~2KB
+ $smallContent | Out-File -FilePath "small-file.txt" -Encoding UTF8 -NoNewline
+
+ # Medium file (1-2 chunks)
+ $mediumContent = "Medium file content " * 30000 # ~600KB
+ $mediumContent | Out-File -FilePath "medium-file.txt" -Encoding UTF8 -NoNewline
+
+ # Large file (multiple chunks)
+ $largeContent = "Large file content " * 100000 # ~2MB
+ $largeContent | Out-File -FilePath "large-file.txt" -Encoding UTF8 -NoNewline
+
+ # Very large file (many chunks)
+ $veryLargeContent = "Very large file content " * 250000 # ~6MB
+ $veryLargeContent | Out-File -FilePath "very-large-file.txt" -Encoding UTF8 -NoNewline
+
+ "Created test files of various sizes"
+ Get-ChildItem "*.txt" | Format-Table Name, @{Name="Size";Expression={"$([math]::Round($_.Length/1KB,2)) KB"}}
+ ""
+
+ "=== 2. Chunked Upload with Different Chunk Sizes ==="
+
+ # Upload with 1MB chunks (default)
+ "Uploading medium file with 1MB chunks..."
+ $result1 = New-MinIOObjectChunked -BucketName $bucketName -Files "medium-file.txt" -ChunkSize 1MB -BucketDirectory "uploads/1mb-chunks"
+ $result1 | Format-Table Name, @{Name="SizeMB";Expression={[math]::Round($_.Size/1MB,2)}}, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+ ""
+
+ # Upload with 512KB chunks
+ "Uploading large file with 512KB chunks..."
+ $result2 = New-MinIOObjectChunked -BucketName $bucketName -Files "large-file.txt" -ChunkSize 512KB -BucketDirectory "uploads/512kb-chunks"
+ $result2 | Format-Table Name, @{Name="SizeMB";Expression={[math]::Round($_.Size/1MB,2)}}, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+ ""
+
+ # Upload with 2MB chunks
+ "Uploading very large file with 2MB chunks..."
+ $result3 = New-MinIOObjectChunked -BucketName $bucketName -Files "very-large-file.txt" -ChunkSize 2MB -BucketDirectory "uploads/2mb-chunks"
+ $result3 | Format-Table Name, @{Name="SizeMB";Expression={[math]::Round($_.Size/1MB,2)}}, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+ ""
+
+ "=== 3. Multiple File Chunked Upload ==="
+ # Upload multiple files in one operation
+ "Uploading multiple files with chunked transfer..."
+ $multiResult = New-MinIOObjectChunked -BucketName $bucketName -Files @("small-file.txt", "medium-file.txt") -ChunkSize 1MB -BucketDirectory "uploads/multi-file"
+ $multiResult | Format-Table Name, @{Name="SizeMB";Expression={[math]::Round($_.Size/1MB,2)}}, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+ ""
+
+ "=== 4. Chunked Download Operations ==="
+
+ # Download with chunked transfer
+ "Downloading large file with chunked transfer..."
+ $downloadResult = Get-MinIOObjectContentChunked -BucketName $bucketName -ObjectName "uploads/2mb-chunks/very-large-file.txt" -FilePath "downloaded-very-large.txt" -ChunkSize 1MB
+ $downloadResult | Format-Table @{Name="LocalFile";Expression={$_.FilePath.Name}}, @{Name="SizeMB";Expression={[math]::Round($_.FilePath.Length/1MB,2)}}, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+ ""
+
+ # Verify download integrity
+ if (Test-Path "downloaded-very-large.txt") {
+ $originalSize = (Get-Item "very-large-file.txt").Length
+ $downloadedSize = (Get-Item "downloaded-very-large.txt").Length
+ if ($originalSize -eq $downloadedSize) {
+ "✅ Download integrity verified - sizes match ($originalSize bytes)"
+ } else {
+ "❌ Download integrity check failed - size mismatch"
+ }
+ }
+ ""
+
+ "=== 5. Performance Comparison ==="
+ # Compare regular vs chunked upload for the same file
+ "Comparing regular vs chunked upload performance..."
+
+ # Regular upload
+ $regularStart = Get-Date
+ $regularResult = New-MinIOObject -BucketName $bucketName -Files "large-file.txt" -BucketDirectory "comparison/regular"
+ $regularEnd = Get-Date
+ $regularDuration = $regularEnd - $regularStart
+
+ # Chunked upload
+ $chunkedStart = Get-Date
+ $chunkedResult = New-MinIOObjectChunked -BucketName $bucketName -Files "large-file.txt" -ChunkSize 1MB -BucketDirectory "comparison/chunked"
+ $chunkedEnd = Get-Date
+ $chunkedDuration = $chunkedEnd - $chunkedStart
+
+ "Performance Comparison for large-file.txt:"
+ [PSCustomObject]@{
+ Method = "Regular"
+ Duration = $regularDuration.ToString("mm\:ss\.fff")
+ Speed = $regularResult.AverageSpeedFormatted
+ } | Format-Table
+
+ [PSCustomObject]@{
+ Method = "Chunked"
+ Duration = $chunkedDuration.ToString("mm\:ss\.fff")
+ Speed = $chunkedResult.AverageSpeedFormatted
+ } | Format-Table
+ ""
+
+ "=== 6. Listing Uploaded Files ==="
+ # Show all uploaded files with their details
+ $allUploads = Get-MinIOObject -BucketName $bucketName -Prefix "uploads/" -ObjectsOnly -SortBy "Size" -Descending
+ "All uploaded files (sorted by size):"
+ $allUploads | Format-Table Name, @{Name="SizeMB";Expression={[math]::Round($_.Size/1MB,2)}}, LastModified -AutoSize
+ ""
+
+ "=== 7. Chunk Size Recommendations ==="
+ "Chunk Size Recommendations:"
+ "• Files < 10MB: Use regular upload (New-MinIOObject)"
+ "• Files 10MB - 100MB: Use 1-5MB chunks"
+ "• Files 100MB - 1GB: Use 5-10MB chunks"
+ "• Files > 1GB: Use 10-50MB chunks"
+ "• Network considerations: Smaller chunks for unstable connections"
+ ""
+
+ # Clean up
+ "Cleaning up demo files..."
+ $allObjects = Get-MinIOObject -BucketName $bucketName
+ foreach ($obj in $allObjects) {
+ if (-not $obj.IsDirectory) {
+ Remove-MinIOObject -BucketName $bucketName -ObjectName $obj.Name -Force
+ }
+ }
+ Remove-MinIOBucket -BucketName $bucketName -Force
+ Remove-Item "small-file.txt", "medium-file.txt", "large-file.txt", "very-large-file.txt", "downloaded-very-large.txt" -Force -ErrorAction SilentlyContinue
+
+ "✅ Chunked operations demo completed"
+
+} catch {
+ "❌ Error: $($_.Exception.Message)"
+}
diff --git a/examples/05-Bulk-Operations.ps1 b/examples/05-Bulk-Operations.ps1
new file mode 100644
index 0000000..3fa9450
--- /dev/null
+++ b/examples/05-Bulk-Operations.ps1
@@ -0,0 +1,205 @@
+# PSMinIO Bulk Operations Example
+# Demonstrates bulk file operations, batch processing, and automation scenarios
+
+# Import the module
+Import-Module ..\Module\PSMinIO\PSMinIO.psd1
+
+# Connection details (replace with your actual values)
+$endpoint = "https://minio.example.com"
+$accessKey = "your-access-key"
+$secretKey = "your-secret-key"
+
+try {
+ # Connect to MinIO
+ $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
+ "Connected to MinIO server for bulk operations demo"
+
+ # Create a demo bucket
+ $bucketName = "bulk-demo-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ New-MinIOBucket -BucketName $bucketName
+ "Created demo bucket: $bucketName"
+
+ "=== 1. Creating Multiple Test Files ==="
+ # Create a variety of files for bulk operations
+ $testFiles = @()
+
+ # Create document files
+ for ($i = 1; $i -le 5; $i++) {
+ $fileName = "document-$i.txt"
+ "Document $i content - $(Get-Date)" | Out-File -FilePath $fileName -Encoding UTF8
+ $testFiles += $fileName
+ }
+
+ # Create data files
+ for ($i = 1; $i -le 3; $i++) {
+ $fileName = "data-$i.csv"
+ "Name,Value,Date`nItem$i,$(Get-Random -Minimum 100 -Maximum 1000),$(Get-Date)" | Out-File -FilePath $fileName -Encoding UTF8
+ $testFiles += $fileName
+ }
+
+ # Create log files
+ for ($i = 1; $i -le 4; $i++) {
+ $fileName = "log-$i.log"
+ "$(Get-Date) - Log entry $i`n$(Get-Date) - Another log entry" | Out-File -FilePath $fileName -Encoding UTF8
+ $testFiles += $fileName
+ }
+
+ "Created $($testFiles.Count) test files"
+ Get-ChildItem "*.txt", "*.csv", "*.log" | Format-Table Name, @{Name="Size";Expression={"$($_.Length) bytes"}} -AutoSize
+ ""
+
+ "=== 2. Bulk Upload to Different Directories ==="
+ # Upload different file types to organized directories
+
+ # Upload documents
+ $docFiles = Get-ChildItem "document-*.txt"
+ if ($docFiles) {
+ "Uploading $($docFiles.Count) document files..."
+ $docResults = New-MinIOObject -BucketName $bucketName -Files $docFiles.Name -BucketDirectory "documents/$(Get-Date -Format 'yyyy/MM')"
+ $docResults | Format-Table Name, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+ }
+ ""
+
+ # Upload data files
+ $dataFiles = Get-ChildItem "data-*.csv"
+ if ($dataFiles) {
+ "Uploading $($dataFiles.Count) data files..."
+ $dataResults = New-MinIOObject -BucketName $bucketName -Files $dataFiles.Name -BucketDirectory "data/exports"
+ $dataResults | Format-Table Name, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+ }
+ ""
+
+ # Upload log files
+ $logFiles = Get-ChildItem "log-*.log"
+ if ($logFiles) {
+ "Uploading $($logFiles.Count) log files..."
+ $logResults = New-MinIOObject -BucketName $bucketName -Files $logFiles.Name -BucketDirectory "logs/application/$(Get-Date -Format 'yyyy-MM-dd')"
+ $logResults | Format-Table Name, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+ }
+ ""
+
+ "=== 3. Bulk File Analysis ==="
+ # Analyze uploaded files by type and location
+ $allObjects = Get-MinIOObject -BucketName $bucketName -ObjectsOnly
+
+ "File distribution by directory:"
+ $allObjects | Group-Object { ($_.Name -split '/')[0] } | Format-Table Name, Count -AutoSize
+ ""
+
+ "File distribution by extension:"
+ $allObjects | Group-Object { [System.IO.Path]::GetExtension($_.Name) } | Format-Table Name, Count -AutoSize
+ ""
+
+ "Largest files:"
+ $allObjects | Sort-Object Size -Descending | Select-Object -First 5 | Format-Table Name, @{Name="Size";Expression={"$($_.Size) bytes"}}, LastModified -AutoSize
+ ""
+
+ "=== 4. Batch Download Operations ==="
+ # Download files by pattern/criteria
+
+ # Create download directory
+ $downloadDir = "downloads"
+ if (-not (Test-Path $downloadDir)) {
+ New-Item -ItemType Directory -Path $downloadDir | Out-Null
+ }
+
+ # Download all CSV files
+ $csvObjects = Get-MinIOObject -BucketName $bucketName | Where-Object { $_.Name -like "*.csv" }
+ "Downloading $($csvObjects.Count) CSV files..."
+ foreach ($csvObj in $csvObjects) {
+ $localFileName = [System.IO.Path]::GetFileName($csvObj.Name)
+ $localPath = Join-Path $downloadDir $localFileName
+ $downloadResult = Get-MinIOObjectContent -BucketName $bucketName -ObjectName $csvObj.Name -FilePath $localPath
+ "Downloaded: $($csvObj.Name) -> $localPath"
+ }
+ ""
+
+ # Download files from specific directory
+ $docObjects = Get-MinIOObject -BucketName $bucketName -Prefix "documents/"
+ "Downloading $($docObjects.Count) files from documents directory..."
+ foreach ($docObj in $docObjects) {
+ if (-not $docObj.IsDirectory) {
+ $localFileName = [System.IO.Path]::GetFileName($docObj.Name)
+ $localPath = Join-Path $downloadDir "doc-$localFileName"
+ $downloadResult = Get-MinIOObjectContent -BucketName $bucketName -ObjectName $docObj.Name -FilePath $localPath
+ "Downloaded: $($docObj.Name) -> $localPath"
+ }
+ }
+ ""
+
+ "=== 5. Bulk Operations with Filtering ==="
+ # Demonstrate advanced bulk operations
+
+ # Find and process files by size
+ $largeFiles = Get-MinIOObject -BucketName $bucketName -ObjectsOnly | Where-Object { $_.Size -gt 50 }
+ "Processing $($largeFiles.Count) files larger than 50 bytes:"
+ $largeFiles | Format-Table Name, @{Name="Size";Expression={"$($_.Size) bytes"}}, LastModified -AutoSize
+ ""
+
+ # Find files by date (last hour)
+ $recentFiles = Get-MinIOObject -BucketName $bucketName -ObjectsOnly | Where-Object { $_.LastModified -gt (Get-Date).AddHours(-1) }
+ "Files uploaded in the last hour: $($recentFiles.Count)"
+ $recentFiles | Format-Table Name, LastModified -AutoSize
+ ""
+
+ "=== 6. Bulk Cleanup Operations ==="
+ # Demonstrate selective cleanup
+
+ # Remove files by pattern
+ $logObjects = Get-MinIOObject -BucketName $bucketName | Where-Object { $_.Name -like "*log*" -and -not $_.IsDirectory }
+ "Removing $($logObjects.Count) log files..."
+ foreach ($logObj in $logObjects) {
+ Remove-MinIOObject -BucketName $bucketName -ObjectName $logObj.Name -Force
+ "Removed: $($logObj.Name)"
+ }
+ ""
+
+ # Remove files older than a certain date (simulated)
+ $cutoffDate = (Get-Date).AddMinutes(-5) # 5 minutes ago for demo
+ $oldFiles = Get-MinIOObject -BucketName $bucketName -ObjectsOnly | Where-Object { $_.LastModified -lt $cutoffDate }
+ if ($oldFiles.Count -gt 0) {
+ "Removing $($oldFiles.Count) files older than $cutoffDate..."
+ foreach ($oldFile in $oldFiles) {
+ Remove-MinIOObject -BucketName $bucketName -ObjectName $oldFile.Name -Force
+ "Removed: $($oldFile.Name)"
+ }
+ } else {
+ "No files older than $cutoffDate found"
+ }
+ ""
+
+ "=== 7. Final Statistics ==="
+ # Show final state
+ $remainingObjects = Get-MinIOObject -BucketName $bucketName -ObjectsOnly
+ "Remaining files after cleanup: $($remainingObjects.Count)"
+ if ($remainingObjects.Count -gt 0) {
+ $remainingObjects | Format-Table Name, @{Name="Size";Expression={"$($_.Size) bytes"}}, LastModified -AutoSize
+ }
+ ""
+
+ "Downloaded files:"
+ if (Test-Path $downloadDir) {
+ Get-ChildItem $downloadDir | Format-Table Name, @{Name="Size";Expression={"$($_.Length) bytes"}}, LastWriteTime -AutoSize
+ }
+
+ # Clean up
+ "Cleaning up demo files..."
+ $allObjects = Get-MinIOObject -BucketName $bucketName
+ foreach ($obj in $allObjects) {
+ if (-not $obj.IsDirectory) {
+ Remove-MinIOObject -BucketName $bucketName -ObjectName $obj.Name -Force
+ }
+ }
+ Remove-MinIOBucket -BucketName $bucketName -Force
+
+ # Clean up local files
+ Remove-Item $testFiles -Force -ErrorAction SilentlyContinue
+ if (Test-Path $downloadDir) {
+ Remove-Item $downloadDir -Recurse -Force -ErrorAction SilentlyContinue
+ }
+
+ "✅ Bulk operations demo completed"
+
+} catch {
+ "❌ Error: $($_.Exception.Message)"
+}
diff --git a/examples/06-Enterprise-Automation.ps1 b/examples/06-Enterprise-Automation.ps1
new file mode 100644
index 0000000..4dad6f4
--- /dev/null
+++ b/examples/06-Enterprise-Automation.ps1
@@ -0,0 +1,291 @@
+# PSMinIO Enterprise Automation Example
+# Demonstrates enterprise-grade automation scenarios including monitoring, policies, and scheduled operations
+
+# Import the module
+Import-Module ..\Module\PSMinIO\PSMinIO.psd1
+
+# Connection details (replace with your actual values)
+$endpoint = "https://minio.example.com"
+$accessKey = "your-access-key"
+$secretKey = "your-secret-key"
+
+# Configuration
+$logFile = "minio-automation-$(Get-Date -Format 'yyyyMMdd').log"
+$reportFile = "minio-report-$(Get-Date -Format 'yyyyMMdd-HHmmss').html"
+
+function Write-Log {
+ param([string]$Message, [string]$Level = "INFO")
+ $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
+ $logEntry = "[$timestamp] [$Level] $Message"
+ $logEntry | Out-File -FilePath $logFile -Append -Encoding UTF8
+ $logEntry
+}
+
+try {
+ Write-Log "Starting MinIO enterprise automation demo"
+
+ # Connect to MinIO
+ $connection = Connect-MinIO -Endpoint $endpoint -AccessKey $accessKey -SecretKey $secretKey
+ Write-Log "Connected to MinIO server: $endpoint"
+
+ "=== 1. Infrastructure Health Check ==="
+ # Perform comprehensive health checks
+ Write-Log "Performing infrastructure health check"
+
+ # Check server connectivity and basic operations
+ try {
+ $buckets = Get-MinIOBucket
+ Write-Log "✅ Server connectivity: OK ($($buckets.Count) buckets found)"
+ "✅ Server connectivity: OK ($($buckets.Count) buckets found)"
+ } catch {
+ Write-Log "❌ Server connectivity: FAILED - $($_.Exception.Message)" "ERROR"
+ "❌ Server connectivity: FAILED"
+ throw
+ }
+
+ # Test bucket operations
+ $testBucketName = "health-check-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ try {
+ New-MinIOBucket -BucketName $testBucketName
+ $testExists = Test-MinIOBucketExists -BucketName $testBucketName
+ Remove-MinIOBucket -BucketName $testBucketName -Force
+ Write-Log "✅ Bucket operations: OK"
+ "✅ Bucket operations: OK"
+ } catch {
+ Write-Log "❌ Bucket operations: FAILED - $($_.Exception.Message)" "ERROR"
+ "❌ Bucket operations: FAILED"
+ }
+ ""
+
+ "=== 2. Storage Statistics and Monitoring ==="
+ # Collect comprehensive storage statistics
+ Write-Log "Collecting storage statistics"
+
+ $stats = Get-MinIOStats -MaxObjectsToCount 1000
+ Write-Log "Storage stats collected: $($stats.TotalBuckets) buckets, $($stats.TotalObjects) objects, $($stats.TotalSizeFormatted)"
+
+ "Storage Overview:"
+ $stats | Format-List TotalBuckets, TotalObjects, TotalSizeFormatted, AverageObjectSizeFormatted
+ ""
+
+ # Detailed bucket analysis
+ "Bucket Analysis:"
+ $bucketStats = Get-MinIOBucket -IncludeStatistics
+ $bucketStats | Format-Table Name, @{Name="Objects";Expression={$_.ObjectCount}}, @{Name="Size";Expression={$_.SizeFormatted}}, CreationDate -AutoSize
+ ""
+
+ "=== 3. Automated Backup Operations ==="
+ # Simulate automated backup scenario
+ Write-Log "Starting automated backup operations"
+
+ $backupBucket = "automated-backups-$(Get-Date -Format 'yyyyMMdd')"
+ New-MinIOBucket -BucketName $backupBucket
+ Write-Log "Created backup bucket: $backupBucket"
+
+ # Create sample data to backup
+ $backupData = @{
+ "config-backup.json" = @{
+ timestamp = Get-Date
+ server = $env:COMPUTERNAME
+ version = "1.0"
+ } | ConvertTo-Json
+
+ "database-backup.sql" = "-- Database backup generated on $(Get-Date)`nSELECT * FROM users;"
+
+ "logs-backup.txt" = "Application logs backup`n$(Get-Date) - System started`n$(Get-Date) - Backup initiated"
+ }
+
+ foreach ($file in $backupData.Keys) {
+ $backupData[$file] | Out-File -FilePath $file -Encoding UTF8
+ }
+
+ # Perform backup with organized directory structure
+ $backupDate = Get-Date -Format "yyyy/MM/dd"
+ $backupTime = Get-Date -Format "HH-mm"
+
+ $backupResults = New-MinIOObject -BucketName $backupBucket -Files $backupData.Keys -BucketDirectory "daily-backups/$backupDate/$backupTime"
+ Write-Log "Backup completed: $($backupResults.Count) files uploaded"
+
+ "Backup Results:"
+ $backupResults | Format-Table Name, @{Name="Duration";Expression={$_.Duration}}, @{Name="Speed";Expression={$_.AverageSpeedFormatted}}
+ ""
+
+ "=== 4. Data Lifecycle Management ==="
+ # Simulate data lifecycle management
+ Write-Log "Performing data lifecycle management"
+
+ # Create lifecycle demo bucket
+ $lifecycleBucket = "lifecycle-demo-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ New-MinIOBucket -BucketName $lifecycleBucket
+
+ # Upload files with different "ages" (simulated by different directories)
+ $lifecycleFiles = @(
+ @{ Name = "current-data.txt"; Content = "Current data"; Directory = "current" }
+ @{ Name = "week-old-data.txt"; Content = "Week old data"; Directory = "archive/weekly" }
+ @{ Name = "month-old-data.txt"; Content = "Month old data"; Directory = "archive/monthly" }
+ @{ Name = "year-old-data.txt"; Content = "Year old data"; Directory = "archive/yearly" }
+ )
+
+ foreach ($file in $lifecycleFiles) {
+ $file.Content | Out-File -FilePath $file.Name -Encoding UTF8
+ New-MinIOObject -BucketName $lifecycleBucket -Files $file.Name -BucketDirectory $file.Directory
+ Remove-Item $file.Name -Force
+ }
+
+ # Analyze data by lifecycle stage
+ $allLifecycleObjects = Get-MinIOObject -BucketName $lifecycleBucket -ObjectsOnly
+ "Data Lifecycle Analysis:"
+ $allLifecycleObjects | Group-Object { ($_.Name -split '/')[0] } | Format-Table Name, Count -AutoSize
+ ""
+
+ "=== 5. Security and Compliance Audit ==="
+ # Perform security audit
+ Write-Log "Performing security and compliance audit"
+
+ $auditResults = @()
+
+ # Check bucket policies
+ foreach ($bucket in $buckets) {
+ try {
+ $policy = Get-MinIOBucketPolicy -BucketName $bucket.Name
+ $auditResults += [PSCustomObject]@{
+ Bucket = $bucket.Name
+ HasPolicy = $policy -ne $null
+ PolicySize = if ($policy) { $policy.Length } else { 0 }
+ Status = if ($policy) { "Policy Configured" } else { "No Policy" }
+ }
+ } catch {
+ $auditResults += [PSCustomObject]@{
+ Bucket = $bucket.Name
+ HasPolicy = $false
+ PolicySize = 0
+ Status = "Policy Check Failed"
+ }
+ }
+ }
+
+ "Security Audit Results:"
+ $auditResults | Format-Table Bucket, HasPolicy, Status -AutoSize
+ Write-Log "Security audit completed: $($auditResults.Count) buckets audited"
+ ""
+
+ "=== 6. Performance Monitoring ==="
+ # Monitor performance metrics
+ Write-Log "Collecting performance metrics"
+
+ # Test upload/download performance
+ $perfTestFile = "performance-test.txt"
+ $perfTestContent = "Performance test data " * 1000 # ~20KB
+ $perfTestContent | Out-File -FilePath $perfTestFile -Encoding UTF8 -NoNewline
+
+ $perfBucket = "performance-test-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ New-MinIOBucket -BucketName $perfBucket
+
+ # Measure upload performance
+ $uploadStart = Get-Date
+ $uploadResult = New-MinIOObject -BucketName $perfBucket -Files $perfTestFile
+ $uploadEnd = Get-Date
+ $uploadDuration = $uploadEnd - $uploadStart
+
+ # Measure download performance
+ $downloadStart = Get-Date
+ $downloadResult = Get-MinIOObjectContent -BucketName $perfBucket -ObjectName $perfTestFile -FilePath "downloaded-$perfTestFile"
+ $downloadEnd = Get-Date
+ $downloadDuration = $downloadEnd - $downloadStart
+
+ "Performance Metrics:"
+ [PSCustomObject]@{
+ Operation = "Upload"
+ Duration = $uploadDuration.TotalMilliseconds
+ Speed = $uploadResult.AverageSpeedFormatted
+ FileSize = (Get-Item $perfTestFile).Length
+ } | Format-Table
+
+ [PSCustomObject]@{
+ Operation = "Download"
+ Duration = $downloadDuration.TotalMilliseconds
+ Speed = $downloadResult.AverageSpeedFormatted
+ FileSize = (Get-Item "downloaded-$perfTestFile").Length
+ } | Format-Table
+
+ Write-Log "Performance test completed - Upload: $($uploadDuration.TotalMilliseconds)ms, Download: $($downloadDuration.TotalMilliseconds)ms"
+ ""
+
+ "=== 7. Generating HTML Report ==="
+ # Generate comprehensive HTML report
+ Write-Log "Generating HTML report"
+
+ $htmlReport = @"
+
+
+
+ MinIO Enterprise Report - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
+
+
+
+
+
+
+
Storage Overview
+
Total Buckets: $($stats.TotalBuckets)
+
Total Objects: $($stats.TotalObjects)
+
Total Size: $($stats.TotalSizeFormatted)
+
+
+
+
Health Check Results
+
✅ Server Connectivity: OK
+
✅ Bucket Operations: OK
+
+
+
+
Performance Metrics
+
Upload Performance: $($uploadResult.AverageSpeedFormatted)
+
Download Performance: $($downloadResult.AverageSpeedFormatted)
+
+
+
+
Security Audit
+
$($auditResults.Count) buckets audited
+
Buckets with policies: $(($auditResults | Where-Object HasPolicy).Count)
+
+
+
+"@
+
+ $htmlReport | Out-File -FilePath $reportFile -Encoding UTF8
+ Write-Log "HTML report generated: $reportFile"
+ "📊 HTML report generated: $reportFile"
+ ""
+
+ # Clean up demo resources
+ Write-Log "Cleaning up demo resources"
+ Remove-MinIOBucket -BucketName $backupBucket -Force
+ Remove-MinIOBucket -BucketName $lifecycleBucket -Force
+ Remove-MinIOBucket -BucketName $perfBucket -Force
+ Remove-Item $backupData.Keys, $perfTestFile, "downloaded-$perfTestFile" -Force -ErrorAction SilentlyContinue
+
+ Write-Log "Enterprise automation demo completed successfully"
+ "✅ Enterprise automation demo completed successfully"
+ "📋 Check the log file: $logFile"
+ "📊 View the report: $reportFile"
+
+} catch {
+ Write-Log "❌ Enterprise automation demo failed: $($_.Exception.Message)" "ERROR"
+ "❌ Error: $($_.Exception.Message)"
+}
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..17869d7
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,231 @@
+# PSMinIO Examples
+
+This directory contains comprehensive examples demonstrating various PSMinIO capabilities and usage patterns.
+
+## Prerequisites
+
+Before running these examples:
+
+1. **Install PSMinIO**: Import the module using `Import-Module ..\Module\PSMinIO\PSMinIO.psd1`
+2. **MinIO Server**: Have access to a MinIO server or compatible S3 service
+3. **Credentials**: Update the connection details in each script with your actual values:
+ ```powershell
+ $endpoint = "https://your-minio-server.com"
+ $accessKey = "your-access-key"
+ $secretKey = "your-secret-key"
+ ```
+
+## Example Scripts
+
+### 01-Basic-Operations.ps1
+**Fundamental MinIO operations for beginners**
+
+Demonstrates:
+- Connecting to MinIO server
+- Creating and managing buckets
+- Basic file upload and download
+- Object listing and verification
+- Clean resource management
+
+**Best for**: First-time users, basic automation scripts
+
+---
+
+### 02-Advanced-Object-Listing.ps1
+**Comprehensive object listing and filtering capabilities**
+
+Demonstrates:
+- Advanced filtering with prefixes
+- Sorting by multiple criteria (Name, Size, LastModified, ETag)
+- Result pagination with MaxObjects
+- Files-only filtering (excluding directories)
+- Complex query combinations
+
+**Best for**: Data discovery, content management, reporting
+
+---
+
+### 03-Directory-Management.ps1
+**Creating and managing directory structures**
+
+Demonstrates:
+- Explicit folder creation with New-MinIOFolder
+- Automatic directory creation with BucketDirectory
+- Multi-level nested directory structures
+- Directory-based file organization
+- Date-based directory patterns
+
+**Best for**: Organized file storage, hierarchical data management
+
+---
+
+### 04-Chunked-Operations.ps1
+**Large file handling with chunked transfers**
+
+Demonstrates:
+- Chunked uploads with configurable chunk sizes
+- Chunked downloads for large files
+- Multi-layer progress tracking
+- Performance comparison (regular vs chunked)
+- Chunk size optimization guidelines
+
+**Best for**: Large file transfers, bandwidth optimization, resume capability
+
+---
+
+### 05-Bulk-Operations.ps1
+**Batch processing and bulk file operations**
+
+Demonstrates:
+- Multiple file uploads to organized directories
+- Bulk download operations with filtering
+- File analysis and statistics
+- Pattern-based file processing
+- Selective cleanup operations
+
+**Best for**: Data migration, batch processing, automated workflows
+
+---
+
+### 06-Enterprise-Automation.ps1
+**Enterprise-grade automation and monitoring**
+
+Demonstrates:
+- Infrastructure health checks
+- Storage statistics and monitoring
+- Automated backup operations
+- Data lifecycle management
+- Security and compliance auditing
+- Performance monitoring
+- HTML report generation
+
+**Best for**: Enterprise environments, monitoring systems, compliance reporting
+
+## Usage Patterns
+
+### Quick Start
+```powershell
+# Run a basic example
+.\01-Basic-Operations.ps1
+```
+
+### Customization
+Each script includes configuration variables at the top. Modify these for your environment:
+
+```powershell
+# Connection details
+$endpoint = "https://your-minio-server.com"
+$accessKey = "your-access-key"
+$secretKey = "your-secret-key"
+
+# Optional: Bucket naming
+$bucketName = "your-custom-bucket-name"
+```
+
+### Integration
+These examples can be integrated into larger automation workflows:
+
+```powershell
+# Source common functions
+. .\examples\06-Enterprise-Automation.ps1
+
+# Use specific functions in your scripts
+Write-Log "Starting custom automation"
+$stats = Get-MinIOStats
+```
+
+## Common Scenarios
+
+### Development Environment Setup
+```powershell
+# Local MinIO setup
+$endpoint = "http://localhost:9000"
+$accessKey = "minioadmin"
+$secretKey = "minioadmin"
+```
+
+### Production Environment
+```powershell
+# Production setup with SSL
+$endpoint = "https://minio.company.com"
+$accessKey = $env:MINIO_ACCESS_KEY
+$secretKey = $env:MINIO_SECRET_KEY
+```
+
+### AWS S3 Compatibility
+```powershell
+# AWS S3 setup
+$endpoint = "https://s3.amazonaws.com"
+$accessKey = $env:AWS_ACCESS_KEY_ID
+$secretKey = $env:AWS_SECRET_ACCESS_KEY
+```
+
+## Best Practices
+
+### Error Handling
+All examples include comprehensive error handling:
+```powershell
+try {
+ # MinIO operations
+} catch {
+ "❌ Error: $($_.Exception.Message)"
+} finally {
+ # Cleanup operations
+}
+```
+
+### Resource Cleanup
+Examples automatically clean up created resources:
+- Remove test buckets
+- Delete uploaded files
+- Clean local temporary files
+
+### Logging
+Enterprise examples include structured logging:
+```powershell
+function Write-Log {
+ param([string]$Message, [string]$Level = "INFO")
+ $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
+ "[$timestamp] [$Level] $Message"
+}
+```
+
+### Performance Considerations
+- Use chunked operations for files > 10MB
+- Implement appropriate chunk sizes based on network conditions
+- Monitor transfer speeds and adjust accordingly
+
+## Troubleshooting
+
+### Connection Issues
+- Verify endpoint URL and port
+- Check SSL/TLS configuration
+- Validate access credentials
+- Test network connectivity
+
+### Permission Issues
+- Ensure proper bucket permissions
+- Verify access key has required privileges
+- Check bucket policies if applicable
+
+### Performance Issues
+- Adjust chunk sizes for large files
+- Monitor network bandwidth
+- Consider parallel operations for bulk transfers
+
+## Contributing
+
+When adding new examples:
+1. Follow the existing naming convention
+2. Include comprehensive error handling
+3. Add resource cleanup
+4. Document the example purpose and usage
+5. Update this README with the new example
+
+## Support
+
+For issues with these examples:
+- Check the main [PSMinIO documentation](../docs/USAGE.md)
+- Review error messages and logs
+- Verify your MinIO server configuration
+- Test with basic operations first
diff --git a/src/Cmdlets/GetMinIOObjectCmdlet.cs b/src/Cmdlets/GetMinIOObjectCmdlet.cs
new file mode 100644
index 0000000..5a2090b
--- /dev/null
+++ b/src/Cmdlets/GetMinIOObjectCmdlet.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Linq;
+using System.Management.Automation;
+using PSMinIO.Models;
+using PSMinIO.Utils;
+
+namespace PSMinIO.Cmdlets
+{
+ ///
+ /// Gets information about objects in a MinIO bucket
+ ///
+ [Cmdlet(VerbsCommon.Get, "MinIOObject", SupportsShouldProcess = true)]
+ [OutputType(typeof(MinIOObjectInfo))]
+ public class GetMinIOObjectCmdlet : MinIOBaseCmdlet
+ {
+ ///
+ /// Name of the bucket to list objects from
+ ///
+ [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNullOrEmpty]
+ [Alias("Bucket")]
+ public string BucketName { get; set; } = string.Empty;
+
+ ///
+ /// Optional prefix to filter objects
+ ///
+ [Parameter(Position = 1, ValueFromPipelineByPropertyName = true)]
+ [Alias("Filter")]
+ public string? Prefix { get; set; }
+
+ ///
+ /// Specific object name to retrieve. If specified, only this object is returned.
+ ///
+ [Parameter(ValueFromPipelineByPropertyName = true)]
+ [Alias("Object", "Name")]
+ public string? ObjectName { get; set; }
+
+ ///
+ /// Whether to list objects recursively (default: true)
+ ///
+ [Parameter]
+ public SwitchParameter Recursive { get; set; } = true;
+
+ ///
+ /// Include all versions of objects (for versioned buckets)
+ ///
+ [Parameter]
+ [Alias("Versions")]
+ public SwitchParameter IncludeVersions { get; set; }
+
+ ///
+ /// Maximum number of objects to return
+ ///
+ [Parameter]
+ [ValidateRange(1, 10000)]
+ [Alias("Limit")]
+ public int? MaxObjects { get; set; }
+
+ ///
+ /// Only return objects (exclude directory markers)
+ ///
+ [Parameter]
+ [Alias("FilesOnly")]
+ public SwitchParameter ObjectsOnly { get; set; }
+
+ ///
+ /// Sort objects by the specified property
+ ///
+ [Parameter]
+ [ValidateSet("Name", "Size", "LastModified", "ETag")]
+ public string? SortBy { get; set; }
+
+ ///
+ /// Sort in descending order (default: ascending)
+ ///
+ [Parameter]
+ [Alias("Desc")]
+ public SwitchParameter Descending { get; set; }
+
+ ///
+ /// Processes the cmdlet
+ ///
+ protected override void ProcessRecord()
+ {
+ var targetDescription = !string.IsNullOrWhiteSpace(ObjectName)
+ ? $"object '{ObjectName}'"
+ : !string.IsNullOrWhiteSpace(Prefix)
+ ? $"objects with prefix '{Prefix}'"
+ : "all objects";
+
+ // Determine the prefix to use
+ var searchPrefix = !string.IsNullOrWhiteSpace(ObjectName) ? ObjectName : Prefix;
+
+ if (ShouldProcess(BucketName, $"List {targetDescription}"))
+ {
+ ExecuteOperation("ListObjects", () =>
+ {
+ // Check if bucket exists
+ var bucketExists = Client.BucketExists(BucketName);
+ if (!bucketExists)
+ {
+ WriteError(new ErrorRecord(
+ new InvalidOperationException($"Bucket '{BucketName}' does not exist"),
+ "BucketNotFound",
+ ErrorCategory.ObjectNotFound,
+ BucketName));
+ return;
+ }
+
+ MinIOLogger.WriteVerbose(this,
+ "Listing objects in bucket '{0}' with prefix '{1}', recursive: {2}",
+ BucketName, searchPrefix ?? "(none)", Recursive.IsPresent);
+
+ // Get objects from MinIO
+ var objects = Client.ListObjects(BucketName, searchPrefix, Recursive.IsPresent, IncludeVersions.IsPresent);
+
+ MinIOLogger.WriteVerbose(this, "Found {0} objects", objects.Count);
+
+ // Filter for exact object name match if specified
+ if (!string.IsNullOrWhiteSpace(ObjectName))
+ {
+ objects = objects.Where(obj =>
+ string.Equals(obj.Name, ObjectName, StringComparison.Ordinal)).ToList();
+ }
+
+ // Filter out directory markers if ObjectsOnly is specified
+ if (ObjectsOnly.IsPresent)
+ {
+ objects = objects.Where(obj =>
+ !obj.Name.EndsWith("/") && obj.Size > 0).ToList();
+ }
+
+ // Apply sorting if specified
+ if (!string.IsNullOrWhiteSpace(SortBy))
+ {
+ objects = SortBy.ToLowerInvariant() switch
+ {
+ "name" => Descending.IsPresent
+ ? objects.OrderByDescending(o => o.Name).ToList()
+ : objects.OrderBy(o => o.Name).ToList(),
+ "size" => Descending.IsPresent
+ ? objects.OrderByDescending(o => o.Size).ToList()
+ : objects.OrderBy(o => o.Size).ToList(),
+ "lastmodified" => Descending.IsPresent
+ ? objects.OrderByDescending(o => o.LastModified).ToList()
+ : objects.OrderBy(o => o.LastModified).ToList(),
+ "etag" => Descending.IsPresent
+ ? objects.OrderByDescending(o => o.ETag).ToList()
+ : objects.OrderBy(o => o.ETag).ToList(),
+ _ => objects
+ };
+ }
+
+ // Apply limit if specified
+ if (MaxObjects.HasValue && objects.Count > MaxObjects.Value)
+ {
+ MinIOLogger.WriteVerbose(this,
+ "Limiting results to {0} objects", MaxObjects.Value);
+ objects = objects.Take(MaxObjects.Value).ToList();
+ }
+
+ MinIOLogger.WriteVerbose(this,
+ "Returning {0} objects after filtering and sorting", objects.Count);
+
+ // Output objects
+ foreach (var obj in objects)
+ {
+ WriteObject(obj);
+ }
+
+ }, $"Bucket: {BucketName}, Prefix: {searchPrefix ?? "(none)"}");
+ }
+ }
+ }
+}
diff --git a/src/Cmdlets/GetMinIOObjectContentChunkedCmdlet.cs b/src/Cmdlets/GetMinIOObjectContentChunkedCmdlet.cs
index b515f06..cee5a98 100644
--- a/src/Cmdlets/GetMinIOObjectContentChunkedCmdlet.cs
+++ b/src/Cmdlets/GetMinIOObjectContentChunkedCmdlet.cs
@@ -47,7 +47,7 @@ namespace PSMinIO.Cmdlets
/// Size of each chunk for download (default: 10MB)
///
[Parameter]
- [ValidateRange(1024 * 1024, 100 * 1024 * 1024)] // 1MB to 100MB
+ [ValidateRange(5 * 1024 * 1024, 500 * 1024 * 1024)] // 5MB to 500MB
public long ChunkSize { get; set; } = 10 * 1024 * 1024; // 10MB default
///
@@ -128,17 +128,16 @@ namespace PSMinIO.Cmdlets
ObjectName, BucketName, SizeFormatter.FormatBytes(objectInfo.Size), SizeFormatter.FormatBytes(ChunkSize));
// Download using chunked transfer
- var downloadedFile = DownloadObjectChunked(objectInfo);
-
- if (downloadedFile != null)
+ var downloadResult = DownloadObjectChunked(objectInfo);
+
+ if (downloadResult != null)
{
- MinIOLogger.WriteVerbose(this,
- "Successfully downloaded object '{0}' from bucket '{1}' to '{2}'",
+ MinIOLogger.WriteVerbose(this,
+ "Successfully downloaded object '{0}' from bucket '{1}' to '{2}'",
ObjectName, BucketName, FilePath.FullName);
- // Always return file information
- FilePath.Refresh(); // Refresh to get updated file info
- WriteObject(FilePath);
+ // Return download result with timing information
+ WriteObject(downloadResult);
}
}, $"Bucket: {BucketName}, Object: {ObjectName}, File: {FilePath.FullName}, ChunkSize: {SizeFormatter.FormatBytes(ChunkSize)}");
@@ -167,8 +166,8 @@ namespace PSMinIO.Cmdlets
/// Downloads an object using chunked transfer with resume capability
///
/// Information about the object to download
- /// Downloaded file info or null if failed
- private FileInfo? DownloadObjectChunked(MinIOObjectInfo objectInfo)
+ /// Download result with timing information or null if failed
+ private MinIODownloadResult? DownloadObjectChunked(MinIOObjectInfo objectInfo)
{
ChunkedTransferState? transferState = null;
@@ -210,29 +209,55 @@ namespace PSMinIO.Cmdlets
// Calculate total chunks for progress reporting
var totalChunks = (int)Math.Ceiling((double)objectInfo.Size / ChunkSize);
- // Create progress reporter (single file, so no collection progress)
- var progressReporter = new ChunkedSingleFileProgressReporter(
+ // Validate chunk count to prevent memory issues
+ if (totalChunks > 5000)
+ {
+ var recommendedChunkSize = (long)Math.Ceiling((double)objectInfo.Size / 5000);
+ WriteWarning($"Too many chunks ({totalChunks:N0}) would be created. Consider using a larger chunk size (recommended: {SizeFormatter.FormatBytes(recommendedChunkSize)})");
+ ThrowTerminatingError(new ErrorRecord(
+ new ArgumentException($"Chunk size too small would create {totalChunks:N0} chunks. Maximum allowed is 5,000 chunks."),
+ "TooManyChunks",
+ ErrorCategory.InvalidArgument,
+ ChunkSize));
+ }
+
+ // Create thread-safe progress reporter
+ var progressReporter = new ThreadSafeChunkedProgressReporter(
this,
objectInfo.Size,
totalChunks,
- "Downloading",
- ProgressUpdateInterval);
+ "Downloading");
+
+ progressReporter.StartNewFile(ObjectName, objectInfo.Size, totalChunks);
+
+ // Track timing for this download
+ var startTime = DateTime.UtcNow;
try
{
// Download file using chunked transfer
var result = Client.DownloadFileChunked(transferState, progressReporter, MaxRetries, ParallelDownloads);
-
+
if (result)
{
+ var completionTime = DateTime.UtcNow;
+
// Clean up resume data on successful completion
if (Resume.IsPresent)
{
ChunkedTransferResumeManager.CleanupResumeData(transferState, ResumeDataPath);
}
- progressReporter.CompleteDownload();
- return FilePath;
+ progressReporter.CompleteFile();
+
+ // Process any queued progress updates from the main thread
+ progressReporter.ProcessQueuedUpdates();
+ progressReporter.Complete();
+
+ // Create download result with timing information
+ FilePath.Refresh();
+ var downloadResult = new MinIODownloadResult(FilePath, BucketName, ObjectName, startTime, completionTime);
+ return downloadResult;
}
}
#pragma warning disable CS0168 // Variable is declared but never used - false positive, ex is used in error handling
diff --git a/src/Cmdlets/GetMinIOObjectContentCmdlet.cs b/src/Cmdlets/GetMinIOObjectContentCmdlet.cs
index 548ec40..41a3a8f 100644
--- a/src/Cmdlets/GetMinIOObjectContentCmdlet.cs
+++ b/src/Cmdlets/GetMinIOObjectContentCmdlet.cs
@@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Linq;
using System.Management.Automation;
+using PSMinIO.Models;
using PSMinIO.Utils;
namespace PSMinIO.Cmdlets
@@ -97,6 +98,9 @@ namespace PSMinIO.Cmdlets
try
{
+ // Track timing for this download
+ var startTime = DateTime.UtcNow;
+
// Download the file with progress reporting
Client.DownloadFile(
BucketName,
@@ -104,6 +108,8 @@ namespace PSMinIO.Cmdlets
FilePath.FullName,
bytesTransferred => progressReporter.UpdateProgress(bytesTransferred));
+ var completionTime = DateTime.UtcNow;
+
// Complete progress reporting
progressReporter.Complete();
@@ -111,9 +117,10 @@ namespace PSMinIO.Cmdlets
"Successfully downloaded object '{0}' from bucket '{1}' to '{2}'",
ObjectName, BucketName, FilePath.FullName);
- // Always return file information
- FilePath.Refresh(); // Refresh to get updated file info
- WriteObject(FilePath);
+ // Refresh file info and create download result with timing information
+ FilePath.Refresh();
+ var downloadResult = new MinIODownloadResult(FilePath, BucketName, ObjectName, startTime, completionTime);
+ WriteObject(downloadResult);
}
#pragma warning disable CS0168 // Variable is declared but never used - false positive, ex is used in throw
catch (Exception ex)
diff --git a/src/Cmdlets/NewMinIOFolderCmdlet.cs b/src/Cmdlets/NewMinIOFolderCmdlet.cs
index b0f2717..1f5a7e5 100644
--- a/src/Cmdlets/NewMinIOFolderCmdlet.cs
+++ b/src/Cmdlets/NewMinIOFolderCmdlet.cs
@@ -152,9 +152,8 @@ namespace PSMinIO.Cmdlets
{
MinIOLogger.WriteVerbose(this, "Creating folder level: {0}", fullFolderPath);
- // Create the folder by uploading a zero-byte object
- using var emptyStream = new MemoryStream();
- var etag = Client.UploadStream(BucketName, fullFolderPath, emptyStream, "application/x-directory");
+ // Create the folder using the dedicated method
+ var etag = Client.CreateDirectory(BucketName, fullFolderPath);
var folderInfo = new MinIOObjectInfo(
fullFolderPath,
diff --git a/src/Cmdlets/NewMinIOObjectChunkedCmdlet.cs b/src/Cmdlets/NewMinIOObjectChunkedCmdlet.cs
index 0774a6b..8d4ade2 100644
--- a/src/Cmdlets/NewMinIOObjectChunkedCmdlet.cs
+++ b/src/Cmdlets/NewMinIOObjectChunkedCmdlet.cs
@@ -323,15 +323,8 @@ namespace PSMinIO.Cmdlets
// Calculate total size for overall progress
var totalSize = validFiles.Sum(f => f.Length);
- // Create progress reporter
- var progressReporter = new ChunkedCollectionProgressReporter(
- this,
- validFiles.Length,
- totalSize,
- "Uploading",
- ProgressUpdateInterval);
-
- var uploadedObjects = new System.Collections.Generic.List();
+ // Create thread-safe result collector
+ var resultCollector = new ThreadSafeResultCollector(this);
for (int i = 0; i < validFiles.Length; i++)
{
@@ -342,39 +335,47 @@ namespace PSMinIO.Cmdlets
{
// Calculate chunks for this file
var totalChunks = (int)Math.Ceiling((double)fileInfo.Length / ChunkSize);
+
+ // Create thread-safe progress reporter for this file
+ var progressReporter = new ThreadSafeChunkedProgressReporter(
+ this,
+ fileInfo.Length,
+ totalChunks,
+ "Uploading");
+
progressReporter.StartNewFile(fileInfo.Name, fileInfo.Length, totalChunks);
+ // Track timing for this file
+ var startTime = DateTime.UtcNow;
+
// Upload file using chunked transfer
var uploadedObject = UploadFileChunked(fileInfo, objectName, progressReporter);
if (uploadedObject != null)
{
- uploadedObjects.Add(uploadedObject);
+ // Add timing information
+ uploadedObject.StartTime = startTime;
+ uploadedObject.CompletionTime = DateTime.UtcNow;
+
+ resultCollector.QueueResult(uploadedObject);
progressReporter.CompleteFile();
}
+
+ // Process any queued progress updates from the main thread
+ progressReporter.ProcessQueuedUpdates();
+ progressReporter.Complete();
}
catch (Exception ex)
{
- WriteError(new ErrorRecord(
- ex,
- "ChunkedFileUploadFailed",
- ErrorCategory.WriteError,
- fileInfo));
-
+ resultCollector.QueueError(ex, "ChunkedFileUploadFailed", ErrorCategory.WriteError, fileInfo);
MinIOLogger.WriteVerbose(this, "Failed to upload {0}: {1}", fileInfo.Name, ex.Message);
}
}
- progressReporter.CompleteCollection();
+ MinIOLogger.WriteVerbose(this, "Completed chunked upload: {0} files", validFiles.Length);
- // Always return uploaded objects
- foreach (var obj in uploadedObjects)
- {
- WriteObject(obj);
- }
-
- MinIOLogger.WriteVerbose(this, "Completed chunked upload: {0} files ({1} successful, {2} failed)",
- validFiles.Length, uploadedObjects.Count, validFiles.Length - uploadedObjects.Count);
+ // Process all results from the main thread
+ resultCollector.Complete();
}
///
@@ -382,9 +383,9 @@ namespace PSMinIO.Cmdlets
///
/// File to upload
/// Object name in bucket
- /// Progress reporter
+ /// Thread-safe progress reporter
/// Uploaded object info or null if failed
- private MinIOObjectInfo? UploadFileChunked(FileInfo fileInfo, string objectName, ChunkedCollectionProgressReporter progressReporter)
+ private MinIOObjectInfo? UploadFileChunked(FileInfo fileInfo, string objectName, ThreadSafeChunkedProgressReporter progressReporter)
{
ChunkedTransferState? transferState = null;
@@ -563,16 +564,23 @@ namespace PSMinIO.Cmdlets
{
MinIOLogger.WriteVerbose(this, "Creating bucket directory: {0}", folderPath);
- // Create the directory by uploading a zero-byte object
- using var emptyStream = new MemoryStream();
- Client.UploadStream(BucketName, folderPath, emptyStream, "application/x-directory");
-
- MinIOLogger.WriteVerbose(this, "Successfully created bucket directory: {0}", folderPath);
+ try
+ {
+ // Create the directory using the dedicated method
+ Client.CreateDirectory(BucketName, folderPath);
+ MinIOLogger.WriteVerbose(this, "Successfully created bucket directory: {0}", folderPath);
+ }
+ catch (Exception createEx)
+ {
+ // Directory creation failed, but this is not critical since MinIO creates directories implicitly
+ MinIOLogger.WriteVerbose(this, "Directory creation failed (non-critical): {0} - {1}", folderPath, createEx.Message);
+ }
}
}
catch (Exception ex)
{
- WriteWarning($"Could not create bucket directory '{folderPath}': {ex.Message}");
+ // Listing failed, but this is not critical for the upload operation
+ MinIOLogger.WriteVerbose(this, "Could not check directory existence (non-critical): {0} - {1}", folderPath, ex.Message);
}
}
}
diff --git a/src/Cmdlets/NewMinIOObjectCmdlet.cs b/src/Cmdlets/NewMinIOObjectCmdlet.cs
index 3861cc1..eab19dc 100644
--- a/src/Cmdlets/NewMinIOObjectCmdlet.cs
+++ b/src/Cmdlets/NewMinIOObjectCmdlet.cs
@@ -317,6 +317,9 @@ namespace PSMinIO.Cmdlets
MinIOLogger.WriteVerbose(this, "Uploading file {0}/{1}: {2} -> {3}",
i + 1, validFiles.Length, fileInfo.Name, objectName);
+ // Track timing for this file
+ var startTime = DateTime.UtcNow;
+
// Upload the file
var etag = Client.UploadFile(
BucketName,
@@ -328,6 +331,8 @@ namespace PSMinIO.Cmdlets
overallProgress.UpdateProgress(totalProcessed + bytesTransferred);
});
+ var completionTime = DateTime.UtcNow;
+
totalProcessed += fileInfo.Length;
overallProgress.UpdateProgress(totalProcessed);
@@ -337,7 +342,11 @@ namespace PSMinIO.Cmdlets
fileInfo.Length,
DateTime.UtcNow,
etag,
- BucketName);
+ BucketName)
+ {
+ StartTime = startTime,
+ CompletionTime = completionTime
+ };
// Generate presigned URL if requested
if (ShowURL.IsPresent)
@@ -473,16 +482,23 @@ namespace PSMinIO.Cmdlets
{
MinIOLogger.WriteVerbose(this, "Creating bucket directory: {0}", folderPath);
- // Create the directory by uploading a zero-byte object
- using var emptyStream = new MemoryStream();
- Client.UploadStream(BucketName, folderPath, emptyStream, "application/x-directory");
-
- MinIOLogger.WriteVerbose(this, "Successfully created bucket directory: {0}", folderPath);
+ try
+ {
+ // Create the directory using the dedicated method
+ Client.CreateDirectory(BucketName, folderPath);
+ MinIOLogger.WriteVerbose(this, "Successfully created bucket directory: {0}", folderPath);
+ }
+ catch (Exception createEx)
+ {
+ // Directory creation failed, but this is not critical since MinIO creates directories implicitly
+ MinIOLogger.WriteVerbose(this, "Directory creation failed (non-critical): {0} - {1}", folderPath, createEx.Message);
+ }
}
}
catch (Exception ex)
{
- WriteWarning($"Could not create bucket directory '{folderPath}': {ex.Message}");
+ // Listing failed, but this is not critical for the upload operation
+ MinIOLogger.WriteVerbose(this, "Could not check directory existence (non-critical): {0} - {1}", folderPath, ex.Message);
}
}
}
diff --git a/src/Models/MinIODownloadResult.cs b/src/Models/MinIODownloadResult.cs
new file mode 100644
index 0000000..1bba766
--- /dev/null
+++ b/src/Models/MinIODownloadResult.cs
@@ -0,0 +1,103 @@
+using System;
+using System.IO;
+using PSMinIO.Utils;
+
+namespace PSMinIO.Models
+{
+ ///
+ /// Represents the result of a MinIO download operation with timing and speed information
+ ///
+ public class MinIODownloadResult
+ {
+ ///
+ /// The downloaded file information
+ ///
+ public FileInfo File { get; set; }
+
+ ///
+ /// Name of the bucket the object was downloaded from
+ ///
+ public string BucketName { get; set; }
+
+ ///
+ /// Name of the object that was downloaded
+ ///
+ public string ObjectName { get; set; }
+
+ ///
+ /// Size of the downloaded file in bytes
+ ///
+ public long Size => File?.Length ?? 0;
+
+ ///
+ /// Transfer start time
+ ///
+ public DateTime? StartTime { get; set; }
+
+ ///
+ /// Transfer completion time
+ ///
+ public DateTime? CompletionTime { get; set; }
+
+ ///
+ /// Transfer duration
+ ///
+ public TimeSpan? Duration => StartTime.HasValue && CompletionTime.HasValue ?
+ CompletionTime.Value - StartTime.Value : null;
+
+ ///
+ /// Average transfer speed in bytes per second
+ ///
+ public double? AverageSpeed => Duration.HasValue && Duration.Value.TotalSeconds > 0 ?
+ Size / Duration.Value.TotalSeconds : null;
+
+ ///
+ /// Average transfer speed formatted as string (e.g., "15.2 MB/s")
+ ///
+ public string? AverageSpeedFormatted => AverageSpeed.HasValue ?
+ $"{SizeFormatter.FormatBytes((long)AverageSpeed.Value)}/s" : null;
+
+ ///
+ /// Full path to the downloaded file
+ ///
+ public string FullName => File?.FullName ?? string.Empty;
+
+ ///
+ /// Name of the downloaded file
+ ///
+ public string Name => File?.Name ?? string.Empty;
+
+ ///
+ /// Directory containing the downloaded file
+ ///
+ public string? DirectoryName => File?.DirectoryName;
+
+ ///
+ /// Creates a new MinIODownloadResult
+ ///
+ /// Downloaded file information
+ /// Source bucket name
+ /// Source object name
+ /// Transfer start time
+ /// Transfer completion time
+ public MinIODownloadResult(FileInfo file, string bucketName, string objectName,
+ DateTime? startTime = null, DateTime? completionTime = null)
+ {
+ File = file ?? throw new ArgumentNullException(nameof(file));
+ BucketName = bucketName ?? throw new ArgumentNullException(nameof(bucketName));
+ ObjectName = objectName ?? throw new ArgumentNullException(nameof(objectName));
+ StartTime = startTime;
+ CompletionTime = completionTime;
+ }
+
+ ///
+ /// Returns a string representation of the download result
+ ///
+ public override string ToString()
+ {
+ var duration = Duration?.ToString(@"hh\:mm\:ss\.fff") ?? "Unknown";
+ var speed = AverageSpeedFormatted ?? "Unknown";
+ return $"{BucketName}/{ObjectName} -> {FullName} ({SizeFormatter.FormatBytes(Size)}, {duration}, {speed})";
+ }
+ }
+}
diff --git a/src/Models/MinIOObjectInfo.cs b/src/Models/MinIOObjectInfo.cs
index 24b9a3e..39faead 100644
--- a/src/Models/MinIOObjectInfo.cs
+++ b/src/Models/MinIOObjectInfo.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using PSMinIO.Utils;
namespace PSMinIO.Models
{
@@ -78,6 +79,34 @@ namespace PSMinIO.Models
///
public DateTime? PresignedUrlExpiration { get; set; }
+ ///
+ /// Transfer start time
+ ///
+ public DateTime? StartTime { get; set; }
+
+ ///
+ /// Transfer completion time
+ ///
+ public DateTime? CompletionTime { get; set; }
+
+ ///
+ /// Transfer duration
+ ///
+ public TimeSpan? Duration => StartTime.HasValue && CompletionTime.HasValue ?
+ CompletionTime.Value - StartTime.Value : null;
+
+ ///
+ /// Average transfer speed in bytes per second
+ ///
+ public double? AverageSpeed => Duration.HasValue && Duration.Value.TotalSeconds > 0 ?
+ Size / Duration.Value.TotalSeconds : null;
+
+ ///
+ /// Average transfer speed formatted as string (e.g., "15.2 MB/s")
+ ///
+ public string? AverageSpeedFormatted => AverageSpeed.HasValue ?
+ $"{SizeFormatter.FormatBytes((long)AverageSpeed.Value)}/s" : null;
+
///
/// Creates a new MinIOObjectInfo instance
///
diff --git a/src/Utils/MinIOClientWrapper.cs b/src/Utils/MinIOClientWrapper.cs
index f80f6a4..03de0df 100644
--- a/src/Utils/MinIOClientWrapper.cs
+++ b/src/Utils/MinIOClientWrapper.cs
@@ -452,6 +452,48 @@ namespace PSMinIO.Utils
}
}
+ ///
+ /// Creates a directory object in MinIO (zero-byte object with trailing slash)
+ ///
+ /// Name of the bucket
+ /// Path of the directory (should end with /)
+ /// ETag of the created directory object
+ public string CreateDirectory(string bucketName, string directoryPath)
+ {
+ if (string.IsNullOrWhiteSpace(bucketName))
+ throw new ArgumentException("Bucket name cannot be null or empty", nameof(bucketName));
+
+ if (string.IsNullOrWhiteSpace(directoryPath))
+ throw new ArgumentException("Directory path cannot be null or empty", nameof(directoryPath));
+
+ // Ensure directory path ends with /
+ if (!directoryPath.EndsWith("/"))
+ directoryPath += "/";
+
+ try
+ {
+ // Use a properly configured empty stream
+ using var emptyStream = new MemoryStream(new byte[0]);
+ emptyStream.Position = 0;
+
+ var args = new PutObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(directoryPath)
+ .WithStreamData(emptyStream)
+ .WithObjectSize(0)
+ .WithContentType("application/x-directory");
+
+ Task.Run(async () =>
+ await _client.PutObjectAsync(args, CancellationToken)).GetAwaiter().GetResult();
+
+ return ""; // Directory objects don't have meaningful ETags
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException($"Failed to create directory '{directoryPath}' in bucket '{bucketName}': {ex.Message}", ex);
+ }
+ }
+
///
/// Uploads a stream to MinIO synchronously
///
@@ -475,6 +517,12 @@ namespace PSMinIO.Utils
try
{
+ // Ensure stream is at the beginning
+ if (data.CanSeek)
+ {
+ data.Position = 0;
+ }
+
var args = new PutObjectArgs()
.WithBucket(bucketName)
.WithObject(objectName)
@@ -586,12 +634,12 @@ namespace PSMinIO.Utils
/// Uploads a file using chunked transfer with resume capability
///
/// Transfer state for resume functionality
- /// Progress reporter for updates
+ /// Thread-safe progress reporter for updates
/// Maximum retry attempts per chunk
/// MinIOObjectInfo of uploaded object or null if failed
public MinIOObjectInfo? UploadFileChunked(
ChunkedTransferState transferState,
- ChunkedCollectionProgressReporter progressReporter,
+ ThreadSafeChunkedProgressReporter progressReporter,
int maxRetries = 3)
{
if (transferState == null)
@@ -705,13 +753,13 @@ namespace PSMinIO.Utils
/// Downloads a file using chunked transfer with resume capability
///
/// Transfer state for resume functionality
- /// Progress reporter for updates
+ /// Thread-safe progress reporter for updates
/// Maximum retry attempts per chunk
/// Number of parallel chunk downloads
/// True if download succeeded, false otherwise
public bool DownloadFileChunked(
ChunkedTransferState transferState,
- ChunkedSingleFileProgressReporter progressReporter,
+ ThreadSafeChunkedProgressReporter progressReporter,
int maxRetries = 3,
int parallelDownloads = 3)
{
@@ -724,9 +772,11 @@ namespace PSMinIO.Utils
using var fileStream = new FileStream(transferState.FilePath, FileMode.Create, FileAccess.Write, FileShare.None);
fileStream.SetLength(transferState.TotalSize);
- // Get list of chunks to download
+ // Get list of chunks to download (with safety limit)
var chunksToDownload = new List();
- while (!transferState.IsComplete)
+ var maxChunks = Math.Min(transferState.TotalChunks, 10000); // Safety limit of 10,000 chunks
+
+ for (int i = 0; i < maxChunks && !transferState.IsComplete; i++)
{
var nextChunk = transferState.GetNextChunk();
if (nextChunk == null)
@@ -771,7 +821,7 @@ namespace PSMinIO.Utils
/// Transfer state
/// Chunk to download
/// Target file stream
- /// Progress reporter
+ /// Thread-safe progress reporter
/// Maximum retry attempts
/// Semaphore for controlling parallelism
/// True if chunk downloaded successfully
@@ -779,7 +829,7 @@ namespace PSMinIO.Utils
ChunkedTransferState transferState,
ChunkInfo chunk,
FileStream fileStream,
- ChunkedSingleFileProgressReporter progressReporter,
+ ThreadSafeChunkedProgressReporter progressReporter,
int maxRetries,
SemaphoreSlim semaphore)
{
@@ -795,17 +845,36 @@ namespace PSMinIO.Utils
{
using var chunkStream = new MemoryStream();
+ // Use GetObjectAsync with offset and length for proper chunked download
var getArgs = new GetObjectArgs()
.WithBucket(transferState.BucketName)
.WithObject(transferState.ObjectName)
.WithCallbackStream((stream) =>
{
- // For MinIO 5.0.0, we'll need to implement range requests differently
- // For now, let's use the basic GetObject and handle chunking at the stream level
- var buffer = new byte[chunk.Size];
- stream.Seek(chunk.StartByte, SeekOrigin.Begin);
- var bytesRead = stream.Read(buffer, 0, (int)chunk.Size);
- chunkStream.Write(buffer, 0, bytesRead);
+ // Skip to the start position and read only the chunk size
+ var buffer = new byte[8192]; // 8KB buffer
+ long totalRead = 0;
+ long skipBytes = chunk.StartByte;
+
+ // Skip to start position
+ while (skipBytes > 0)
+ {
+ var toSkip = (int)Math.Min(skipBytes, buffer.Length);
+ var skipped = stream.Read(buffer, 0, toSkip);
+ if (skipped == 0) break; // End of stream
+ skipBytes -= skipped;
+ }
+
+ // Read the chunk data
+ while (totalRead < chunk.Size)
+ {
+ var toRead = (int)Math.Min(chunk.Size - totalRead, buffer.Length);
+ var bytesRead = stream.Read(buffer, 0, toRead);
+ if (bytesRead == 0) break; // End of stream
+
+ chunkStream.Write(buffer, 0, bytesRead);
+ totalRead += bytesRead;
+ }
});
await _client.GetObjectAsync(getArgs, CancellationToken);
diff --git a/src/Utils/ThreadSafeChunkedProgressReporter.cs b/src/Utils/ThreadSafeChunkedProgressReporter.cs
new file mode 100644
index 0000000..ae26f98
--- /dev/null
+++ b/src/Utils/ThreadSafeChunkedProgressReporter.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Management.Automation;
+using System.Threading;
+
+namespace PSMinIO.Utils
+{
+ ///
+ /// Thread-safe wrapper for chunked progress reporting that can be safely called from background threads
+ ///
+ public class ThreadSafeChunkedProgressReporter
+ {
+ private readonly ThreadSafeProgressCollector _progressCollector;
+ private readonly string _operationName;
+ private readonly long _totalSize;
+ private readonly int _totalChunks;
+ private readonly DateTime _startTime;
+
+ // Activity IDs for progress hierarchy
+ private const int FileActivityId = 1;
+ private const int ChunkActivityId = 2;
+
+ // Current state (using Interlocked for long values since volatile doesn't support long)
+ private volatile int _currentChunk = 0;
+ private long _currentChunkSize = 0;
+ private long _totalBytesTransferred = 0;
+ private volatile string _currentFileName = string.Empty;
+
+ public ThreadSafeChunkedProgressReporter(
+ PSCmdlet cmdlet,
+ long totalSize,
+ int totalChunks,
+ string operationName)
+ {
+ _progressCollector = new ThreadSafeProgressCollector(cmdlet);
+ _totalSize = totalSize;
+ _totalChunks = totalChunks;
+ _operationName = operationName;
+ _startTime = DateTime.UtcNow;
+ }
+
+ ///
+ /// Starts a new file operation (thread-safe)
+ ///
+ public void StartNewFile(string fileName, long fileSize, int totalChunks)
+ {
+ _currentFileName = fileName;
+ Interlocked.Exchange(ref _totalBytesTransferred, 0);
+ _currentChunk = 0;
+
+ _progressCollector.QueueVerboseMessage("Starting {0} of file: {1} ({2})",
+ _operationName.ToLower(), fileName, SizeFormatter.FormatBytes(fileSize));
+
+ _progressCollector.QueueProgressUpdate(
+ FileActivityId,
+ $"{_operationName} File",
+ $"{_operationName}: {fileName}",
+ 0);
+ }
+
+ ///
+ /// Starts a new chunk operation (thread-safe)
+ ///
+ public void StartNewChunk(int chunkNumber, long chunkSize)
+ {
+ _currentChunk = chunkNumber;
+ Interlocked.Exchange(ref _currentChunkSize, chunkSize);
+
+ _progressCollector.QueueVerboseMessage("File {0}: Starting chunk {1}/{2} ({3})",
+ _currentFileName, chunkNumber, _totalChunks, SizeFormatter.FormatBytes(chunkSize));
+
+ _progressCollector.QueueProgressUpdate(
+ ChunkActivityId,
+ "Current Chunk",
+ $"Chunk {chunkNumber}/{_totalChunks}",
+ 0,
+ FileActivityId);
+ }
+
+ ///
+ /// Updates chunk progress (thread-safe)
+ ///
+ public void UpdateChunkProgress(long bytesTransferred)
+ {
+ var newTotal = Interlocked.Add(ref _totalBytesTransferred, bytesTransferred);
+ var currentChunkSize = Interlocked.Read(ref _currentChunkSize);
+
+ var chunkPercent = currentChunkSize > 0 ?
+ (int)((double)bytesTransferred / currentChunkSize * 100) : 100;
+ var filePercent = _totalSize > 0 ?
+ (int)((double)newTotal / _totalSize * 100) : 100;
+
+ // Update chunk progress
+ _progressCollector.QueueProgressUpdate(
+ ChunkActivityId,
+ "Current Chunk",
+ $"Chunk {_currentChunk}/{_totalChunks} - {SizeFormatter.FormatBytes(bytesTransferred)}/{SizeFormatter.FormatBytes(_currentChunkSize)}",
+ Math.Min(chunkPercent, 100),
+ FileActivityId);
+
+ // Update file progress
+ _progressCollector.QueueProgressUpdate(
+ FileActivityId,
+ $"{_operationName} File",
+ $"{_operationName}: {_currentFileName} - {SizeFormatter.FormatBytes(newTotal)}/{SizeFormatter.FormatBytes(_totalSize)}",
+ Math.Min(filePercent, 100));
+ }
+
+ ///
+ /// Completes the current chunk (thread-safe)
+ ///
+ public void CompleteChunk(string? chunkETag = null)
+ {
+ _progressCollector.QueueVerboseMessage("File {0}: Completed chunk {1}/{2}{3}",
+ _currentFileName, _currentChunk, _totalChunks,
+ !string.IsNullOrEmpty(chunkETag) ? $" - ETag: {chunkETag}" : "");
+
+ _progressCollector.QueueProgressCompletion(ChunkActivityId, "Current Chunk", FileActivityId);
+ }
+
+ ///
+ /// Completes the current file (thread-safe)
+ ///
+ public void CompleteFile()
+ {
+ var elapsed = DateTime.UtcNow - _startTime;
+ _progressCollector.QueueVerboseMessage("File {0}: {1} completed in {2} - Total size: {3}",
+ _currentFileName, _operationName.ToLower(), elapsed.ToString(@"hh\:mm\:ss"),
+ SizeFormatter.FormatBytes(_totalSize));
+
+ _progressCollector.QueueProgressCompletion(FileActivityId, $"{_operationName} File");
+ }
+
+ ///
+ /// Reports a chunk error (thread-safe)
+ ///
+ public void ReportChunkError(Exception error, int retryAttempt, int maxRetries)
+ {
+ _progressCollector.QueueVerboseMessage("File {0}: Chunk {1}/{2} failed (attempt {3}/{4}): {5}",
+ _currentFileName, _currentChunk, _totalChunks, retryAttempt, maxRetries, error.Message);
+ }
+
+ ///
+ /// Processes all queued updates from the main thread (must be called from main thread)
+ ///
+ public void ProcessQueuedUpdates()
+ {
+ _progressCollector.ProcessQueuedUpdates();
+ }
+
+ ///
+ /// Completes all operations and processes final updates (must be called from main thread)
+ ///
+ public void Complete()
+ {
+ _progressCollector.Complete();
+ }
+
+ ///
+ /// Gets the number of pending updates
+ ///
+ public int PendingUpdates => _progressCollector.PendingProgressUpdates + _progressCollector.PendingVerboseMessages;
+ }
+}
diff --git a/src/Utils/ThreadSafeProgressCollector.cs b/src/Utils/ThreadSafeProgressCollector.cs
new file mode 100644
index 0000000..b34af0a
--- /dev/null
+++ b/src/Utils/ThreadSafeProgressCollector.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Concurrent;
+using System.Management.Automation;
+using System.Threading;
+
+namespace PSMinIO.Utils
+{
+ ///
+ /// Thread-safe progress data collector that accumulates progress updates from background threads
+ /// and allows the main thread to safely report them to PowerShell
+ ///
+ public class ThreadSafeProgressCollector
+ {
+ private readonly PSCmdlet _cmdlet;
+ private readonly ConcurrentQueue _progressQueue = new();
+ private readonly ConcurrentQueue _verboseQueue = new();
+ private readonly object _lockObject = new();
+ private volatile bool _isCompleted = false;
+
+ public ThreadSafeProgressCollector(PSCmdlet cmdlet)
+ {
+ _cmdlet = cmdlet ?? throw new ArgumentNullException(nameof(cmdlet));
+ }
+
+ ///
+ /// Queues a progress update from a background thread
+ ///
+ public void QueueProgressUpdate(int activityId, string activity, string statusDescription, int percentComplete, int parentActivityId = -1)
+ {
+ if (_isCompleted) return;
+
+ _progressQueue.Enqueue(new ProgressUpdate
+ {
+ ActivityId = activityId,
+ Activity = activity,
+ StatusDescription = statusDescription,
+ PercentComplete = percentComplete,
+ ParentActivityId = parentActivityId,
+ Timestamp = DateTime.UtcNow
+ });
+ }
+
+ ///
+ /// Queues a progress completion from a background thread
+ ///
+ public void QueueProgressCompletion(int activityId, string activity, int parentActivityId = -1)
+ {
+ if (_isCompleted) return;
+
+ _progressQueue.Enqueue(new ProgressUpdate
+ {
+ ActivityId = activityId,
+ Activity = activity,
+ StatusDescription = "Completed",
+ PercentComplete = 100,
+ ParentActivityId = parentActivityId,
+ IsCompleted = true,
+ Timestamp = DateTime.UtcNow
+ });
+ }
+
+ ///
+ /// Queues a verbose message from a background thread
+ ///
+ public void QueueVerboseMessage(string message, params object[] args)
+ {
+ if (_isCompleted) return;
+
+ _verboseQueue.Enqueue(new VerboseMessage
+ {
+ Message = args.Length > 0 ? string.Format(message, args) : message,
+ Timestamp = DateTime.UtcNow
+ });
+ }
+
+ ///
+ /// Processes all queued updates from the main thread (safe to call PowerShell methods)
+ ///
+ public void ProcessQueuedUpdates()
+ {
+ // Process verbose messages first
+ while (_verboseQueue.TryDequeue(out var verboseMessage))
+ {
+ MinIOLogger.WriteVerbose(_cmdlet, verboseMessage.Message);
+ }
+
+ // Process progress updates
+ while (_progressQueue.TryDequeue(out var progressUpdate))
+ {
+ var progressRecord = new ProgressRecord(
+ progressUpdate.ActivityId,
+ progressUpdate.Activity,
+ progressUpdate.StatusDescription)
+ {
+ PercentComplete = progressUpdate.PercentComplete
+ };
+
+ if (progressUpdate.ParentActivityId >= 0)
+ {
+ progressRecord.ParentActivityId = progressUpdate.ParentActivityId;
+ }
+
+ if (progressUpdate.IsCompleted)
+ {
+ progressRecord.RecordType = ProgressRecordType.Completed;
+ }
+
+ _cmdlet.WriteProgress(progressRecord);
+ }
+ }
+
+ ///
+ /// Marks the collector as completed (no more updates will be accepted)
+ ///
+ public void Complete()
+ {
+ _isCompleted = true;
+
+ // Process any remaining updates
+ ProcessQueuedUpdates();
+ }
+
+ ///
+ /// Gets the number of pending progress updates
+ ///
+ public int PendingProgressUpdates => _progressQueue.Count;
+
+ ///
+ /// Gets the number of pending verbose messages
+ ///
+ public int PendingVerboseMessages => _verboseQueue.Count;
+
+ ///
+ /// Progress update data structure
+ ///
+ private class ProgressUpdate
+ {
+ public int ActivityId { get; set; }
+ public string Activity { get; set; } = string.Empty;
+ public string StatusDescription { get; set; } = string.Empty;
+ public int PercentComplete { get; set; }
+ public int ParentActivityId { get; set; } = -1;
+ public bool IsCompleted { get; set; }
+ public DateTime Timestamp { get; set; }
+ }
+
+ ///
+ /// Verbose message data structure
+ ///
+ private class VerboseMessage
+ {
+ public string Message { get; set; } = string.Empty;
+ public DateTime Timestamp { get; set; }
+ }
+ }
+}
diff --git a/src/Utils/ThreadSafeResultCollector.cs b/src/Utils/ThreadSafeResultCollector.cs
new file mode 100644
index 0000000..7d76005
--- /dev/null
+++ b/src/Utils/ThreadSafeResultCollector.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Management.Automation;
+using PSMinIO.Models;
+
+namespace PSMinIO.Utils
+{
+ ///
+ /// Thread-safe result collector that accumulates results from background threads
+ /// and allows the main thread to safely output them via WriteObject
+ ///
+ public class ThreadSafeResultCollector
+ {
+ private readonly PSCmdlet _cmdlet;
+ private readonly ConcurrentQueue