> ## Content Index
> Fetch the complete content index at: https://blog.sudowl.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# How I Use Claude to Write PowerShell Scripts in 30 Seconds
- URL: https://blog.sudowl.com/how-i-use-claude-powershell-scripts/
- Published: 2026-03-29T10:00:00.000Z
- Updated: 2026-10-17T01:30:07.000Z
- Description: My workflow for getting usable PowerShell out of Claude: a prompt with constraints, -WhatIf first, PSScriptAnalyzer, and the 5.1 vs 7 traps it falls into.
- Author: Lukas
- Tags: Windows Server, AI

The "30 seconds" in the title is the generation part. Describe the job, get a script back, done. The part that actually takes time is reading it before it touches a production box — and that part I don't skip, ever. This post is about making the 30 seconds produce something worth reading.

**TL;DR:** tell it the PowerShell version, demand `-WhatIf` support, ask for read-only first, lint the output, run it with `-WhatIf` before you run it for real.

```text
Write a PowerShell script for Windows PowerShell 5.1 on Windows Server 2022.
Task: <what it should do>.
Constraints: CmdletBinding with SupportsShouldProcess, parameters instead of
hardcoded paths, no aliases, no external modules unless I name them,
Write-Verbose for progress, fail loudly on errors.
Output: only the script, then a short list of assumptions you made.

```

```powershell
Install-Module PSScriptAnalyzer -Scope CurrentUser
Invoke-ScriptAnalyzer -Path .\script.ps1
.\script.ps1 -WhatIf -Verbose

```

## Why constraints beat clever prompts

Ask "write me a script that cleans up old logs" and you get *a* script. It might use PowerShell 7 syntax on your 5.1 server. It might hardcode `C:\Logs`. It might call `Remove-Item -Recurse -Force` on whatever path it guessed. None of that is the model being dumb — you didn't tell it anything, so it filled in the blanks with the most common case.

Every line in my template exists because leaving it out produced something I had to fix:

- **Version.** Windows PowerShell 5.1 and PowerShell 7 are not the same language. Ternary operators, `??`, `ForEach-Object -Parallel` — all 7-only. Servers usually run 5.1 by default. Say which one.
- **`SupportsShouldProcess`.** Gives you `-WhatIf` and `-Confirm` for free. Any script that deletes, moves, or changes something gets this. Non-negotiable.
- **Parameters.** Hardcoded paths make scripts single-use. Parameters with sane defaults make them tools.
- **No aliases.** `gci | ? {...} | % {...}` is fine in a shell, unreadable in a file you'll open again in six months.
- **Assumptions list.** This is the most useful line. It makes the model write down what it guessed, and that's exactly where the bugs are.

## A worked example

Here's the kind of task I'd hand over: delete log files older than N days under a folder, recursively, with a dry-run. Below is the shape of script I expect back — I wrote this one out by hand to show the target, but it's what a good answer to the template looks like:

```powershell
[CmdletBinding(SupportsShouldProcess)]
param(
    [Parameter(Mandatory)]
    [ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
    [string]$Path,

    [ValidateRange(1, 3650)]
    [int]$Days = 30,

    [string]$Filter = '*.log'
)

$ErrorActionPreference = 'Stop'
$cutoff = (Get-Date).AddDays(-$Days)
Write-Verbose "Removing $Filter older than $cutoff under $Path"

$files = Get-ChildItem -LiteralPath $Path -Filter $Filter -File -Recurse |
    Where-Object { $_.LastWriteTime -lt $cutoff }

$removed = 0
foreach ($file in $files) {
    if ($PSCmdlet.ShouldProcess($file.FullName, 'Remove file')) {
        Remove-Item -LiteralPath $file.FullName
        $removed++
    }
}

Write-Verbose "Removed $removed of $($files.Count) candidate files"

```

What I check when reading something like this:

- `-File` on `Get-ChildItem` — without it, a folder named `old.log` gets matched too.
- `-LiteralPath` instead of `-Path` — paths with `[` or `]` break `-Path` because it treats them as wildcards.
- `ValidateRange` on `$Days` — stops someone passing `0` and nuking today's logs.
- `ShouldProcess` wrapping the delete — so `-WhatIf` really does nothing.

If the generated version is missing one of those, I don't fix it by hand. I tell the model what's wrong and let it regenerate — that keeps the conversation as the record of why the script looks the way it does.

## Where I run this

