# Install (or remove) the Internet Use Control kids' client as a Scheduled Task # that starts at logon (AGENTS.md ยง11 Phase 4). # # The task runs as the signed-in kid's account, in that account's interactive # session, which is where the child's apps and the warning window live. No # password is stored: the task uses an interactive logon token, so it starts # when the child logs on and the child cannot see a service window. # # powershell -ExecutionPolicy Bypass -File install-scheduled-task.ps1 ` # -ClientDir "C:\ProgramData\WebRestrict\client" ` # -Config "C:\ProgramData\WebRestrict\client.json" # # Remove it again with: # # powershell -ExecutionPolicy Bypass -File install-scheduled-task.ps1 -Remove # # The equivalent one-line schtasks form (what the phase proof used) is: # # schtasks /Create /TN "WebRestrict Kids Client" /SC ONLOGON /F ^ # /TR "\"\" -m webrestrict --config \"C:\ProgramData\WebRestrict\client.json\"" [CmdletBinding()] param( [string]$TaskName = "WebRestrict Kids Client", [string]$Account = "$env:USERDOMAIN\$env:USERNAME", [string]$Config = "$env:ProgramData\WebRestrict\client.json", [string]$ClientDir = "$env:ProgramData\WebRestrict\client", [string]$StateDir = "$env:ProgramData\WebRestrict\state", [string]$Python = "", [switch]$SkipAcl, [switch]$Remove ) $ErrorActionPreference = "Stop" if ($Remove) { schtasks /Delete /TN $TaskName /F Write-Host "removed scheduled task '$TaskName'" exit 0 } # The state directory holds the log, the cached config and the usage roll-up. # The task runs with a limited interactive token, so the account needs an # explicit Modify grant: a file created from an elevated shell inherits only # Administrators, and the limited task is then refused with "Access denied". if (-not $SkipAcl) { if (-not (Test-Path $StateDir)) { New-Item -ItemType Directory -Force -Path $StateDir | Out-Null } icacls $StateDir /grant "${Account}:(OI)(CI)M" /T /C | Out-Null Write-Host "granted Modify on $StateDir to $Account" } function Resolve-Python { param([string]$Explicit) if ($Explicit) { return $Explicit } foreach ($candidate in @("pythonw.exe", "python.exe")) { $found = Get-Command $candidate -ErrorAction SilentlyContinue if ($found) { $path = $found.Source # pythonw.exe has no console, so a logon start shows no window. $windowless = Join-Path (Split-Path $path) "pythonw.exe" if (Test-Path $windowless) { return $windowless } return $path } } throw "python was not found on PATH; pass -Python " } $pythonExe = Resolve-Python -Explicit $Python $arguments = "-m webrestrict --config `"$Config`"" $action = New-ScheduledTaskAction -Execute $pythonExe -Argument $arguments -WorkingDirectory $ClientDir $trigger = New-ScheduledTaskTrigger -AtLogOn -User $Account $settings = New-ScheduledTaskSettingsSet ` -AllowStartIfOnBatteries ` -DontStopIfGoingOnBatteries ` -StartWhenAvailable ` -RestartCount 3 ` -RestartInterval (New-TimeSpan -Minutes 1) ` -ExecutionTimeLimit (New-TimeSpan -Seconds 0) ` -MultipleInstances IgnoreNew $principal = New-ScheduledTaskPrincipal -UserId $Account -LogonType Interactive -RunLevel Limited Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger ` -Settings $settings -Principal $principal -Force | Out-Null Write-Host "registered '$TaskName'" Write-Host " account : $Account (interactive, at logon)" Write-Host " command : $pythonExe $arguments" Write-Host " workdir : $ClientDir" schtasks /Query /TN $TaskName /V /FO LIST