diff --git a/Active-Directory/import_User_to_group.ps1 b/Active-Directory/import_User_to_group.ps1 new file mode 100644 index 0000000..70c73ec --- /dev/null +++ b/Active-Directory/import_User_to_group.ps1 @@ -0,0 +1,344 @@ +<# +.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()