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.
Table of Contents
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 and RDP connection 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 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.
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 · Get-ADUser · Test-NetConnection · Requirements for remote commands
Related: Get-ADUser PowerShell Examples: 25 Queries for Active Directory · AD Account Lockout Source: Event 4740 Tracing · Locked Out AD Users Report: PowerShell Script with Lockout Source · Windows Firewall Group Policy: 5 Steps to Deploy Secure Rules · Export AD Users to CSV: PowerShell Script with Last Logon
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.