# PowerShell Cheat Sheet for Windows Server Admins

Source: https://srvscripts.com/guides/powershell-cheat-sheet-windows-server/
Updated: 2026-10-06
Publisher: srvScripts (https://srvscripts.com/)

**Short answer:** Most Windows Server admin work in PowerShell comes down to a small set of cmdlets: `Get-Service`/`Restart-Service` for services, `Get-WinEvent -FilterHashtable` for event logs, `Test-NetConnection` and `Resolve-DnsName` for networking, `Get-NetFirewallRule` for the firewall, `Get-Volume` and `Get-HotFix` for disks and updates, and `Get-ADUser`/`Search-ADAccount` for Active Directory. Pipe results into `Where-Object`, `Select-Object` and `Export-Csv`, and add `-WhatIf` before anything that changes state.

We ran these commands on our Windows lab on 7 October 2026: a Windows Server 2025 Standard domain controller (build 26100, Windows PowerShell 5.1) for the test domain contoso.com, and a domain-joined Windows 11 Pro client (build 22631). Read-only cmdlets ran as shown. Cmdlets that change state (Restart-Service, Unlock-ADAccount, New-NetFirewallRule and similar) ran with `-WhatIf` only. The remoting cmdlets and `Register-ScheduledTask` were syntax-checked only, because they have no `-WhatIf` and our lab sessions cannot open remote sessions.

## Help and discovery

| Command | What it does |
| --- | --- |
| Get-Help Get-WinEvent -Examples | Usage examples (after help is downloaded) |
| Get-Help Get-WinEvent -Online | Opens the Microsoft Learn page in a browser |
| Update-Help | Downloads local help files (run as administrator, needs internet) |
| Get-Command -Noun Service | Every cmdlet that works on services |
| Get-Command *firewall* | Search by wildcard |
| Get-Command -Module ActiveDirectory | Everything in a module (151 commands on our DC) |
| Get-Service W32Time | Get-Member | Properties and methods of the objects a cmdlet returns |

On our fresh Server 2025 DC, `Get-Help` showed only the syntax and no examples, because Windows ships without the full help files. Run `Update-Help` once, or use `-Online`. `Get-Member` is the one to remember: it tells you which property names you can filter and sort on.

## Services and processes

| Command | What it does |
| --- | --- |
| Get-Service W32Time, WinRM, DNS | Status and start type of named services |
| Get-Service | Where-Object { $_.StartType -eq 'Automatic' -and $_.Status -ne 'Running' } | Automatic services that are not running |
| Restart-Service W32Time | Restart a service (add -WhatIf to preview) |
| Stop-Service Spooler / Start-Service Spooler | Stop or start |
| Set-Service RemoteRegistry -StartupType Disabled | Change the start type |
| Get-CimInstance Win32_Service -Filter "Name='WinRM'" | Service account and binary path |
| Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 5 | Top memory users |
| Get-Process lsass -IncludeUserName | Process owner (needs an elevated session) |
| Stop-Process -Name notepad | Kill by name (or -Id) |

```
PS> Get-Service W32Time, WinRM, NTDS, DNS | Format-Table Name, Status, StartType

Name     Status StartType
----     ------ ---------
DNS     Running Automatic
NTDS    Running Automatic
W32Time Running Automatic
WinRM   Running Automatic

PS> Restart-Service W32Time -WhatIf
What if: Performing the operation "Restart-Service" on target "Windows Time (W32Time)".
```

The “automatic but stopped” filter is a quick health check after a reboot. On our DC it listed services such as `wuauserv` and `sppsvc`, which Windows starts when needed and stops again when idle. Only investigate the ones you recognise as always-on.

## Event logs with Get-WinEvent -FilterHashtable

`-FilterHashtable` filters inside the event log service, so it is much faster than piping every event to `Where-Object`. Level 1 is Critical, 2 Error, 3 Warning.

```
# Errors and critical events in the System log, last 24 hours
Get-WinEvent -FilterHashtable @{LogName='System'; Level=1,2; StartTime=(Get-Date).AddDays(-1)}

# Failed logons (4625) in the last 7 days, counted
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddDays(-7)} |
    Measure-Object

# Account lockouts (4740, on a DC) with user and source computer
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4740} -MaxEvents 10 |
    Select-Object TimeCreated,
        @{n='User';  e={$_.Properties[0].Value}},
        @{n='Source';e={$_.Properties[1].Value}}

# Shutdowns and restarts: 1074 = who restarted, 6006 = log stopped, 6005 = log started
Get-WinEvent -FilterHashtable @{LogName='System'; Id=1074,6005,6006} -MaxEvents 6

# Log sizes and record counts
Get-WinEvent -ListLog System, Security, Application
```

On our DC, the lockout query found a real lockout we had caused while testing:

```
TimeCreated          User Source
-----------          ---- ------
10/6/2026 2:19:21 PM erin WINCLIENT
```

For 4625 on Server 2025, the failed account name is `$_.Properties[5].Value` and the source IP is `$_.Properties[19].Value` (we checked the positions against the event XML). On our DC, most of 622 failures in a week had `-` as the IP, which means a local or Kerberos-related failure rather than a network logon. Our guides on [tracing lockouts with event 4740](/guides/ad-account-lockout-source-event-4740/) and [RDP connection event IDs](/guides/rdp-connection-logs-event-ids/) go deeper.

## Networking and firewall

| Command | What it does |
| --- | --- |
| Get-NetIPAddress -AddressFamily IPv4 | IP addresses per interface |
| Get-NetIPConfiguration | IP, gateway and DNS servers in one view |
| Get-DnsClientServerAddress -AddressFamily IPv4 | Which DNS servers the host uses |
| Test-NetConnection 203.0.113.10 -Port 443 | Ping plus a TCP port test (replaces telnet) |
| Test-NetConnection example.com -Port 80 -InformationLevel Quiet | Returns just True or False, for scripts |
| Test-NetConnection 203.0.113.10 -TraceRoute | Traceroute |
| Resolve-DnsName example.com -Type MX -Server 1.1.1.1 | DNS query against a chosen server |
| Resolve-DnsName _ldap._tcp.dc._msdcs.contoso.com -Type SRV | Find domain controllers through DNS |
| Get-NetTCPConnection -State Listen | Listening ports with owning process ID (like netstat -ano) |
| Get-NetFirewallProfile | Firewall on/off per profile, default action, log path |
| Get-NetFirewallRule -DisplayGroup 'Remote Desktop' | Rules in a built-in group |
| Get-NetFirewallRule -DisplayName 'Remote Desktop - User Mode (TCP-In)' | Get-NetFirewallPortFilter | Ports a rule covers |
| Get-NetFirewallRule -Enabled True -Direction Inbound -Action Allow | All active inbound allow rules (163 on our DC) |

Map listening ports to process names in one line:

```
Get-NetTCPConnection -State Listen |
    Sort-Object LocalPort -Unique |
    Select-Object LocalPort, OwningProcess,
        @{n='Process'; e={(Get-Process -Id $_.OwningProcess).Name}}
```

```
LocalPort OwningProcess Process
--------- ------------- -------
       53          4092 dns
       88           992 lsass
      389           992 lsass
      445             4 System
     3389          1324 svchost
     5985             4 System
```

(Trimmed to the domain controller ports.) Port 5985 is WinRM over HTTP. It shows as System (PID 4) because WinRM listens through HTTP.sys, the kernel HTTP driver. To add a rule for one source address, preview it first:

```
New-NetFirewallRule -DisplayName 'Allow SQL 1433 from app' -Direction Inbound `
    -Protocol TCP -LocalPort 1433 -RemoteAddress 203.0.113.10 -Action Allow -WhatIf
```

Remove `-WhatIf` to create it. To roll back, `Disable-NetFirewallRule -DisplayName 'Allow SQL 1433 from app'` keeps the rule but switches it off. For domain-wide rules, use [Windows Firewall through Group Policy](/guides/windows-firewall-group-policy/) instead of per-server rules.

## Disks, updates and files

| Command | What it does |
| --- | --- |
| Get-Volume | Drive letters, file system, size, free space, health |
| Get-PSDrive -PSProvider FileSystem | Used and free space per drive letter |
| Get-Disk | Physical disks, partition style (GPT/MBR), status |
| Get-HotFix | Sort-Object InstalledOn -Descending | Installed updates, newest first |
| Get-HotFix -Id KB5122870 | Is one update installed? |
| Get-ComputerInfo -Property OsName, OsVersion, OsLastBootUpTime | OS version and last boot |
| Get-ChildItem C:\Logs -Recurse -File | Sort-Object Length -Descending | Select-Object -First 10 | Largest files under a folder |
| Get-Content C:\Logs\app.log -Tail 50 -Wait | Last 50 lines, then follow (like tail -f) |
| Select-String -Path C:\Logs\*.log -Pattern 'error' | Search inside files (like grep) |
| Get-FileHash file.iso -Algorithm SHA256 | Checksum of a download |
| Get-Acl C:\Share | Select-Object -ExpandProperty Access | NTFS permissions on a folder |

```
PS> Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 3

HotFixID  Description     InstalledOn
--------  -----------     -----------
KB5122870 Security Update 10/6/2026 12:00:00 AM
KB5122871 Security Update 10/6/2026 12:00:00 AM
KB5126052 Update          10/6/2026 12:00:00 AM
```

`Get-HotFix` reads the Win32_QuickFixEngineering class. Microsoft’s documentation for that class says updates supplied by Windows Installer (MSI) or the Windows Update site are not returned, so check the Windows Update history too when an update seems to be missing.

Before you run `Remove-Item -Recurse` on a log or temp folder, run the same command with `-WhatIf` and read the list. There is no recycle bin for files deleted from PowerShell.

## Local users and groups

| Command | What it does |
| --- | --- |
| Get-LocalUser | Local accounts, enabled state, last logon |
| Get-LocalGroupMember -Group Administrators | Who is a local admin, including domain groups |
| Add-LocalGroupMember -Group 'Remote Desktop Users' -Member 'CONTOSO\bob' | Grant RDP to a domain user |
| Disable-LocalUser -Name Guest | Disable a local account |

On our Windows 11 client, `Get-LocalGroupMember -Group Administrators` returned `CONTOSO\Domain Admins` (source ActiveDirectory) and the local Administrator. On the domain controller the same command failed with `Group Administrators was not found.`, and `Get-LocalUser` listed domain accounts. A DC has no local account database, so use the AD cmdlets there.

## Active Directory basics

These need the ActiveDirectory module (on a DC, or RSAT on an admin machine).

| Command | What it does |
| --- | --- |
| Get-ADUser bob -Properties LastLogonDate, PasswordLastSet, LockedOut | One user with extra properties |
| Get-ADUser -Filter 'Enabled -eq $false' -SearchBase 'OU=Lab,DC=contoso,DC=com' | Disabled users in an OU |
| Get-ADUser -Filter "Name -like 'a*'" | Wildcard search |
| Search-ADAccount -LockedOut -UsersOnly | Currently locked accounts |
| Unlock-ADAccount -Identity erin | Unlock (preview with -WhatIf) |
| Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly | No logon in 90 days, including never logged on |
| Search-ADAccount -PasswordNeverExpires -UsersOnly | Accounts with non-expiring passwords |
| Get-ADGroupMember 'Server-Admins' -Recursive | Members including nested groups |
| Get-ADPrincipalGroupMembership bob | Groups a user belongs to |
| Get-ADComputer -Filter * -Properties OperatingSystem | Computers and their OS |
| Get-ADDefaultDomainPasswordPolicy | Lockout threshold and password rules |

`-Recursive` makes a real difference. In our lab, the Server-Admins group contains only the group IT-Admins:

```
PS> Get-ADGroupMember 'Server-Admins' | Format-Table SamAccountName, objectClass
SamAccountName objectClass
-------------- -----------
IT-Admins      group

PS> Get-ADGroupMember 'Server-Admins' -Recursive | Format-Table SamAccountName, objectClass
SamAccountName objectClass
-------------- -----------
alice          user
bob            user
```

A filter such as `Get-ADUser -Filter 'LastLogonDate -lt $cut'` skips accounts that have never logged on, because their LastLogonDate is empty. In our lab that query returned 0 users, while `Search-ADAccount -AccountInactive` returned 10. Use Search-ADAccount, or check for empty values separately, before you disable stale accounts.

More query patterns are in [25 Get-ADUser examples](/guides/get-aduser-powershell-examples/).

## Remoting, output and scheduled tasks

Remoting (syntax checked; needs WinRM enabled on the target and Kerberos or explicit credentials):

```
Enter-PSSession -ComputerName srv01                 # interactive shell on one server
Invoke-Command -ComputerName srv01, srv02 -ScriptBlock { Get-Service W32Time }
Invoke-Command -ComputerName srv01 -FilePath C:\Scripts\check.ps1 -Credential (Get-Credential)
Test-WSMan -ComputerName srv01                      # is WinRM reachable?
```

If `Test-WSMan` fails with “WinRM cannot complete the operation”, WinRM is off or blocked by the firewall on the target. That is what we got from our DC to the client, which is one reason the remoting rows are only syntax-checked. `Invoke-Command` runs in parallel against many computers (32 at a time by default; change it with `-ThrottleLimit`).

Shaping and exporting output:

| Command | What it does |
| --- | --- |
| Where-Object Status -eq 'Running' | Filter objects |
| Select-Object Name, Status, StartType | Pick columns |
| Sort-Object Name / Group-Object Status | Sort or count by value |
| Export-Csv C:\Reports\services.csv -NoTypeInformation | Save to CSV (the switch matters on 5.1) |
| Out-GridView | Sortable, filterable window (desktop sessions only) |

Microsoft’s documentation says `Out-GridView` does not work on Server Core or Nano Server, because it needs a user interface. In our SSH session it showed nothing at all, so use `Export-Csv` in scripts and remote sessions.

Scheduled tasks:

```
Get-ScheduledTask | Where-Object TaskPath -notlike '\Microsoft\*'      # your own tasks
Get-ScheduledTask -TaskName 'Nightly cleanup' | Get-ScheduledTaskInfo  # last run and result

$a = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -File C:\Scripts\cleanup.ps1'
$t = New-ScheduledTaskTrigger -Daily -At 3am
$p = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName 'Nightly cleanup' -Action $a -Trigger $t -Principal $p
```

We ran the three `New-ScheduledTask*` lines on the client to check the objects. `Register-ScheduledTask` has no `-WhatIf`, so we did not run it. In `Get-ScheduledTaskInfo`, a LastTaskResult of 0 means success, and 267011 (0x41303) means the task has not run yet.

**Official documentation:** [Get-WinEvent](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.diagnostics/get-winevent) · [Get-ADUser](https://learn.microsoft.com/en-us/powershell/module/activedirectory/get-aduser) · [Test-NetConnection](https://learn.microsoft.com/en-us/powershell/module/nettcpip/test-netconnection) · [Requirements for remote commands](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_remote_requirements)

**Related:** [Get-ADUser PowerShell Examples: 25 Queries for Active Directory](/guides/get-aduser-powershell-examples/) · [AD Account Lockout Source: Event 4740 Tracing](/guides/ad-account-lockout-source-event-4740/) · [Locked Out AD Users Report: PowerShell Script with Lockout Source](/scripts/ad-locked-out-users-report/) · [Windows Firewall Group Policy: 5 Steps to Deploy Secure Rules](/guides/windows-firewall-group-policy/) · [Export AD Users to CSV: PowerShell Script with Last Logon](/scripts/export-ad-users-csv/)

## Frequently asked questions

### How do I check if a port is open in PowerShell?

Use Test-NetConnection with -Port, for example Test-NetConnection 203.0.113.10 -Port 443. TcpTestSucceeded shows True if the port accepted a connection. Add -InformationLevel Quiet to get only True or False.

### What is the PowerShell equivalent of netstat -ano?

Get-NetTCPConnection. Use -State Listen for listening ports and look up the OwningProcess ID with Get-Process -Id to see the program name.

### How do I find locked out users in Active Directory?

Run Search-ADAccount -LockedOut -UsersOnly. To find where the lockout came from, query event 4740 in the Security log of the domain controller holding the PDC emulator role.

### Why is Get-WinEvent -FilterHashtable faster than Where-Object?

The hashtable filter is applied by the event log service, so only matching events are returned. Where-Object receives every event first and then throws most of them away.

### Do these commands work in PowerShell 7?

Most do. The cmdlets here come from Windows modules such as NetTCPIP, NetSecurity, ScheduledTasks and ActiveDirectory, which also load in PowerShell 7 on Windows. Test in your own environment, because a few older modules need the Windows PowerShell compatibility layer.
