Last active
August 4, 2023 04:25
-
-
Save anpin/59b736fc8959251c4c811f90618f2306 to your computer and use it in GitHub Desktop.
Simple PS script to configure locally hosted nodejs web-kiosk on windows 10 pro with FTP to update content
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
# Variables | |
$serverPort = 8080 # Change to the desired port for your Node.js server | |
$nodePort = 8081 # Change to the desired port for your Node.js server | |
$kioskURL = "http://localhost:$serverPort" # The URL that the kiosk browser will open | |
$siteName = "InteractiveKiosk" | |
$sitePath = "IIS:\Sites\$siteName" | |
$physicalPath = "C:\$siteName" | |
$contentPath = "$physicalPath/content" | |
$ftpGroup = "IISFTP" | |
$ftpServerPort = 21 | |
# Check if running with administrative privileges | |
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { | |
Write-Host "Please run the script as an administrator." | |
exit | |
} | |
function wingetInstall { | |
#workaround https://github.com/microsoft/winget-cli/issues/1861 | |
Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.7.3 -OutFile .\microsoft.ui.xaml.2.7.3.zip | |
Expand-Archive .\microsoft.ui.xaml.2.7.3.zip | |
Add-AppxPackage .\microsoft.ui.xaml.2.7.3\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx | |
#install winget https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget | |
$progressPreference = 'silentlyContinue' | |
$latestWingetMsixBundleUri = $(Invoke-RestMethod https://api.github.com/repos/microsoft/winget-cli/releases/latest).assets.browser_download_url | Where-Object {$_.EndsWith(".msixbundle")} | |
$latestWingetMsixBundle = $latestWingetMsixBundleUri.Split("/")[-1] | |
Write-Information "Downloading winget to artifacts directory..." | |
Invoke-WebRequest -Uri $latestWingetMsixBundleUri -OutFile "./$latestWingetMsixBundle" | |
Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx | |
Add-AppxPackage Microsoft.VCLibs.x64.14.00.Desktop.appx | |
Add-AppxPackage $latestWingetMsixBundle | |
} | |
function rdp { | |
# Enable Remote Desktop (RDP) for remote access | |
# NOTE: This requires administrative privileges to execute | |
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name 'fDenyTSConnections' -Value 0 | |
Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' | |
} | |
function install { | |
Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServer -All | |
Enable-WindowsOptionalFeature -Online -FeatureName IIS-FTPSvc -All | |
winget install OpenJS.NodeJS | |
$iisNodeInstaller = "https://github.com/Azure/iisnode/releases/download/v0.2.26/iisnode-full-v0.2.26-x64.msi" | |
Invoke-WebRequest -Uri $iisNodeInstaller -OutFile "C:\iisnode_installer.msi" | |
Start-Process msiexec.exe -ArgumentList "/i C:\iisnode_installer.msi /quiet /qn /norestart" -Wait | |
Remove-Item -Path "C:\iisnode_installer.msi" -Force | |
$urlRewrite = "https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi" | |
Invoke-WebRequest -Uri $urlRewrite -OutFile "C:\url_rewrite_installer.msi" | |
Start-Process msiexec.exe -ArgumentList "/i C:\url_rewrite_installer.msi /quiet /qn /norestart" -Wait | |
Remove-Item -Path "C:\url_rewrite_installer.msi" -Force | |
} | |
function createServer { | |
import-Module WebAdministration | |
New-Item -Path $physicalPath -ItemType Directory -Force | |
New-Item -Path $contentPath -ItemType Directory -Force | |
# Create a simple Node.js server | |
$indexHtml = @" | |
<!doctype html> | |
<html lang="en" data-theme="light"> | |
<head> | |
<title>Kiosk Demo</title> | |
<meta charset="utf-8"> | |
<style> | |
body{margin:0;overflow:hidden} | |
</style> | |
<script type="module" src="https://unpkg.com/[email protected]/lib/p5.min.js"></script> | |
<!-- p5.js drawing example --> | |
<script type="text/javascript"> | |
// All the paths | |
let paths = []; | |
// Are we painting? | |
let painting = false; | |
// How long until the next circle | |
let next = 0; | |
// Where are we now and where were we? | |
let current; | |
let previous; | |
function setup() { | |
createCanvas(windowWidth, windowHeight); | |
current = createVector(0,0); | |
previous = createVector(0,0); | |
}; | |
function windowResized() { | |
resizeCanvas(windowWidth, windowHeight); | |
} | |
function draw() { | |
background(200); | |
// If it's time for a new point | |
if (millis() > next && painting) { | |
// Grab mouse position | |
current.x = mouseX; | |
current.y = mouseY; | |
// New particle's force is based on mouse movement | |
let force = p5.Vector.sub(current, previous); | |
force.mult(0.05); | |
// Add new particle | |
paths[paths.length - 1].add(current, force); | |
// Schedule next circle | |
next = millis() + random(100); | |
// Store mouse values | |
previous.x = current.x; | |
previous.y = current.y; | |
} | |
// Draw all paths | |
for( let i = 0; i < paths.length; i++) { | |
paths[i].update(); | |
paths[i].display(); | |
} | |
} | |
// Start it up | |
function mousePressed() { | |
next = 0; | |
painting = true; | |
previous.x = mouseX; | |
previous.y = mouseY; | |
paths.push(new Path()); | |
} | |
// Stop | |
function mouseReleased() { | |
painting = false; | |
} | |
// A Path is a list of particles | |
class Path { | |
constructor() { | |
this.particles = []; | |
this.hue = random(100); | |
} | |
add(position, force) { | |
// Add a new particle with a position, force, and hue | |
this.particles.push(new Particle(position, force, this.hue)); | |
} | |
// Display plath | |
update() { | |
for (let i = 0; i < this.particles.length; i++) { | |
this.particles[i].update(); | |
} | |
} | |
// Display plath | |
display() { | |
// Loop through backwards | |
for (let i = this.particles.length - 1; i >= 0; i--) { | |
// If we shold remove it | |
if (this.particles[i].lifespan <= 0) { | |
this.particles.splice(i, 1); | |
// Otherwise, display it | |
} else { | |
this.particles[i].display(this.particles[i+1]); | |
} | |
} | |
} | |
} | |
// Particles along the path | |
class Particle { | |
constructor(position, force, hue) { | |
this.position = createVector(position.x, position.y); | |
this.velocity = createVector(force.x, force.y); | |
this.drag = 0.95; | |
this.lifespan = 255; | |
} | |
update() { | |
// Move it | |
this.position.add(this.velocity); | |
// Slow it down | |
this.velocity.mult(this.drag); | |
// Fade it out | |
this.lifespan--; | |
} | |
// Draw particle and connect it with a line | |
// Draw a line to another | |
display(other) { | |
stroke(0, this.lifespan); | |
fill(0, this.lifespan/2); | |
ellipse(this.position.x,this.position.y, 8, 8); | |
// If we need to draw a line | |
if (other) { | |
line(this.position.x, this.position.y, other.position.x, other.position.y); | |
} | |
} | |
} | |
</script> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
</head> | |
<body> | |
<div id="sutil-app"></div> | |
</body> | |
</html> | |
"@ | |
$serverCode = @" | |
const http = require('http'); | |
const fs = require('fs'); | |
const server = http.createServer((req, res) => { | |
const index = fs.readFileSync('index.html'); | |
res.writeHead(200, { 'Content-Type': 'text/html' }); | |
res.end(index); | |
}); | |
server.listen(process.env.PORT, () => { | |
console.log('Node.js server is running on port process.env.PORT'); | |
}); | |
"@ | |
$iisConfig = @" | |
<configuration> | |
<system.webServer> | |
<handlers> | |
<add name="iisnode" path="content/main.js" verb="*" modules="iisnode" /> | |
</handlers> | |
<iisnode | |
node_env="%node_env%" | |
nodeProcessCommandLine="%programfiles%\nodejs\node.exe" | |
nodeProcessCountPerApplication="1" | |
maxConcurrentRequestsPerProcess="1024" | |
maxNamedPipeConnectionRetry="100" | |
namedPipeConnectionRetryDelay="250" | |
maxNamedPipeConnectionPoolSize="512" | |
maxNamedPipePooledConnectionAge="30000" | |
asyncCompletionThreadCount="0" | |
initialRequestBufferSize="4096" | |
maxRequestBufferSize="65536" | |
watchedFiles="*.js;iisnode.yml" | |
uncFileChangesPollingInterval="5000" | |
gracefulShutdownTimeout="60000" | |
loggingEnabled="true" | |
logDirectory="logs" | |
debuggingEnabled="true" | |
debugHeaderEnabled="false" | |
debuggerPortRange="5058-6058" | |
debuggerPathSegment="debug" | |
maxLogFileSizeInKB="128" | |
maxTotalLogFileSizeInKB="1024" | |
maxLogFiles="20" | |
devErrorsEnabled="true" | |
flushResponse="false" | |
enableXFF="false" | |
promoteServerVars="" | |
/> | |
<rewrite> | |
<rules> | |
<rule name="sendToNode"> | |
<match url=".*" /> | |
<action type="Rewrite" url="content/main.js" /> | |
</rule> | |
</rules> | |
</rewrite> | |
</system.webServer> | |
</configuration> | |
"@ | |
Set-Content -Path "C:\main.js" -Value $serverCode | |
Set-Content -Path "C:\index.html" -Value $indexHtml | |
Set-Content -Path "C:\web.config" -Value $iisConfig | |
Copy-Item -Path "C:\main.js" -Destination $contentPath -Force | |
Copy-Item -Path "C:\index.html" -Destination $contentPath -Force | |
Copy-Item -Path "C:\web.config" -Destination $physicalPath -Force | |
New-WebAppPool -Name $siteName -Force | |
# New-WebFtpSite -Name $siteName -Port $ftpServerPort -PhysicalPath $physicalPath -Force | |
New-Website -Name $siteName -Port $serverPort -PhysicalPath $physicalPath -ApplicationPool $siteName -Force | |
New-WebBinding $siteName -Port $ftpServerPort -Protocol ftp -IPAddress * | |
Set-WebConfigurationProperty -filter /system.webServer -name 'sections["handlers"].overrideModeDefault' -value Allow -pspath iis:\ | |
} | |
# Set up the IIS application pool for the Node.js site | |
function firewall { | |
Enable-NetFirewallRule -DisplayName "FTP Server (FTP Traffic-In)" | |
Enable-NetFirewallRule -DisplayName "FTP Server Passive (FTP Passive Traffic-In)" | |
Enable-NetFirewallRule -DisplayName "FTP Server Secure (FTP SSL Traffic-In)" | |
Enable-NetFirewallRule -DisplayName "FTP Server (FTP Traffic-Out)" | |
Enable-NetFirewallRule -DisplayName "FTP Server Secure (FTP SSL Traffic-Out)" | |
New-NetFirewallRule -DisplayName "FTP IIS" -Direction Inbound -Protocol TCP -LocalPort 20-21 -Action Allow | |
} | |
function addUser { | |
$ftpUserName = Read-Host 'Enter FTP username:' | |
$ftpPassword = Read-Host 'Enter FTP user password:' | |
New-LocalGroup -Name $ftpGroup | |
# Set up FTP user account | |
$secureFtpPassword = ConvertTo-SecureString -String $ftpPassword -AsPlainText -Force | |
New-LocalUser -Name $ftpUserName -Password $secureFtpPassword -Description "FTP User Account for Node.js Content" -UserMayNotChangePassword | |
Add-LocalGroupMember -Group $ftpGroup -Member $ftpUserName | |
} | |
function ftpConfig { | |
Set-ItemProperty -Path $sitePath -Name ftpServer.security.authentication.basicAuthentication.enabled -Value $True | |
Set-ItemProperty -Path $sitePath -Name ftpServer.security.ssl.controlChannelPolicy -Value 0 | |
Set-ItemProperty -Path $sitePath -Name ftpServer.security.ssl.dataChannelPolicy -Value 0 | |
Add-WebConfiguration "/system.ftpServer/security/authorization" -Value @{accessType = "Allow"; roles = "$ftpGroup"; permissions = "Read,Write"} -PSPath "IIS:\" -Location $siteName -AtIndex 0 | |
} | |
# It is better to use Windows kiosk mode with edge, | |
# but configuring via powershell is not possible for now, | |
# so proceed manually | |
# type kiosk in search -> pick the settings section -> enter user name -> select edge -> enter link -> reboot -> done | |
# function configureKiosk { | |
# $browserPath = "C:\Program Files\Google\Chrome\Application\chrome.exe" | |
# $browserArguments = "--kiosk $kioskURL --edge-kiosk-type=fullscreen --no-first-run" | |
# $runtime = @" | |
# Start-Process "$browserPath" -ArgumentList "$browserArguments" | |
# "@ | |
# $runtimePath = "~\Desktop\kiosk.ps1" | |
# Set-Content -Path $runtimePath -Value $runtime | |
# New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "KioskBrowser" -Value "$browserPath $browserArguments" -PropertyType String -Force | |
# } | |
# function autoLogon { | |
# $Username = Read-Host 'Enter username for auto-logon (f.e. contoso\user1)' | |
# $Pass = Read-Host "Enter password for $Username" | |
# $RegistryPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' | |
# Set-ItemProperty $RegistryPath 'AutoAdminLogon' -Value "1" -Type String | |
# Set-ItemProperty $RegistryPath 'DefaultUsername' -Value "$Username" -type String | |
# Set-ItemProperty $RegistryPath 'DefaultPassword' -Value "$Pass" -type String | |
# } | |
# function restart { | |
# s$restart = Read-Host 'Do you want to restart your computer now for testing auto-logon? (Y/N)' | |
# If ($restart -eq 'Y') { | |
# Restart-Computer -Force | |
# } | |
# else { | |
# Restart-WebAppPool -Name $siteName | |
# Write-Host "Installation and configuration completed successfully." | |
# } | |
# } | |
rdp | |
wingetInstall | |
install | |
createServer | |
addUser | |
ftpConfig | |
firewall | |
# configureKiosk | |
# autoLogon | |
# restart |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To run it directly from github use this.
DO NOT RUN RANDOM SCRIPTS FROM THE INTERNET THIS IS VERY BAD PRACTICE