Skip to content

Instantly share code, notes, and snippets.

@cw2k
Last active February 15, 2026 20:36
Show Gist options
  • Select an option

  • Save cw2k/26e1818303e0061e38d6a612ac52b1d5 to your computer and use it in GitHub Desktop.

Select an option

Save cw2k/26e1818303e0061e38d6a612ac52b1d5 to your computer and use it in GitHub Desktop.
Windows10: Removes Nag "Activate Windows" in Windows 10
<#
RemoveNag2_ActivateWindows.ps1
--------------------------------
Purpose:
Removes that "Activate Windows" watermark created by explorer.exe
on Windows 10 systems.
The script attempts a clean shutdown of the hidden Worker Window
hosting the watermark by sending WM_DESTROY and WM_CLOSE.
If the window survives, it falls back to SW_HIDE.
Source Reference:
Self: https://gist.github.com/cw2k/26e1818303e0061e38d6a612ac52b1d5
Based on the technique described by Paul π on SuperUser:
https://superuser.com/a/1908410
Retrieved 2026‑02‑14, License: CC BY‑SA 4.0
Auto‑Run Instructions:
1. Create a shortcut (*.lnk) to this script.
2. Edit the shortcut target and prepend:
powershell.exe -ExecutionPolicy Bypass -File
3. Test the shortcut.
4. Move it into the Startup folder:
shell:startup
Notes:
• This script does NOT modify system files or licensing components.
• It only interacts with the watermark window created by explorer.exe.
• WM_DESTROY does not fully destroy the window; WM_CLOSE is required.
• If explorer recreates the watermark, the script can be run again.
#>
function Hide-Watermark {
# Load Win32 API definitions once
if (-not ([System.Management.Automation.PSTypeName]'Win32').Type) {
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Win32
{
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError=true)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}
"@
}
$className = "Worker Window"
$windowTitle = ""
# Try to locate the watermark window
$hWnd = [Win32]::FindWindow($className, $windowTitle)
if ($hWnd -ne [IntPtr]::Zero) {
$hex = ("0x{0:X}" -f $hWnd.ToInt64())
"Class: '$className' handle: $hex"
"Sending WM_DESTROY and WM_CLOSE ..."
$WM_DESTROY = 0x0002
$WM_CLOSE = 0x0010
[void][Win32]::SendMessage($hWnd, $WM_DESTROY, [IntPtr]::Zero, [IntPtr]::Zero)
[void][Win32]::SendMessage($hWnd, $WM_CLOSE, [IntPtr]::Zero, [IntPtr]::Zero)
$hWnd = [Win32]::FindWindow($className, $windowTitle)
if ($hWnd -eq [IntPtr]::Zero) {
"'Activate Windows' watermark successfully destroyed."
}
else {
"Fallback: hiding watermark window ..."
$SW_SHOW = 5
$SW_HIDE = 0
[Win32]::ShowWindow($hWnd, $SW_HIDE)
}
}
else {
# If watermark not found, check if Explorer is running
$hWnd = [Win32]::FindWindow("Shell_TrayWnd", $windowTitle)
if ($hWnd -eq [IntPtr]::Zero) {
Write-Error "Explorer.exe is not running."
}
else {
"'Activate Windows' watermark not found or already removed."
}
}
}
Hide-Watermark
@cw2k
Copy link
Author

cw2k commented Feb 15, 2026

Some nice API programming concepts to learn from the code:

How to register for an event without pooling

KeyAPIs:
CreateEvent / setEvent
RegisterWaitForSingleObject

Okay the setEvent is not in the example (but somewhere in SLRegisterWindowsEvent), but yes setting the Event to false will trigger RegisterWaitForSingleObject and let it execute it's callback function

