Last active
February 15, 2026 20:36
-
-
Save cw2k/26e1818303e0061e38d6a612ac52b1d5 to your computer and use it in GitHub Desktop.
Windows10: Removes Nag "Activate Windows" in Windows 10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| 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 |
Author
Author
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
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
The WaitForSingleObject Callback propagates the event onto the windows messages queue via
"SendMessage"