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