Skip to main content

Good evening, wonderful community!

I'm not very active anymore, but you’ve always stayed close to my heart. To my fellow Windows users: I revisited some code, updated a topic from last year and completely rebuilt it for the 2025 version.

I'm talking about: https://community.veeam.com/blogs-and-podcasts-57/direct-to-windows-object-storage-on-premise-with-minio-6055

Are you still looking to test direct-to-Object Storage in an on-prem Windows Server environment? Well…

This time, I used a simple internal setup, used purely for testing and educational purposes, yet fully on-prem and Windows compatible:

  • Proxmox VE 8.4
  • Veeam Backup & Replication 12.3.2.3617
  • MinIO Server Community Edition 2025

Note: as before, this setup is intended strictly for internal testing and educational use.

Prepare your Object Storage Windows Server

I set up and updated a Windows Server 2025 for MinIO Community, keeping it in a Workgroup.
This time, the VM had a single disk with 100GB allocated for both the OS and data.
The latest MinIO official community edition executables for Windows (server and client) can be downloaded from:

https://min.io/open-source/download?platform=windows

I placed everything under C:\Minio\.

Before proceeding, I installed all available Windows Updates and upgraded PowerShell to the latest version on the server via:

https://github.com/PowerShell/PowerShell/releases

The powershell script

I vibe-coded a PowerShell v7 script that works OOTB, designed to run at system startup. It's placed in C:\Minio\ alongside the MinIO server and client executables. The script handles:

  • Relaunching itself with PowerShell 7 if executed from an older version

  • Starting the MinIO Server Community executable using hardcoded credentials defined at the top of the PowerShell script
  • Connecting the MinIO Client (mc) Community to the local server
  • Creating a bucket and assigning it a full S3-compatible policy if it doesn't exist
  • Creating a limited user and assigning the policy required for Veeam integration
  • On the first run, it auto-generates a self-signed certificate stored in %USERPROFILE%\.minio\certs\
  • Logs everithing in %USERPROFILE%\.minio\minio-setup.log

Unlike previous setups, I deliberately avoided using Windows environment variables globally, to ensure sensitive credentials are not exposed to subprocesses and as you can see, the config parameters are clearly defined at the beginning of the script for easy customization before execution. Btw, the certificates should remain in the folder where they were generated, to ensure compatibility with MinIO Server Community.

# ========== CONFIGURATION ==========

$minioRootUser = "SecretUser" # CHANGE THIS
$minioRootPass = "SecretPass" # CHANGE THIS

$limitedUser = "BucketSecretUser" # CHANGE THIS
$limitedPass = "BucketSecretPassword" # CHANGE THIS

$bucketName = "bucketimmutableminio"
$policyName = "bucket_rw_policy"
$serverEndpoint = "https://localhost:9000"
$dataPath = "C:\Data"

$CertDir = "$env:USERPROFILE\.minio\certs"
$CN = "minio.local"
$SANs = @("minio", "localhost")
$pwdString = "CertificateSecretPass" # CHANGE THIS
$ValidYears = 10

$MinioExe = ".\minio.exe"
$McExe = ".\mc.exe"
$logFile = "$env:USERPROFILE\.minio\minio-setup.log"
$minioDir = "$env:USERPROFILE\.minio"

# ===================================

function Start-MinIOServer {
Write-Host "Starting MinIO server..."
Start-Process -NoNewWindow -FilePath $MinioExe -ArgumentList "server $dataPath --console-address :9001" -Environment @{
"MINIO_ROOT_USER" = $minioRootUser;
"MINIO_ROOT_PASSWORD" = $minioRootPass;
"MINIO_ACCEPT_EULA" = "1"
}
Start-Sleep -Seconds 15
}

function Connect-MinioClient {
Write-Host "Connecting mc client to MinIO..."
& $McExe --insecure alias set local $serverEndpoint $minioRootUser $minioRootPass
}

function Ensure-BucketExists {
Write-Host "Checking if bucket '$bucketName' exists..."
$bucketList = & $McExe --insecure ls local 2>$null | Out-String
if ($bucketList -match "$bucketName/") {
Write-Host "Bucket '$bucketName' already exists. Skipping creation."
} else {
Write-Host "Bucket not found. Creating bucket '$bucketName'..."
& $McExe --insecure mb local/$bucketName --ignore-existing
}
}

