BXUP Track progress & test yourself — free
IT Support / Service Desk / Sysadmin · Hands-on lab · ~6 min read
IT SUPPORT / SERVICE DESK / SYSADMIN · HANDS-ON LAB

Lab 06 · PowerShell Safety Clinic

Generate a bulk offboarding script with AI, then run the mandatory safety review — -WhatIf, least privilege, error paths, secrets, scoped filters, PSScriptAnalyzer — and separately risk-flag a legacy script whose two planted dangers, an unscoped Remove and a plaintext credential, have been waiting in production since 2019.

Meera inherited the sysadmin seat at a Hyderabad logistics firm along with a scripts folder nobody can explain. The previous admin left; his automations did not. This week she has two jobs: build a proper bulk-offboarding script for the quarterly contractor exit — with AI doing the drafting — and decide whether offboard_old.ps1, which has run unattended every Friday for seven years, is safe to keep. The research this track is built on found that 100% of studied AI-generated admin scripts contained exploitable flaws. The review protocol is therefore not a formality. It is the skill.

Nothing in this lab runs against a real directory. You will review, harden and dry-run — the discipline transfers; the blast radius does not.

Time & tier

~75 minutes hands-on · Core tier (maps to module M4). $0 — any free AI chat, PowerShell 7 (or Windows PowerShell 5.1), and PSScriptAnalyzer (Install-Module PSScriptAnalyzer -Scope CurrentUser, free). A throwaway VM is nice but optional — no step here executes destructive code.

Setup

Save the block below as offboard_old.ps1. This is a condensed excerpt of the 300-line legacy script — the sections that matter are intact and unmodified. Do not run it. Two of its behaviors are dangerous enough to have deleted this company's file server, and part of your job is to find them cold, before any AI sees the file.

# offboard_old.ps1 - runs Fridays 18:00 via Task Scheduler
# author: (unknown) last modified 2019-11-22
Set-ExecutionPolicy Bypass -Force
$admin = "CORP\svc-offboard"
$password = "Sv3rL0g!2019"
$sec = ConvertTo-SecureString $password -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($admin,$sec)

