#requires -Version 5.1 <# .SYNOPSIS Generates a stalwart-cli apply NDJSON plan (Domain + Account/User + aliases) from a migration accounts.csv file. #> param( [Parameter(Mandatory = $true)][string]$CsvPath, [Parameter(Mandatory = $true)][string]$Domain, [Parameter(Mandatory = $true)][string]$OutFile ) $rows = @(Import-Csv -Path $CsvPath) $missing = $rows | Where-Object { -not $_.new_password } if ($missing) { throw "accounts.csv has $($missing.Count) row(s) with no new_password set. Fill them in before generating the plan." } $lines = New-Object System.Collections.Generic.List[string] # Domain is upserted so the plan works whether or not the wizard already created it. $domainOp = [ordered]@{ "@type" = "upsert" object = "Domain" matchOn = @("name") value = @{ "dom" = @{ name = $Domain } } } $lines.Add(($domainOp | ConvertTo-Json -Compress -Depth 10)) $accounts = [ordered]@{} foreach ($row in $rows) { $localPart = $row.email.Split('@')[0] $clientId = "acct-$localPart" $aliases = @{} if ($row.aliases) { foreach ($aliasLocal in ($row.aliases -split ';' | Where-Object { $_ })) { $aliases["alias-$aliasLocal"] = @{ name = $aliasLocal; domainId = "#dom" } } } $quotas = @{} if ($row.quota_mb) { $quotas["maxDiskQuota"] = [int64]$row.quota_mb * 1MB } $accounts[$clientId] = @{ "@type" = "User" name = $localPart domainId = "#dom" credentials = @{ "0" = @{ "@type" = "Password"; secret = $row.new_password } } memberGroupIds = @{} roles = @{ "@type" = "User" } permissions = @{ "@type" = "Inherit" } quotas = $quotas aliases = $aliases encryptionAtRest = @{ "@type" = "Disabled" } } } $accountOp = [ordered]@{ "@type" = "upsert" object = "Account" matchOn = @("name") value = $accounts } $lines.Add(($accountOp | ConvertTo-Json -Compress -Depth 10)) $lines | Set-Content -Path $OutFile -Encoding utf8 Write-Host "Wrote $($lines.Count) operation(s) covering $($rows.Count) account(s) to $OutFile"