Windows-LogOff#
Professional PowerShell 7+ utility for forcibly logging off Windows users and cleaning up residual processes. Designed for system administrators managing multi-session Windows environments.
Table of Contents#
1. Overview#
Windows-LogOff (logoff.ps1) is a production-grade PowerShell 7+ script that provides system administrators with a reliable, auditable method for forcibly terminating user sessions on Windows systems. Unlike simple logoff.exe wrappers or Task Manager approaches, this utility implements a two-stage cleanup pipeline:
Stage 1 — Issues a proper
logoffcommand to each detected session, allowing Windows to sendWM_QUERYENDSESSIONmessages to applications (graceful shutdown attempt).Stage 2 — Performs a residual process sweep using
Get-Process -IncludeUserName, force-terminating any processes that survived Stage 1 (orphaned background tasks, detached services, Session 0 processes).
The script is fully compatible with PowerShell 7.0 and later, supports -WhatIf / -Confirm for dry-run testing, and produces structured PSCustomObject output suitable for logging, piping, and automation pipelines.
2. Features#
Dual-Stage Cleanup Pipeline — Graceful logoff followed by forced process termination ensures complete user removal.
PowerShell 7.0+ Compatible — Built on modern PowerShell with native
$PSStylecolor output, null-conditional operators, and advanced function patterns.SupportsShouldProcess — Full
-WhatIfand-Confirmsupport for safe dry-run testing before production execution.Structured Output — Returns
PSCustomObjectresults withSummaryandDetailsproperties for programmatic consumption.Built-in Help System — Comprehensive comment-based help accessible via
Get-Helpor-Helpparameter.Session Safety — Automatically skips the current controller session unless explicitly overridden with
-AllowCurrentSession.Locale-Aware Session Parsing — Uses regex-based parsing of
quser/query useroutput instead of fragile column-index approaches.Elevation Enforcement —
#Requires -RunAsAdministratorensures the script runs with required privileges.Detailed Verbose Logging — Step-by-step diagnostics via
-Verbosefor operational transparency.Configurable Delay — Adjustable pause between session logoff and process sweep (
-PostLogoffDelaySeconds).Selective Stage Execution — Skip either stage independently with
-SkipLogoffor-SkipProcessTermination.Domain and Local User Support — Works with both local accounts and domain-joined user contexts.
3. Prerequisites#
| Requirement | Detail |
|---|---|
| Operating System | Windows 10, Windows 11, Windows Server 2016 through Server 2025 |
| PowerShell Version | PowerShell 7.0 or higher (PowerShell 7.5+ recommended) |
| Privileges | Administrator (elevated) — required for session enumeration and process termination |
| Dependencies | Built-in Windows utilities: quser / query user, logoff.exe |
| Execution Policy | Script execution must be permitted (see Execution Policy) |
4. Installation#
4.1 Clone the Repository#
git clone https://github.com/paulmann/Windows-LogOff.git
cd Windows-LogOff
4.2 Download Directly (No Git)#
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/paulmann/Windows-LogOff/refs/heads/main/logoff.ps1" -OutFile ".\logoff.ps1"
4.3 Unblock the Script#
Windows may block downloaded scripts. Remove the Zone.Identifier mark-of-the-web:
Unblock-File -Path ".\logoff.ps1"
4.4 Set Execution Policy#
If script execution is restricted on your system:
# For current session only (recommended for testing)
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
# For current user permanently
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
4.5 Verify PowerShell Version#
$PSVersionTable.PSVersion
# Should display: 7.0 or higher
5. Configuration#
Windows-LogOff requires no configuration files. All behavior is controlled via command-line parameters at runtime. The script automatically detects:
The current computer name via
$env:COMPUTERNAMEActive sessions via
quser/query userProcess ownership via
Get-Process -IncludeUserName
Optional: Custom User Context#
For domain environments or when automatic context detection is ambiguous:
.\logoff.ps1 -UserName 'jsmith' -UserContext 'CONTOSO' -Verbose
This forces process owner matching against CONTOSO\jsmith instead of the default $env:COMPUTERNAME.
6. Usage#
6.1 Basic Syntax#
.\logoff.ps1 -UserName <string> [-Parameter ...]
6.2 Parameters#
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
-UserName |
string |
Yes | — | Target username without domain prefix |
-UserContext |
string |
No | $env:COMPUTERNAME |
Computer or domain prefix for process owner matching |
-SkipLogoff |
switch |
No | $false |
Skip session logoff; only terminate processes |
-SkipProcessTermination |
switch |
No | $false |
Skip process termination; only log off sessions |
-AllowCurrentSession |
switch |
No | $false |
Allow logoff of the current controller session |
-PostLogoffDelaySeconds |
int |
No | 3 |
Delay between logoff and process sweep (0–120) |
-Help / -? / -H |
switch |
No | $false |
Display built-in help and exit |
-Verbose |
switch |
No | — | Enable detailed diagnostic output |
-WhatIf |
switch |
No | — | Show what would happen without making changes |
-Confirm |
switch |
No | — | Prompt for confirmation before each action |
6.3 Examples#
# Display built-in help
.\logoff.ps1 -Help
# Show help via Get-Help
Get-Help .\logoff.ps1 -Full
Get-Help .\logoff.ps1 -Examples
# Dry-run: see what would happen (no changes made)
.\logoff.ps1 -UserName 'md' -WhatIf
# Standard execution with verbose output
.\logoff.ps1 -UserName 'md' -Verbose
# Log off sessions only (skip process cleanup)
.\logoff.ps1 -UserName 'md' -SkipProcessTermination -Verbose
# Process cleanup only (skip session logoff)
.\logoff.ps1 -UserName 'md' -SkipLogoff -Verbose
# Allow termination of your own session (dangerous)
.\logoff.ps1 -UserName 'md' -AllowCurrentSession -Confirm:$false -Verbose
# Custom domain context for process matching
.\logoff.ps1 -UserName 'jsmith' -UserContext 'CONTOSO' -Verbose
# Export results to JSON for auditing
.\logoff.ps1 -UserName 'md' -Verbose | ConvertTo-Json -Depth 5 | Out-File "logoff-result.json"
7. How It Works#
7.1 Stage 1: Graceful Session Logoff#
The script begins by enumerating active user sessions using the Windows quser command (fallback to query user). It parses the output using regex to extract:
UserName — the logged-in account name
SessionId — the numeric session identifier
State — session state (Active, Disc, Idle, etc.)
For each matching session, logoff.exe <SessionId> /SERVER:localhost is invoked. This triggers the following Windows subsystem behavior:
csrss.exesendsWM_QUERYENDSESSIONto all GUI processes in the session.Applications receive ~5 seconds (controlled by
HungAppTimeout) to save data and exit cleanly.If a process hangs or ignores the message, Windows forcibly terminates it and destroys the session.
All session-bound processes are cleaned up by the OS.
Important: logoff only affects processes inside the target session. Detached background processes, scheduled tasks, or Session 0 processes are NOT affected.
7.2 Stage 2: Residual Process Sweep#
After a configurable delay (default: 3 seconds), the script performs a system-wide process scan:
Get-Process -IncludeUserNameretrieves all running processes with owner information.Each process is filtered by
UserNamematching the target account.The current PowerShell host process (
$PID) is automatically excluded for safety.Remaining processes are terminated via
Stop-Process -Force(Win32TerminateProcess).
TerminateProcess provides no grace period — the process is immediately unloaded from memory. This is the correct behavior for stuck or orphaned processes but will result in data loss for any unsaved work.
8. Safety & Security Model#
Windows-LogOff implements multiple layers of protection to prevent accidental self-destruction or unintended system damage:
| Safety Measure | Description |
|---|---|
| Current Session Skip | The session running the script is never logged off unless -AllowCurrentSession is explicitly provided. |
| Current Process Skip | The PowerShell host process ($PID) is excluded from force-termination. |
| Elevation Check | The script validates administrator privileges at startup and aborts if not elevated. |
| Platform Check | #Requires -Version 7.0 and $IsWindows validation prevent execution on unsupported systems. |
| Input Validation | Username is validated with regex ^[\w.\-]+$ to prevent injection attacks. |
| WhatIf Support | -WhatIf allows full simulation of all actions before any real changes are made. |
| Structured Results | Every action produces an auditable PSCustomObject record with status, timestamp, and exit code. |
Warning: This script is destructive by design. When executed, it will:
Terminate all active sessions for the target user.
Force-kill all processes owned by that user.
Cause permanent data loss for any unsaved work.
Always test with -WhatIf first. Never use on a user who may have unsaved documents unless the situation demands it.
9. Troubleshooting#
9.1 Common Issues#
"Session logoff stage failed: You cannot call a method on a null-valued expression"
This typically occurs when quser output cannot be parsed. Causes:
Non-English Windows locale (column headers differ).
No active sessions for the target user.
Terminal Server / RDS environment with unusual session format.
Fix: Run with -SkipLogoff -SkipProcessTermination -Verbose first to see what sessions are detected. Update to the latest script version which uses regex-based parsing.
"Cannot find process with IncludeUserName — access denied"
Get-Process -IncludeUserName requires elevated privileges.
Fix: Run PowerShell as Administrator. Right-click → "Run as Administrator".
"Script execution is disabled on this system"
The default execution policy on some Windows installations blocks scripts.
Fix:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Unblock-File -Path ".\logoff.ps1"
"The script skips my own session even though I want it gone"
By default, the current controller session is protected.
Fix: Use -AllowCurrentSession:
.\logoff.ps1 -UserName 'md' -AllowCurrentSession -Confirm:$false -Verbose
"Some processes remain after the script completes"
A small number of system-protected processes may not be terminable even by administrators. This is expected Windows behavior for critical system threads.
Fix: Review the structured output for processes marked Status: Failed with Access Denied messages. These are typically system-protected and safe to ignore.
9.2 Debugging Workflow#
# Step 1: Dry run to see what would happen
.\logoff.ps1 -UserName 'targetuser' -WhatIf -Verbose
# Step 2: Sessions only (safe)
.\logoff.ps1 -UserName 'targetuser' -SkipProcessTermination -Verbose
# Step 3: Full execution with JSON export
.\logoff.ps1 -UserName 'targetuser' -Verbose | ConvertTo-Json -Depth 5
10. Contributing#
Contributions are welcome! To contribute:
Fork this repository.
Create a feature branch:
git checkout -b feature/your-feature-name.Make your changes following the existing code style (PSR-like formatting,
script:-scoped helpers, structured output).Test thoroughly — always include
-WhatIftesting before production use.Submit a Pull Request with a clear description of changes.
Code Style Guidelines#
Use
script:scope for helper functions defined inbegin {}.All output must be structured
PSCustomObjectrecords.Use
$PSStylefor colored console output with graceful fallback.Every
catchblock must log the error and populate the results list.Never use positional parameter binding in
process {}— always use named parameters.
11. License#
This project is distributed under the PolyForm Noncommercial License 1.0.0. See the LICENSE file for full terms.
In short:
You are free to use, modify, and distribute this software.
The software is provided "as is" without warranty of any kind.
You must retain the