WMIC is gone. On both machines behind this CMD commands cheat sheet, Windows Server 2025 and Windows 11 24H2, wmic os get caption now answers with 'wmic' is not recognized, so plenty of older Windows command lists break on the first line you paste. The rest of Command Prompt still ships in every install, and it is still the fastest way to partition a disk, read an IP, kill a process or add a user.
The reference tables come first and double as the printable PDF. After them, the commands that bite get real output: diskpart refusing an offline disk, robocopy exit codes, a net user password that hangs a script, and what replaces WMIC. Every output shown here was captured on Windows Server 2025 (build 26100.32230) and Windows 11 24H2 lab VMs in October 2026, and most table rows ran there too.
Want it on paper? Download the one-page PDF of the reference tables below, with files, diskpart, ipconfig and netsh, processes, services, users and repair on a single landscape sheet.
Windows CMD commands cheat sheet
Each table groups the commands by job. Anything that changes disks, services, users, the firewall or boot settings needs an elevated prompt, and the next section shows how to get one. Most rows ran on the Server 2025 VM; the rest are standard forms from Microsoft’s Windows commands reference.
Navigate and get help
| Command | What it does |
|---|---|
help | List built-in commands with one-line descriptions |
robocopy /? | Full syntax for any command |
cd /d F:\ | Change drive and folder in one step |
dir /a /o:-d | All files including hidden, newest first |
where curl tar | Show where a command lives on PATH |
ver | Windows build number |
set | List environment variables |
setx NAME value | Save a user variable for new sessions |
cls | Clear the screen |
Files and folders
| Command | What it does |
|---|---|
mkdir C:\lab\demo\logs | Create a folder and its parents |
copy /y a.txt b.txt | Copy a file, overwrite without asking |
xcopy src dst\ /e /i /h /y | Copy a tree with empty and hidden files |
robocopy src dst /MIR /MT:8 | Mirror a folder with 8 threads |
move c.txt logs\ | Move a file |
ren b.txt c.txt | Rename a file |
del /s /q *.tmp | Delete matching files in all subfolders |
rmdir /s /q logs | Delete a folder and everything in it |
attrib +h a.txt | Hide a file (-h unhides) |
tree /a /f | Folder tree in plain ASCII |
mklink /d link target | Create a directory symlink |
type a.txt | Print a file |
Search and compare text
| Command | What it does |
|---|---|
findstr /i /n "error" app.log | Case-insensitive search with line numbers |
findstr /s /i /m "error" * | Files under this folder that contain a string |
find /c /v "" app.log | Count lines in a file |
sort /r app.log | Sort lines in reverse order |
fc a.txt b.txt | Show the differences between two files |
dir /b /s *.log | Find files by name, full paths only |
echo text | clip | Send output to the clipboard |
Disks and volumes
| Command | What it does |
|---|---|
diskpart | Open the partitioning shell (admin) |
list disk / list volume | Disks and volumes, inside diskpart |
select disk 1 | Pick the disk the next commands act on |
online disk | Bring an offline disk online |
attributes disk clear readonly | Make the selected disk writable |
clean | Wipe every partition on the selected disk |
convert gpt | Switch an empty disk to GPT |
create partition primary | One partition using all free space |
format fs=ntfs quick label=DATA | Quick NTFS format, inside diskpart |
assign letter=F | Give the volume a drive letter |
diskpart /s script.txt | Run diskpart commands from a file |
format F: /fs:ntfs /q /v:DATA /y | Quick format from the normal prompt |
fsutil volume diskfree F: | Free and used bytes on a volume |
vol F: / label F: BACKUP | Show or set a volume label |
Network
| Command | What it does |
|---|---|
ipconfig /all | IP, MAC, DHCP lease and DNS servers |
ipconfig /flushdns | Clear the DNS resolver cache |
ipconfig /release & ipconfig /renew | New DHCP lease (one line, so a remote session survives the drop) |
ping -n 4 1.1.1.1 | Send four echo requests |
tracert -d 1.1.1.1 | Trace the route without DNS lookups |
pathping -n 1.1.1.1 | Route plus per-hop packet loss |
nslookup -type=mx example.com | Query a DNS record type |
netstat -ano | findstr :3389 | Who listens on a port, with PID |
arp -a | IP to MAC table |
route print -4 | IPv4 routing table |
getmac /v | MAC address per adapter |
netsh interface ipv4 show config | Address, gateway and DNS per interface |
netsh interface ipv4 set dnsservers name="Ethernet" static 1.1.1.1 primary | Set a static DNS server |
netsh advfirewall show allprofiles state | Firewall on or off per profile |
netsh advfirewall firewall add rule name="Allow TCP 8080" dir=in action=allow protocol=TCP localport=8080 | Open an inbound port |
curl -I https://example.com | Fetch HTTP headers (curl.exe is built in) |
Processes and services
| Command | What it does |
|---|---|
tasklist /fi "imagename eq ping.exe" | Find a process by name |
tasklist /svc | Services hosted in each process |
tasklist /fi "memusage gt 100000" | Processes using more than ~100 MB |
taskkill /im ping.exe /f | Force-kill by image name |
taskkill /pid 2792 /t /f | Kill a PID and its child processes |
sc query Spooler | Service state |
sc qc Spooler | Start type, binary path, account |
sc config Spooler start= demand | Set start type: auto, demand, disabled |
sc queryex Spooler | State plus the service PID |
net stop Spooler / net start Spooler | Stop or start, waiting for the result |
Users and permissions
| Command | What it does |
|---|---|
whoami /groups | Your groups and integrity level |
net user | List local accounts |
net user joshua Lab-Pass-26! /add | Create a local user |
net localgroup Administrators joshua /add | Make a user a local admin |
net user joshua /active:no | Disable an account |
net user joshua /delete | Delete an account |
net accounts | Password and lockout policy |
icacls C:\data /grant joshua:(OI)(CI)M | Grant inherited Modify on a folder |
icacls file.txt /reset | Replace explicit ACLs with inherited ones |
takeown /f file.txt | Take ownership of a file |
net share | List shared folders |
System info, power and tasks
| Command | What it does |
|---|---|
systeminfo | OS, boot time, RAM, hotfixes |
hostname | Computer name |
driverquery | Installed drivers |
shutdown /r /t 0 | Restart now (/s shuts down) |
shutdown /a | Cancel a pending shutdown |
powercfg /l | Power plans, active one starred |
schtasks /create /tn "Lab\Cleanup" /tr "cmd /c del /q C:\lab\tmp\*" /sc daily /st 02:00 /ru SYSTEM | Daily task as SYSTEM |
schtasks /query /tn "Lab\Cleanup" /fo list | Next run time and status |
w32tm /query /status | Time source and last sync |
w32tm /resync | Sync the clock now |
gpupdate /force | Reapply Group Policy |
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v DisplayVersion | Feature release, for example 24H2 |
Repair and boot
| Command | What it does |
|---|---|
DISM /Online /Cleanup-Image /CheckHealth | Is the component store flagged corrupt |
DISM /Online /Cleanup-Image /RestoreHealth | Repair the component store |
sfc /verifyonly | Check system files, change nothing |
sfc /scannow | Check and repair system files |
chkdsk C: /scan | Online NTFS scan, no reboot |
chkdsk F: /f | Fix errors, needs the volume locked |
bcdedit /enum {current} | Boot entry of the running OS |
Batch syntax
| Command | What it does |
|---|---|
cmd1 && cmd2 | Run cmd2 only if cmd1 succeeds |
cmd1 || cmd2 | Run cmd2 only if cmd1 fails |
cmd > out.txt 2>&1 | Send output and errors to one file |
cmd 2>nul | Hide error messages |
echo %ERRORLEVEL% | Exit code of the last command |
for /l %%i in (1,1,3) do echo %%i | Counting loop (single % at the prompt) |
for %%f in (*.txt) do echo %%~nxf | Loop over files, name and extension |
if exist file.txt (echo found) else (echo missing) | Branch on a file |
cmd /v:on /c "set N=qs & echo !N!" | Delayed expansion on one line |
Open CMD as administrator
Most of the useful commands on this page fail with Access is denied from a normal prompt, so start elevated. For any admin account other than the built-in Administrator, membership of Administrators is not enough on its own, because UAC gives your normal console a filtered token. On Windows 11 and Server 2025 alike, the reliable route is the Start menu:
- Press the Windows key and type
cmd. - Press Ctrl+Shift+Enter, or pick Run as administrator.
- Accept the UAC prompt. The title bar now starts with Administrator.
Windows 11 24H2 and later also ship Microsoft’s own sudo.exe, but it’s off by default. On the lab VM, sudo config answered with this:
Sudo is disabled on this machine. To enable it, go to the Developer Settings page in the Settings app
Turn it on in Settings, System, Advanced, Enable sudo, or from an elevated prompt once. You can choose between three modes. The forceNewWindow mode is the default Microsoft recommends, because it opens the elevated command in a separate window instead of sharing your unelevated console, and normal is the Linux-like inline mode that Microsoft warns carries more risk:
sudo config --enable forceNewWindow
It confirms the mode in one line. From then on you can type sudo diskpart in a normal prompt, accept the UAC dialog, and the elevated diskpart opens in its own window, so you don’t need to keep a second admin console open all day.
Sudo is currently in Force New Window mode on this machine
Server 2025 has no sudo.exe at all (where sudo returned INFO: Could not find "sudo".). where winget came back empty on both VMs too, because commands there ran as SYSTEM and winget is a per-user app alias: the Windows 11 VM had App Installer, and its winget.exe answered v1.29.290 when called by full path. The winget package manager guide covers installing it where it’s missing.
Copy folders with robocopy
Use robocopy for anything bigger than a few files. It retries, logs, runs multithreaded and mirrors, and the one thing that trips people is its exit code, because a successful copy returns 1, not 0. This mirrors a 40-file folder with eight threads and prints only the summary:
robocopy C:\lab\src C:\lab\dst /MIR /MT:8 /R:1 /W:1 /NP /NFL /NDL
Read the summary table before you trust the job. The Copied column tells you what moved and Skipped tells you what was already current, but FAILED is the one to alert on, because robocopy keeps going after a file it can’t copy and you only find out here. This first run copied all 40 files.
Options : *.* /NDL /NFL /S /E /DCOPY:DA /COPY:DAT /PURGE /MIR /NP /MT:8 /R:1 /W:1
------------------------------------------------------------------------------
Total Copied Skipped Mismatch FAILED Extras
Files : 40 40 0 0 0 0
Always set /R and /W. Without them robocopy retries a locked file a million times with 30 seconds between tries, which looks exactly like a hung backup job. /MIR also deletes anything in the destination that isn’t in the source, so point it at the right folder. Swapping source and destination by mistake empties your backup, and robocopy will not ask first.
Running the same copy four ways on the lab VM gave four different exit codes. Anything below 8 is success, so test for if %ERRORLEVEL% GEQ 8 in your scripts. A check for any non-zero code marks every normal copy as a failure, which is how scheduled backups end up alerting every night:
| Exit code | What happened in the lab | Meaning |
|---|---|---|
| 1 | First run, 40 files copied | Files copied, no failures |
| 0 | Second run, 40 skipped | Nothing to do |
| 2 | Extra file in dst, one deleted from src | Extras found and purged by /MIR |
| 16 | Source folder missing | Fatal: ERROR 2 (0x00000002) Accessing Source Directory |
One more trap showed up while capturing these. robocopy ... & echo %ERRORLEVEL% on one line printed 0 every time, because CMD expands %ERRORLEVEL% when it reads the line, before robocopy runs. Put the echo or if on its own line. The batch syntax section further down shows the same parse-time expansion breaking set.
Partition a new disk with diskpart
A fresh data disk on Windows Server shows up offline and read-only, and the usual five-line diskpart recipe does not handle that. The lab VM got a new 4 GB disk, hot-added while it ran. list disk showed it straight away, so you don’t need a rescan or a reboot first:
Disk ### Status Size Free Dyn Gpt
-------- ------------- ------- ------- --- ---
Disk 0 Online 64 GB 1024 KB *
Disk 1 Offline 4096 MB 4096 MB
Check the disk number twice before anything else, because clean wipes whichever disk is selected without asking. Size and the Offline status are the giveaways here. Scripted diskpart is also easier to review than typing at the DISKPART> prompt, so save the commands in a text file:
notepad C:\lab\scripts\newdisk.txt
This first version is the recipe most guides give. It works on a disk that’s already online and writable, which is why it’s so widely copied, but it’s missing two lines for a new disk on a server, and the run below shows what happens without them.
select disk 1
clean
convert gpt
create partition primary
format fs=ntfs quick label=DATA
assign letter=E
Run it with /s from an elevated prompt. diskpart executes the lines in order and stops at the first error, so a failure halfway leaves the disk in whatever state the previous line produced, and you’ll need to check it before running anything again.
diskpart /s C:\lab\scripts\newdisk.txt
On the lab VM it failed on the third line.
DiskPart has encountered an error: The media is write protected
The clean step reported success, then convert gpt hit the read-only flag and the script stopped there. That’s a confusing pair of messages, because you’d expect a write-protected disk to refuse the clean too:
Disk 1 is now the selected disk.
DiskPart succeeded in cleaning the disk.
DiskPart has encountered an error: The media is write protected.
See the System Event Log for more information.
Nothing is wrong with the disk. Server editions default to the Offline Shared SAN policy, so every new disk on a shared bus type (SCSI, iSCSI, SAS, Fibre Channel) arrives offline and read-only. That includes virtio-scsi and most hypervisor data disks, which is why this one did. It is a safety default for shared storage, since two servers writing to the same LUN would corrupt it. san and attributes disk inside diskpart show both settings:
SAN Policy : Offline Shared
Disk 1 is now the selected disk.
Current Read-only State : Yes
Read-only : Yes
Boot Disk : No
So the fix belongs in the script, not in Disk Management. The fixed version adds two lines after select disk, and the order matters because the read-only flag can only be cleared once the disk is online:
online diskbrings the disk online.attributes disk clear readonlydrops the write protection.- The original clean, convert, create, format and assign lines follow unchanged.
This is the exact fixdisk.txt the second run used, with a list volume at the end. Don’t add online disk to a script for a disk that’s already online, because diskpart answers This disk is already online. as a Virtual Disk Service error and the /s run stops there:
select disk 1
online disk
attributes disk clear readonly
clean
convert gpt
create partition primary
format fs=ntfs quick label=DATA
assign letter=E
list volume
Everything up to the format succeeded this time. The last two lines are where the next error came from.
Virtual Disk Service error: The specified drive letter is not free to be assigned
The second run got all the way to the last step and failed on the letter. On a Proxmox or other cloud-init VM, E: is often the config CD, because it mounts before you add data disks. list volume showed exactly that:
Volume ### Ltr Label Fs Type Size Status Info
---------- --- ----------- ----- ---------- ------- --------- --------
Volume 0 E config-2 CDFS CD-ROM 4096 KB Healthy
Volume 1 C Windows NTFS Partition 63 GB Healthy Boot
Volume 2 EFI FAT32 Partition 100 MB Healthy System
Volume 3 NTFS Partition 789 MB Healthy Hidden
Volume 4 DATA NTFS Partition 4079 MB Healthy
The partition and format were already done, so only the letter was left. select volume 4 followed by assign letter=F finished it. This capture puts the failing run, the fixed run and the finished F: volume from the Server 2025 VM in one window:

