Short answer: open Notepad, type your commands, then choose File, Save as. Set Save as type to All files (*.*) and give the file a name ending in .bat, for example backup.bat. Double-click the file to run it, or right-click it and choose Run as administrator. Start it with @echo off so only the output shows.
@echo off
echo Hello from %COMPUTERNAME%
echo Today is %DATE%, the time is %TIME%
pauseThat is a complete batch file: cmd.exe runs it line by line as if you had typed each line at the prompt. Below: saving it correctly, the syntax for real scripts, running it elevated or on a schedule, and three scripts for a Windows server. The syntax follows Microsoft Learn’s Windows Commands reference (the pages for if, call, robocopy, forfiles and schtasks among them) as of October 2026; batch syntax has hardly changed in decades, so it applies to Windows 10 and 11 as well as Windows Server 2016 to 2025.
How to create a .bat file in Notepad
- 1Open Notepad
Press
Win+R, typenotepadand press Enter. Any plain text editor works; a word processor such as Word does not, because it saves formatting along with the text. - 2Type the commands
One command per line, exactly as you would type them in Command Prompt. Put
@echo offon the first line. - 3Save it with a .bat extension
File, Save as. Change Save as type from Text Documents (*.txt) to All files (*.*) and type a name such as
backup.bat. Typing the name in double quotes,"backup.bat", also stops Notepad from adding.txt. Keep script names free of spaces; it saves quoting later. - 4Check the encoding
Leave Encoding on ANSI or plain UTF-8, never UTF-8 with BOM. The section below explains why.
- 5Make sure it is really .bat
Windows hides known extensions by default, so
backup.bat.txtlooks likebackup.bat. In File Explorer turn on File name extensions (in the View tab or the View menu, depending on the Windows version). A real batch file shows the type “Windows Batch File” in the Details view.
Encoding: ANSI, UTF-8 and the BOM
cmd reads a batch file in the console’s code page, not as Unicode. Plain ASCII (English letters, digits, the usual symbols) is safe in every encoding. Two things break:
- A byte order mark. “UTF-8 with BOM” puts three invisible bytes in front of the first line. cmd treats them as part of the first command and fails with an error like
'@echo' is not recognized as an internal or external command. Save again as UTF-8 or ANSI. - Non-ASCII characters. A path such as
C:\Daten\Ü-Berichtein a UTF-8 file arrives garbled. Keep UTF-8 and putchcp 65001 >nulon the second line, which switches the console to UTF-8 before the rest is read. Saving as ANSI does not help on its own: Notepad’s ANSI is the Windows code page (1252 on Western systems), while cmd reads in the console’s OEM code page (850 or 437), so the Ü still arrives garbled.
Batch file cheat sheet
| Task | Syntax |
|---|---|
| Hide the commands, show only their output | @echo off |
| Comment | REM text |
| Set and read a variable | set "NAME=value", then %NAME% |
| Arithmetic | set /a COUNT+=1 |
| Ask the user for input | set /p ANSWER=Continue? |
| First argument, without quotes | %~1 |
| All arguments | %* |
| Folder the script lives in | %~dp0 |
| Condition | if exist "C:\data" echo yes |
| Loop over files | for %%F in (*.log) do echo %%F |
| Jump to a label | goto :done |
| Call a subroutine | call :backup |
| Run the next command only on success / failure | cmd1 && cmd2 || cmd3 |
| Discard output and errors | command >nul 2>&1 |
| Wait for a key press | pause |
| End the script with an exit code | exit /b 1 |
@echo off: why every script starts with it
By default cmd prints each command before it runs it, so the window fills with your own script. echo off stops that for the rest of the file, and the @ in front hides the echo off line itself. Use echo. to print an empty line. To see what a script really executes while you debug it, change the first line to @echo on for a run.
Comments: REM and ::
REM is the official comment command. :: is a label that can never be jumped to, which makes it work as a comment and you will see it in many scripts. It has one trap: inside a parenthesised block (the body of an if or for), a :: line can cause syntax errors. Use REM inside blocks and either one elsewhere.
Variables: set, %VAR% and delayed expansion
@echo off
set "SITE=web01"
set "TARGET=D:\Backup\%SITE%"
echo Backing up to %TARGET%
set /a KEEP=7*2
echo Keeping %KEEP% days of logs
set /p ANSWER=Continue? (y/n)
if /i not "%ANSWER%"=="y" exit /b 1- No spaces around the equals sign.
set NAME = web01creates a variable calledNAME(with a trailing space) whose value starts with a space. The quoted formset "NAME=web01"also keeps stray spaces at the end of the line out of the value. - Read with percent signs:
%NAME%. Windows provides many already:%COMPUTERNAME%,%USERNAME%,%TEMP%,%USERPROFILE%,%ProgramFiles%,%DATE%and%TIME%. Typesetalone to list them. - Keep variables local with
setlocalat the top: everything you set disappears when the script ends instead of leaking into the Command Prompt you started it from. - A literal percent sign is written
%%in a batch file, while one%is enough at the interactive prompt.
Why a variable does not change inside a loop
cmd expands %VAR% when it reads a line, and it reads a whole parenthesised block at once. Inside a for or if block, %COUNT% therefore keeps the value it had before the block started. Delayed expansion fixes this: enable it with setlocal EnableDelayedExpansion and write !COUNT! instead, which is expanded each time the line runs.
@echo off
setlocal EnableDelayedExpansion
set COUNT=0
for %%F in ("C:\app\logs\*.log") do (
set /a COUNT+=1
echo !COUNT!: %%~nxF
)
echo %COUNT% log files in total
endlocalArguments: %1, %~1, %* and %~dp0
Whatever follows the script name on the command line arrives as %1 to %9; %* is all of them, and %0 is the script itself. The tilde modifiers from the call reference are the useful part:
| Syntax | Expands to | Example result |
|---|---|---|
%1 | First argument as typed, quotes included | "C:\My Files" |
%~1 | First argument without surrounding quotes | C:\My Files |
%~f1 | Full path of the first argument | C:\scripts\in.txt |
%~nx1 | File name and extension only | in.txt |
%~dp0 | Drive and folder of the script, with a trailing backslash | C:\scripts\ |
%~nx0 | The script's own file name | deploy.bat |
@echo off
setlocal
if "%~1"=="" (
echo Usage: %~nx0 SITE [--dry-run]
exit /b 1
)
set "SITE=%~1"
cd /d "%~dp0"
echo Running from %~dp0 for site %SITE%
if /i "%~2"=="--dry-run" (
echo Dry run: nothing copied
exit /b 0
)cd /d "%~dp0" matters more than it looks. A script started with Run as administrator, or by Task Scheduler, does not start in its own folder but usually in C:\Windows\System32, so every relative path in it points at the wrong place. Changing to %~dp0 first, or using full paths throughout, avoids that.
if, for and goto
@echo off
if exist "C:\inetpub\logs" echo IIS logs are present
if not exist "D:\Backup" mkdir "D:\Backup"
if defined BACKUP_TARGET echo Target is %BACKUP_TARGET%
if /i "%~1"=="force" echo Forced run
rem each .log file in a folder, with its size
for %%F in ("C:\app\logs\*.log") do echo %%~nxF %%~zF bytes
rem each subfolder
for /d %%D in ("C:\Sites\*") do echo Site: %%~nxD
rem each .config file below a folder, recursively
for /r "C:\app" %%F in (*.config) do echo %%F
rem each line of a text file
for /f "usebackq delims=" %%H in ("C:\scripts\hosts.txt") do (
ping -n 1 %%H >nul && echo %%H is up || echo %%H is DOWN
)
goto :done
echo This line is skipped
:done
echo FinishedIn a batch file the loop variable takes two percent signs (%%F); at the interactive prompt it takes one (%F). Copying a loop from a web page into a file without doubling them gives an error ending in “was unexpected at this time”. if /i compares without regard to case, and the quotes around both sides keep the comparison from breaking when a variable is empty.
Error levels and exit codes
Every program leaves an exit code in ERRORLEVEL: 0 normally means success, anything else a problem. There are three ways to react:
@echo off
ping -n 1 192.0.2.10 >nul
if errorlevel 1 (
echo Host did not answer
) else (
echo Host is up
)
findstr /i "error" "C:\app\app.log" >nul && echo Errors found || echo Log is clean
if %ERRORLEVEL% GEQ 8 echo robocopy-style failure
exit /b 0if errorlevel 1 is true for 1 or higher, not only for exactly 1. For an exact or ranged test compare the variable: if %ERRORLEVEL% EQU 2, or GEQ, LSS and the other comparison words. End your script with exit /b N so whoever runs it (you, another script, Task Scheduler) gets a meaningful code. Not every program follows the 0-means-success rule: robocopy returns 1 when it copied files successfully, and only 8 or higher means a failure.
pause: keeping the window open
A double-clicked batch file closes its window as soon as it finishes, error messages included. pause prints “Press any key to continue . . .” and waits. Use it while you develop the script and remove it for anything unattended, or a scheduled run waits forever for a key nobody presses.
How do I run a batch file as administrator?
Right-click the file and choose Run as administrator, or start it from a Command Prompt or PowerShell window that was opened that way. A script that needs elevation can check for it and stop with a clear message instead of failing halfway with Access is denied.:
@echo off
net session >nul 2>&1
if errorlevel 1 (
echo Right-click this file and choose Run as administrator.
pause
exit /b 1
)
cd /d "%~dp0"net session only succeeds in an elevated window, which makes it a convenient test. It also fails when the Server service is stopped, which is rare on a server but would make the check report a missing elevation.
Run a batch file at startup or on a schedule
- At your sign-in: press
Win+R, typeshell:startupand put the file, or a shortcut to it, in the folder that opens.shell:common startupdoes the same for every user. Both run only when someone signs in. - At boot, with nobody signed in: a server usually restarts after updates without anyone logging on, so use Task Scheduler with a startup trigger.
- On a schedule: Task Scheduler again, the Windows counterpart of crontab on Linux.
schtasks /create /tn "Nightly backup" /tr "C:\scripts\backup.bat" /sc daily /st 03:00 /ru SYSTEM /rl highest
schtasks /create /tn "Start app at boot" /tr "C:\scripts\start-app.bat" /sc onstart /ru SYSTEM /delay 0001:00
schtasks /run /tn "Nightly backup"/ru SYSTEM runs the task without a stored password and whether or not anyone is logged on; /delay 0001:00 waits one minute after boot (the format is mmmm:ss). The last line runs the task once right away, which is the quickest test. A scheduled script has no window, so let it write a log file, as the backup example below does.
.bat vs .cmd vs .ps1
| .bat | .cmd | .ps1 | |
|---|---|---|---|
| Runs in | cmd.exe | cmd.exe | PowerShell |
| Double-click runs it | Yes | Yes | No: opens in Notepad by default |
| Language | Batch | Batch (identical) | PowerShell |
| ERRORLEVEL after a successful set, path or prompt | Left unchanged | Reset to 0 | $? and $LASTEXITCODE instead |
| Execution policy applies | No | No | Yes: RemoteSigned by default on Windows Server |
| Best for | Short chains of commands and old tools | The same; the newer extension | Logic, objects, services, anything over a page |
.bat and .cmd differ only in those few built-ins that reset ERRORLEVEL in a .cmd file. Once a script needs real logic, a .ps1 is easier to get right, and a one-line batch file can start it: powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0job.ps1". The bypass applies to that one process only, not to the server.
Three batch files for a Windows server
Back up a folder with robocopy, with a dated log
@echo off
setlocal
set "SRC=C:\inetpub\wwwroot"
set "DST=D:\Backup\wwwroot"
set "LOGDIR=D:\Backup\logs"
for /f %%D in ('powershell -NoProfile -Command "Get-Date -Format yyyy-MM-dd"') do set "TODAY=%%D"
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
robocopy "%SRC%" "%DST%" /MIR /R:2 /W:5 /NP /LOG:"%LOGDIR%\backup-%TODAY%.log"
if %ERRORLEVEL% GEQ 8 (
echo Backup FAILED with robocopy code %ERRORLEVEL%, see the log
exit /b 1
)
echo Backup OK, robocopy code %ERRORLEVEL%
exit /b 0/MIRmirrors: it also deletes files in the destination that are gone from the source. Use/Einstead if the backup should keep them./R:2 /W:5matters for unattended runs. Robocopy’s defaults are one million retries with 30 seconds between them, so a single locked file can stall a scheduled job for days.- The date comes from PowerShell because
%DATE%follows the regional settings, and a substring trick that works on one server cuts the wrong digits on another. - Point
DSTat a second disk or another machine. A copy on the same disk protects against mistakes, not against losing the disk.
Restart a service by name
@echo off
rem usage: restart-service.bat SERVICE_NAME (run as administrator)
if "%~1"=="" (
echo Usage: %~nx0 SERVICE_NAME
exit /b 1
)
powershell -NoProfile -Command "Restart-Service -Name '%~1' -Force -ErrorAction Stop"
if errorlevel 1 (
echo Could not restart %~1
exit /b 1
)
echo %~1 restartedThe service name is the short one from sc query or Get-Service, for example W3SVC for IIS, not the display name. net stop plus net start works too, but when other services depend on the one you stop, net stop lists them and asks for a Y/N confirmation that nobody answers in an unattended script. With -Force, PowerShell stops the dependants and starts the ones that were running again afterwards.
Clean out temp files and old logs
@echo off
rem files older than 14 days: system temp folder and an app log folder
forfiles /p "C:\Windows\Temp" /s /m *.* /d -14 /c "cmd /c if @isdir==FALSE del /q @path" 2>nul
forfiles /p "C:\app\logs" /m *.log /d -14 /c "cmd /c del /q @path" 2>nul
rem everything in the temp folder of the account running the script
del /q /s "%TEMP%\*" >nul 2>&1
echo Cleanup finished
exit /b 0forfiles /d -14 selects files last changed 14 or more days ago, and @path is each file’s full path in quotes. When nothing matches, forfiles prints an error that 2>nul hides. Files in use are skipped, not deleted. Run it once with echo in front of del to see what it would remove.
Common batch file problems
| Symptom | Cause | Fix |
|---|---|---|
| Double-clicking opens Notepad | The file is backup.bat.txt | Turn on file name extensions and rename it |
| A window flashes and disappears | The script ends (often with an error) and the window closes | Run it from an open Command Prompt, or add pause while testing |
Odd characters before @echo in the first error | Saved as UTF-8 with BOM | Save again as UTF-8 or ANSI |
'xyz' is not recognized as an internal or external command | Typo, or the program is not in PATH | Use the program's full path |
The system cannot find the path specified. | Relative path; the script started in System32 | cd /d "%~dp0" |
Access is denied. | The command needs elevation | Run as administrator |
| A variable keeps its old value in a loop | Expansion at parse time | setlocal EnableDelayedExpansion and !VAR! |
| ... was unexpected at this time. | Single % on a loop variable, or unbalanced parentheses | %%F in files; quote paths that contain parentheses |
| Accented letters come out garbled | File encoding and console code page differ | Keep UTF-8 and add chcp 65001 >nul at the top |
Batch files on a Windows VPS
On a server, batch files are mostly glue: start an app after a reboot, rotate logs, copy a site to a backup disk. Keep them in one folder such as C:\scripts, give each a log file, and schedule them as SYSTEM. Connect over Remote Desktop first (the guide to connecting over RDP covers every client), and install the tools your scripts call with a package manager, as in winget and Chocolatey on Windows Server; a browser for downloads is one PowerShell block away in installing Chrome on Windows Server.
A Windows VPS from VMHeaven runs Windows Server 2022 or 2019 and you log in as Administrator, so every script on this page has the rights it needs. If a script ever cuts off Remote Desktop, for example with a firewall command, the VNC console in the panel still shows the screen and lets you undo it.
Frequently asked
How do I create a .bat file in Windows?
Open Notepad, type the commands one per line, then choose File, Save as. Set Save as type to All files (*.*) and type a name ending in .bat, such as backup.bat, and keep the encoding on ANSI or UTF-8 without BOM. Double-click the file to run it, or right-click it and choose Run as administrator.
What does @echo off do in a batch file?
It stops cmd from printing every command before running it, so the window shows only the output. The @ in front hides the echo off line itself. Change it to @echo on for a run when you need to see which commands a script actually executes.
What is the difference between .bat and .cmd files?
Both run in cmd.exe with the same batch language. The one practical difference is that in a .cmd file a few built-in commands such as set and path reset ERRORLEVEL to 0 when they succeed, while a .bat file leaves it unchanged. A .ps1 file is a PowerShell script, a different language, and opens in Notepad when double-clicked.
How do I run a batch file automatically at startup?
For your own sign-in, put the file or a shortcut to it in the folder that shell:startup opens. To run it at boot without anyone signing in, which is what a server needs, create a scheduled task with schtasks /create, a startup trigger (/sc onstart) and /ru SYSTEM.