Building sleep-pc: A .NET Native AOT Tool for Automating Windows Sleep
Andrew Lock details the development of ‘sleep-pc,’ a native AOT .NET tool for putting Windows PCs to sleep after a timeout, highlighting his approach, toolchain choices, and packaging for NuGet.
sleep-pc: A .NET Native AOT Tool to Make Windows Sleep After a Timeout
Author: Andrew Lock
GitHub: andrewlock/sleep-pc
NuGet: sleep-pc
Overview
This post describes the creation of sleep-pc
, a small command-line tool for Windows, written in C# and built with the latest .NET (8/10) Native AOT features. The tool puts your Windows PC to sleep after a configurable timeout, using the Win32 SetSuspendState
API. Andrew Lock walks through the design and the lessons learned packaging the tool for NuGet as a .NET CLI tool.
Background: Why Automate Sleep?
Like many, Andrew found it difficult to make his Windows laptop reliably sleep after certain actions (such as finishing a playlist in Windows Media Player Legacy). Tired of power management struggles, he decided to automate the process: a tiny tool that sleeps the laptop after a specified period.
First Attempt: Simple Win32 Sleep Via P/Invoke
A proof-of-concept version simply waits, then calls the Win32 API:
using System.Runtime.InteropServices;
var wait = TimeSpan.FromSeconds(60 * 60); // 1 hour
Console.WriteLine($"Waiting for {wait}");
Thread.Sleep(wait);
Console.WriteLine("Sleeping!");
SetSuspendState(false, false, false);
[DllImport("PowrProf.dll", SetLastError = true)]
static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);
This uses SetSuspendState
from PowrProf.dll
to trigger sleep. Tweaks around force/hibernate state were also explored.
Improving The Tool: CLI Arguments & Native AOT
To make the tool user-friendly and robust:
- Added argument parsing & help text, leveraging ConsoleAppFramework (fast, AOT-safe, zero-reflection CLI toolkit).
- Compiled using Native AOT features enabled by .NET 8/10.
- Published as a .NET global tool (installable via NuGet).
Sample argument parsing:
await ConsoleApp.RunAsync(args, App.Countdown);
public static async Task Countdown(
[Range(1, 99 * 60 * 60)] uint sleepDelaySeconds = 3600,
bool dryRun = false,
CancellationToken ct = default)
{
// timer logic...
}
Native AOT & Trimming
- Added
<PublishAot>true</PublishAot>
in the.csproj
to publish a natively compiled, small executable (3.3MB). - Used
.NET 10
tools features for packaging, including a compromise NuGet pack for maximum compatibility. - Explored further trimming and analyzed binary size with Sizoscope.
csproj NuGet packing highlights:
<PackAsTool>true</PackAsTool>
<ToolCommandName>sleep-pc</ToolCommandName>
<PackageId>sleep-pc</PackageId>
<PackageVersion>0.1.0</PackageVersion>
<Authors>Andrew Lock</Authors>
Console Output Polish
- Chose not to use Spectre.Console (not fully Native AOT compatible).
- Implemented an in-place countdown timer in the console for better user feedback via simple backspace tricks.
Packaging and Installation
- Packaged for NuGet with dual support: framework-dependent (
net8.0
) and platform-specific Native AOT (net10.0
win-x64). - Users can install globally via:
dotnet tool install -g sleep-pc
.
Summary
Andrew Lock provides a practical, well-documented guide for small utility development in modern .NET. The post covers:
- P/Invoke interop
- Command-line app design
- Native AOT compilation and trimming
- NuGet tool packaging with compatibility for .NET 8/10
- Lessons for minimizing binary size and maximizing performance
Reference links:
This post appeared first on “Andrew Lock’s Blog”. Read the entire article here