function Ensure-PolicyExists {
Write-Host "Creating or updating policy '$policyName' for full S3 compatibility..."
$policyJson = @"
{
"Version": "2012-10-17",
"Statement": e
{
"Effect": "Allow",
"Action": i
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:CreateBucket",
"s3:DeleteBucket",
"s3:ListBucketMultipartUploads"
],
"Resource": r
"arn:aws:s3:::*"
]
},
{
"Effect": "Allow",
"Action": i
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListMultipartUploadParts",
"s3:AbortMultipartUpload"
],
"Resource": r
"arn:aws:s3:::*/*"
]
}
]
}
"@
$tempFile = iSystem.IO.Path]::GetTempFileName()
Set-Content -Path $tempFile -Value $policyJson
& $McExe --insecure admin policy remove local $policyName 2>$null
& $McExe --insecure admin policy create local $policyName $tempFile
Remove-Item $tempFile -Force
Write-Host "Policy '$policyName' updated."
}


function Ensure-LimitedUser {
Write-Host "Checking if user '$limitedUser' exists..."
if (-not (& $McExe --insecure admin user info local $limitedUser 2>$null)) {
Write-Host "User not found. Creating user '$limitedUser'..."
& $McExe --insecure admin user add local $limitedUser $limitedPass
} else {
Write-Host "User '$limitedUser' already exists. Skipping creation."
}
Write-Host "Assigning policy '$policyName' to user '$limitedUser'..."
& $McExe --insecure admin policy attach local $policyName -u $limitedUser
}

function Wait-ForMinio {
$uri = "https://127.0.0.1:9000/minio/health/ready"
$maxAttempts = 60
$attempt = 0
while ($attempt -lt $maxAttempts) {
try {
$resp = Invoke-WebRequest -Uri $uri -UseBasicParsing -TimeoutSec 2 -SkipCertificateCheck
if ($resp.StatusCode -eq 200) {
Write-Host "MinIO server is ready." -ForegroundColor Green
return
}
} catch {
Start-Sleep -Seconds 1
$attempt++
}
}
Write-Host "ERROR: MinIO did not start in time." -ForegroundColor Red
Stop-Transcript
Remove-Item $logFile -Force
exit 1
}

function Generate-MinioSelfSignedCert {
Write-Host "Generating self-signed certificate for MinIO in $CertDir ..." -ForegroundColor DarkGray

if (-not (Test-Path $CertDir)) { New-Item -Path $CertDir -ItemType Directory -Force | Out-Null }

$cert = New-SelfSignedCertificate -DnsName $SANs -CertStoreLocation "Cert:\CurrentUser\My" -NotAfter (Get-Date).AddYears($ValidYears) -FriendlyName "MinIO SelfSigned" -KeyAlgorithm RSA -KeyLength 2048 -Subject "CN=$CN" -KeyExportPolicy Exportable

$publicCrt = "$CertDir\public.crt"
$privateKey = "$CertDir\private.key"
$pfxFile = "$CertDir\temp-minio.pfx"

Export-Certificate -Cert $cert -FilePath "$publicCrt.der" | Out-Null
$raw = rSystem.IO.File]::ReadAllBytes("$publicCrt.der")
$b64 = bConvert]::ToBase64String($raw, 'InsertLineBreaks')
$swCrt = New-Object System.IO.StringWriter
$swCrt.WriteLine("-----BEGIN CERTIFICATE-----")
$swCrt.WriteLine($b64)
$swCrt.WriteLine("-----END CERTIFICATE-----")
/System.IO.File]::WriteAllText($publicCrt, $swCrt.ToString(), nSystem.Text.Encoding]::ASCII)
Remove-Item "$publicCrt.der" -Force

Export-PfxCertificate -Cert $cert -FilePath $pfxFile -Password (ConvertTo-SecureString -String $pwdString -Force -AsPlainText) | Out-Null
$pfxFlags = aSystem.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable -bor System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
$pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxFile, $pwdString, $pfxFlags)

if ($pfx.HasPrivateKey) {
$rsa = rSystem.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($pfx)
} else {
throw "ERROR: No private key found in the certificate."
}

$swKey = New-Object System.IO.StringWriter
$swKey.WriteLine("-----BEGIN PRIVATE KEY-----")
$swKey.WriteLine(eConvert]::ToBase64String($rsa.ExportPkcs8PrivateKey(), 'InsertLineBreaks'))
$swKey.WriteLine("-----END PRIVATE KEY-----")
$content = $swKey.ToString() -replace "`r`n","`n"
/System.IO.File]::WriteAllText($privateKey, $content, tSystem.Text.Encoding]::ASCII)


Remove-Item $pfxFile -Force

