165 lines
8.5 KiB
PowerShell
165 lines
8.5 KiB
PowerShell
# OOBE for Windows LTSC desktops and laptops. Runs fully unattended (no prompts).
|
|
# Pulled by autounattend.xml at first logon, or run by hand:
|
|
# irm https://url.isworking.fyi/oobe-desktop | iex
|
|
# Log: C:\ProgramData\OOBE\oobe-desktop.log
|
|
|
|
$MeshAgentUrl = "https://rmm.iamchrisama.com/meshagents?id=4&meshid=zxl@U2zM95zh9ZNqah@9mUEjCJ3ptGOE6s5cjsGniacVU1fjRXtVKCKlKJN4aJQW&installflags=0"
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
# The progress bar makes Invoke-WebRequest extremely slow on Windows PowerShell 5.1
|
|
$ProgressPreference = 'SilentlyContinue'
|
|
# Older LTSC builds don't enable TLS 1.2 by default, which breaks GitHub downloads
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
|
|
|
$WorkDir = Join-Path $env:ProgramData 'OOBE'
|
|
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
|
|
Start-Transcript -Path (Join-Path $WorkDir 'oobe-desktop.log') -Append | Out-Null
|
|
|
|
# Check if running with elevated privileges (after the transcript starts, so this still leaves a log)
|
|
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
|
Write-Warning "Not running as administrator, stopping. Rerun from an elevated PowerShell."
|
|
Stop-Transcript | Out-Null
|
|
Exit 1
|
|
}
|
|
|
|
function Save-Download($Uri, $OutFile) {
|
|
for ($Attempt = 1; $Attempt -le 3; $Attempt++) {
|
|
try {
|
|
Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing
|
|
return $true
|
|
} catch {
|
|
Write-Warning "Download attempt $Attempt of $Uri failed: $_"
|
|
Start-Sleep -Seconds 10
|
|
}
|
|
}
|
|
return $false
|
|
}
|
|
|
|
# Wait for internet access, the network may not be up yet during first logon
|
|
function Wait-Network {
|
|
$MeshHost = ([uri]$MeshAgentUrl).Host
|
|
$Deadline = (Get-Date).AddMinutes(5)
|
|
while ((Get-Date) -lt $Deadline) {
|
|
$Client = New-Object System.Net.Sockets.TcpClient
|
|
try { if ($Client.ConnectAsync($MeshHost, 443).Wait(5000)) { return } } catch {} finally { $Client.Close() }
|
|
Write-Output "Waiting for network..."
|
|
Start-Sleep -Seconds 5
|
|
}
|
|
Write-Warning "No network after 5 minutes, continuing anyway."
|
|
}
|
|
|
|
# Balanced plan: dim the display after 5 minutes, sleep after 30 minutes.
|
|
# Turning the display off stays at the plan default (10 minutes plugged in).
|
|
$Balanced = '381b4222-f694-41f0-9685-ff5bb260df2e'
|
|
powercfg /setactive $Balanced
|
|
|
|
# "Dim display after" (seconds). Only has a visible effect on screens Windows can
|
|
# control the brightness of (laptops, all-in-ones); external monitors ignore it.
|
|
$DisplaySubgroup = '7516b95f-f776-4464-8c53-06167f40cc99'
|
|
$DimTimeout = '17aaa29b-8b43-4b94-aafe-35f64daaf1ee'
|
|
powercfg /setacvalueindex $Balanced $DisplaySubgroup $DimTimeout 300
|
|
powercfg /setdcvalueindex $Balanced $DisplaySubgroup $DimTimeout 300
|
|
|
|
powercfg /change standby-timeout-ac 30
|
|
powercfg /change standby-timeout-dc 30
|
|
powercfg /setactive $Balanced
|
|
|
|
# Treat the network as Private and suppress the "allow this PC to be discoverable" prompt
|
|
Wait-Network
|
|
New-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff' -Force | Out-Null
|
|
Get-NetConnectionProfile | Where-Object NetworkCategory -ne 'DomainAuthenticated' | Set-NetConnectionProfile -NetworkCategory Private
|
|
|
|
# Enable ping (ICMP Echo) requests
|
|
if (-not (Get-NetFirewallRule -Name 'OOBE-ICMPv4-In' -ErrorAction SilentlyContinue)) {
|
|
New-NetFirewallRule -Name 'OOBE-ICMPv4-In' -DisplayName "Allow ICMP Echo Request" -Protocol ICMPv4 -IcmpType 8 -Enabled True | Out-Null
|
|
}
|
|
|
|
# Allow Remote Desktop (firewall groups are referenced by resource ID so this works on non-English installs)
|
|
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0
|
|
Enable-NetFirewallRule -Group '@FirewallAPI.dll,-28752'
|
|
|
|
# Enable c$ (AutoShareWks must be 1; 0 disables the admin shares)
|
|
reg add "HKLM\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters" /f /v AutoShareWks /t REG_DWORD /d 1
|
|
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v "LocalAccountTokenFilterPolicy" /t REG_DWORD /d 1 /f
|
|
Enable-NetFirewallRule -Group '@FirewallAPI.dll,-28502'
|
|
|
|
# Disable UAC prompt
|
|
Set-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows\CurrentVersion\policies\system' -Name "ConsentPromptBehaviorAdmin" -Value 0
|
|
|
|
# Dark Mode and no mouse acceleration, for the current user and the default profile (future users)
|
|
function Set-UserPreferences($Hive) {
|
|
reg add "$Hive\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" /v AppsUseLightTheme /t REG_DWORD /d 0 /f | Out-Null
|
|
reg add "$Hive\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" /v SystemUsesLightTheme /t REG_DWORD /d 0 /f | Out-Null
|
|
# "Enhance pointer precision" off
|
|
reg add "$Hive\Control Panel\Mouse" /v MouseSpeed /t REG_SZ /d 0 /f | Out-Null
|
|
reg add "$Hive\Control Panel\Mouse" /v MouseThreshold1 /t REG_SZ /d 0 /f | Out-Null
|
|
reg add "$Hive\Control Panel\Mouse" /v MouseThreshold2 /t REG_SZ /d 0 /f | Out-Null
|
|
}
|
|
Set-UserPreferences 'HKCU'
|
|
$DefaultProfile = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList').Default
|
|
reg load 'HKU\OOBEDefault' "$DefaultProfile\NTUSER.DAT" | Out-Null
|
|
if ($LASTEXITCODE -eq 0) {
|
|
Set-UserPreferences 'HKU\OOBEDefault'
|
|
[gc]::Collect()
|
|
reg unload 'HKU\OOBEDefault' | Out-Null
|
|
}
|
|
|
|
# Install MeshCentral agent silently as a service
|
|
if (Get-Service -Name 'Mesh Agent' -ErrorAction SilentlyContinue) {
|
|
Write-Output "Mesh Agent is already installed."
|
|
} else {
|
|
$MeshInstaller = Join-Path $WorkDir 'meshagent.exe'
|
|
if (Save-Download $MeshAgentUrl $MeshInstaller) {
|
|
# -fullinstall installs and starts the service without showing the install dialog
|
|
$Process = Start-Process -FilePath $MeshInstaller -ArgumentList '-fullinstall' -WindowStyle Hidden -PassThru
|
|
if (-not $Process.WaitForExit(300000)) {
|
|
Write-Warning "Mesh Agent installer still running after 5 minutes, continuing."
|
|
}
|
|
}
|
|
if (-not (Get-Service -Name 'Mesh Agent' -ErrorAction SilentlyContinue)) {
|
|
Write-Warning "Mesh Agent service was not found after install."
|
|
}
|
|
}
|
|
|
|
# Install OpenSSH
|
|
foreach ($Capability in 'OpenSSH.Client~~~~0.0.1.0', 'OpenSSH.Server~~~~0.0.1.0') {
|
|
if ((Get-WindowsCapability -Online -Name $Capability).State -ne 'Installed') {
|
|
Add-WindowsCapability -Online -Name $Capability | Out-Null
|
|
}
|
|
}
|
|
|
|
Set-Service -Name sshd -StartupType 'Automatic'
|
|
Start-Service sshd
|
|
|
|
# Use PowerShell instead of cmd.exe for SSH sessions
|
|
if (Test-Path 'HKLM:\SOFTWARE\OpenSSH') {
|
|
New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force | Out-Null
|
|
}
|
|
|
|
if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) {
|
|
Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."
|
|
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
|
|
} else {
|
|
Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."
|
|
}
|
|
|
|
# Install Winget (LTSC ships without the Store / App Installer)
|
|
# Run in a child process (the installer script calls exit, which would end this script too),
|
|
# with a time limit so it can't hold up the restart. Its output goes to winget-install*.log.
|
|
$WingetInstaller = Join-Path $WorkDir 'winget-install.ps1'
|
|
if (Save-Download 'https://github.com/asheroto/winget-install/releases/latest/download/winget-install.ps1' $WingetInstaller) {
|
|
$Process = Start-Process powershell.exe -ArgumentList '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $WingetInstaller, '-Force' -PassThru `
|
|
-RedirectStandardOutput (Join-Path $WorkDir 'winget-install.log') -RedirectStandardError (Join-Path $WorkDir 'winget-install-errors.log')
|
|
if ($Process.WaitForExit(900000)) {
|
|
Write-Output "Winget installer exited with code $($Process.ExitCode)"
|
|
} else {
|
|
Write-Warning "Winget installer still running after 15 minutes, continuing."
|
|
}
|
|
}
|
|
|
|
# Restart to finish OpenSSH and driver installation.
|
|
# Delayed so the script can exit cleanly and the log is flushed before Windows goes down.
|
|
Write-Output "OOBE complete, restarting in 10 seconds."
|
|
Stop-Transcript | Out-Null
|
|
shutdown.exe /r /t 10 /d p:4:1 /c "OOBE complete, restarting to finish installation."
|