Security tips / Windows / Digital Forensics

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

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.

Work with me →