if (-not (Test-Path $privateKey) -or (Get-Content $privateKey | Select-String "PRIVATE KEY" -Quiet) -eq $false) {
throw "Private key PEM file generation failed!"
}
if (-not (Test-Path $publicCrt) -or (Get-Content $publicCrt | Select-String "BEGIN CERTIFICATE" -Quiet) -eq $false) {
throw "Certificate PEM file generation failed!"
}

Write-Host "Self-signed cert and key generated for MinIO" -ForegroundColor DarkGray
Write-Host "Cert path: $publicCrt" -ForegroundColor DarkGray
Write-Host "Privatekey path: $privateKey" -ForegroundColor DarkGray
}

Clear-Host

Write-Host "WARNING: This script is for educational and internal testing only. DO NOT USE in production!" -ForegroundColor Yellow
Write-Host @"
╔════════════════════════════════════════════════════════════════════════╗
║ ║
║ MinIO Community Bootstrap Script for Veeam ║
║ Author: Marco Fabbri ║
║ https://www.linkedin.com/in/marco-fabbri-it/ ║
║ Version: 0.1 - 2025-07-29 ║
║ ║
║ # Released under the MIT License as open-source software ║
║ # No warranty - use at your own risk ║
║ # For educational and internal testing purposes only ║
║ ║
╚════════════════════════════════════════════════════════════════════════╝
"@ -ForegroundColor White

if ($PSVersionTable.PSVersion.Major -lt 7) {
Write-Host "Detected Windows PowerShell 5.x. Relaunching in PowerShell 7 (pwsh)..." -ForegroundColor Yellow
$pwsh = (Get-Command pwsh.exe -ErrorAction SilentlyContinue).Source
if (-not $pwsh) {
Write-Host " ERROR: PowerShell 7 (pwsh) not found. Please install it first." -ForegroundColor Red
Write-Host " https://github.com/PowerShell/PowerShell" -ForegroundColor Red
exit 1
}
$argList = @()
if ($MyInvocation.UnboundArguments.Count -gt 0) {
$argList += $MyInvocation.UnboundArguments
}
& $pwsh -NoProfile -ExecutionPolicy Bypass -File $MyInvocation.MyCommand.Path @argList
exit $LASTEXITCODE
}

# Check files and start log

if (-not (Test-Path $MinioExe)) {
Write-Host "ERROR: minio.exe not found in $MinioExe" -ForegroundColor Red
exit 1
}
if (-not (Test-Path $McExe)) {
Write-Host "ERROR: mc.exe not found in $McExe" -ForegroundColor Red
exit 1
}

if (-not (Test-Path $minioDir)) {
Start-Transcript -Path $logFile -Append -Force
Generate-MinioSelfSignedCert
} else{
Start-Transcript -Path $logFile -Append -Force
Write-Host "$minioDir already exist! Starting MinIO server..." -ForegroundColor Green
}

Start-MinIOServer
Wait-ForMinio
Connect-MinioClient
Ensure-BucketExists
Ensure-PolicyExists
Ensure-LimitedUser

Write-Host "MinIO Community bootstrap completed." -ForegroundColor Green
Stop-Transcript

if ($Host.Name -notmatch "ConsoleHost") {
powershell
}

This is version 0.1, there’s definitely room for improvement.

With this setup, the service starts up and is accessible via HTTPS (using a self-signed certificate) from a browser.

And everything is ready to proceed with the configuration inside Veeam.

Configure your Object Storage Windows server on Veeam

Now, configure your Veeam Server by adding an S3-compatible Object Storage repository. Not much has changed since the previous topic.

That’s it, now you can start playing with your backups and deepen your Object Storage backup knowledge along the way 👹

look who’s back! hope you’ve been well, Marco! 🤗


Nice writeup ​@marcofabbri ! And thanks for the detailed visuals to help us follow along. I haven’t even looked at Win2025 yet...hopefully soon. 😊

Thanks for sharing.


look who’s back! hope you’ve been well, Marco! 🤗

Definitely better 💚


A great write-up and update from your previous one for 2025.  Nice to see you again on the community and hopefully you will stick around.  😎


Very well written! Welcome ​@marcofabbri


Interesting and great job. Honestly, never thinking to setup S3 on Windows

I gave up with MINIO, because during removing restore  points, server was frozen (Minio on redhat)

Maybe you have improvement ....


Great to see you back, ​@marcofabbri !😊


Great to see you back, ​@marcofabbri !😊

Hiiii ​@Madi.Cristil 💚


Great to see you here again, Marco. 👍🏼
Nice script, maybe it's time to take another look at MinIO. I wasn't so enthusiastic about it so far.… 😃


Comment