<# .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()