Skip to content

Instantly share code, notes, and snippets.

@asheroto
Last active August 19, 2025 20:29
Show Gist options
  • Save asheroto/48337e8451f5a5a01ba2c270426a5350 to your computer and use it in GitHub Desktop.
Save asheroto/48337e8451f5a5a01ba2c270426a5350 to your computer and use it in GitHub Desktop.
PowerShell script to control monitor power state: place all displays into standby with -TurnOff or wake them with -TurnOn.

Screen Controller (PowerShell)

A simple PowerShell script to reliably control monitor/display state.

This uses the same method as when a computer display times out through normal power settings. Unlike tools like ControlMyMonitor that change monitor hardware settings and actually power off the monitor, this script uses Windows messaging to place displays into standby (not a full power-off) and runs entirely in PowerShell with no external dependencies. You can wake the screens manually by moving the mouse or pressing a key and allowing a few seconds for them to resume, or wake them remotely through the script.

Use -TurnOff to put displays into standby, or -TurnOn to wake them.

Usage

# Turn monitors off
.\ScreenController.ps1 -TurnOff

# Wake monitors
.\ScreenController.ps1 -TurnOn

Or if calling from cmd:

# Turn monitors off
powershell.exe -ExecutionPolicy Bypass -File .\ScreenController.ps1 -TurnOff

# Wake monitors
powershell.exe -ExecutionPolicy Bypass -File .\ScreenController.ps1 -TurnOn

How it Works

  • TurnOff: Uses Windows messaging to place all connected monitors into standby (not just a black screensaver).
  • TurnOn: Uses Windows messaging to bring the displays back on. A simulated key press has been added for compatibility with systems where the ON command alone may not reliably wake the monitors.

Common Use Case

Set up with Task Scheduler or Group Policy Preferences to automatically:

  • Turn monitors off at 6 PM
  • Wake monitors at 6 AM

Requirements

  • Windows 10/11
  • PowerShell 5.1 or later
  • Must run in the context of a logged-in user (interactive session), not as SYSTEM
<#
.SYNOPSIS
Controls monitor power state via PowerShell.
.DESCRIPTION
Use -TurnOff to place all displays into standby. Use -TurnOn to wake them.
Works like the normal Windows display idle timeout (standby), not a black screensaver.
.PARAMETER TurnOff
Put all attached displays into standby.
.PARAMETER TurnOn
Wake displays by setting them to on. A simulated key press is also sent for compatibility with systems that may not respond reliably.
.EXAMPLE
.\ScreenController.ps1 -TurnOff
.EXAMPLE
.\ScreenController.ps1 -TurnOn
.NOTES
Author: asheroto
URL: https://gist.github.com/asheroto/48337e8451f5a5a01ba2c270426a5350
Requires: Windows 10/11, PowerShell 5.1+
Important: Must run in the interactive user session. If using Task Scheduler or NinjaOne, set Run only when user is logged on and run as the current logged-in user.
#>
#requires -version 5.1
param(
[switch]$TurnOn,
[switch]$TurnOff
)
Set-StrictMode -Version 2.0
$ErrorActionPreference = 'Stop'
function Assert-UserContext {
<#
.SYNOPSIS
Ensures the script is running in an interactive user session.
#>
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$sid = $id.User.Value
$name = $id.Name
$isSystem = ($sid -eq 'S-1-5-18') -or ($name -eq 'NT AUTHORITY\SYSTEM') -or ($name -eq 'SYSTEM')
if ($isSystem -or -not [Environment]::UserInteractive) {
Write-Output "[Error] No logged-in user session detected. Run as the current logged-in user."
Write-Output "[Hint] If scheduling, set 'Run only when user is logged on' and choose the user account."
exit 3
}
}
# user32 interop
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public static class Native {
[DllImport("user32.dll", SetLastError=true)]
public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam, uint fuFlags, uint uTimeout, out IntPtr lpdwResult);
[DllImport("user32.dll", SetLastError=true)]
public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
}
"@ | Out-Null
function Set-MonitorPower {
<#
.SYNOPSIS
Set displays to on, low-power, or standby.
#>
param([ValidateSet(0,1,2)][int]$State)
$HWND_BROADCAST = [IntPtr]0xffff
$WM_SYSCOMMAND = 0x0112
$SC_MONITORPOWER= 0xF170
$SMTO_NORMAL = 0x0000
[IntPtr]$res = [IntPtr]::Zero
[void][Native]::SendMessageTimeout($HWND_BROADCAST, $WM_SYSCOMMAND, [IntPtr]$SC_MONITORPOWER, [IntPtr]$State, $SMTO_NORMAL, 2000, [ref]$res)
switch ($State) {
0 { Write-Output "Displays turned on." }
1 { Write-Output "Displays set to low-power mode." }
2 { Write-Output "Displays turned off (standby mode)." }
}
}
function Send-WakeInput {
<#
.SYNOPSIS
Sends a harmless key event to wake displays.
#>
$VK_SHIFT = 0x10
[Native]::keybd_event([byte]$VK_SHIFT, 0, 0, 0)
Start-Sleep -Milliseconds 50
[Native]::keybd_event([byte]$VK_SHIFT, 0, 2, 0)
Write-Output "Wake input sent."
}
# Arg validation
if (($TurnOn -and $TurnOff) -or (-not $TurnOn -and -not $TurnOff)) {
Write-Output "Specify exactly one of -TurnOn or -TurnOff."
exit 1
}
# Ensure interactive user context
Assert-UserContext
if ($TurnOff) {
Set-MonitorPower -State 2
exit 0
}
if ($TurnOn) {
Send-WakeInput
Set-MonitorPower -State 0
exit 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment