Professional PowerShell 7+ utility for forcibly logging off Windows users and cleaning up residual processes. Designed for system administrators managing multi-session Windows environments.
- Overview
- Features
- Prerequisites
- Installation
- Configuration
- Usage
- How It Works
- Safety & Security Model
- Troubleshooting
- Contributing
- License
- Acknowledgments
- Author & Support
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.
- 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.
| 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) |
git clone https://github.com/paulmann/Windows-LogOff.git
cd Windows-LogOffInvoke-WebRequest -Uri "https://raw.githubusercontent.com/paulmann/Windows-LogOff/refs/heads/main/logoff.ps1" -OutFile ".\logoff.ps1"Windows may block downloaded scripts. Remove the Zone.Identifier mark-of-the-web:
Unblock-File -Path ".\logoff.ps1"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$PSVersionTable.PSVersion
# Should display: 7.0 or higherWindows-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:COMPUTERNAME - Active sessions via
quser/query user - Process ownership via
Get-Process -IncludeUserName
For domain environments or when automatic context detection is ambiguous:
.\logoff.ps1 -UserName 'jsmith' -UserContext 'CONTOSO' -VerboseThis forces process owner matching against CONTOSO\jsmith instead of the default $env:COMPUTERNAME.
.\logoff.ps1 -UserName <string> [-Parameter ...]| 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 |
# 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"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.
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.
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.
"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.
# 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 5Contributions 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.
- 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.
This project is distributed under the MIT License. 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