Guides

How to create a .bat file in Windows: Notepad, syntax and examples

How to create a .bat file: save it from Notepad as All files with a .bat name, then add @echo off, variables, arguments, if/for and three server scripts.

Published ~11 min read
On this page

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.

hello.bat
@echo off
echo Hello from %COMPUTERNAME%
echo Today is %DATE%, the time is %TIME%
pause

That 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

  1. 1Open Notepad

    Press Win+R, type notepad and press Enter. Any plain text editor works; a word processor such as Word does not, because it saves formatting along with the text.

  2. 2Type the commands

    One command per line, exactly as you would type them in Command Prompt. Put @echo off on the first line.

  3. 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.

  4. 4Check the encoding

    Leave Encoding on ANSI or plain UTF-8, never UTF-8 with BOM. The section below explains why.

  5. 5Make sure it is really .bat

    Windows hides known extensions by default, so backup.bat.txt looks like backup.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\Ü-Berichte in a UTF-8 file arrives garbled. Keep UTF-8 and put chcp 65001 >nul on 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

TaskSyntax
Hide the commands, show only their output@echo off
CommentREM text
Set and read a variableset "NAME=value", then %NAME%
Arithmeticset /a COUNT+=1
Ask the user for inputset /p ANSWER=Continue?
First argument, without quotes%~1
All arguments%*
Folder the script lives in%~dp0
Conditionif exist "C:\data" echo yes
Loop over filesfor %%F in (*.log) do echo %%F
Jump to a labelgoto :done
Call a subroutinecall :backup
Run the next command only on success / failurecmd1 && cmd2 || cmd3
Discard output and errorscommand >nul 2>&1
Wait for a key presspause
End the script with an exit codeexit /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

variables.bat
@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 = web01 creates a variable called NAME (with a trailing space) whose value starts with a space. The quoted form set "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%. Type set alone to list them.
  • Keep variables local with setlocal at 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.

count.bat
@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
endlocal

Arguments: %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:

SyntaxExpands toExample result
%1First argument as typed, quotes included"C:\My Files"
%~1First argument without surrounding quotesC:\My Files
%~f1Full path of the first argumentC:\scripts\in.txt
%~nx1File name and extension onlyin.txt
%~dp0Drive and folder of the script, with a trailing backslashC:\scripts\
%~nx0The script's own file namedeploy.bat
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

flow.bat
@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 Finished

In 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:

errors.bat
@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 0

if 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.:

top of an admin-only script
@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, type shell:startup and put the file, or a shortcut to it, in the folder that opens. shell:common startup does 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.
cmd (Administrator)
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 incmd.execmd.exePowerShell
Double-click runs itYesYesNo: opens in Notepad by default
LanguageBatchBatch (identical)PowerShell
ERRORLEVEL after a successful set, path or promptLeft unchangedReset to 0$? and $LASTEXITCODE instead
Execution policy appliesNoNoYes: RemoteSigned by default on Windows Server
Best forShort chains of commands and old toolsThe same; the newer extensionLogic, 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

C:\scripts\backup.bat
@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
  • /MIR mirrors: it also deletes files in the destination that are gone from the source. Use /E instead if the backup should keep them.
  • /R:2 /W:5 matters 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 DST at 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

C:\scripts\restart-service.bat
@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 restarted

The 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

C:\scripts\clean-temp.bat
@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 0

forfiles /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

SymptomCauseFix
Double-clicking opens NotepadThe file is backup.bat.txtTurn on file name extensions and rename it
A window flashes and disappearsThe script ends (often with an error) and the window closesRun it from an open Command Prompt, or add pause while testing
Odd characters before @echo in the first errorSaved as UTF-8 with BOMSave again as UTF-8 or ANSI
'xyz' is not recognized as an internal or external commandTypo, or the program is not in PATHUse the program's full path
The system cannot find the path specified.Relative path; the script started in System32cd /d "%~dp0"
Access is denied.The command needs elevationRun as administrator
A variable keeps its old value in a loopExpansion at parse timesetlocal 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 garbledFile encoding and console code page differKeep 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.

Related articles

All guides