/*
    _RegisterLicensingPolicyChangeEvent
    -----------------------------------
    Registers a Windows event that fires whenever the system licensing
    policy changes. The function:

      1. Creates an unnamed auto-reset event.
      2. Registers a threadpool wait on that event.
      3. Subscribes the event to the licensing engine via
         SLRegisterWindowsEvent("msft:rm/event/policychanged").

    When the licensing engine signals the event, the threadpool invokes
    _cb_PolicyEventChanged(), which sends DDM_END to the watermark window.
    This triggers a full re-evaluation of the activation watermark.
*/
void CImmersiveWatermark::_RegisterLicensingPolicyChangeEvent(ctxWatermark* Context)
{
    if (Context->hEvent_policychanged)
		return;

    //
    // Create the event (auto-reset = FALSE, initial state = TRUE)
    hEvent = 
		CreateEventW(nullptr, FALSE, TRUE, nullptr);
		
    Context->hEvent_policychanged = hEvent;
    if (!hEvent) 		goto Error_getLastError: 

    //
    // Register threadpool wait callback
    if (!
		RegisterWaitForSingleObject(
            &Context->hPolicychanged_Wait,
             Context->hEvent_policychanged,
            CImmersiveWatermark::_cb_PolicyEventChanged,
            Context,
            INFINITE,
            0)
		)		goto Error_getLastError: 

    //
    // Subscribe to the licensing engine event
    //
    if ( Error = 
		SLRegisterWindowsEvent(
			L"msft:rm/event/policychanged",
			Context->hEvent_policychanged
		)
		 < 0)	goto Error;

    return;


Error_getLastError:
    Error = getLastError();
	
Error:

    //
    // Logging only — original code does NOT clean up the event or wait handle.
    if (doLogging & 4)
    {
        McTemplateU0q_EventWriteTransfer(
            &Microsoft_Windows_Shell_Core_Provider_Context,
            &Activation_Watermark_Register_Licensing_Event_Error,
            Error);
    }
}

The WaitForSingleObject Callback propagates the event onto the windows messages queue via
"SendMessage"

/*
    _cb_PolicyEventChanged
    -----------------------
    Threadpool callback invoked when the licensing policy event is signaled.
    Simply forwards a DDM_END message to the watermark window, causing the
    watermark logic to refresh its activation state.
*/
void CImmersiveWatermark::_cb_PolicyEventChanged(ctxWatermark *Context)
{
  if ( hWnd = Context->hWnd_Messages )
    SendMessageW(hWnd, DDM_END, 0, 0);
}

@cw2k
Copy link
Author

cw2k commented Feb 15, 2026

And here the WndProc from the M$ Explorer.exe for the Watermark

<#		
	Alle relevanten WM\_ Codes aus deinem WndProc
	Ich gebe dir die komplette Liste, exakt wie _NotificationWndProc sie verarbeitet.
	
	🔹 Standard Windows Messages
	Name	Hex	Bedeutung
	WM_DESTROY	0x0002	Fenster zerstört
	WM_CLOSE	0x0010	Fenster schließen

	WM_COMMAND	0x0111	Klick / Menü -WParam 0x64
	WM_TIMER	0x0113	Timer -WParam 0x65
	WM_MOUSEFIRST	0x0200	Mausbewegung
	WM_THEMECHANGED	0x031A	Theme geändert
	WM_DISPLAYCHANGE	0x007E	Display geändert
	WM_SETTINGCHANGE	0x001A	System‑Einstellungen
	WM_SYSCOLORCHANGE	0x0015	Farben geändert
	WM_PAINT	0x000F	Repaint
	WM_ACTIVATE	0x0006	Aktivierung
	WM_ACTIVATEAPP	0x001C	App‑Aktivierung
	WM_KEYDOWN	0x0100	Taste gedrückt -WParam 0x0D (ENTER)
	WM_DRAWITEM	0x002B	Owner‑draw
	WM_ERASEBKGND	0x0014	Hintergrund löschen

	🔹 Custom Messages (Shell / Watermark)
	Name	Hex	Bedeutung
	DDM_DRAW	0x500	Watermark zeichnen
	DDM_END	0x501	Lizenz‑Änderung
	NIN_BALLOONUSERCLICK	0x405	Klick auf Balloon *Closes WM-Label
	NIN_POPUPOPEN	0x406	Tray‑Popup geöffnet
	NIN_POPUPCLOSE	0x407	Tray‑Popup geschlossen