Mostly in [Claude Code](/claude-code-hooks-automate-workflow/), the CLI, sitting in the folder where the script will live. It can read the neighbouring scripts, so "follow the style of the other scripts in this folder" actually means something. For one-off questions I use the non-interactive mode:

```bash
claude -p "PowerShell 5.1: list local accounts on this server that haven't logged in for 90 days. Read-only. Script only, then assumptions."

```

For scripts that touch AD or remote machines, I give it the real context: domain name, OU paths, which cmdlets are available on the box. An example of the context block I paste for AD work:

```text
Context: Windows Server 2022, Windows PowerShell 5.1, RSAT ActiveDirectory
module installed. Domain lab.example.com. Servers live in
OU=Servers,OU=Computers,OU=Corp,DC=lab,DC=example,DC=com.
I am running this as a delegated admin, not Domain Admin.

```

That's the same OU layout from my [AD series](/active-directory-part-2-ous-users-gpo-basics/), and scripts written against it slot straight in — for example around [domain joins](/join-windows-server-ad-domain-powershell/).

## The review loop

1. **Read it.** Every line. If I can't explain a line, I ask the model to explain it, then I check that explanation against `Get-Help`.
2. **Verify every cmdlet and parameter exists.** Models occasionally invent a parameter that sounds right. This catches it in seconds:

```powershell
Get-Command Get-ADComputer -Syntax
(Get-Command Remove-Item).Parameters.Keys

```

1. **Lint.** `Invoke-ScriptAnalyzer` flags aliases, unused variables, missing `ShouldProcess` on functions with state-changing verbs, plaintext password parameters. Feed the warnings back to the model if there are many.
2. **Dry run.** `-WhatIf -Verbose` on the real target. Read the output. Count the "What if:" lines — does that number make sense?
3. **Run for real**, on one machine first.
4. **Schedule it** if it's recurring — that part is in [Task Scheduler automation with PowerShell](/windows-task-scheduler-powershell-automation/).

Step 4 is where most bad surprises show up. A "clean up old files" script that wants to remove ten thousand files when you expected fifty is a bug, not a cleanup.

## Gotcha: PowerShell 7 syntax on a 5.1 box

The most common failure. The script parses fine in the model's head and dies on the server with a parse error on `??` or `? :`. Or it uses `ConvertFrom-Json -AsHashtable`, which doesn't exist in 5.1\. Check what you're on:

```powershell
$PSVersionTable.PSVersion

```

and put that version in the prompt. If you can install PowerShell 7 on your servers, great — then say 7\. Just say *something*.

## Gotcha: encoding and umlauts

Windows PowerShell 5.1 reads a `.ps1` without BOM as the system ANSI codepage. A script saved as UTF-8 without BOM that contains `ä`, `ü` or `€` in a string turns those into garbage at runtime. Save scripts as UTF-8 **with** BOM if they contain anything non-ASCII, or keep strings ASCII.

Same story on output: in 5.1, `Out-File` and `>` write UTF-16LE by default, which surprises whatever tool reads the file next. Ask for `-Encoding UTF8` explicitly when the output goes to another program.

## Gotcha: confident wrong answers

The script that looks cleanest can still be wrong — a filter that matches nothing, a property name that doesn't exist on that object (which PowerShell silently treats as `$null`, so `Where-Object` just returns nothing). Set `Set-StrictMode -Version Latest` at the top while testing; it turns those silent `$null`s into errors.

## The actual point

Claude doesn't replace knowing PowerShell. It replaces typing it. You still need to read the output, which is why I keep the [PowerShell sysadmin cheatsheet](/powershell-sysadmin-cheatsheet/) around — it's the layer that lets you tell a good generated script from a plausible one. The prompts I use for other sysadmin work, Linux included, are in the [sysadmin AI prompt cheat sheet](/sysadmin-ai-prompt-cheatsheet/).

---

### Related posts

- [The Sysadmin Prompt Cheat Sheet — AI Commands That Actually Work](https://blog.diengdoh.com/sysadmin-ai-prompt-cheatsheet/?ref=blog.sudowl.com)
- [Two-Way Syncing a Windows Desktop to a Storage Box with rclone bisync and Task Scheduler](https://blog.diengdoh.com/rclone-bisync-windows-desktop-storage-box/?ref=blog.sudowl.com)
- [Microsoft 365 Expired? Rescuing a Mailbox from the Local OST into a PST](https://blog.diengdoh.com/export-m365-mailbox-ost-to-pst/?ref=blog.sudowl.com)