Files
2026-06-18 08:30:25 +01:00

345 lines
13 KiB
PowerShell

<#
.SYNOPSIS
GUI tool to import users from a CSV and add them to an Active Directory group.
.DESCRIPTION
Opens a Windows Forms GUI. The user selects a CSV file (Username or
SamAccountName column) and picks a target AD group, then clicks Import.
Results and a log are shown in the output panel.
.PARAMETER LogPath
Optional path for the log file. Defaults to script directory with a timestamp.
#>
param (
[string]$LogPath = (Join-Path $PSScriptRoot ("import_log_" + (Get-Date -Format 'yyyyMMdd_HHmmss') + ".txt"))
)
# ---------------------------------------------------------------------------
# Ensure ActiveDirectory module is available
# ---------------------------------------------------------------------------
if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) {
# Installing RSAT requires elevation - check before attempting
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Error "ActiveDirectory module not found. Please run this script as Administrator to install it automatically, or install RSAT manually."
exit 1
}
Write-Host "ActiveDirectory module not found. Attempting to install..." -ForegroundColor Yellow
$os = (Get-CimInstance Win32_OperatingSystem).Caption
if ($os -match "Server") {
try {
Import-Module ServerManager -ErrorAction Stop
Add-WindowsFeature RSAT-AD-PowerShell -ErrorAction Stop | Out-Null
Write-Host "ActiveDirectory module installed via Add-WindowsFeature." -ForegroundColor Green
} catch {
Write-Error "Failed to install ActiveDirectory module on Server: $_"
exit 1
}
} else {
try {
$feature = Get-WindowsCapability -Name "Rsat.ActiveDirectory*" -Online -ErrorAction Stop |
Where-Object State -ne Installed |
Select-Object -First 1
if ($feature) {
Add-WindowsCapability -Name $feature.Name -Online -ErrorAction Stop | Out-Null
Write-Host "ActiveDirectory module installed via Add-WindowsCapability." -ForegroundColor Green
} else {
Write-Host "RSAT ActiveDirectory feature already present." -ForegroundColor Green
}
} catch {
Write-Error "Failed to install ActiveDirectory module on Windows client: $_"
exit 1
}
}
}
try {
Import-Module ActiveDirectory -ErrorAction Stop
} catch {
Write-Error "ActiveDirectory module could not be loaded: $_"
exit 1
}
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
function Write-Log {
param([string]$Message, [string]$Level = 'INFO')
$entry = "[{0}] [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
$entry | Out-File -FilePath $LogPath -Append -Encoding UTF8
$color = switch ($Level) {
'ERROR' { [System.Drawing.Color]::Salmon }
'WARN' { [System.Drawing.Color]::Goldenrod }
default { [System.Drawing.Color]::White }
}
Write-OutputLine $entry $color
}
function Write-OutputLine {
param([string]$Text, [System.Drawing.Color]$Color = [System.Drawing.Color]::White)
$script:rtbOutput.SelectionStart = $script:rtbOutput.TextLength
$script:rtbOutput.SelectionLength = 0
$script:rtbOutput.SelectionColor = $Color
$script:rtbOutput.AppendText("$Text`n")
$script:rtbOutput.ScrollToCaret()
[System.Windows.Forms.Application]::DoEvents()
}
function Start-Import {
param([string]$CsvPath, [string]$GroupName)
$script:btnImport.Enabled = $false
$script:rtbOutput.Clear()
try {
$group = Get-ADGroup -Identity $GroupName -ErrorAction Stop
Write-Log "Target group: $($group.DistinguishedName)"
} catch {
Write-Log "Group '$GroupName' not found: $_" -Level ERROR
$script:btnImport.Enabled = $true
return
}
try {
$csv = Import-Csv -Path $CsvPath -ErrorAction Stop
} catch {
Write-Log "Failed to read CSV: $_" -Level ERROR
$script:btnImport.Enabled = $true
return
}
if ($csv.Count -eq 0) {
Write-Log "CSV is empty." -Level WARN
$script:btnImport.Enabled = $true
return
}
$header = $csv[0].PSObject.Properties.Name |
Where-Object { $_ -in @('Username', 'SamAccountName') } |
Select-Object -First 1
if (-not $header) {
$found = $csv[0].PSObject.Properties.Name -join ', '
Write-Log "CSV must contain a 'Username' or 'SamAccountName' column. Found: $found" -Level ERROR
$script:btnImport.Enabled = $true
return
}
Write-Log "Processing $($csv.Count) row(s) using column '$header'."
$stats = @{ Added = 0; Skipped = 0; NotFound = 0; Errors = 0 }
foreach ($row in $csv) {
$sam = $row.$header.Trim()
if ([string]::IsNullOrWhiteSpace($sam)) {
Write-Log "Empty username - skipping." -Level WARN
$stats.Skipped++
continue
}
try {
$user = Get-ADUser -Identity $sam -ErrorAction Stop
} catch {
Write-Log "User '$sam' not found in AD - skipping." -Level WARN
$stats.NotFound++
continue
}
$isMember = Get-ADGroupMember -Identity $group -Recursive |
Where-Object { $_.SamAccountName -eq $sam }
if ($isMember) {
Write-Log "User '$sam' is already a member - skipping." -Level WARN
$stats.Skipped++
continue
}
try {
Add-ADGroupMember -Identity $group -Members $user -ErrorAction Stop
Write-Log "Added '$sam' to '$GroupName'."
$stats.Added++
} catch {
Write-Log "Failed to add '$sam': $_" -Level ERROR
$stats.Errors++
}
}
$summary = "Done. Added: {0} | Skipped: {1} | Not found: {2} | Errors: {3}" -f `
$stats.Added, $stats.Skipped, $stats.NotFound, $stats.Errors
Write-Log $summary
Write-Log "Log saved to: $LogPath"
$script:btnImport.Enabled = $true
}
# ---------------------------------------------------------------------------
# Form
# ---------------------------------------------------------------------------
$form = New-Object System.Windows.Forms.Form
$form.Text = "AD Group Import"
$form.Size = New-Object System.Drawing.Size(680, 560)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
$form.MaximizeBox = $false
$form.BackColor = [System.Drawing.Color]::FromArgb(30, 30, 30)
$form.ForeColor = [System.Drawing.Color]::White
$form.Font = New-Object System.Drawing.Font('Segoe UI', 9)
$pad = 16
# --- CSV row ---
$lblCsv = New-Object System.Windows.Forms.Label
$lblCsv.Text = "CSV File:"
$lblCsv.Location = New-Object System.Drawing.Point($pad, 20)
$lblCsv.Size = New-Object System.Drawing.Size(80, 22)
$form.Controls.Add($lblCsv)
$txtCsv = New-Object System.Windows.Forms.TextBox
$txtCsv.Location = New-Object System.Drawing.Point(100, 18)
$txtCsv.Size = New-Object System.Drawing.Size(440, 22)
$txtCsv.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 45)
$txtCsv.ForeColor = [System.Drawing.Color]::White
$txtCsv.BorderStyle = "FixedSingle"
$form.Controls.Add($txtCsv)
$btnBrowse = New-Object System.Windows.Forms.Button
$btnBrowse.Text = "Browse..."
$btnBrowse.Location = New-Object System.Drawing.Point(550, 16)
$btnBrowse.Size = New-Object System.Drawing.Size(96, 26)
$btnBrowse.BackColor = [System.Drawing.Color]::FromArgb(0, 122, 204)
$btnBrowse.ForeColor = [System.Drawing.Color]::White
$btnBrowse.FlatStyle = "Flat"
$btnBrowse.FlatAppearance.BorderSize = 0
$form.Controls.Add($btnBrowse)
$btnBrowse.Add_Click({
$ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Filter = "CSV files (*.csv)|*.csv|All files (*.*)|*.*"
$ofd.Title = "Select CSV file"
if ($ofd.ShowDialog() -eq "OK") {
$txtCsv.Text = $ofd.FileName
}
})
# --- Group row ---
$lblGroup = New-Object System.Windows.Forms.Label
$lblGroup.Text = "AD Group:"
$lblGroup.Location = New-Object System.Drawing.Point($pad, 58)
$lblGroup.Size = New-Object System.Drawing.Size(80, 22)
$form.Controls.Add($lblGroup)
$cmbGroup = New-Object System.Windows.Forms.ComboBox
$cmbGroup.Location = New-Object System.Drawing.Point(100, 56)
$cmbGroup.Size = New-Object System.Drawing.Size(440, 22)
$cmbGroup.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 45)
$cmbGroup.ForeColor = [System.Drawing.Color]::White
$cmbGroup.FlatStyle = "Flat"
$cmbGroup.AutoCompleteMode = "SuggestAppend"
$cmbGroup.AutoCompleteSource = "ListItems"
$form.Controls.Add($cmbGroup)
$btnLoadGroups = New-Object System.Windows.Forms.Button
$btnLoadGroups.Text = "Load Groups"
$btnLoadGroups.Location = New-Object System.Drawing.Point(550, 54)
$btnLoadGroups.Size = New-Object System.Drawing.Size(96, 26)
$btnLoadGroups.BackColor = [System.Drawing.Color]::FromArgb(0, 122, 204)
$btnLoadGroups.ForeColor = [System.Drawing.Color]::White
$btnLoadGroups.FlatStyle = "Flat"
$btnLoadGroups.FlatAppearance.BorderSize = 0
$form.Controls.Add($btnLoadGroups)
$btnLoadGroups.Add_Click({
$btnLoadGroups.Text = "Loading..."
$btnLoadGroups.Enabled = $false
[System.Windows.Forms.Application]::DoEvents()
try {
$groups = Get-ADGroup -Filter * | Select-Object -ExpandProperty Name | Sort-Object
$cmbGroup.Items.Clear()
$cmbGroup.Items.AddRange($groups)
Write-OutputLine "Loaded $($groups.Count) groups." ([System.Drawing.Color]::LightGreen)
} catch {
Write-OutputLine "Failed to load groups: $_" ([System.Drawing.Color]::Salmon)
}
$btnLoadGroups.Text = "Load Groups"
$btnLoadGroups.Enabled = $true
})
# --- Separator ---
$sep = New-Object System.Windows.Forms.Panel
$sep.Location = New-Object System.Drawing.Point($pad, 94)
$sep.Size = New-Object System.Drawing.Size(632, 1)
$sep.BackColor = [System.Drawing.Color]::FromArgb(70, 70, 70)
$form.Controls.Add($sep)
# --- Import button ---
$script:btnImport = New-Object System.Windows.Forms.Button
$script:btnImport.Text = "Import Users"
$script:btnImport.Location = New-Object System.Drawing.Point($pad, 104)
$script:btnImport.Size = New-Object System.Drawing.Size(632, 34)
$script:btnImport.BackColor = [System.Drawing.Color]::FromArgb(16, 124, 16)
$script:btnImport.ForeColor = [System.Drawing.Color]::White
$script:btnImport.FlatStyle = "Flat"
$script:btnImport.FlatAppearance.BorderSize = 0
$script:btnImport.Font = New-Object System.Drawing.Font('Segoe UI', 10, [System.Drawing.FontStyle]::Bold)
$form.Controls.Add($script:btnImport)
$script:btnImport.Add_Click({
$csvPath = $txtCsv.Text.Trim()
$groupName = $cmbGroup.Text.Trim()
if (-not $csvPath) {
[System.Windows.Forms.MessageBox]::Show("Please select a CSV file.", "Validation", "OK", "Warning") | Out-Null
return
}
if (-not (Test-Path $csvPath -PathType Leaf)) {
[System.Windows.Forms.MessageBox]::Show("CSV file not found:`n$csvPath", "Validation", "OK", "Warning") | Out-Null
return
}
if (-not $groupName) {
[System.Windows.Forms.MessageBox]::Show("Please enter or select an AD group.", "Validation", "OK", "Warning") | Out-Null
return
}
Start-Import -CsvPath $csvPath -GroupName $groupName
})
# --- Output panel ---
$lblOutput = New-Object System.Windows.Forms.Label
$lblOutput.Text = "Output:"
$lblOutput.Location = New-Object System.Drawing.Point($pad, 150)
$lblOutput.Size = New-Object System.Drawing.Size(80, 18)
$form.Controls.Add($lblOutput)
$script:rtbOutput = New-Object System.Windows.Forms.RichTextBox
$script:rtbOutput.Location = New-Object System.Drawing.Point($pad, 170)
$script:rtbOutput.Size = New-Object System.Drawing.Size(632, 310)
$script:rtbOutput.BackColor = [System.Drawing.Color]::FromArgb(12, 12, 12)
$script:rtbOutput.ForeColor = [System.Drawing.Color]::White
$script:rtbOutput.Font = New-Object System.Drawing.Font('Consolas', 9)
$script:rtbOutput.ReadOnly = $true
$script:rtbOutput.BorderStyle = "None"
$script:rtbOutput.ScrollBars = "Vertical"
$form.Controls.Add($script:rtbOutput)
# --- Status bar ---
$lblStatus = New-Object System.Windows.Forms.Label
$lblStatus.Text = "Log: $LogPath"
$lblStatus.Location = New-Object System.Drawing.Point($pad, 490)
$lblStatus.Size = New-Object System.Drawing.Size(632, 18)
$lblStatus.ForeColor = [System.Drawing.Color]::Gray
$lblStatus.Font = New-Object System.Drawing.Font('Segoe UI', 8)
$form.Controls.Add($lblStatus)
# ---------------------------------------------------------------------------
$form.ShowDialog() | Out-Null
$form.Dispose()