Confirm the volume from the normal prompt before you copy anything onto it. diskpart reports success per line, but fsutil reads the mounted filesystem, so it proves the letter, the format and the size all landed together.
fsutil volume diskfree F:
About 19 MB of a fresh 4 GB NTFS volume goes to the filesystem itself (the MFT, log file and metadata). If Total bytes is far below the disk size you added, the partition didn’t take all the free space, and list partition inside diskpart will show the gap.
Total free bytes : 4,252,749,824 ( 4.0 GB)
Total bytes : 4,277,137,408 ( 4.0 GB)
Total quota free bytes : 4,252,749,824 ( 4.0 GB)
Unavailable pool bytes : 0 ( 0.0 KB)
Quota unavailable pool bytes : 0 ( 0.0 KB)
Used bytes : 20,193,280 (19.3 MB)
Splitting one disk into several sized partitions and deleting them again is covered step by step in the diskpart partitioning walkthrough. Whatever layout you build, read list disk first: an Offline status in that table means the two extra lines go into your script.
Check the network with ipconfig, netstat and netsh
Start every network problem with ipconfig. It answers the first three questions (do you have an address, which gateway, from DHCP or not) in one screen. Use /all when you also need DNS servers and the lease times, since the short form leaves them out:
ipconfig
A 169.254.x.x address here means DHCP never answered. A valid address with no gateway means the machine can only reach its own subnet:
Windows IP Configuration
Ethernet adapter Ethernet:
Connection-specific DNS Suffix . : lab.example.com
Link-local IPv6 Address . . . . . : fe80::c44c:abb8:ea1:4ca%6
IPv4 Address. . . . . . . . . . . : 10.0.1.50
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 10.0.1.1
To find what’s holding a port, filter netstat -ano by the port and read the PID in the last column. You’ll use this most when a service won’t start because something else already took its port. Port 3389 is Remote Desktop:
netstat -ano -p tcp | findstr :3389
PID 1196 is listening on every address. A 0.0.0.0 local address means any interface, so the port is reachable from the network unless the firewall blocks it, while 127.0.0.1 would mean local connections only.
TCP 0.0.0.0:3389 0.0.0.0:0 LISTENING 1196
Feed that PID to tasklist /svc /fi "pid eq 1196" and you get the service name, which on the lab VM was TermService inside an svchost.exe. That two-step lookup is faster than opening Resource Monitor. It also works over SSH or a remote shell, where you don’t have a GUI at all.
netsh still runs the Windows Firewall from CMD, even though Microsoft’s own docs now recommend PowerShell for networking. Rules need a unique name, because you show and delete them by it:
netsh advfirewall firewall add rule name="Allow TCP 8080" dir=in action=allow protocol=TCP localport=8080
It prints a bare Ok. and nothing else. Read the rule back to check the profiles it landed in, since a rule without profile= applies to Domain, Private and Public. You’ll want to know that before the server joins a network you don’t control:
netsh advfirewall firewall show rule name="Allow TCP 8080"
The rule is enabled, inbound, TCP 8080 from any address. If you only want the port open on a trusted network, add profile=private or remoteip=10.0.1.0/24 when you create it, because widening a rule later is easier than finding out a public profile had it open.
Rule Name: Allow TCP 8080
----------------------------------------------------------------------
Enabled: Yes
Direction: In
Profiles: Domain,Private,Public
Grouping:
LocalIP: Any
RemoteIP: Any
Protocol: TCP
LocalPort: 8080
RemotePort: Any
Edge traversal: No
Action: Allow
Ok.
Remove it the same way with netsh advfirewall firewall delete rule name="Allow TCP 8080", which answered Deleted 1 rule(s). on the lab VM. One netsh context doesn’t work on servers: netsh wlan show profiles returns The Wireless AutoConfig Service (wlansvc) is not running. on any machine without a Wi-Fi adapter, which was true of both lab VMs.
Find and stop processes
tasklist with a filter is the CMD version of ps | grep. Filter by image name to get the PID, because taskkill /im kills every process with that name, and on a busy server that is rarely what you want:
tasklist /fi "imagename eq PING.EXE"
The Session Name column says whether the process belongs to a logged-in user or to services (session 0). That matters when you kill things, since ending a session 0 process can take a service down with it. Here the ping was started in the background by a service account, so it sits in session 0.
Image Name PID Session Name Session# Mem Usage
========================= ======== ================ =========== ============
PING.EXE 2792 Services 0 5,116 K
On the lab VM there was only one, so killing by name was safe. /f ends the process outright instead of sending a close request, which a hidden console program like this one has no window to receive:
taskkill /im PING.EXE /f
Success prints the PID it ended, which is worth keeping in a log when a script kills something on a schedule:
SUCCESS: The process "PING.EXE" with PID 2792 has been terminated.
A name or PID that doesn’t exist returns ERROR: The process "notreal.exe" not found. with exit code 128, so a cleanup script that runs taskkill on something that already exited will report failure. Add 2>nul and ignore the code when that’s expected. On the memory side, tasklist /fi "memusage gt 100000" listed only Defender’s MsMpEng.exe at 282,564 K and one svchost.exe on the idle Server 2025 VM.
Manage services with sc and net
Use sc to read and change a service’s configuration, and net start or net stop to change its state. The split matters because sc start returns as soon as it sends the request (the lab capture showed START_PENDING right after it), while net waits until the service gets there. Read the configuration first:
sc qc Spooler
START_TYPE and SERVICE_START_NAME are the two lines you’ll check most. A service that won’t start after a reboot is usually set to demand or disabled, and one that fails with access errors is often running under the wrong account.
[SC] QueryServiceConfig SUCCESS
SERVICE_NAME: Spooler
TYPE : 110 WIN32_OWN_PROCESS (interactive)
START_TYPE : 2 AUTO_START
ERROR_CONTROL : 1 NORMAL
BINARY_PATH_NAME : C:\WINDOWS\System32\spoolsv.exe
LOAD_ORDER_GROUP : SpoolerGroup
TAG : 0
DISPLAY_NAME : Print Spooler
DEPENDENCIES : RPCSS
: http
SERVICE_START_NAME : LocalSystem
Change the start type with start= followed by a space, which is how Microsoft documents the syntax. Server 2025 also accepted start=disabled without the space in the lab, but keeping it means the line matches the docs on every build your script meets:
sc config Spooler start= demand
It answers with a single line. The start type only decides how the service starts from now on. It does not stop or start the running copy, so the current state stays as it was until you act on it:
[SC] ChangeServiceConfig SUCCESS
Then restart it and wait for the result. net stop reports both the request and the final state, so you can chain the start onto it safely:
net stop Spooler && net start Spooler
Both halves finished cleanly on the lab VM, where they ran as two separate commands. If the stop half fails, && skips the start, so the stop error stays the last thing on screen instead of being buried under a second message:
The Print Spooler service is stopping.
The Print Spooler service was stopped successfully.
The Print Spooler service is starting.
The Print Spooler service was started successfully.
For a quick state check without the full block, sc query state= all | find /c "SERVICE_NAME" counted 218 services on the Server 2025 VM, and sc queryex adds the PID you’d pass to taskkill when a service hangs in STOP_PENDING.
Add local users and grant folder access
net user manages local accounts on Windows 11 and on standalone servers. Domain accounts live in Active Directory instead, which the AD DS setup guide for Windows Server covers. The lab run hit one surprise that matters the moment you script user creation, because it hangs instead of failing.
The password entered is longer than 14 characters
Passing a 15-character password to net user ... /add does not fail. It stops and asks a legacy question about pre-Windows 2000 clients, and in a script, a scheduled task or a remote session with no keyboard, nobody answers:
The password entered is longer than 14 characters. Computers
with Windows prior to Windows 2000 will not be able to use
this account. Do you want to continue this operation? (Y/N) [Y]:
No valid response was provided.
The account is not created, and every later line in the script then fails too: net localgroup answered There is no such global user or group, and icacls answered No mapping between account names and security IDs was done. Interactively, answer Y and carry on. In unattended scripts, keep the initial password at 14 characters or fewer and force a change at first logon, or create the account in PowerShell with New-LocalUser, which has no such prompt.
The working sequence below uses a 12-character lab password. Pick your own. For anything beyond a lab, pull it from a secret store instead of typing it, because a password on the command line ends up in the console history and in any audit log that records process command lines.
- Create the user:
net user joshua Lab-Pass-26! /add - Add it to a group:
net localgroup Administrators joshua /add - Grant Modify on a folder, inherited by files and subfolders:
icacls C:\lab\demo /grant joshua:(OI)(CI)M - Check the account:
net user joshua
Each of the first three printed The command completed successfully. or the icacls equivalent. Those messages only mean the syntax was accepted, though. The ACL readback is where you confirm the grant actually landed on the folder you meant:
icacls C:\lab\demo
The explicit entry for QS-01\joshua has no (I), so it was set on this folder rather than inherited, and (OI)(CI) means it flows down to files and subfolders:
C:\lab\demo BUILTIN\Administrators:(F)
QS-01\joshua:(OI)(CI)(M)
NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
BUILTIN\Administrators:(I)(OI)(CI)(F)
BUILTIN\Users:(I)(OI)(CI)(RX)
BUILTIN\Users:(I)(CI)(AD)
BUILTIN\Users:(I)(CI)(WD)
CREATOR OWNER:(I)(OI)(CI)(IO)(F)
Successfully processed 1 files; Failed processing 0 files
net accounts on the same VM showed the default local policy: maximum password age 42 days, minimum length 0, lockout after 10 bad attempts for 10 minutes. That 42-day expiry applies to new local users too, which is why net user joshua listed an expiry date six weeks out.
Replace WMIC commands
WMIC isn’t on either lab VM, and on Windows 11 you can’t add it back as an optional feature anymore. Microsoft’s WMIC removal notice says the August 2026 preview update removed it from 24H2 and 25H2, and that only wmic.exe is gone, not WMI itself. That notice covers Windows 11 only and no longer lists Server 2025, but wmic.exe wasn’t on the Server 2025 VM either. Both Server 2025 and Windows 11 gave the same answer when you ask for it.
'wmic' is not recognized as an internal or external command,
operable program or batch file.
The fix is to call Get-CimInstance through powershell from the same CMD prompt or batch file. Every replacement below ran from a .cmd file on Server 2025 (the disk one with an extra rounded FreeGB column in the lab), and the quoting is the part that’s easy to get wrong:
| Old WMIC | Replacement from CMD | Lab result |
|---|---|---|
wmic os get caption,version | powershell -NoProfile -Command "Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, LastBootUpTime" | Windows Server 2025 Datacenter, 10.0.26100 |
wmic logicaldisk get deviceid,freespace | powershell -NoProfile -Command "Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' | Select-Object DeviceID, FreeSpace" | C: and F: with free space |
wmic process where name="lsass.exe" get processid | powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \"Name='lsass.exe'\" | Select-Object ProcessId, Name" | 912 lsass.exe |
wmic qfe list | powershell -NoProfile -Command "Get-CimInstance Win32_QuickFixEngineering | Select-Object -Last 3 HotFixID, InstalledOn" | Last three KBs |
wmic cpu get name | powershell -NoProfile -Command "Get-CimInstance Win32_Processor | Select-Object Name, NumberOfLogicalProcessors" | CPU model, 4 logical CPUs |
wmic bios get serialnumber | powershell -NoProfile -Command "(Get-CimInstance Win32_BIOS).SerialNumber" | Empty on a QEMU VM, filled on hardware |
Single quotes work inside the double-quoted -Command string, which is why the disk filter is the easy one. The process filter keeps its WQL value in single quotes, but the whole -Filter string needs double quotes escaped as \", or CMD ends the string early. For a plain OS name without PowerShell, systeminfo | findstr /B /C:"OS Name" still works, and it’s the one WMIC habit you can keep in pure CMD.
Write batch files that behave
Most broken batch files come down to when CMD expands variables, not to the commands themselves. CMD reads a whole line, or a whole parenthesised block, before it runs any of it. A short test script on the lab VM reproduced each of these rules, so you can check yours against them:
- Double the percent sign for loop variables in a
.cmdfile (%%f) and use one at the prompt (%f). Typing%%fat the prompt returns%%f was unexpected at this time. set NAME=qs & echo %NAME%on one line printedECHO is off., because%NAME%was expanded beforesetran. Split the lines, or usecmd /v:onwith!NAME!.2>nulhides only stderr.dir C:\nope 2>nulstill printed the volume header andDirectory of C:\, because those go to stdout.timeout /t 2fails in anything without a console (scheduled tasks, remote runs) withERROR: Input redirection is not supported, exiting the process immediately., even with/nobreak. Useping -n 3 127.0.0.1 >nulas a two-second sleep there.
The for modifiers save a lot of string handling. %%~nxf is the file name with extension and %%~zf is its size in bytes, so this line in a batch file lists matching files and sizes:
for %%f in (C:\lab\src\file1*.txt) do echo %%~nxf %%~zf
Each 17-byte test file came back on its own line. At the prompt you’d type the same loop with single percent signs, and that one difference is behind most “works in the console, fails in the .bat” reports.
file10.txt 17
file11.txt 17
file12.txt 17
file13.txt 17
[... file14.txt to file19.txt trimmed, same size ...]
Windows also ships OpenSSH in C:\Windows\System32\OpenSSH, already on PATH on the Server 2025 VM, so the SSH commands cheat sheet applies from CMD unchanged when a batch file needs to reach a Linux box.
Scan and repair Windows with sfc, DISM and chkdsk
Run the three repair tools in a fixed order, because each one depends on the layer under it. sfc restores system files from the component store, so a damaged store means sfc can’t fix anything:
DISM /Online /Cleanup-Image /CheckHealth, then/RestoreHealthonly if it reports corruption.sfc /scannow, orsfc /verifyonlyto look without changing anything.chkdsk C: /scanfor the filesystem, online.
On the healthy Server 2025 VM, CheckHealth took about a second, sfc /verifyonly about a minute, and chkdsk C: /scan 23 seconds for 192,000 file records. A damaged system takes much longer, because RestoreHealth may download replacement files from Windows Update. Start with the DISM check:
DISM /Online /Cleanup-Image /CheckHealth
The image version line matches the build ver reports, which is a quick way to confirm DISM is looking at the running OS:
Deployment Image Servicing and Management tool
Version: 10.0.26100.5074
Image Version: 10.0.26100.32230
No component store corruption detected.
The operation completed successfully.
sfc ended with Windows Resource Protection did not find any integrity violations., and chkdsk closed with Windows has scanned the file system and found no problems.. If sfc reports files it couldn’t repair, run DISM /Online /Cleanup-Image /RestoreHealth and then sfc again. Running sfc twice without fixing the store first gives you the same failure twice.
Chkdsk cannot run because the volume is in use by another process
chkdsk F: /f needs an exclusive lock, and the lab script couldn’t get one on a volume nothing else was using. The cause was the script itself: an earlier cd /d F:\ left the prompt’s working directory on F:, and that open handle blocks the lock. chkdsk then offered a forced dismount and finally a check at next restart. Change to another drive (cd /d C:\) before you run /f or /r on a data volume, and expect the restart offer on C:, which is always in use.
For a new server, the Windows Server 2025 install guide gets you to the first elevated prompt, and every table above works from there. Keep the robocopy exit codes and the diskpart online disk line close, because those were the two that broke scripts in this lab that looked correct on paper. The printable CMD cheat sheet keeps the tables next to your keyboard.