#>
LRESULT __fastcall CImmersiveWatermark::_NotificationWndProc(
    struct_a1_Struct* a1,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    const LRESULT Discard = 0;

    switch (msg)
    {
        case WM_COMMAND:
            // User clicked the watermark or a related UI element.
            // Attempt to open the activation settings page.		
            if (CImmersiveWatermark::_Watermark_OpenSettingsPageActivate__WPARAM__0x64(a1, wParam) < 0)
			  return Discard;

        case WM_TIMER:
            // Timer-driven state machine (fade-in, fade-out, repositioning, etc.)		
            if (CImmersiveWatermark::Watermark_Timer(a1, wParam) >= 0)
                return Discard;

        case WM_MOUSEFIRST:
            // First mouse event after the watermark becomes visible.
            if (a1->latch_MOUSEFIRST)
            {
                a1->latch_MOUSEFIRST = 0;
                InvalidateRect(a1->hWnd_SendMessage, 0, 0);
                return Discard;
            }

        case DDM_DRAW:
            // Custom Shell message: render the watermark.
            CImmersiveWatermark::_Activation_Watermark_Render(a1);
            return Discard;

        case DDM_END:
            // Licensing state changed (activation, grace period, etc.)
            CImmersiveWatermark::_LicenseChangeNotification(a1);
            return Discard;

        case NIN_BALLOONUSERCLICK:
            if (CImmersiveWatermark::UpdateRegTimestamp(a1, lParam) < 0)
                return Discard;
            return 1;

        case NIN_POPUPOPEN:
            // Tray popup opened → watermark may need to update.
            CImmersiveWatermark::_NIN_POPUPOPEN(a1);
            return Discard;

        case NIN_POPUPCLOSE:
            // Tray popup closed → register licensing policy watcher.
			//  ~ See other me other post~
            CImmersiveWatermark::_RegisterLicensingPolicyChangeEvent(a1);
            return Discard;

        case WM_THEMECHANGED:
        case WM_DISPLAYCHANGE:
        case WM_SYSCOLORCHANGE:
            // DPI, theme, or system settings changed.
            if (CImmersiveWatermark::_OnDisplayChange(a1) >= 0)
                return Discard;

        case WM_SETTINGCHANGE:
            if (wParam == 47)
            {
                if ((a1->ExStyle == 2 || a1->ExStyle == 3) && a1->byte101_flag1_WM_SETTINGCHANGE)
                {
                    if (CImmersiveWatermark::UpdateLayeredWindo(a1) >= 0)
                        return Discard;
                }
            }
            else if (wParam == 67)
            {
                if (CImmersiveWatermark::_OnDisplayChange(a1) >= 0)
                    return Discard;
            }

            if (E_FAIL >= 0)
                return Discard;

        case WM_KEYDOWN:
            // Forward keyboard events to the "SendMessage" window.
            if (wParam == VK_TAB) //9
            {
                SetFocus(a1->hWnd_SendMessage);
                return Discard;
            }
            if (wParam == VK_RETURN  || wParam == VK_ADD) //13 43
            {
                SendMessageW(a1->hWnd_SendMessage, WM_KEYDOWN, wParam, lParam);
                return Discard;
            }

        case WM_DRAWITEM:
            // Owner-draw rendering for specific UI elements.
            if (((DRAWITEMSTRUCT*)lParam)->CtlID == 4)
            {
                if (CImmersiveWatermark::____WM_DRAWITEM(a1, lParam) >= 0)
                    return Discard;
            }

        case WM_ACTIVATEAPP:
        case WM_ACTIVATE:
            if (a1->ExStyle != 1)
                goto DefWindowProc
            if (!wParam && a1->byte100_flag0_SendNIN_POPUPOPEN)
                SetTimer(a1->hWnd_Messages, 0x65, 500, 0);
            return Discard;

        case WM_PAINT:
            // Only paint if the watermark is in the correct mode.
            if (a1->ExStyle != 1)
                goto DefWindowProc
            if (CImmersiveWatermark::PAint(a1) >= 0)
                return Discard;

        case WM_DESTROY:
            // Cleanup internal resources, but does NOT destroy the window.
            if (a1->hWnd_Messages)
            {
                a1->byte101_flag1_WM_SETTINGCHANGE = 0;
                SetWindowLongPtrW(a1->hWnd_Messages, -21, 0);
                CImmersiveWatermark::send_DDM_BEGIN(a1);
            }

        case WM_CLOSE:
            return Discard;

        case WM_ERASEBKGND:
            return 1;

        default:
    }
	DefWindowProc:
	return DefWindowProcW(a1->hWnd_Messages, msg, wParam, lParam);

}

