177 lines
6.3 KiB
PowerShell
177 lines
6.3 KiB
PowerShell
param(
|
||
[Parameter(Mandatory=$true)]
|
||
[string]$InputFile
|
||
)
|
||
|
||
# Read meta JSON
|
||
try {
|
||
$json = Get-Content -Raw -Path $InputFile -ErrorAction Stop
|
||
$meta = $json | ConvertFrom-Json
|
||
} catch {
|
||
Write-Output (@{ success = $false; message = "Failed to read/parse meta JSON: $($_.Exception.Message)" } | ConvertTo-Json -Compress)
|
||
exit 1
|
||
}
|
||
|
||
$csvFile = [string]$meta.input_file
|
||
# PowerShell 5.1 doesn't support the null-coalescing operator '??'.
|
||
# Use an explicit check here to set the default delimiter.
|
||
$delimiter = [string]$meta.delimiter
|
||
if ([string]::IsNullOrWhiteSpace($delimiter)) { $delimiter = ',' }
|
||
$hasHeader = [bool]($meta.has_header -as [bool])
|
||
$dryRun = [bool]($meta.dry_run -as [bool])
|
||
$defaultOu = [string]$meta.default_ou
|
||
|
||
|
||
if (-not (Test-Path -Path $csvFile)) {
|
||
Write-Output (@{ success = $false; message = "CSV file not found: $csvFile" } | ConvertTo-Json -Compress)
|
||
exit 1
|
||
}
|
||
|
||
# Ensure ActiveDirectory module is available
|
||
try {
|
||
Import-Module ActiveDirectory -ErrorAction Stop
|
||
} catch {
|
||
Write-Output (@{ success = $false; message = "ActiveDirectory PowerShell module not available: $($_.Exception.Message)" } | ConvertTo-Json -Compress)
|
||
exit 1
|
||
}
|
||
|
||
# Read CSV
|
||
try {
|
||
if ($hasHeader) {
|
||
$items = Import-Csv -Path $csvFile -Delimiter $delimiter -ErrorAction Stop
|
||
} else {
|
||
# Use default headers
|
||
$headers = 'samaccountname','displayname','mail','password','groups'
|
||
$items = Import-Csv -Path $csvFile -Delimiter $delimiter -Header $headers -ErrorAction Stop
|
||
}
|
||
} catch {
|
||
Write-Output (@{ success = $false; message = "Failed to parse CSV: $($_.Exception.Message)" } | ConvertTo-Json -Compress)
|
||
exit 1
|
||
}
|
||
|
||
$actor = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||
$results = @()
|
||
$successCount = 0
|
||
$failCount = 0
|
||
|
||
foreach ($row in $items) {
|
||
$sam = $row.samaccountname
|
||
$display = $row.displayname
|
||
$mail = $row.mail
|
||
$pass = $row.password
|
||
$groups = $row.groups
|
||
|
||
if ([string]::IsNullOrWhiteSpace($ou) -and -not [string]::IsNullOrWhiteSpace($defaultOu)) {
|
||
$ou = $defaultOu
|
||
}
|
||
|
||
# Password hint text (German)
|
||
$passwordHint = @"
|
||
Das sollten die Anforderungen an das Passwort sein:
|
||
|
||
- mindestens 7 Zeichen
|
||
- darf den Benutzer-/Accountnamen nicht enthalten (bzw. keine zu großen Teile davon)
|
||
- muss Zeichen aus mindestens 3 von 4 Kategorien enthalten:
|
||
1) Großbuchstaben (A–Z)
|
||
2) Kleinbuchstaben (a–z)
|
||
3) Ziffern (0–9)
|
||
4) Sonderzeichen (alles, was kein Buchstabe/Zahl ist, z. B. ! ? # _ - . , usw.)
|
||
"@
|
||
|
||
function Test-PasswordRequirements {
|
||
param(
|
||
[string]$Password,
|
||
[string]$SamAccountName
|
||
)
|
||
|
||
$errors = @()
|
||
|
||
if ([string]::IsNullOrEmpty($Password) -or $Password.Length -lt 7) {
|
||
$errors += 'Passwort muss mindestens 7 Zeichen lang sein.'
|
||
}
|
||
|
||
# Categories: uppercase, lowercase, digit, special
|
||
$categories = 0
|
||
if ($Password -match '[A-Z]') { $categories++ }
|
||
if ($Password -match '[a-z]') { $categories++ }
|
||
if ($Password -match '\d') { $categories++ }
|
||
if ($Password -match '[^A-Za-z0-9]') { $categories++ }
|
||
if ($categories -lt 3) {
|
||
$errors += 'Passwort muss Zeichen aus mindestens 3 von 4 Kategorien enthalten (Groß, Klein, Ziffern, Sonderzeichen).'
|
||
}
|
||
|
||
# Check for username inclusion or large parts of it (case-insensitive)
|
||
if (-not [string]::IsNullOrEmpty($SamAccountName)) {
|
||
$pwLower = $Password.ToLowerInvariant()
|
||
$samLower = $SamAccountName.ToLowerInvariant()
|
||
|
||
if ($pwLower -like "*${samLower}*") {
|
||
$errors += 'Passwort darf den Benutzernamen nicht enthalten.'
|
||
} else {
|
||
# Check for substrings of username of length >= 4
|
||
$minLen = 4
|
||
if ($samLower.Length -ge $minLen) {
|
||
for ($len = $minLen; $len -le $samLower.Length; $len++) {
|
||
for ($start = 0; $start -le $samLower.Length - $len; $start++) {
|
||
$sub = $samLower.Substring($start, $len)
|
||
if ($pwLower -like "*${sub}*") {
|
||
$errors += 'Passwort darf keine größeren Teile des Benutzernamens enthalten.'
|
||
break 2
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return $errors
|
||
}
|
||
|
||
if ([string]::IsNullOrWhiteSpace($sam) -or [string]::IsNullOrWhiteSpace($pass)) {
|
||
$results += @{ sam = $sam; success = $false; message = 'Missing samaccountname or password' }
|
||
$failCount++
|
||
continue
|
||
}
|
||
|
||
# Validate password according to requirements, also during dry run so issues are visible early
|
||
$pwErrors = Test-PasswordRequirements -Password $pass -SamAccountName $sam
|
||
if ($pwErrors.Count -gt 0) {
|
||
$results += @{ sam = $sam; success = $false; message = ('Invalid password: ' + ($pwErrors -join ' | ')); hint = $passwordHint }
|
||
$failCount++
|
||
continue
|
||
}
|
||
|
||
if ($dryRun) {
|
||
$results += @{ sam = $sam; success = $true; message = 'DRY RUN: would create (password validated)' }
|
||
$successCount++
|
||
continue
|
||
}
|
||
|
||
try {
|
||
$props = @{ Name = if ($display -and $display -ne '') { $display } else { $sam }; SamAccountName = $sam; Enabled = $true }
|
||
if ($mail -and $mail -ne '') { $props['EmailAddress'] = $mail }
|
||
if ($ou -and $ou -ne '') { $props['Path'] = $ou }
|
||
$securePass = ConvertTo-SecureString $pass -AsPlainText -Force
|
||
$props['AccountPassword'] = $securePass
|
||
|
||
New-ADUser @props -ErrorAction Stop
|
||
|
||
if ($groups -and $groups -ne '') {
|
||
$groupList = $groups -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }
|
||
foreach ($g in $groupList) {
|
||
Add-ADGroupMember -Identity $g -Members $sam -ErrorAction Stop
|
||
}
|
||
}
|
||
|
||
$results += @{ sam = $sam; success = $true; message = 'Created' }
|
||
$successCount++
|
||
} catch {
|
||
$results += @{ sam = $sam; success = $false; message = $_.Exception.Message }
|
||
$failCount++
|
||
}
|
||
}
|
||
|
||
$output = @{ success = $failCount -eq 0; message = "Created $successCount users, $failCount failures"; details = $results; actor = $actor }
|
||
Write-Output ($output | ConvertTo-Json -Compress)
|
||
exit 0
|