PowerShell for Defenders: 15 Commands Every Windows Admin Should Know
Attackers love PowerShell. It is installed on every Windows machine, it is trusted by the operating system, and it can do almost anything an admin can do. The good news: everything an intruder can see with it, you can see first.
This post walks through 15 commands I use when I sit down at a Windows box and want to know, in a few minutes, whether something is wrong. No add-ons, no third-party tools. Just what ships with Windows 10, 11 and Server.
Before you start
- Only run these on systems you own or are authorized to manage. None of them change anything, but authorization is the line between admin work and a security incident.
- Open PowerShell as Administrator (right-click the Start button, choose Terminal (Admin) or Windows PowerShell (Admin)). Several commands return partial results without it.
- These commands read state. They do not kill processes, delete files or change settings. Investigate first, act second.
1. Get-ComputerInfo: know what you are looking at
Get-ComputerInfo | Select-Object CsName, OsName, OsVersion, OsLastBootUpTime, CsDomain
Why it matters: the first question in any investigation is which machine is this, what is it running, and when did it last reboot? An unexpected reboot time is often the first clue that something was installed.
2. Get-LocalUser: who has an account here?
Get-LocalUser | Select-Object Name, Enabled, LastLogon, PasswordLastSet
Why it matters: attackers create local accounts as a backdoor. Look for names you do not recognize, accounts that are enabled but should not be, and the built-in Administrator or Guest account enabled when policy says disabled.
3. Get-LocalGroupMember: who is an administrator?
Get-LocalGroupMember -Group "Administrators"
Why it matters: membership in the local Administrators group is the prize. Every name here can do anything on this machine. If a regular user or an unknown domain account appears, stop and find out why.
Practical tip: check Remote Desktop Users the same way. RDP access is quieter than admin access and just as useful to an intruder.
4. Get-NetTCPConnection: what is talking to the internet?
Get-NetTCPConnection -State Established |
Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, OwningProcess |
Sort-Object RemoteAddress
Why it matters: malware has to phone home. This shows every live connection and the process ID behind it. Unknown remote addresses on odd ports deserve a look.
5. Match connections to programs
Get-NetTCPConnection -State Established | ForEach-Object {
$p = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue
[pscustomobject]@{ Process = $p.ProcessName; Path = $p.Path; Remote = "$($_.RemoteAddress):$($_.RemotePort)" }
} | Sort-Object Process | Format-Table -AutoSize
Why it matters: a process ID is a number. A process name and path is an answer. A program called svchost.exe running from C:\Users\Public instead of C:\Windows\System32 is a classic sign of trouble.
6. Listening ports: what doors are open?
Get-NetTCPConnection -State Listen | Select-Object LocalPort, OwningProcess | Sort-Object LocalPort -Unique
Why it matters: a listening port is a service waiting for someone to connect. On a workstation, the list should be short. An unexpected listener on a high port is how many remote-access tools wait for their operator.
7. Get-Process: what is running, and from where?
Get-Process | Select-Object Name, Id, Path, StartTime -ErrorAction SilentlyContinue |
Sort-Object StartTime -Descending | Select-Object -First 25
Why it matters: the most recently started processes are the most interesting during an incident. Pay attention to anything running from AppData, Temp, Downloads or Public folders.
8. Get-Service: which services are running?
Get-Service | Where-Object Status -eq "Running" | Select-Object Name, DisplayName | Sort-Object Name
Why it matters: a service survives reboots and runs without a logged-in user. That makes it a favourite persistence method. Compare the list with a known-good machine of the same build.
9. Get-ScheduledTask: the other favourite hiding place
Get-ScheduledTask | Where-Object State -ne "Disabled" |
Select-Object TaskName, TaskPath, State | Sort-Object TaskPath
Then inspect anything suspicious:
(Get-ScheduledTask -TaskName "NameHere").Actions
Why it matters: scheduled tasks run programs on a timer or at logon. Attackers name them to look boring, such as Update or Maintenance, and point them at a script in a user folder. The Actions property shows exactly what runs.
10. Run keys: programs that start at logon
$keys = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run",
"HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce",
"HKCU:\Software\Microsoft\Windows\CurrentVersion\Run",
"HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
foreach ($k in $keys) { Get-ItemProperty -Path $k -ErrorAction SilentlyContinue }
Why it matters: this is the oldest persistence trick in Windows and it still works. Every entry should map to software you recognize.
11. Get-WinEvent: failed logons
Get-WinEvent -FilterHashtable @{ LogName = "Security"; Id = 4625 } -MaxEvents 50 |
Select-Object TimeCreated, @{n="Account";e={$_.Properties[5].Value}}, @{n="Source";e={$_.Properties[19].Value}}
Why it matters: Event ID 4625 is a failed logon. A burst of them against one account from one address is a password-guessing attack in progress. I cover the full list of event IDs worth knowing in Windows Event Logs: 10 Event IDs That Reveal an Intruder.
12. Get-WinEvent: successful logons by type
Get-WinEvent -FilterHashtable @{ LogName = "Security"; Id = 4624 } -MaxEvents 200 |
Where-Object { $_.Properties[8].Value -in 3, 10 } |
Select-Object TimeCreated, @{n="Account";e={$_.Properties[5].Value}}, @{n="Type";e={$_.Properties[8].Value}}, @{n="From";e={$_.Properties[18].Value}}
Why it matters: logon type 10 is Remote Desktop and type 3 is a network logon (file shares, remote admin tools). Those are the types an intruder uses. A type 10 logon at 03:00 from an address you do not recognize is not a false alarm.
13. Get-MpComputerStatus: is Defender actually on?
Get-MpComputerStatus | Select-Object AMServiceEnabled, RealTimeProtectionEnabled, AntivirusSignatureLastUpdated, QuickScanEndTime
Why it matters: one of the first things malware does is switch off real-time protection. If this says False and nobody on your team did it, treat the machine as compromised.
14. Get-NetFirewallProfile: is the firewall on?
Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction
Why it matters: all three profiles (Domain, Private, Public) should show Enabled: True and DefaultInboundAction: Block. Anything else is an open door.
15. Get-HotFix: how far behind on patches?
Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 10 HotFixID, Description, InstalledOn
Why it matters: if the newest patch is months old, the machine is exposed to every vulnerability published since. Patching is boring and it is still the single most effective control you have.
Bonus: a 2-minute audit script
Paste this into an elevated PowerShell window and it writes a plain-text report to your desktop. Run it on a healthy machine today so you have something to compare against on a bad day.
$out = "$env:USERPROFILE\Desktop\audit-$(Get-Date -Format yyyyMMdd-HHmm).txt"
"=== SYSTEM ===" | Out-File $out
Get-ComputerInfo | Select-Object CsName, OsName, OsVersion, OsLastBootUpTime | Out-File $out -Append
"=== LOCAL USERS ===" | Out-File $out -Append
Get-LocalUser | Select-Object Name, Enabled, LastLogon | Out-File $out -Append
"=== ADMINISTRATORS ===" | Out-File $out -Append
Get-LocalGroupMember -Group Administrators | Out-File $out -Append
"=== LISTENING PORTS ===" | Out-File $out -Append
Get-NetTCPConnection -State Listen | Select-Object LocalPort, OwningProcess | Sort-Object LocalPort -Unique | Out-File $out -Append
"=== ESTABLISHED CONNECTIONS ===" | Out-File $out -Append
Get-NetTCPConnection -State Established | Select-Object RemoteAddress, RemotePort, OwningProcess | Out-File $out -Append
"=== SCHEDULED TASKS (enabled) ===" | Out-File $out -Append
Get-ScheduledTask | Where-Object State -ne Disabled | Select-Object TaskName, TaskPath | Out-File $out -Append
"=== RUN KEYS ===" | Out-File $out -Append
Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run" | Out-File $out -Append
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" | Out-File $out -Append
"=== DEFENDER / FIREWALL ===" | Out-File $out -Append
Get-MpComputerStatus | Select-Object RealTimeProtectionEnabled, AntivirusSignatureLastUpdated | Out-File $out -Append
Get-NetFirewallProfile | Select-Object Name, Enabled | Out-File $out -Append
"=== LAST 10 PATCHES ===" | Out-File $out -Append
Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 10 | Out-File $out -Append
Write-Host "Report written to $out"
Quick reference
| Question | Command |
|---|---|
| Who has accounts? | Get-LocalUser |
| Who is admin? | Get-LocalGroupMember Administrators |
| What is connected out? | Get-NetTCPConnection -State Established |
| What is listening? | Get-NetTCPConnection -State Listen |
| What starts automatically? | Run keys, Get-ScheduledTask, Get-Service |
| Who logged in, and how? | Get-WinEvent with ID 4624 |
| Is protection on? | Get-MpComputerStatus, Get-NetFirewallProfile |
Common mistakes
- Running without elevation. Half the commands return partial data in a normal window and you will conclude the machine is clean.
- Trusting process names. Names are free. Paths and signatures are not. Always look at
Path. - Having no baseline. You cannot spot the odd scheduled task if you have never looked at the normal ones. Run the audit script on a clean machine first.
- Investigating on the live box for hours. If you see clear signs of compromise, isolate the machine from the network and preserve the evidence. Do not start “cleaning”.
FAQ
Do I need PowerShell 7 for these?
No. Everything above works in Windows PowerShell 5.1, which is built into Windows. PowerShell 7 works too.
Will these commands set off alerts in my EDR?
Possibly, and that is fine. Security tools flag reconnaissance commands because attackers use them. Tell your security team before you run an audit script across the fleet.
Can I run these against a remote machine?
Yes. Wrap them in Invoke-Command -ComputerName NAME -ScriptBlock { ... } if PowerShell Remoting is enabled. That is how you check 50 machines in one go.
Final thoughts
An intruder with PowerShell is dangerous because they can see everything. The fix is not to fear the tool. It is to use it better than they do, and to use it before they do. Fifteen commands, a baseline report, and ten minutes a month will catch most of what gets through.
Useful resources
- Microsoft Learn: PowerShell documentation
- Microsoft Sysinternals (Autoruns and Process Explorer go deeper than the commands above)
- MITRE ATT&CK: Boot or Logon Autostart Execution
Know the attack. Own the defense.
Tagged:PowerShellWindowsBlue teamIncident response
Need this done for your organization?
I run security and compliance programs for a living. I can review, set up, or train your team.