$leavers = Import-Csv "\\fs01\it\leavers.csv"
foreach ($row in $leavers) {
    $u = $row.username
    Write-Host "Processing $u"
    Disable-ADAccount -Identity $u -Credential $cred
    Get-ADUser $u -Properties MemberOf | Select -Expand MemberOf |
        ForEach-Object { Remove-ADGroupMember $_ -Members $u `
            -Confirm:$false -Credential $cred }
    # archive then remove home drive
    Copy-Item "\\fs01\home\$u" "\\fs01\archive\$u" -Recurse -Force
    Remove-Item "\\fs01\home\$u" -Recurse -Force
    # clear old profile
    Get-ChildItem "\\fs01\profiles\" | Where-Object {
        $_.Name -like "*$u*" } | Remove-Item -Recurse -Force
    Set-ADUser $u -Description "Offboarded $(Get-Date)" -Credential $cred
}
Write-Host "Done."

Also create contractors.csv — the input for the new script you will generate:

username,manager,exit_date
c.rakesh,priya.m,2026-08-14
c.anjali,priya.m,2026-08-14
c.dmitri,sunil.v,2026-08-21
,priya.m,2026-08-14
c.wei,sunil.v,2026-08-31

Yes, row four has an empty username. That is deliberate — the CSV is the test of whether your generated script validates input, because offboard_old.ps1 demonstrably does not.

Guided steps

  1. Read the legacy script yourself first. Fifteen minutes, no AI. List every risk you can find in legacy-risk-report.md, each as: line/behavior · what goes wrong · trigger condition. This pre-AI pass matters: your review skill is what you are training, and you need a baseline of what you catch unaided.
  2. Now get the AI's read. Paste the script with: "Explain this script section by section, then list every security and operational risk, ranked by severity, with the exact line and the condition that triggers each." Compare against your list. Log in the report: what the AI found that you missed, and — this happens — what you found that the AI missed or under-ranked. Expected output: between you, at minimum the two planted dangers plus at least four secondary risks.
  3. Trace the empty-username row through the old script by hand. Walk $u = "" through each line. Write down exactly what Remove-Item "\\fs01\home\$u" -Recurse -Force and the wildcard profile cleanup do when $u is empty. This is the moment the lab exists for: say the blast radius out loud, in writing.
  4. Generate the replacement. Prompt: "Write a PowerShell offboarding script that reads contractors.csv and for each user: disables the AD account, removes group memberships (recording them first), archives the home directory, and stamps the description. Requirements: SupportsShouldProcess with -WhatIf, validate every CSV row and skip-with-log on bad rows, no credentials in the script, try/catch with a transcript log, scope all filters to exact SamAccountName, and emit a rollback file of removed group memberships." Save as offboard_new.ps1.
  5. Run the six-gate review on the AI's draft. Check each gate and record pass/fail in review-checklist.md: (1) -WhatIf genuinely wired through every destructive call, not just declared; (2) least privilege — no Domain Admin assumptions, credential prompted or delegated, never stored; (3) error paths — a failed disable must stop that user's pipeline, not continue to deletion; (4) no hardcoded secrets anywhere, including "example" ones; (5) filters scoped — no -Filter *, no wildcard matches on names; (6) input validation — the empty row must be skipped and logged. AI drafts routinely fail two or three of these on the first pass. Fix each failure yourself or via a targeted re-prompt, and record the before/after.
  6. Run PSScriptAnalyzer.Invoke-ScriptAnalyzer -Path .\offboard_new.ps1 -Severity Warning,Error. Expected output: findings such as PSAvoidUsingPlainTextForPassword or PSUseShouldProcessForStateChangingFunctions if your hardening was incomplete. Iterate until clean, and paste the final clean output into the checklist.
  7. Dry-run against the CSV. Execute with -WhatIf only. Expected output: WhatIf lines for the four valid users, one logged skip for the malformed row, and zero actual changes. If the malformed row produced a WhatIf line targeting an empty identity, your validation gate is cosmetic — fix it.
  8. Write the verdict on the legacy script. Close legacy-risk-report.md with a recommendation a manager can act on: retire / rewrite / harden, the two dangers named in plain language, and what the Friday job should be replaced with. One paragraph, no jargon.
Warning

Never let "it has run fine for seven years" stand as a safety argument. offboard_old.ps1 has run fine because its kill condition — one empty cell in a CSV nobody validates — has not happened yet. Safety is a property of the script, not of its luck to date.

A note on how you prompt for the replacement: the requirements list in step 4 is doing real work. An open-ended "write me an offboarding script" reliably produces the same defect classes the legacy script has — no validation, no -WhatIf, optimistic error handling — because the AI reproduces the average script on the internet, and the average script is unsafe. Stating the gates in the prompt gets you perhaps eighty percent of the way; the review protocol exists for the twenty percent the AI claims to have implemented but did not wire through. Prompting well and reviewing anyway is the posture; prompting well instead of reviewing is how the vulnerability studies got their 100% figure.

Pro Tip

Keep your step-4 prompt and the six-gate checklist together in your prompt library — the pair is reusable for every destructive script you generate from now on, and the checklist is the interview artifact that proves you review what AI writes.

Verify

  • legacy-risk-report.md contains your pre-AI findings, the AI comparison, and both planted dangers named with trigger conditions.
  • The empty-username trace states the concrete blast radius of the unscoped Remove operations.
  • offboard_new.ps1 passes all six review gates, with the checklist showing at least one AI-draft failure you caught and fixed.
  • PSScriptAnalyzer output is clean at Warning severity and pasted into the checklist.
  • The -WhatIf dry run shows four planned changes, one logged skip, zero executions.
  • The management verdict is one paragraph a non-scripter could act on.

Self-grading rubric (pass = 7/10): both planted dangers found before AI assistance (0–2) · empty-row blast radius correctly traced (0–2) · six gates checked with at least one real AI-draft defect caught (0–3) · analyzer clean + honest dry run (0–2) · actionable verdict (0–1).

▸Answer key — open only after both reports are writtenoptional deep dive — click to expand

The two planted dangers:

  • The unscoped Remove chain. With an empty username cell, $u = "" makes Remove-Item "\\fs01\home\$u" -Recurse -Force resolve to \\fs01\home\ — recursively force-deleting every user's home directory on the server. The wildcard profile cleanup is worse in a different way: -like "*$u*" with empty $u becomes -like "**", matching every profile folder and removing them all. Two independent estate-destroying paths from one blank cell, no validation, no -WhatIf, no confirmation (-Confirm:$false), scheduled to run unattended.
  • The plaintext credential.$password = "Sv3rL0g!2019" sits in a script on a share, for the svc-offboard account — which, given what the script does, holds account-disable, group-modify and file-delete rights across the estate. Anyone with read access to \\fs01\it\ owns those privileges. Seven years unrotated, per the modified date.

Secondary risks a complete report also names: Set-ExecutionPolicy Bypass -Force at machine scope; no error handling anywhere — a failed Disable-ADAccount still proceeds to delete the user's files; Get-ADUser $u unscoped by server/OU; group memberships removed with no record, making rollback impossible; Copy-Item failure does not stop the Remove-Item that follows (archive silently skipped, data gone); no logging or transcript; CSV read from a writable share — anyone who can edit leavers.csv can offboard anyone.

Common AI-draft failures at step 5 (catching any counts): -WhatIf declared via [CmdletBinding(SupportsShouldProcess)] but destructive cmdlets called without $PSCmdlet.ShouldProcess guards, so -WhatIf silently does nothing; a "sample" credential or Get-Credential result exported to disk; validation that checks the row exists but not that the username resolves in AD; rollback file written after deletions instead of before.

Scoring: unscoped Remove found unaided = 1 · plaintext credential found unaided = 1 · blast radius traced concretely (names \\fs01\home\ and the ** wildcard) = 2 · ≥1 genuine AI-draft defect caught and fixed = 2 · four secondary risks named = 1 · analyzer clean = 1 · dry run shows skip-with-log = 1 · verdict actionable = 1. 8+/10 means the review protocol is a habit, not a checklist.

Key Takeaway:

AI writes the script in thirty seconds; the six-gate review is where the sysadmin earns the title — because every studied AI-generated admin script shipped with exploitable flaws, and the legacy script that "ran fine for years" was one blank cell away from deleting the file server.

This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in