|
|
|
@@ -1,26 +1,27 @@
|
|
|
|
|
# Status panel remote access for NA Ingest - run ONCE on each Windows PC
|
|
|
|
|
# (OBS PC, Lighting PC, Bible TV PC), logged in as the normal user.
|
|
|
|
|
# Status panel remote access for {{INGEST_NAME}} - run ONCE on {{PC_NAME}}, logged in as the normal user.
|
|
|
|
|
# (Generated by the settings page from the computer's record; run it again after adding a program.)
|
|
|
|
|
#
|
|
|
|
|
# 1. Save this file as C:\Users\<you>\windows-setup.ps1
|
|
|
|
|
# 2. Start menu -> type PowerShell -> right-click -> Run as administrator
|
|
|
|
|
# 3. Set-ExecutionPolicy RemoteSigned -Scope CurrentUser (answer Y; once per PC)
|
|
|
|
|
# 4. Run it: & "$env:USERPROFILE\windows-setup.ps1"
|
|
|
|
|
# 5. Send the "RESULT" block it prints back to Claude. If it says RESTART THIS PC,
|
|
|
|
|
# restart, then repeat steps 2 and 4.
|
|
|
|
|
# 5. Paste the "RESULT" block it prints into the settings page (Computers > Setup). If it says
|
|
|
|
|
# RESTART THIS PC, restart, then repeat steps 2 and 4.
|
|
|
|
|
#
|
|
|
|
|
# It turns on Windows' built-in SSH server, lets NA Ingest log in with its key
|
|
|
|
|
# (no password), and creates "start" tasks for the apps it finds (OBS, Resolume
|
|
|
|
|
# Arena, rtpMIDI, the Bible app). The tasks start the app on this desktop, which
|
|
|
|
|
# It turns on Windows' built-in SSH server, lets {{INGEST_NAME}} log in with its key
|
|
|
|
|
# (no password), and creates "start" tasks for the programs listed for this PC
|
|
|
|
|
# ({{PROGRAMS}}). The tasks start the program on this desktop, which
|
|
|
|
|
# a plain SSH command cannot do on Windows. Safe to run more than once.
|
|
|
|
|
|
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
|
$Key = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHM2BPUpVpbMxKumLayVSutUsFWUXEfJ/4vxIJ0thbgY status-panel@NA-Ingest'
|
|
|
|
|
$IngestIPs = @('10.1.1.1', '10.6.3.222') # NA Ingest on the 10.1 and 10.6 networks
|
|
|
|
|
$Key = '{{KEY}}'
|
|
|
|
|
$KeyMark = '{{KEY_COMMENT}}'
|
|
|
|
|
$IngestIPs = @({{INGEST_IPS}}) # {{INGEST_NAME}}'s addresses
|
|
|
|
|
$notes = @()
|
|
|
|
|
|
|
|
|
|
# 1. OpenSSH Server, started now and at every boot, allowed through the firewall
|
|
|
|
|
$cap = Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*' | Select-Object -First 1
|
|
|
|
|
if (-not $cap) { throw 'This Windows has no OpenSSH Server feature to install - send this message to Claude.' }
|
|
|
|
|
if (-not $cap) { throw 'This Windows has no OpenSSH Server feature to install - note this on the settings page.' }
|
|
|
|
|
$restart = $false
|
|
|
|
|
if ($cap.State -ne 'Installed') {
|
|
|
|
|
Write-Host 'Installing the OpenSSH Server (Windows downloads it - this can take 10 minutes)...'
|
|
|
|
@@ -29,7 +30,7 @@ if ($cap.State -ne 'Installed') {
|
|
|
|
|
if (-not (Get-Service sshd -ErrorAction SilentlyContinue)) {
|
|
|
|
|
$state = (Get-WindowsCapability -Online -Name $cap.Name).State
|
|
|
|
|
Write-Host ''
|
|
|
|
|
Write-Host '===== RESULT (send this to Claude) ====='
|
|
|
|
|
Write-Host '===== RESULT (paste this into the settings page) ====='
|
|
|
|
|
Write-Host ('pc=' + $env:COMPUTERNAME + ' sshd=missing openssh=' + $state + ' restart_needed=' + $restart)
|
|
|
|
|
Write-Host 'RESTART THIS PC, then run this script again.'
|
|
|
|
|
Write-Host '========================================'
|
|
|
|
@@ -42,21 +43,21 @@ if (-not (Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction Silentl
|
|
|
|
|
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True `
|
|
|
|
|
-Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | Out-Null
|
|
|
|
|
}
|
|
|
|
|
# Windows' own rule covers "Private" networks only; open it on any network, but to NA Ingest alone.
|
|
|
|
|
# Windows' own rule covers "Private" networks only; open it on any network, but to {{INGEST_NAME}} alone.
|
|
|
|
|
Set-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -Profile Any -RemoteAddress $IngestIPs
|
|
|
|
|
Enable-NetFirewallRule -Name 'OpenSSH-Server-In-TCP'
|
|
|
|
|
|
|
|
|
|
# 2. NA Ingest's key. Administrators read C:\ProgramData\ssh\administrators_authorized_keys,
|
|
|
|
|
# 2. {{INGEST_NAME}}'s key. Administrators read C:\ProgramData\ssh\administrators_authorized_keys,
|
|
|
|
|
# everyone else their own .ssh\authorized_keys - write both.
|
|
|
|
|
$adminKeys = Join-Path $env:ProgramData 'ssh\administrators_authorized_keys'
|
|
|
|
|
if (-not (Test-Path $adminKeys) -or -not (Select-String -Path $adminKeys -SimpleMatch 'status-panel@NA-Ingest' -Quiet)) {
|
|
|
|
|
if (-not (Test-Path $adminKeys) -or -not (Select-String -Path $adminKeys -SimpleMatch $KeyMark -Quiet)) {
|
|
|
|
|
Add-Content -Path $adminKeys -Value $Key -Encoding ascii
|
|
|
|
|
}
|
|
|
|
|
icacls $adminKeys /inheritance:r /grant '*S-1-5-32-544:F' /grant '*S-1-5-18:F' | Out-Null
|
|
|
|
|
$userSsh = Join-Path $env:USERPROFILE '.ssh'
|
|
|
|
|
New-Item -ItemType Directory -Force -Path $userSsh | Out-Null
|
|
|
|
|
$userKeys = Join-Path $userSsh 'authorized_keys'
|
|
|
|
|
if (-not (Test-Path $userKeys) -or -not (Select-String -Path $userKeys -SimpleMatch 'status-panel@NA-Ingest' -Quiet)) {
|
|
|
|
|
if (-not (Test-Path $userKeys) -or -not (Select-String -Path $userKeys -SimpleMatch $KeyMark -Quiet)) {
|
|
|
|
|
Add-Content -Path $userKeys -Value $Key -Encoding ascii
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -66,22 +67,18 @@ function Find-First($candidates) {
|
|
|
|
|
return $null
|
|
|
|
|
}
|
|
|
|
|
$roots = @($env:ProgramFiles, ${env:ProgramFiles(x86)}, (Join-Path $env:LOCALAPPDATA 'Programs')) | Where-Object { $_ }
|
|
|
|
|
$bible = Get-ChildItem -Path $roots -Filter '*.exe' -Recurse -Depth 3 -ErrorAction SilentlyContinue |
|
|
|
|
|
Where-Object { $_.FullName -match 'bible' -and $_.Name -notmatch 'uninst|update|crash|helper' } |
|
|
|
|
|
Select-Object -First 1 -ExpandProperty FullName
|
|
|
|
|
# Apps can live on any local drive (the LiveStream PC has OBS on D:, the Lighting PC has Vista on G:).
|
|
|
|
|
# Programs can live on any local drive; a program file is looked for under the usual folders, then on each drive.
|
|
|
|
|
$drives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Free -ne $null } | ForEach-Object { $_.Root.TrimEnd('\') }
|
|
|
|
|
$vista = Find-First (@($drives | ForEach-Object { "$_\Vista3\Vista.exe" }) +
|
|
|
|
|
@(Get-ChildItem -Path $roots -Filter 'Vista.exe' -Recurse -Depth 3 -ErrorAction SilentlyContinue | ForEach-Object FullName))
|
|
|
|
|
function Find-Exe($name) {
|
|
|
|
|
$hit = Get-ChildItem -Path $roots -Filter $name -Recurse -Depth 3 -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch 'uninst|update|crash|helper' } | Select-Object -First 1 -ExpandProperty FullName
|
|
|
|
|
if (-not $hit) { $hit = Get-ChildItem -Path $drives -Filter $name -Recurse -Depth 2 -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch 'uninst|update|crash|helper|Windows\\' } | Select-Object -First 1 -ExpandProperty FullName }
|
|
|
|
|
return $hit
|
|
|
|
|
}
|
|
|
|
|
$apps = @(
|
|
|
|
|
@{ Name = 'OBS'; Exe = (Find-First @($drives | ForEach-Object { "$_\Program Files\obs-studio\bin\64bit\obs64.exe" })); Args = '--disable-shutdown-check' },
|
|
|
|
|
@{ Name = 'Vista'; Exe = $vista; Args = '' },
|
|
|
|
|
@{ Name = 'Arena'; Exe = (Find-First (Get-ChildItem "$env:ProgramFiles\Resolume Arena*\Arena.exe" -ErrorAction SilentlyContinue | ForEach-Object FullName)); Args = '' },
|
|
|
|
|
@{ Name = 'rtpMIDI'; Exe = (Find-First @("${env:ProgramFiles(x86)}\Tobias Erichsen\rtpMIDI\rtpMIDI.exe", "$env:ProgramFiles\Tobias Erichsen\rtpMIDI\rtpMIDI.exe")); Args = '' },
|
|
|
|
|
@{ Name = 'BibleApp'; Exe = $bible; Args = ''; AtLogon = $true } # also opens it 15 s after login
|
|
|
|
|
{{APPS}}
|
|
|
|
|
)
|
|
|
|
|
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Limited
|
|
|
|
|
# An app ticked "Run this program as an administrator" (OBS on the LiveStream PC) cannot start from a
|
|
|
|
|
# A program ticked "Run this program as an administrator" cannot start from a
|
|
|
|
|
# Limited task: Windows refuses with 0x800702E4. Its task runs with highest privileges instead.
|
|
|
|
|
$elevated = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Highest
|
|
|
|
|
$layers = @{}
|
|
|
|
@@ -109,10 +106,12 @@ foreach ($app in $apps) {
|
|
|
|
|
$tasks += ('{0} = {1}{2}' -f $app.Name, $app.Exe, $(if ($app.AtLogon) { ' (also at login)' } else { '' }))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# rtpMIDI: if its service ever stops unexpectedly, Windows starts it again (5 s, 5 s, then 30 s).
|
|
|
|
|
if (Get-Service rtpMIDIService -ErrorAction SilentlyContinue) {
|
|
|
|
|
sc.exe failure rtpMIDIService reset= 86400 actions= restart/5000/restart/5000/restart/30000 | Out-Null
|
|
|
|
|
$notes += 'rtpMIDI service restarts itself if it stops'
|
|
|
|
|
# Services the panel starts: if one ever stops unexpectedly, Windows starts it again (5 s, 5 s, then 30 s).
|
|
|
|
|
foreach ($svc in @({{SERVICES}})) {
|
|
|
|
|
if (Get-Service $svc -ErrorAction SilentlyContinue) {
|
|
|
|
|
sc.exe failure $svc reset= 86400 actions= restart/5000/restart/5000/restart/30000 | Out-Null
|
|
|
|
|
$notes += ($svc + ' restarts itself if it stops')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 4. Wake-on-LAN: let the wired adapter wake the PC (the BIOS setting must be on too)
|
|
|
|
@@ -125,7 +124,7 @@ foreach ($nic in $nics) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host ''
|
|
|
|
|
Write-Host '===== RESULT (send this to Claude) ====='
|
|
|
|
|
Write-Host '===== RESULT (paste this into the settings page) ====='
|
|
|
|
|
Write-Host ('pc=' + $env:COMPUTERNAME + ' user=' + $env:USERNAME + ' domain=' + $env:USERDOMAIN)
|
|
|
|
|
Write-Host ('sshd=' + (Get-Service sshd).Status)
|
|
|
|
|
foreach ($nic in $nics) { Write-Host ('nic=' + $nic.Name + ' mac=' + $nic.MacAddress + ' ip=' + ((Get-NetIPAddress -InterfaceIndex $nic.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue).IPAddress -join ',') + ' network=' + (Get-NetConnectionProfile -InterfaceIndex $nic.ifIndex -ErrorAction SilentlyContinue).NetworkCategory) }
|