Why Batch Scripts Fail to Log Off Remote Windows Sessions

Summary

An automation attempt to clear user sessions across multiple remote Windows servers via a Batch script failed to execute any logoff commands. The script intended to iterate through a list of servers, parse the output of query session, and issue logoff or reset session commands while protecting the current user’s session. Despite appearing to run without errors, the target sessions remained active, rendering the application update deployment impossible.

Root Cause

The failure stems from a fundamental misunderstanding of command-line parsing and whitespace handling in Windows Batch.

  • Parsing Logic Error: The script uses for /f "tokens=3" ... to identify the current user’s session ID. However, the query session output format is non-deterministic; if a session state is “Disconnected,” the column alignment shifts. This causes the script to capture the wrong string as the Session ID.
  • Token Misalignment: In the remote loop, the script attempts to parse tokens=1,2,3,4. Because query session output varies depending on whether a session is active, disconnected, or a system service, the index of the Session ID column changes.
  • Empty Variable Comparison: Due to the shifting columns, the !ID! variable often becomes empty or contains non-numeric strings (like “Disc”), causing the if not "!ID!"=="" check to fail or the subsequent logoff command to receive invalid arguments.
  • Logic Redundancy and Conflict: The script contains two separate loops attempting to do the same thing with different parsing logic, creating a high probability of logical short-circuiting where the SKIP conditions are never met or are met incorrectly.

Why This Happens in Real Systems

In production environments, this phenomenon is known as Brittle Automation. It happens because:

  • Non-Structured Output: Commands like query session were designed for human readability, not machine parsing. They rely on visual padding (spaces) rather than delimiters like commas or tabs.
  • State-Dependent Output: The structure of the data changes based on the system state (e.g., a user disconnecting changes the number of columns in the output).
  • Environment Drift: A script that works on a local machine during testing often fails on remote servers because the session types (Console vs. RDP vs. Services) differ across the fleet.

Real-World Impact

  • Deployment Failure: Automated maintenance windows are missed because the prerequisite step (clearing sessions) fails silently.
  • Resource Contention: Application updates may attempt to overwrite files currently locked by the very users the script failed to log off, leading to corrupted installations.
  • Silent Failures: Because the script uses >nul 2>&1, it suppresses all error messages, making it impossible for SREs to diagnose why the logout failed without manual intervention.

Example or Code (if necessary and relevant)

A more robust approach in Batch requires handling the “Disconnected” state explicitly to find the ID.

@echo off
setlocal EnableDelayedExpansion

set "SERVERS=NRD01-A NRD01-B NRD02-A NRD02-B"
set "CURRENT_USER=%USERNAME%"

for %%S in (%SERVERS%) do (
    echo Processing Server: %%S
    for /f "skip=1 tokens=1-4" %%A in ('query session /server:%%S 2^>nul') do (
        set "VAR1=%%A"
        set "VAR2=%%B"
        set "VAR3=%%C"
        set "VAR4=%%D"

        set "S_USER="
        set "S_ID="

        rem Logic to handle shifting columns based on 'Disc' state
        if /I "!VAR2!"=="Disc" (
            set "S_USER=!VAR1!"
            set "S_ID=!VAR3!"
        ) else if /I "!VAR1!"=="Disc" (
            set "S_USER=!VAR2!"
            set "S_ID=!VAR3!"
        ) else (
            set "S_USER=!VAR1!"
            set "S_ID=!VAR2!"
        )

        rem Check if user is not the current runner and ID is numeric
        if /I not "!S_USER!"=="!CURRENT_USER!" (
            if not "!S_ID!"=="" (
                echo Logging off user !S_USER! with ID !S_ID! on %%S
                logoff !S_ID! /server:%%S
            )
        )
    )
)
pause

How Senior Engineers Fix It

  • Avoid Parsing Human-Readable Text: If forced to use Batch, engineers write “defensive parsing” logic that accounts for every possible column shift.
  • Implement Logging: Never use >nul 2>&1 in critical automation. Instead, redirect output to a centralized log file with timestamps.
  • Prefer Structured Data: A senior engineer would fight for PowerShell or Python to use objects rather than strings. If PowerShell is blocked, they would look for WMI (Windows Management Instrumentation) via wmic, which provides structured, machine-readable data.
  • Fail Fast: The script should check if the logoff command actually succeeded by checking the %ERRORLEVEL% and halting the update if it fails.

Why Juniors Miss It

  • Happy Path Assumption: Juniors write code that works when the input is perfectly formatted (the “Happy Path”) but fail to account for edge cases like disconnected sessions.
  • Over-reliance on findstr: They use string searching to find a user but fail to realize that once the user is found, the positional data (the ID) is no longer predictable.
  • Silent Error Suppression: They treat >nul 2>&1 as a way to “clean up” the console, not realizing they are effectively blinding themselves to the root cause of the failure.

Leave a Comment