nathanbabcock / nightlight-cli

Control Windows 10/11's Night Light feature programmatically 🌓
20 stars 2 forks source link

Powershell version #2

Open exscape opened 1 year ago

exscape commented 1 year ago

I translated the required code to Powershell and got it to work in Windows 11 22H2. (Haven't upgraded to 23H2 yet, but it would be good to know whether this solution works there, too.)

I'm not sure how to lay it out (file-wise) if I were to submit a pull request.
Thanks to your suggestion (on Stack Overflow) I used ChatGPT to do most of the work, but had to fix it up in quite a few places. Still way, way easier than doing all the work though, especially as I'm still a Powershell beginner.

If you want to add the code yourself, here it is:


class NightLight {
    [string] $_key = 'Software\\Microsoft\\Windows\\CurrentVersion\\CloudStore\\Store\\DefaultAccount\\Current\\default$windows.data.bluelightreduction.bluelightreductionstate\\windows.data.bluelightreduction.bluelightreductionstate'
    [Microsoft.Win32.RegistryKey] $_registryKey

    [bool] get_Enabled() {
        if (-not $this.get_Supported()) { return $false }
        $data = $this._registryKey.GetValue("Data") -as [byte[]]
        if (-not $data) { return $false }
        return $data[18] -eq 0x15
    }

    set_Enabled([bool] $value) {
        if ($this.get_Supported() -and ($this.get_Enabled() -ne $value)) {
            $this.Toggle()
        }
    }

    [bool] get_Supported() {
        return $this._registryKey -ne $null
    }

    NightLight() {
        $this._registryKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($this._key, $true)
        if ($this._registryKey -eq $null) {
            Write-Host "Failed to open registry key!"
            Exit
        }
    }

    [void] Toggle() {
        $data = $this._registryKey.GetValue("Data") -as [byte[]]
        $newData = New-Object byte[] 43

        if ($this.get_Enabled()) {
            $newData = New-Object byte[] 41
            [Array]::Copy($data, 0, $newData, 0, 22)
            [Array]::Copy($data, 25, $newData, 23, 43 - 25)
            $newData[18] = 0x13
        }
        else {
            [Array]::Copy($data, 0, $newData, 0, 22)
            [Array]::Copy($data, 23, $newData, 25, 41 - 23)
            $newData[18] = 0x15
            $newData[23] = 0x10
            $newData[24] = 0x00
        }

        for ($i = 10; $i -lt 15; $i++) {
            if ($newData[$i] -ne 0xff) {
                $newData[$i]++
                break
            }
        }

        $this._registryKey.SetValue("Data", $newData, [Microsoft.Win32.RegistryValueKind]::Binary)
        $this._registryKey.Flush()
    }

    [void] Dispose() {
        $this._registryKey.Close()
    }
}

# Usage:

# $nightLight = [NightLight]::new()

# $nightLight.set_Enabled($true)
# $nightLight.set_Enabled($false)
# $nightLight.Toggle()
exscape commented 1 year ago

Well, the morning after it's not working, despite zero changes (and not even a reboot), so I guess it needs work.

Exception calling "Copy" with "5" argument(s): "Source array was not long enough. Check srcIndex and length, and the array's lower bounds."
At C:\Users\...\nightlight.ps1:42 char:13
+             [Array]::Copy($data, 23, $newData, 25, 41 - 23)

After a manual toggle in the Settings app, it works again.
The registry value when it didn't work (and night light was disabled) was:

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\CloudStore\Store\DefaultAccount\Current\default$windows.data.bluelightreduction.bluelightreductionstate\windows.data.bluelightreduction.bluelightreductionstate]
"Data"=hex:43,42,01,00,0a,02,01,00,2a,06,92,e2,a7,aa,06,2a,2b,0e,10,43,42,01,00,c6,14,af,f7,e2,84,9f,aa,84,ed,01,00,00,00,00

These two both work:

"Data"=hex:43,42,01,00,0a,02,01,00,2a,06,e5,f7,a7,aa,06,2a,2b,0e,13,43,42,01,00,d0,0a,02,c6,14,eb,a3,ab,94,86,ab,84,ed,01,00,00,00,00  
"Data"=hex:43,42,01,00,0a,02,01,00,2a,06,c4,f8,a7,aa,06,2a,2b,0e,13,43,42,01,00,d0,0a,02,c6,14,e4,b4,89,e8,89,ab,84,ed,01,00,00,00,00
lucasmarotta commented 2 months ago

@exscape

I also implemented a powershell version of this script, but just using functions and the normal Set-ItemProperty, and I noticed that the problem seems to be the NightLight scheduler. Every time the nightlight is enabled by the scheduler (not the user going to settings) the registry assumes a binary with length of 40 and the 18 character is 0x12 instead of 0x15 like the string below:

0x43,0x42,0x01,0x00,0x0a,0x02,0x01,0x00,0x2a,0x06,0xcd,0x8b,0x80,0xcd,0x06,0x2a,0x2b,0x0e,0x12,
0x43,0x42,0x01,0x00,0x10,0x00,0xc6,0x14,0xe5,0xc6,0xb2,0x82,0xfe,0xad,0xc1,0xed,0x01,0x00,0x00,
0x00,0x00

The problem is that even if you try to fix this strange state with a previously known state of 43 size, the registry reverts back to this 40 size state. This can be fixed by going to the Settings application and disabling and re-enabling nightlight. After that, even the previous 43 state (which is slightly different) will be accepted.

So the ideal here is to get this enabled state from the scheduler, from

0x43,0x42,0x01,0x00,0x0a,0x02,0x01,0x00,0x2a,0x06,0xcd,0x8b,0x80,0xcd,0x06,0x2a,0x2b,0x0e,0x12,
0x43,0x42,0x01,0x00,0x10,0x00,0xc6,0x14,0xe5,0xc6,0xb2,0x82,0xfe,0xad,0xc1,0xed,0x01,0x00,0x00,
0x00,0x00

to something like this

0x43,0x42,0x01,0x00,0x0a,0x02,0x01,0x00,0x2a,0x06,0xa8,0x8b,0x80,0xcd,0x06,0x2a,0x2b,0x0e,0x15,
0x43,0x42,0x01,0x00,0x10,0x00,0xd0,0x0a,0x02,0xc6,0x14,0xfb,0xf1,0xea,0xb0,0xe9,0x97,0xc1,0xed,
0x01,0x00,0x00,0x00,0x00

But that is accepted, which I don't really understand. Or try to go from the 40 size to the disabled 41 size state correctly.

Enyium commented 2 months ago

May I direct your attention to my command line program? https://github.com/Enyium/sem-reg-rs

I also documented the registry values: https://github.com/Enyium/sem-reg-rs/blob/master/src/cloud_store/night_light/docs.md