# Kaizant Glass network helper, published by Kaizant (https://kaizant.com). # # Serves this PC's network connection to the kaizant-glass board, which a browser # cannot read for itself. It answers on http://localhost:3990/ with # {"kind":"wifi"|"wired"|"none","name":"...","signal":0-100}, read on every request. # # Run it with no arguments (right-click, Run with PowerShell) to install or update it. # It copies itself to %LOCALAPPDATA%\Kaizant\Glass, removes every other way an older # copy was set to start (scheduled tasks, Startup shortcuts, Run keys), stops any copy # that is running, starts the installed one in the background through a Startup # shortcut, checks that it answers, and then prints its readings until the window is # closed. Running it again is always safe. # # -Serve is how the Startup shortcut runs the installed copy. -RemoveTasks is how an # install removes scheduled tasks Windows only lets an administrator delete. param([switch]$Serve, [switch]$RemoveTasks) $Port = 3990 $Url = "http://localhost:$Port/" $InstallDir = Join-Path $env:LOCALAPPDATA 'Kaizant\Glass' $Installed = Join-Path $InstallDir 'network.ps1' $StartupDir = [Environment]::GetFolderPath('Startup') $Shortcut = Join-Path $StartupDir 'Kaizant Glass network helper.lnk' $RunKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' $PowerShell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' $ServeArgs = "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$Installed`" -Serve" # The connection carrying this PC's internet: the physical adapter behind the cheapest # default route. Wifi reports its network name and signal from netsh, wired the name # of its network profile. function Read-Network { $net = [ordered]@{ kind = 'none'; name = ''; signal = 0 } $up = @(Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object Status -eq 'Up') $route = Get-NetRoute -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue | Where-Object { $up.ifIndex -contains $_.InterfaceIndex } | Sort-Object { $_.RouteMetric + $_.InterfaceMetric } | Select-Object -First 1 if (-not $route) { return $net } $adapter = $up | Where-Object ifIndex -eq $route.InterfaceIndex | Select-Object -First 1 if ($adapter.NdisPhysicalMedium -eq 9) { $net.kind = 'wifi' foreach ($line in (netsh wlan show interfaces)) { if ($line -match '^\s*SSID\s*:\s*(.*)$') { $net.name = $Matches[1].Trim() } elseif ($line -match ':\s*(\d+)\s*%\s*$') { $net.signal = [int]$Matches[1] } } } else { $net.kind = 'wired' $netProfile = Get-NetConnectionProfile -InterfaceIndex $route.InterfaceIndex -ErrorAction SilentlyContinue | Select-Object -First 1 $net.name = if ($netProfile.Name) { $netProfile.Name } else { $adapter.Name } } $net } # Answers the board for as long as this process lives. While the port is taken, or # after the listener fails, it waits and starts again. function Start-Serving { while ($true) { $listener = [System.Net.HttpListener]::new() $listener.Prefixes.Add($Url) try { $listener.Start() while ($listener.IsListening) { $context = $listener.GetContext() try { $response = $context.Response $response.Headers.Add('Access-Control-Allow-Origin', '*') $response.Headers.Add('Access-Control-Allow-Private-Network', 'true') $response.Headers.Add('Cache-Control', 'no-store') if ($context.Request.HttpMethod -ne 'OPTIONS') { $body = [System.Text.Encoding]::UTF8.GetBytes((Read-Network | ConvertTo-Json -Compress)) $response.ContentType = 'application/json' $response.OutputStream.Write($body, 0, $body.Length) } } catch { $response.StatusCode = 500 } finally { $response.Close() } } } catch { } finally { $listener.Close() } Start-Sleep -Seconds 10 } } # Whether a command line, shortcut or task action starts this helper or the wifi.ps1 # it replaces. function Test-Helper($text) { $text -like '*wifi.ps1*' -or $text -like "*$Installed*" } # Every scheduled task that starts a copy of the helper. function Get-HelperTasks { Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.Actions | Where-Object { Test-Helper "$($_.Execute) $($_.Arguments)" } } } # Stops and deletes each task, and returns the ones Windows would not let go. function Remove-HelperTasks { foreach ($task in Get-HelperTasks) { try { Stop-ScheduledTask -InputObject $task -ErrorAction SilentlyContinue Unregister-ScheduledTask -InputObject $task -Confirm:$false -ErrorAction Stop } catch { $task } } } function Step($n, $text) { Write-Host (" {0}. {1}" -f $n, $text) } if ($Serve) { Start-Serving exit } if ($RemoveTasks) { Remove-HelperTasks | Out-Null exit } Write-Host '' Write-Host 'Kaizant Glass network helper' -ForegroundColor Cyan Write-Host 'Published by Kaizant, https://kaizant.com' Write-Host '' Write-Host 'The Glass board cannot see the network from inside a browser. This helper reads' Write-Host "this PC's connection from Windows (wifi name and signal, or the wired network's" Write-Host "name) and answers the board on $Url. It listens on this PC only and sends" Write-Host 'nothing anywhere.' Write-Host '' try { if (-not $PSCommandPath) { throw 'Save network.ps1 to a file and run that file; it cannot install itself from a pasted copy.' } Step 1 "Copying the helper to $InstallDir" New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null if ($PSCommandPath -ne $Installed) { Copy-Item -LiteralPath $PSCommandPath -Destination $Installed -Force } Unblock-File -LiteralPath $Installed Step 2 'Removing scheduled tasks that start an older copy' $stuck = @(Remove-HelperTasks) if ($stuck.Count -gt 0) { Write-Host ' Windows needs administrator approval to remove these:' $stuck | ForEach-Object { Write-Host " $($_.TaskPath)$($_.TaskName)" } try { Start-Process -FilePath $PowerShell -Verb RunAs -Wait -WindowStyle Hidden ` -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$Installed`" -RemoveTasks" } catch { } if (@(Get-HelperTasks).Count -gt 0) { Write-Host ' Not removed. An older copy may still start at sign-in; it gives way to this one.' -ForegroundColor Yellow } else { Write-Host ' Removed.' } } Step 3 'Removing other Startup shortcuts and Run entries for the helper' $shell = New-Object -ComObject WScript.Shell Get-ChildItem -LiteralPath $StartupDir -Filter '*.lnk' -ErrorAction SilentlyContinue | Where-Object { $_.FullName -ne $Shortcut } | Where-Object { $link = $shell.CreateShortcut($_.FullName) Test-Helper "$($link.TargetPath) $($link.Arguments)" } | Remove-Item -Force $run = Get-ItemProperty -Path $RunKey -ErrorAction SilentlyContinue if ($run) { $run.PSObject.Properties | Where-Object { $_.Name -notlike 'PS*' -and (Test-Helper "$($_.Value)") } | ForEach-Object { Remove-ItemProperty -Path $RunKey -Name $_.Name } } Step 4 'Adding the installed copy to Startup, so it runs at every sign-in' $link = $shell.CreateShortcut($Shortcut) $link.TargetPath = $PowerShell $link.Arguments = $ServeArgs $link.WorkingDirectory = $InstallDir $link.WindowStyle = 7 $link.Description = 'Kaizant Glass network helper' $link.Save() Step 5 'Stopping every running copy and starting the installed one' Get-CimInstance Win32_Process -Filter "Name = 'powershell.exe' OR Name = 'pwsh.exe'" | Where-Object { $_.ProcessId -ne $PID -and (Test-Helper $_.CommandLine) } | ForEach-Object { Invoke-CimMethod -InputObject $_ -MethodName Terminate | Out-Null } Start-Sleep -Seconds 1 Remove-Item -LiteralPath (Join-Path $InstallDir 'wifi.ps1') -Force -ErrorAction SilentlyContinue Start-Process -FilePath $PowerShell -ArgumentList $ServeArgs -WindowStyle Hidden Step 6 'Checking that it answers' $answered = $false foreach ($try in 1..15) { try { Invoke-RestMethod -Uri $Url -TimeoutSec 2 | Out-Null $answered = $true break } catch { Start-Sleep -Seconds 1 } } if (-not $answered) { throw "The helper started but is not answering on $Url. Another program may be using port $Port." } Write-Host ' It answers. The board will show the connection within a few seconds.' } catch { Write-Host '' Write-Host "Stopped: $($_.Exception.Message)" -ForegroundColor Red Write-Host '' Read-Host 'Press Enter to close' exit 1 } Write-Host '' Write-Host 'Readings follow. Closing this window stops them here, not the helper.' Write-Host '' $last = $null while ($true) { try { $net = Invoke-RestMethod -Uri $Url -TimeoutSec 5 $now = switch ($net.kind) { 'wifi' { 'Wifi {0} {1}%' -f $net.name, $net.signal } 'wired' { 'Wired {0}' -f $net.name } default { 'not connected' } } } catch { $now = "the helper is not answering on $Url" } if ($now -ne $last) { Write-Host ('{0} {1}' -f (Get-Date -Format 'HH:mm:ss'), $now) $last = $now } Start-Sleep -Seconds 5 }