<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Batch · Loosely Decoupled</title>
    <link>https://looselydecoupled.com/topics/batch/</link>
    <description>cmd.exe scripting — the control flow, quoting and expansion rules that still run production work, and what changes when you move it to PowerShell.</description>
    <language>en-us</language>
    
    <copyright>© 2026 Emmanuel Tsouris</copyright>
    <lastBuildDate>Mon, 07 Sep 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://looselydecoupled.com/topics/batch/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Why Does My Batch File Exit When I Call a Function?</title>
      <link>https://looselydecoupled.com/batch-file-exits-when-calling-function/</link>
      <guid isPermaLink="true">https://looselydecoupled.com/batch-file-exits-when-calling-function/</guid>
      <pubDate>Mon, 07 Sep 2026 00:00:00 +0000</pubDate>
      <dc:creator>Emmanuel Tsouris</dc:creator>
      <category>Windows Server</category>
      <category>Batch</category>
      <category>Automation</category>
      
      <description>The subroutine runs before anything calls it, and its goto :eof ends the whole script, because cmd.exe reads straight down through a label.</description>
      <content:encoded><![CDATA[<div class="answer" data-answer="true">
  <p class="answer__label" data-pagefind-ignore>TL;DR</p>
  <p>A batch label does not create a function body that <code>cmd.exe</code> skips. Execution
runs straight through the label unless you redirect it.</p>
<p>Put the main work first, end it with <code>exit /b</code>, and place subroutines below it:</p>
<div class="code-block code-block--wrap" data-copyable data-copy-label="BATCH">
  <div class="code-block__head"><span class="code-block__lang">BATCH</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-batch" data-lang="batch"><span class="line"><span class="cl"><span class="p">@</span><span class="k">echo</span> off
</span></span><span class="line"><span class="cl"><span class="k">setlocal</span> EnableExtensions
</span></span><span class="line"><span class="cl"><span class="k">call</span> <span class="p">:</span><span class="nl">timestamp</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> [<span class="nv">%stamp%</span>] Work starts here
</span></span><span class="line"><span class="cl"><span class="k">exit</span> /b 0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">:</span><span class="nl">timestamp</span>
</span></span><span class="line"><span class="cl"><span class="k">set</span> <span class="s2">&#34;stamp=</span><span class="nv">%DATE%</span><span class="s2"> </span><span class="nv">%TIME%</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">goto</span> <span class="p">:</span><span class="nl">eof</span></span></span></code></pre></div></div>
  </div>
</div><p><code>call :timestamp</code> creates a return point. <code>goto :eof</code> returns from that call.
Reached in the main batch context, it ends the batch instead.</p>

</div>

<p>You add a timestamp helper to a working batch file. The helper looks reasonable.
The next run prints almost nothing, and the real work never starts.</p>
<p>It&rsquo;s tempting to debug the timestamp expression. First check whether the
script ever reaches the line that calls it.</p>
<h2 id="minimal-reproduction-the-call-never-happens">Minimal reproduction: the call never happens<a class="heading-anchor" href="https://looselydecoupled.com/batch-file-exits-when-calling-function/#minimal-reproduction-the-call-never-happens"
     aria-label="Link to section: Minimal reproduction: the call never happens" data-pagefind-ignore></a>
</h2>
<p>Save this as <code>fall-through.cmd</code>:</p>
<div class="code-block code-block--wrap" data-copyable data-copy-label="fall-through.cmd">
  <div class="code-block__head"><span class="code-block__lang code-block__file">fall-through.cmd</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-batch" data-lang="batch"><span class="line"><span class="cl"><span class="p">@</span><span class="k">echo</span> off
</span></span><span class="line"><span class="cl"><span class="k">echo</span> Starting script
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">:</span><span class="nl">timestamp</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> Inside timestamp
</span></span><span class="line"><span class="cl"><span class="k">set</span> <span class="s2">&#34;stamp=</span><span class="nv">%DATE%</span><span class="s2"> </span><span class="nv">%TIME%</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">goto</span> <span class="p">:</span><span class="nl">eof</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">call</span> <span class="p">:</span><span class="nl">timestamp</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> [<span class="nv">%stamp%</span>] Doing the work</span></span></code></pre></div></div>
  </div>
</div><p>Run it from an existing Command Prompt so the output stays visible:</p>
<div class="console"><div class="code-block code-block--wrap console__output">
    <p class="console__output-label">Expected output</p>
    <div class="code-block__scroll"><pre><code><span class="line"><span class="console__token">C:\scripts&gt;</span> <span class="console__token">cmd</span> <span class="console__token">/d</span> <span class="console__token">/e:on</span> <span class="console__token">/c</span> <span class="console__token">fall-through.cmd</span>
</span><span class="line"><span class="console__token">Starting</span> <span class="console__token">script</span>
</span><span class="line"><span class="console__token">Inside</span> <span class="console__token">timestamp</span>
</span><span class="line">
</span><span class="line"><span class="console__token">C:\scripts&gt;</span></span></code></pre>
    </div>
  </div></div><p>That output is the clue: the helper ran even though the <code>call</code> below it was
never reached. Execution arrived there by reading downward.</p>
<p>A label marks a location. It doesn&rsquo;t surround or hide the commands beneath it.
The <code>goto :eof</code> then ends the current batch context, so both remaining lines
are unreachable on this path. Microsoft&rsquo;s <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/goto" rel="external noopener" target="_blank">goto reference<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/batch-file-exits-when-calling-function/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a>
documents the special end-of-file target; you don&rsquo;t need to define an <code>:eof</code> label.</p>
<h2 id="end-the-main-path-with-exit-b-before-the-subroutines">End the main path with <code>exit /b</code> before the subroutines<a class="heading-anchor" href="https://looselydecoupled.com/batch-file-exits-when-calling-function/#end-the-main-path-with-exit-b-before-the-subroutines"
     aria-label="Link to section: End the main path with exit /b before the subroutines" data-pagefind-ignore></a>
</h2>
<p>Here is the complete repaired example:</p>
<div class="code-block code-block--wrap" data-copyable data-copy-label="no-fall-through.cmd">
  <div class="code-block__head"><span class="code-block__lang code-block__file">no-fall-through.cmd</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-batch" data-lang="batch"><span class="line"><span class="cl"><span class="p">@</span><span class="k">echo</span> off
</span></span><span class="line"><span class="cl"><span class="k">setlocal</span> EnableExtensions
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">echo</span> Starting script
</span></span><span class="line"><span class="cl"><span class="k">call</span> <span class="p">:</span><span class="nl">timestamp</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> [<span class="nv">%stamp%</span>] Doing the work
</span></span><span class="line"><span class="cl"><span class="k">exit</span> /b 0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">:</span><span class="nl">timestamp</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> Inside timestamp
</span></span><span class="line"><span class="cl"><span class="k">set</span> <span class="s2">&#34;stamp=</span><span class="nv">%DATE%</span><span class="s2"> </span><span class="nv">%TIME%</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">goto</span> <span class="p">:</span><span class="nl">eof</span></span></span></code></pre></div></div>
  </div>
</div><p>Expected output, with the timestamp varying by locale and clock:</p>
<div class="code-block code-block--wrap" data-copyable data-copy-label="Text">
  <div class="code-block__head"><span class="code-block__lang">Text</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Starting script
</span></span><span class="line"><span class="cl">Inside timestamp
</span></span><span class="line"><span class="cl">[&lt;local date&gt; &lt;local time&gt;] Doing the work</span></span></code></pre></div></div>
  </div>
</div><p>The main path calls the helper, resumes after the call, does its work, and exits
before reaching the helper a second time. The helper is now reachable only
through an intentional call.</p>
<p>Moving a subroutine to the bottom <strong>without</strong> the main-path exit is incomplete.
The call returns correctly, but after the main work finishes, execution falls
into the helper again.</p>
<h2 id="what-call-goto-and-exit-b-each-do">What <code>call</code>, <code>goto</code> and <code>exit /b</code> each do<a class="heading-anchor" href="https://looselydecoupled.com/batch-file-exits-when-calling-function/#what-call-goto-and-exit-b-each-do"
     aria-label="Link to section: What call, goto and exit /b each do" data-pagefind-ignore></a>
</h2>
<p><code>call :label</code> starts a new batch context within the same file. When that context
ends, execution resumes at the statement after the call. <code>goto label</code> only jumps;
it doesn&rsquo;t arrange a return. See Microsoft&rsquo;s <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/call" rel="external noopener" target="_blank">call reference<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/batch-file-exits-when-calling-function/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a>.</p>
<div class="table-wrap" data-scrollable data-label="Table: Command">
  <table>
    <thead>
      <tr><th scope="col">Command</th><th scope="col">Effect</th></tr>
    </thead>
    <tbody>
      <tr><td><code>call :timestamp</code></td><td>Enter the subroutine with a return point</td></tr>
      <tr><td><code>goto timestamp</code></td><td>Jump to the label without a return point</td></tr>
      <tr><td><code>goto :eof</code></td><td>End the current batch context</td></tr>
      <tr><td><code>exit /b 0</code></td><td>End the current batch context and set a success status</td></tr>
      <tr><td><code>exit</code></td><td>Exit the command interpreter</td></tr>
    </tbody>
  </table>
</div>
<div class="admonition admonition--warning" role="note">
  <p class="admonition__label">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/batch-file-exits-when-calling-function/#i-triangle-alert"/></svg>
    <span>Warning</span>
  </p>
  <p>Plain <code>exit</code> inside a batch file exits the <em>command interpreter</em>, not only the
script. Run it from a Command Prompt you were using and that window closes,
taking any unsaved session state with it. <code>exit /b</code> is almost always what you
want.</p>
</div><p>The <code>/b</code> matters. <code>exit /b</code> ends the current batch context instead of cmd.exe,
as Microsoft&rsquo;s <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/exit" rel="external noopener" target="_blank">exit reference<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/batch-file-exits-when-calling-function/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a>
says. A <code>call :label</code> runs in its own context, so inside a subroutine <code>exit /b</code>
returns to the caller, and at the top level it ends the file. Use an explicit
code when the caller needs a result.</p>
<p>These label-call and <code>:eof</code> examples assume command extensions are enabled, as
they normally are. The repaired file makes that requirement explicit with
<code>setlocal EnableExtensions</code>.</p>
<h2 id="helpers-can-also-go-above-main">Helpers can also go above main<a class="heading-anchor" href="https://looselydecoupled.com/batch-file-exits-when-calling-function/#helpers-can-also-go-above-main"
     aria-label="Link to section: Helpers can also go above main" data-pagefind-ignore></a>
</h2>
<p>There&rsquo;s no rule that subroutines must live at the bottom. If you prefer helpers
first, jump over them on entry:</p>
<div class="code-block code-block--wrap" data-copyable data-copy-label="BATCH">
  <div class="code-block__head"><span class="code-block__lang">BATCH</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-batch" data-lang="batch"><span class="line"><span class="cl"><span class="p">@</span><span class="k">echo</span> off
</span></span><span class="line"><span class="cl"><span class="k">setlocal</span> EnableExtensions
</span></span><span class="line"><span class="cl"><span class="k">goto</span> <span class="nl">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">:</span><span class="nl">timestamp</span>
</span></span><span class="line"><span class="cl"><span class="k">set</span> <span class="s2">&#34;stamp=</span><span class="nv">%DATE%</span><span class="s2"> </span><span class="nv">%TIME%</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">goto</span> <span class="p">:</span><span class="nl">eof</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">:</span><span class="nl">main</span>
</span></span><span class="line"><span class="cl"><span class="k">call</span> <span class="p">:</span><span class="nl">timestamp</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> [<span class="nv">%stamp%</span>] Doing the work
</span></span><span class="line"><span class="cl"><span class="k">exit</span> /b 0</span></span></code></pre></div></div>
  </div>
</div><p>Placement is a readability choice. An unambiguous execution path is the fix.</p>
<h2 id="common-failures">Common failures<a class="heading-anchor" href="https://looselydecoupled.com/batch-file-exits-when-calling-function/#common-failures"
     aria-label="Link to section: Common failures" data-pagefind-ignore></a>
</h2>
<p><strong>The window disappears immediately.</strong> Run the file from an existing Command
Prompt. A window opened only to run a batch file can close when that file
ends; the disappearance alone doesn&rsquo;t tell you which command caused it.</p>
<p><strong>The helper runs twice.</strong> Look for a missing <code>exit /b</code> or <code>goto :eof</code> between
the end of the main work and the first helper label.</p>
<p><strong>The main work never resumes.</strong> Check for <code>goto timestamp</code> where you intended
<code>call :timestamp</code>, or plain <code>exit</code> inside the helper.</p>
<p><strong>I only needed a timestamp in a log.</strong> A separate subroutine may be unnecessary:</p>
<div class="code-block code-block--wrap" data-copyable data-copy-label="BATCH">
  <div class="code-block__head"><span class="code-block__lang">BATCH</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-batch" data-lang="batch"><span class="line"><span class="cl"><span class="k">echo</span> [<span class="nv">%DATE%</span> <span class="nv">%TIME%</span>] Backup started<span class="p">&gt;&gt;</span><span class="s2">&#34;backup.log&#34;</span></span></span></code></pre></div></div>
  </div>
</div><p>This is a display timestamp, not a locale-independent filename format. Keep
this simple example outside a parenthesized block, where percent-variable
expansion introduces a separate timing issue.</p>
<p>When a batch file stops early, follow the lines it actually executes. A label
that looks like a function declaration is still on that path.</p>
<div class="tested" id="tested">
  <p class="label label--seam">Tested with</p><dl class="tested__rows">
    <div class="tested__row"><dt>OS</dt> <dd>Windows Server 2019 (10.0.17763)</dd></div>
    <div class="tested__row"><dt>Shell</dt> <dd>cmd.exe</dd></div>
    <div class="tested__row"><dt>Date</dt> <dd>October 2026</dd></div>
  </dl>
</div>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