An here if you ever wondered what
HKCU\Software\Microsoft\Windows\CurrentVersion\Watermark
Timestamp
is used for:

__int64 __fastcall CImmersiveWatermark__UpdateRegTimestamp(struct_a1_Struct* a1, int a2)
{

    a1->TimestampForETWLogging = GetTickCount64();

    int v5 = a2 & 0xF;

    if ((v5 == a1->BitmapType && a1->ActivationWatermark)
        || (a1->BitmapType = v5, a1->WatermarkType = a2, !a1->hWnd_Messages)
        || ((v4 = CImmersiveWatermark::_Activation_Watermark_Render(a1)), v4 >= 0))
    {
        if (a1->BitmapType == 2 || a1->BitmapType == 3)
        {
            CImmersiveWatermark__UpdateLayeredWindo(a1);
        }
        else
        {
            a1->byte100_flag0_SendNIN_POPUPOPEN = 0;
            ShowWindow (a1->hWnd_SendMessage, 0);
            ShowWindow( a1->hWnd_Messages, 1);
            SetFocus(   a1->hWnd_Messages);
        }

        a1->byte101_flag1_WM_SETTINGCHANGE = 1;

        if (ETWLogging > 5 && tlgKeywordOn(&ETWLogging, 0x200000000000LL))
        {
            DisplayTime = GetTickCount64() - a1->TimestampForETWLogging;
            BitmapType = a1->BitmapType;

            tlgWriteTransfer_EventWriteTransfer__(
                &ETWLogging,
                &ActivationWatermark,
                0,
                0,
                &BitmapType,
                &DisplayTime);
        }

        if (CImmersiveWatermark___RegTimestampCheck(a1))
            CImmersiveWatermark___RegTimestampSet();
    }

    return v4;
}

... it is triggered through msg NIN_BALLOONUSERCLICK
And just updates every 24 hour the timestampvalue
(maybe to read out by some other programm)

LSTATUS __fastcall CImmersiveWatermark___RegTimestampSet()
{
    result = RegCreateKeyExW(
        HKEY_CURRENT_USER,
        L"Software\\Microsoft\\Windows\\CurrentVersion\\Watermark",
        0, nullptr, 0,
        KEY_WRITE | KEY_QUERY_VALUE,
        nullptr, &hKey, nullptr);

    if (!result)
    {
        GetSystemTimeAsFileTime(&ft);

        result = RegSetKeyValueW(
            hKey, nullptr,
            L"Timestamp",
            REG_BINARY,
            &ft,sizeof(ft));

        if (!result && ETWLogging > 5)
        {
            if (tlgKeywordOn(&ETWLogging, 0x200000000000LL))
                tlgWriteTransfer_EventWriteTransfer(
                    0, &ActivationWatermarkDisplayed,
                    0,0, 2,&UserData);
        }
    }

    if (hKey)
        RegCloseKey(hKey);

    return result;
}
bool __fastcall CImmersiveWatermark___RegTimestampCheck(struct_a1_Struct* a1)
{
    DWORD status = 8;
    unsigned __int64 dataIn = 0;
    FILETIME ft;

    if (RegGetValueW(
            HKEY_CURRENT_USER,
            L"Software\\Microsoft\\Windows\\CurrentVersion\\Watermark",
            L"Timestamp",
            RRF_RT_REG_BINARY, 0,
            &dataIn,
            &status))
        return true;

    if (status != 8)
        return true;

    GetSystemTimeAsFileTime(&ft);

    if (*(unsigned __int64*)&ft <= dataIn)
        return true;
	
	// 24 hours as seconds = 864 000 000 000
    if (*(unsigned __int64*)&ft - dataIn >= 864000000000ULL)
        return true;

    return false;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment