Complete Script Generator feature including: Backend: - ScriptCategory, ScriptTemplate, ScriptGeneration models - ScriptTemplateEngine with substitution, filters, sanitization - CRUD + share API endpoints with permission checks - Integration tests for permissions and sharing - Migration 057 with AD User Management seed templates Frontend — Script Library: - Browse templates with category tabs and search - Configure pane with parameter form and script generation - Script preview with live substitution and copy/download - scriptGeneratorStore Zustand store Frontend — Template Editor: - Full CRUD form with metadata, script body (Monaco Editor), parameters - ParameterSchemaBuilder with visual builder + JSON toggle - ScriptManagePage with routing and nav link Frontend — Parameter Detector: - Client-side PowerShell parameter detection engine - Detects script-level param() blocks and variable assignments - Type inference from PS type annotations and value patterns - ParameterDetectorStepper one-by-one review UI with accept/skip Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
691 lines
33 KiB
Python
691 lines
33 KiB
Python
"""add script templates
|
|
|
|
Revision ID: 057
|
|
Revises: 056
|
|
Create Date: 2026-03-12
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.dialects.postgresql import UUID, JSONB, ENUM as PG_ENUM
|
|
import uuid
|
|
import json
|
|
from datetime import datetime, timezone
|
|
|
|
revision = "057"
|
|
down_revision = "056"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# ── Create enum ──────────────────────────────────────────────────────
|
|
op.execute("CREATE TYPE script_complexity AS ENUM ('beginner', 'intermediate', 'advanced')")
|
|
|
|
# ── script_categories ────────────────────────────────────────────────
|
|
op.create_table(
|
|
"script_categories",
|
|
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
|
sa.Column("name", sa.String(100), nullable=False),
|
|
sa.Column("slug", sa.String(100), nullable=False, unique=True),
|
|
sa.Column("description", sa.Text, nullable=True),
|
|
sa.Column("icon", sa.String(50), nullable=True),
|
|
sa.Column("sort_order", sa.Integer, nullable=False, server_default=sa.text("0")),
|
|
sa.Column("is_active", sa.Boolean, nullable=False, server_default=sa.text("true")),
|
|
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
|
)
|
|
op.create_index("ix_script_categories_slug", "script_categories", ["slug"])
|
|
|
|
# ── script_templates ─────────────────────────────────────────────────
|
|
op.create_table(
|
|
"script_templates",
|
|
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
|
sa.Column("category_id", UUID(as_uuid=True), sa.ForeignKey("script_categories.id", ondelete="RESTRICT"), nullable=False),
|
|
sa.Column("team_id", UUID(as_uuid=True), sa.ForeignKey("teams.id", ondelete="CASCADE"), nullable=True),
|
|
sa.Column("created_by", UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True),
|
|
sa.Column("name", sa.String(200), nullable=False),
|
|
sa.Column("slug", sa.String(200), nullable=False),
|
|
sa.Column("description", sa.Text, nullable=True),
|
|
sa.Column("use_case", sa.Text, nullable=True),
|
|
sa.Column("script_body", sa.Text, nullable=False),
|
|
sa.Column("parameters_schema", JSONB, nullable=False, server_default=sa.text("'{}'::jsonb")),
|
|
sa.Column("default_values", JSONB, nullable=False, server_default=sa.text("'{}'::jsonb")),
|
|
sa.Column("validation_rules", JSONB, nullable=False, server_default=sa.text("'{}'::jsonb")),
|
|
sa.Column("tags", JSONB, nullable=False, server_default=sa.text("'[]'::jsonb")),
|
|
sa.Column("complexity", PG_ENUM("beginner", "intermediate", "advanced", name="script_complexity", create_type=False), nullable=False, server_default=sa.text("'beginner'")),
|
|
sa.Column("estimated_runtime", sa.String(50), nullable=True),
|
|
sa.Column("requires_elevation", sa.Boolean, nullable=False, server_default=sa.text("true")),
|
|
sa.Column("requires_modules", JSONB, nullable=False, server_default=sa.text("'[]'::jsonb")),
|
|
sa.Column("version", sa.Integer, nullable=False, server_default=sa.text("1")),
|
|
sa.Column("is_verified", sa.Boolean, nullable=False, server_default=sa.text("false")),
|
|
sa.Column("is_active", sa.Boolean, nullable=False, server_default=sa.text("true")),
|
|
sa.Column("usage_count", sa.Integer, nullable=False, server_default=sa.text("0")),
|
|
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
|
)
|
|
op.create_index("ix_script_templates_category_id", "script_templates", ["category_id"])
|
|
op.create_index("ix_script_templates_team_id", "script_templates", ["team_id"])
|
|
op.create_index("ix_script_templates_slug", "script_templates", ["slug"])
|
|
|
|
# ── script_generations ───────────────────────────────────────────────
|
|
op.create_table(
|
|
"script_generations",
|
|
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
|
sa.Column("template_id", UUID(as_uuid=True), sa.ForeignKey("script_templates.id", ondelete="RESTRICT"), nullable=False),
|
|
sa.Column("user_id", UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("team_id", UUID(as_uuid=True), sa.ForeignKey("teams.id", ondelete="SET NULL"), nullable=True),
|
|
sa.Column("session_id", UUID(as_uuid=True), sa.ForeignKey("sessions.id", ondelete="SET NULL"), nullable=True),
|
|
sa.Column("parameters_used", JSONB, nullable=False, server_default=sa.text("'{}'::jsonb")),
|
|
sa.Column("generated_script", sa.Text, nullable=False),
|
|
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
|
)
|
|
op.create_index("ix_script_generations_template_id", "script_generations", ["template_id"])
|
|
op.create_index("ix_script_generations_user_id", "script_generations", ["user_id"])
|
|
op.create_index("ix_script_generations_session_id", "script_generations", ["session_id"])
|
|
|
|
# ── Seed: Active Directory category ──────────────────────────────────
|
|
now = datetime.now(timezone.utc)
|
|
cat_id = uuid.UUID("00000000-0000-0000-0000-000000000001")
|
|
|
|
conn = op.get_bind()
|
|
conn.execute(
|
|
sa.text("""
|
|
INSERT INTO script_categories (id, name, slug, description, icon, sort_order, is_active, created_at, updated_at)
|
|
VALUES (:id, :name, :slug, :description, :icon, :sort_order, true, :now, :now)
|
|
"""),
|
|
{
|
|
"id": cat_id,
|
|
"name": "Active Directory",
|
|
"slug": "active-directory",
|
|
"description": "User account and group management scripts for Active Directory environments",
|
|
"icon": "shield-check",
|
|
"sort_order": 1,
|
|
"now": now,
|
|
}
|
|
)
|
|
|
|
templates = _get_seed_templates(cat_id, now)
|
|
for tmpl in templates:
|
|
conn.execute(
|
|
sa.text("""
|
|
INSERT INTO script_templates (
|
|
id, category_id, name, slug, description, use_case,
|
|
script_body, parameters_schema, default_values, validation_rules,
|
|
tags, complexity, estimated_runtime, requires_elevation,
|
|
requires_modules, version, is_verified, is_active, usage_count,
|
|
created_at, updated_at
|
|
) VALUES (
|
|
:id, :category_id, :name, :slug, :description, :use_case,
|
|
:script_body, CAST(:parameters_schema AS jsonb), CAST(:default_values AS jsonb), CAST(:validation_rules AS jsonb),
|
|
CAST(:tags AS jsonb), :complexity, :estimated_runtime, :requires_elevation,
|
|
CAST(:requires_modules AS jsonb), 1, true, true, 0,
|
|
:now, :now
|
|
)
|
|
"""),
|
|
tmpl,
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_table("script_generations")
|
|
op.drop_table("script_templates")
|
|
op.drop_table("script_categories")
|
|
op.execute("DROP TYPE IF EXISTS script_complexity")
|
|
|
|
|
|
def _get_seed_templates(cat_id: uuid.UUID, now: datetime) -> list[dict]:
|
|
"""Return seed data for the six AD User Management templates."""
|
|
|
|
def tmpl(
|
|
id_suffix: int,
|
|
name: str,
|
|
slug: str,
|
|
description: str,
|
|
use_case: str,
|
|
script_body: str,
|
|
parameters_schema: dict,
|
|
complexity: str,
|
|
estimated_runtime: str,
|
|
requires_elevation: bool,
|
|
tags: list[str],
|
|
) -> dict:
|
|
return {
|
|
"id": uuid.UUID(f"00000000-0000-0000-0001-{id_suffix:012d}"),
|
|
"category_id": cat_id,
|
|
"name": name,
|
|
"slug": slug,
|
|
"description": description,
|
|
"use_case": use_case,
|
|
"script_body": script_body,
|
|
"parameters_schema": json.dumps(parameters_schema),
|
|
"default_values": json.dumps({}),
|
|
"validation_rules": json.dumps({}),
|
|
"tags": json.dumps(tags),
|
|
"complexity": complexity,
|
|
"estimated_runtime": estimated_runtime,
|
|
"requires_elevation": requires_elevation,
|
|
"requires_modules": json.dumps([]),
|
|
"now": now,
|
|
}
|
|
|
|
return [
|
|
|
|
# ── 1: Create AD User ─────────────────────────────────────────────
|
|
tmpl(
|
|
id_suffix=1,
|
|
name="Create AD User Account",
|
|
slug="create-ad-user",
|
|
description="Creates a new Active Directory user account with full property configuration.",
|
|
use_case="Use when onboarding a new employee or creating a service account in Active Directory.",
|
|
complexity="intermediate",
|
|
estimated_runtime="< 5 seconds",
|
|
requires_elevation=True,
|
|
tags=["active-directory", "user-management", "onboarding"],
|
|
parameters_schema={
|
|
"parameters": [
|
|
{"key": "first_name", "label": "First Name", "type": "text", "required": True, "group": "User Identity", "order": 1, "validation": {"pattern": "^[a-zA-Z\\-']+$", "maxLength": 64}},
|
|
{"key": "last_name", "label": "Last Name", "type": "text", "required": True, "group": "User Identity", "order": 2, "validation": {"pattern": "^[a-zA-Z\\-']+$", "maxLength": 64}},
|
|
{"key": "sam_account_name", "label": "SAM Account Name", "type": "text", "required": True, "group": "User Identity", "order": 3, "placeholder": "jsmith", "help_text": "The logon name (typically firstname.lastname or first initial + last name). Max 20 characters."},
|
|
{"key": "upn_suffix", "label": "UPN Suffix", "type": "text", "required": True, "group": "User Identity", "order": 4, "placeholder": "contoso.com"},
|
|
{"key": "ou_path", "label": "Organizational Unit", "type": "text", "required": True, "group": "AD Configuration", "order": 5, "placeholder": "OU=Users,DC=contoso,DC=com", "validation": {"pattern": "^(OU|CN)=.+"}},
|
|
{"key": "password", "label": "Initial Password", "type": "password", "required": True, "group": "Security", "order": 6, "sensitive": True},
|
|
{"key": "job_title", "label": "Job Title", "type": "text", "required": False, "group": "Profile", "order": 7},
|
|
{"key": "department", "label": "Department", "type": "text", "required": False, "group": "Profile", "order": 8},
|
|
{"key": "groups", "label": "Security Groups", "type": "multi_text", "required": False, "group": "Group Membership", "order": 9, "placeholder": "Domain Users"},
|
|
{"key": "force_password_change", "label": "Force Password Change at Logon", "type": "boolean", "required": False, "group": "Security", "order": 10, "default": True},
|
|
{"key": "account_enabled", "label": "Enable Account", "type": "boolean", "required": False, "group": "Security", "order": 11, "default": True},
|
|
]
|
|
},
|
|
script_body=r"""# ============================================================
|
|
# Create AD User Account
|
|
# Generated by ResolutionFlow Script Generator
|
|
# Template Version: 1
|
|
# ============================================================
|
|
|
|
#Requires -Modules ActiveDirectory
|
|
#Requires -RunAsAdministrator
|
|
|
|
$FirstName = '{{ first_name }}'
|
|
$LastName = '{{ last_name }}'
|
|
$SamAccountName = '{{ sam_account_name }}'
|
|
$UPNSuffix = '{{ upn_suffix }}'
|
|
$OUPath = '{{ ou_path }}'
|
|
$Password = {{ password | as_secure_string }}
|
|
$ForcePasswordChange = {{ force_password_change | as_bool }}
|
|
$AccountEnabled = {{ account_enabled | as_bool }}
|
|
{% if job_title %}
|
|
$JobTitle = '{{ job_title }}'
|
|
{% endif %}
|
|
{% if department %}
|
|
$Department = '{{ department }}'
|
|
{% endif %}
|
|
{% if groups %}
|
|
$Groups = @({{ groups | as_array }})
|
|
{% endif %}
|
|
|
|
$DisplayName = "$FirstName $LastName"
|
|
$UPN = "$SamAccountName@$UPNSuffix"
|
|
|
|
try {
|
|
Import-Module ActiveDirectory -ErrorAction Stop
|
|
Write-Host "[CHECK] ActiveDirectory module loaded" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[ERROR] ActiveDirectory module not available: $_" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
try {
|
|
$userParams = @{
|
|
Name = $DisplayName
|
|
GivenName = $FirstName
|
|
Surname = $LastName
|
|
SamAccountName = $SamAccountName
|
|
UserPrincipalName = $UPN
|
|
Path = $OUPath
|
|
AccountPassword = $Password
|
|
Enabled = $AccountEnabled
|
|
ChangePasswordAtLogon = $ForcePasswordChange
|
|
}
|
|
{% if job_title %}
|
|
$userParams["Title"] = $JobTitle
|
|
{% endif %}
|
|
{% if department %}
|
|
$userParams["Department"] = $Department
|
|
{% endif %}
|
|
|
|
New-ADUser @userParams -ErrorAction Stop
|
|
Write-Host "[OK] User account created: $SamAccountName ($DisplayName)" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[ERROR] Failed to create user: $_" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
{% if groups %}
|
|
foreach ($group in $Groups) {
|
|
try {
|
|
Add-ADGroupMember -Identity $group -Members $SamAccountName -ErrorAction Stop
|
|
Write-Host "[OK] Added to group: $group" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[WARN] Could not add to group '$group': $_" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
{% endif %}
|
|
|
|
Write-Host ""
|
|
Write-Host "[DONE] User $SamAccountName created successfully." -ForegroundColor Cyan
|
|
Write-Host " UPN: $UPN" -ForegroundColor Cyan
|
|
""",
|
|
),
|
|
|
|
# ── 2: Disable AD User ────────────────────────────────────────────
|
|
tmpl(
|
|
id_suffix=2,
|
|
name="Disable AD User Account",
|
|
slug="disable-ad-user",
|
|
description="Disables an Active Directory user account with optional group removal and OU relocation.",
|
|
use_case="Use when offboarding an employee or temporarily suspending access.",
|
|
complexity="beginner",
|
|
estimated_runtime="< 5 seconds",
|
|
requires_elevation=True,
|
|
tags=["active-directory", "user-management", "offboarding"],
|
|
parameters_schema={
|
|
"parameters": [
|
|
{"key": "sam_account_name", "label": "SAM Account Name", "type": "text", "required": True, "group": "Target Account", "order": 1, "placeholder": "jsmith"},
|
|
{"key": "disable_reason", "label": "Reason", "type": "text", "required": False, "group": "Audit", "order": 2, "placeholder": "Employee offboarding"},
|
|
{"key": "remove_groups", "label": "Remove from All Groups", "type": "boolean", "required": False, "group": "Options", "order": 3, "default": False},
|
|
{"key": "move_to_disabled_ou", "label": "Move to Disabled OU", "type": "boolean", "required": False, "group": "Options", "order": 4, "default": False},
|
|
{"key": "disabled_ou_path", "label": "Disabled OU Path", "type": "text", "required": False, "group": "Options", "order": 5, "placeholder": "OU=Disabled,DC=contoso,DC=com"},
|
|
]
|
|
},
|
|
script_body=r"""# ============================================================
|
|
# Disable AD User Account
|
|
# Generated by ResolutionFlow Script Generator
|
|
# ============================================================
|
|
|
|
#Requires -Modules ActiveDirectory
|
|
#Requires -RunAsAdministrator
|
|
|
|
$SamAccountName = '{{ sam_account_name }}'
|
|
{% if disable_reason %}
|
|
$DisableReason = '{{ disable_reason }}'
|
|
{% endif %}
|
|
$RemoveGroups = {{ remove_groups | as_bool }}
|
|
$MoveToDisabled = {{ move_to_disabled_ou | as_bool }}
|
|
{% if disabled_ou_path %}
|
|
$DisabledOUPath = '{{ disabled_ou_path }}'
|
|
{% endif %}
|
|
|
|
try {
|
|
Import-Module ActiveDirectory -ErrorAction Stop
|
|
} catch {
|
|
Write-Host "[ERROR] ActiveDirectory module not available: $_" -ForegroundColor Red; exit 1
|
|
}
|
|
|
|
try {
|
|
$user = Get-ADUser -Identity $SamAccountName -Properties MemberOf -ErrorAction Stop
|
|
Write-Host "[CHECK] Found user: $($user.DistinguishedName)" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[ERROR] User not found: $SamAccountName" -ForegroundColor Red; exit 1
|
|
}
|
|
|
|
try {
|
|
Disable-ADAccount -Identity $SamAccountName -ErrorAction Stop
|
|
Write-Host "[OK] Account disabled: $SamAccountName" -ForegroundColor Green
|
|
{% if disable_reason %}
|
|
Set-ADUser -Identity $SamAccountName -Description $DisableReason
|
|
Write-Host "[OK] Disable reason recorded in Description field" -ForegroundColor Green
|
|
{% endif %}
|
|
} catch {
|
|
Write-Host "[ERROR] Failed to disable account: $_" -ForegroundColor Red; exit 1
|
|
}
|
|
|
|
{% if remove_groups %}
|
|
if ($RemoveGroups) {
|
|
$groups = $user.MemberOf
|
|
foreach ($group in $groups) {
|
|
try {
|
|
Remove-ADGroupMember -Identity $group -Members $SamAccountName -Confirm:$false -ErrorAction Stop
|
|
Write-Host "[OK] Removed from group: $group" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[WARN] Could not remove from group: $group" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
}
|
|
{% endif %}
|
|
|
|
{% if move_to_disabled_ou %}
|
|
if ($MoveToDisabled) {
|
|
try {
|
|
Move-ADObject -Identity $user.DistinguishedName -TargetPath $DisabledOUPath -ErrorAction Stop
|
|
Write-Host "[OK] Moved to disabled OU: $DisabledOUPath" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[WARN] Could not move to disabled OU: $_" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
{% endif %}
|
|
|
|
Write-Host ""
|
|
Write-Host "[DONE] Account $SamAccountName has been disabled." -ForegroundColor Cyan
|
|
""",
|
|
),
|
|
|
|
# ── 3: Reset AD Password ──────────────────────────────────────────
|
|
tmpl(
|
|
id_suffix=3,
|
|
name="Reset AD Password",
|
|
slug="reset-ad-password",
|
|
description="Resets an Active Directory user password with optional account unlock.",
|
|
use_case="Use when a user has forgotten their password or their account is locked out.",
|
|
complexity="beginner",
|
|
estimated_runtime="< 5 seconds",
|
|
requires_elevation=True,
|
|
tags=["active-directory", "password", "account-management"],
|
|
parameters_schema={
|
|
"parameters": [
|
|
{"key": "sam_account_name", "label": "SAM Account Name", "type": "text", "required": True, "group": "Target Account", "order": 1},
|
|
{"key": "new_password", "label": "New Password", "type": "password", "required": True, "group": "Security", "order": 2, "sensitive": True},
|
|
{"key": "force_change_at_logon", "label": "Force Change at Next Logon", "type": "boolean", "required": False, "group": "Security", "order": 3, "default": True},
|
|
{"key": "unlock_account", "label": "Unlock Account if Locked", "type": "boolean", "required": False, "group": "Options", "order": 4, "default": True},
|
|
]
|
|
},
|
|
script_body=r"""# ============================================================
|
|
# Reset AD Password
|
|
# Generated by ResolutionFlow Script Generator
|
|
# ============================================================
|
|
|
|
#Requires -Modules ActiveDirectory
|
|
#Requires -RunAsAdministrator
|
|
|
|
$SamAccountName = '{{ sam_account_name }}'
|
|
$NewPassword = {{ new_password | as_secure_string }}
|
|
$ForceChange = {{ force_change_at_logon | as_bool }}
|
|
$UnlockAccount = {{ unlock_account | as_bool }}
|
|
|
|
try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Host "[ERROR] $_ " -ForegroundColor Red; exit 1 }
|
|
|
|
try {
|
|
Set-ADAccountPassword -Identity $SamAccountName -NewPassword $NewPassword -Reset -ErrorAction Stop
|
|
Write-Host "[OK] Password reset for: $SamAccountName" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[ERROR] Failed to reset password: $_" -ForegroundColor Red; exit 1
|
|
}
|
|
|
|
if ($ForceChange) {
|
|
Set-ADUser -Identity $SamAccountName -ChangePasswordAtLogon $true
|
|
Write-Host "[OK] User must change password at next logon" -ForegroundColor Green
|
|
}
|
|
|
|
if ($UnlockAccount) {
|
|
try {
|
|
Unlock-ADAccount -Identity $SamAccountName -ErrorAction Stop
|
|
Write-Host "[OK] Account unlocked" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[WARN] Could not unlock account (may not have been locked): $_" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "[DONE] Password reset complete for $SamAccountName." -ForegroundColor Cyan
|
|
""",
|
|
),
|
|
|
|
# ── 4: Unlock AD Account ──────────────────────────────────────────
|
|
tmpl(
|
|
id_suffix=4,
|
|
name="Unlock AD Account",
|
|
slug="unlock-ad-account",
|
|
description="Unlocks a locked-out Active Directory user account and optionally shows lockout information.",
|
|
use_case="Use when a user is locked out after too many failed login attempts.",
|
|
complexity="beginner",
|
|
estimated_runtime="< 5 seconds",
|
|
requires_elevation=True,
|
|
tags=["active-directory", "account-management", "lockout"],
|
|
parameters_schema={
|
|
"parameters": [
|
|
{"key": "sam_account_name", "label": "SAM Account Name", "type": "text", "required": True, "group": "Target Account", "order": 1},
|
|
{"key": "show_lockout_info", "label": "Show Lockout Source Info", "type": "boolean", "required": False, "group": "Options", "order": 2, "default": False, "help_text": "Queries domain controllers for lockout source (may be slow)"},
|
|
]
|
|
},
|
|
script_body=r"""# ============================================================
|
|
# Unlock AD Account
|
|
# Generated by ResolutionFlow Script Generator
|
|
# ============================================================
|
|
|
|
#Requires -Modules ActiveDirectory
|
|
#Requires -RunAsAdministrator
|
|
|
|
$SamAccountName = '{{ sam_account_name }}'
|
|
$ShowLockoutInfo = {{ show_lockout_info | as_bool }}
|
|
|
|
try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Host "[ERROR] $_ " -ForegroundColor Red; exit 1 }
|
|
|
|
try {
|
|
$user = Get-ADUser -Identity $SamAccountName -Properties LockedOut, BadLogonCount, LastBadPasswordAttempt -ErrorAction Stop
|
|
Write-Host "[CHECK] User found: $($user.DistinguishedName)" -ForegroundColor Green
|
|
Write-Host " Locked Out: $($user.LockedOut)" -ForegroundColor Cyan
|
|
Write-Host " Bad Logon Count: $($user.BadLogonCount)" -ForegroundColor Cyan
|
|
Write-Host " Last Bad Password Attempt: $($user.LastBadPasswordAttempt)" -ForegroundColor Cyan
|
|
} catch {
|
|
Write-Host "[ERROR] User not found: $SamAccountName" -ForegroundColor Red; exit 1
|
|
}
|
|
|
|
if (-not $user.LockedOut) {
|
|
Write-Host "[INFO] Account is not currently locked." -ForegroundColor Yellow
|
|
} else {
|
|
try {
|
|
Unlock-ADAccount -Identity $SamAccountName -ErrorAction Stop
|
|
Write-Host "[OK] Account unlocked: $SamAccountName" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[ERROR] Failed to unlock account: $_" -ForegroundColor Red; exit 1
|
|
}
|
|
}
|
|
|
|
{% if show_lockout_info %}
|
|
if ($ShowLockoutInfo) {
|
|
Write-Host ""
|
|
Write-Host "[INFO] Querying domain controllers for lockout source..." -ForegroundColor Cyan
|
|
try {
|
|
$DCs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
|
|
foreach ($DC in $DCs) {
|
|
$events = Get-WinEvent -ComputerName $DC -FilterHashtable @{
|
|
LogName = "Security"
|
|
Id = 4740
|
|
} -MaxEvents 10 -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Properties[0].Value -eq $SamAccountName }
|
|
foreach ($e in $events) {
|
|
Write-Host " DC: $DC | Source: $($e.Properties[1].Value) | Time: $($e.TimeCreated)" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Host "[WARN] Could not query lockout source: $_" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
{% endif %}
|
|
|
|
Write-Host ""
|
|
Write-Host "[DONE] Unlock operation complete for $SamAccountName." -ForegroundColor Cyan
|
|
""",
|
|
),
|
|
|
|
# ── 5: Delete AD User ─────────────────────────────────────────────
|
|
tmpl(
|
|
id_suffix=5,
|
|
name="Delete AD User Account",
|
|
slug="delete-ad-user",
|
|
description="Permanently deletes an Active Directory user account with optional CSV backup of user properties.",
|
|
use_case="Use when permanently removing an account that is no longer needed. Prefer Disable for departing employees.",
|
|
complexity="advanced",
|
|
estimated_runtime="< 10 seconds",
|
|
requires_elevation=True,
|
|
tags=["active-directory", "user-management", "destructive"],
|
|
parameters_schema={
|
|
"parameters": [
|
|
{"key": "sam_account_name", "label": "SAM Account Name", "type": "text", "required": True, "group": "Target Account", "order": 1},
|
|
{"key": "confirm_deletion", "label": "I confirm this account should be permanently deleted", "type": "boolean", "required": True, "group": "Confirmation", "order": 2, "default": False},
|
|
{"key": "backup_to_csv", "label": "Back Up User Properties to CSV First", "type": "boolean", "required": False, "group": "Options", "order": 3, "default": True},
|
|
{"key": "backup_path", "label": "CSV Backup Path", "type": "text", "required": False, "group": "Options", "order": 4, "placeholder": "C:\\Backups\\ad-user-backup.csv"},
|
|
]
|
|
},
|
|
script_body=r"""# ============================================================
|
|
# Delete AD User Account
|
|
# Generated by ResolutionFlow Script Generator
|
|
# DESTRUCTIVE OPERATION
|
|
# ============================================================
|
|
|
|
#Requires -Modules ActiveDirectory
|
|
#Requires -RunAsAdministrator
|
|
|
|
$SamAccountName = '{{ sam_account_name }}'
|
|
$ConfirmDeletion = {{ confirm_deletion | as_bool }}
|
|
$BackupToCSV = {{ backup_to_csv | as_bool }}
|
|
{% if backup_path %}
|
|
$BackupPath = '{{ backup_path }}'
|
|
{% else %}
|
|
$BackupPath = "C:\Temp\ad-user-backup-$SamAccountName-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
|
|
{% endif %}
|
|
|
|
try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Host "[ERROR] $_ " -ForegroundColor Red; exit 1 }
|
|
|
|
if (-not $ConfirmDeletion) {
|
|
Write-Host "[ABORT] Deletion not confirmed. Set confirm_deletion to true to proceed." -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
try {
|
|
$user = Get-ADUser -Identity $SamAccountName -Properties * -ErrorAction Stop
|
|
Write-Host "[CHECK] Found user: $($user.DistinguishedName)" -ForegroundColor Yellow
|
|
Write-Host "[WARN] About to permanently delete this account." -ForegroundColor Yellow
|
|
} catch {
|
|
Write-Host "[ERROR] User not found: $SamAccountName" -ForegroundColor Red; exit 1
|
|
}
|
|
|
|
if ($BackupToCSV) {
|
|
try {
|
|
$user | Select-Object Name, SamAccountName, UserPrincipalName, DistinguishedName, EmailAddress, Title, Department, Enabled, Created, Modified |
|
|
Export-Csv -Path $BackupPath -NoTypeInformation -ErrorAction Stop
|
|
Write-Host "[OK] User properties backed up to: $BackupPath" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[WARN] Could not write backup: $_" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
|
|
try {
|
|
Remove-ADUser -Identity $SamAccountName -Confirm:$false -ErrorAction Stop
|
|
Write-Host "[OK] User account permanently deleted: $SamAccountName" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "[ERROR] Failed to delete user: $_" -ForegroundColor Red; exit 1
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "[DONE] Account $SamAccountName has been permanently deleted." -ForegroundColor Cyan
|
|
""",
|
|
),
|
|
|
|
# ── 6: Bulk User Import ───────────────────────────────────────────
|
|
tmpl(
|
|
id_suffix=6,
|
|
name="Bulk User Import from CSV",
|
|
slug="bulk-user-import",
|
|
description="Imports multiple Active Directory user accounts from a CSV file with dry-run support and detailed logging.",
|
|
use_case="Use when onboarding multiple employees at once from an HR system export.",
|
|
complexity="advanced",
|
|
estimated_runtime="1-2 minutes",
|
|
requires_elevation=True,
|
|
tags=["active-directory", "bulk", "onboarding", "csv"],
|
|
parameters_schema={
|
|
"parameters": [
|
|
{"key": "csv_path", "label": "CSV File Path", "type": "text", "required": True, "group": "Source", "order": 1, "placeholder": "C:\\Imports\\new-users.csv", "help_text": "CSV must have columns: FirstName, LastName, SamAccountName, UPN, Department, JobTitle"},
|
|
{"key": "ou_path", "label": "Target Organizational Unit", "type": "text", "required": True, "group": "AD Configuration", "order": 2, "placeholder": "OU=Users,DC=contoso,DC=com"},
|
|
{"key": "default_password", "label": "Default Password", "type": "password", "required": True, "group": "Security", "order": 3, "sensitive": True},
|
|
{"key": "groups", "label": "Add All Users to Groups", "type": "multi_text", "required": False, "group": "Group Membership", "order": 4},
|
|
{"key": "force_password_change", "label": "Force Password Change at Logon", "type": "boolean", "required": False, "group": "Security", "order": 5, "default": True},
|
|
{"key": "log_path", "label": "Log File Path", "type": "text", "required": False, "group": "Options", "order": 6, "placeholder": "C:\\Logs\\bulk-import.log"},
|
|
{"key": "dry_run", "label": "Dry Run (Preview Only - No Changes Made)", "type": "boolean", "required": False, "group": "Options", "order": 7, "default": False},
|
|
]
|
|
},
|
|
script_body=r"""# ============================================================
|
|
# Bulk User Import from CSV
|
|
# Generated by ResolutionFlow Script Generator
|
|
# ============================================================
|
|
# CSV Format: FirstName, LastName, SamAccountName, UPN, Department, JobTitle
|
|
# ============================================================
|
|
|
|
#Requires -Modules ActiveDirectory
|
|
#Requires -RunAsAdministrator
|
|
|
|
$CSVPath = '{{ csv_path }}'
|
|
$OUPath = '{{ ou_path }}'
|
|
$DefaultPassword = {{ default_password | as_secure_string }}
|
|
$ForceChange = {{ force_password_change | as_bool }}
|
|
$DryRun = {{ dry_run | as_bool }}
|
|
{% if log_path %}
|
|
$LogPath = '{{ log_path }}'
|
|
{% else %}
|
|
$LogPath = "C:\Temp\bulk-import-$(Get-Date -Format 'yyyyMMdd-HHmmss').log"
|
|
{% endif %}
|
|
{% if groups %}
|
|
$DefaultGroups = @({{ groups | as_array }})
|
|
{% endif %}
|
|
|
|
function Write-Log {
|
|
param($Message, $Level = "INFO")
|
|
$line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [$Level] $Message"
|
|
Add-Content -Path $LogPath -Value $line -ErrorAction SilentlyContinue
|
|
$color = switch ($Level) { "ERROR" { "Red" } "WARN" { "Yellow" } "OK" { "Green" } default { "White" } }
|
|
Write-Host $line -ForegroundColor $color
|
|
}
|
|
|
|
try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Log "ActiveDirectory module not available: $_" "ERROR"; exit 1 }
|
|
|
|
if (-not (Test-Path $CSVPath)) { Write-Log "CSV file not found: $CSVPath" "ERROR"; exit 1 }
|
|
|
|
$users = Import-Csv -Path $CSVPath
|
|
Write-Log "Loaded $($users.Count) users from CSV"
|
|
if ($DryRun) { Write-Log "DRY RUN MODE - no changes will be made" "WARN" }
|
|
|
|
$successCount = 0
|
|
$errorCount = 0
|
|
|
|
foreach ($row in $users) {
|
|
$sam = $row.SamAccountName
|
|
try {
|
|
$existing = Get-ADUser -Filter "SamAccountName -eq '$sam'" -ErrorAction SilentlyContinue
|
|
if ($existing) { Write-Log "SKIP: $sam already exists" "WARN"; continue }
|
|
|
|
if (-not $DryRun) {
|
|
New-ADUser `
|
|
-Name "$($row.FirstName) $($row.LastName)" `
|
|
-GivenName $row.FirstName `
|
|
-Surname $row.LastName `
|
|
-SamAccountName $sam `
|
|
-UserPrincipalName $row.UPN `
|
|
-Path $OUPath `
|
|
-AccountPassword $DefaultPassword `
|
|
-Enabled $true `
|
|
-ChangePasswordAtLogon $ForceChange `
|
|
-Title $row.JobTitle `
|
|
-Department $row.Department `
|
|
-ErrorAction Stop
|
|
|
|
{% if groups %}
|
|
foreach ($group in $DefaultGroups) {
|
|
try { Add-ADGroupMember -Identity $group -Members $sam -ErrorAction Stop }
|
|
catch { Write-Log "WARN: $sam - could not add to group '$group'" "WARN" }
|
|
}
|
|
{% endif %}
|
|
}
|
|
Write-Log "$(if ($DryRun) { 'PREVIEW' } else { 'CREATED' }): $sam ($($row.FirstName) $($row.LastName))" "OK"
|
|
$successCount++
|
|
} catch {
|
|
Write-Log "FAILED: $sam - $_" "ERROR"
|
|
$errorCount++
|
|
}
|
|
}
|
|
|
|
Write-Log ""
|
|
Write-Log "[DONE] Import complete. Success: $successCount | Errors: $errorCount | Log: $LogPath" "INFO"
|
|
""",
|
|
),
|
|
|
|
]
|