Skip to content

Instantly share code, notes, and snippets.

@anpin
Last active August 4, 2023 04:25
Show Gist options
  • Save anpin/59b736fc8959251c4c811f90618f2306 to your computer and use it in GitHub Desktop.
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
# 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/p5@1.7.0/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
@anpin
Copy link
Author

anpin commented Jul 21, 2023

To run it directly from github use this.
DO NOT RUN RANDOM SCRIPTS FROM THE INTERNET THIS IS VERY BAD PRACTICE

$url = "https://gist.githubusercontent.com/anpin/59b736fc8959251c4c811f90618f2306/raw/96d95762a52b21340a0831e8908739ecffc5d945/kiosk.ps1"
Invoke-Expression $($(Invoke-WebRequest -UseBasicParsing $url).Content)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment