Fix first-logon provisioning never starting
- Bound every installer wait with WaitForExit and a timeout instead of Start-Process -Wait, which in Windows PowerShell also waits for any process the installer leaves running and can block forever. This covers the VirtIO step in the answer files and the guest tools, Mesh agent and Winget installs in the scripts. - specialize pass (SYSTEM) sets ConsentPromptBehaviorAdmin=0 so nothing at first logon can stall on a UAC prompt. - Step 4 now logs to C:\ProgramData\OOBE\firstlogon.log, saves the script to C:\ProgramData\OOBE and starts it elevated in its own window. - Scripts start their transcript before the admin check, so an early stop still leaves a log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>master
parent
713463cd94
commit
f208e07d72
|
|
@ -3,12 +3,6 @@
|
|||
# irm https://url.isworking.fyi/oobe-desktop | iex
|
||||
# Log: C:\ProgramData\OOBE\oobe-desktop.log
|
||||
|
||||
# Check if running with elevated privileges
|
||||
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
Write-Host "Please run this script as an administrator."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$MeshAgentUrl = "https://rmm.iamchrisama.com/meshagents?id=4&meshid=zxl@U2zM95zh9ZNqah@9mUEjCJ3ptGOE6s5cjsGniacVU1fjRXtVKCKlKJN4aJQW&installflags=0"
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
|
@ -21,6 +15,13 @@ $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 {
|
||||
|
|
@ -110,7 +111,10 @@ if (Get-Service -Name 'Mesh Agent' -ErrorAction SilentlyContinue) {
|
|||
$MeshInstaller = Join-Path $WorkDir 'meshagent.exe'
|
||||
if (Save-Download $MeshAgentUrl $MeshInstaller) {
|
||||
# -fullinstall installs and starts the service without showing the install dialog
|
||||
Start-Process -FilePath $MeshInstaller -ArgumentList '-fullinstall' -WindowStyle Hidden -Wait
|
||||
$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."
|
||||
|
|
@ -140,10 +144,17 @@ if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyCon
|
|||
}
|
||||
|
||||
# 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
|
||||
# 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) {
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $WingetInstaller -Force
|
||||
$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.
|
||||
|
|
|
|||
39
oobe-vms.ps1
39
oobe-vms.ps1
|
|
@ -3,12 +3,6 @@
|
|||
# irm https://url.isworking.fyi/oobe-vms | iex
|
||||
# Log: C:\ProgramData\OOBE\oobe-vms.log
|
||||
|
||||
# Check if running with elevated privileges
|
||||
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
Write-Host "Please run this script as an administrator."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$MeshAgentUrl = "https://rmm.iamchrisama.com/meshagents?id=4&meshid=zxl@U2zM95zh9ZNqah@9mUEjCJ3ptGOE6s5cjsGniacVU1fjRXtVKCKlKJN4aJQW&installflags=0"
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
|
@ -21,6 +15,13 @@ $WorkDir = Join-Path $env:ProgramData 'OOBE'
|
|||
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
|
||||
Start-Transcript -Path (Join-Path $WorkDir 'oobe-vms.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 {
|
||||
|
|
@ -75,8 +76,14 @@ if (Get-Service -Name 'QEMU-GA' -ErrorAction SilentlyContinue) {
|
|||
ForEach-Object { $Store.Add($_) }
|
||||
$Store.Close()
|
||||
|
||||
$Process = Start-Process -FilePath $GuestTools -ArgumentList '/install', '/quiet', '/norestart' -Wait -PassThru
|
||||
Write-Output "VirtIO guest tools installer exited with code $($Process.ExitCode) (0 = success, 3010 = reboot required)"
|
||||
# Wait on the installer only, with a time limit: Start-Process -Wait also waits for anything
|
||||
# the installer leaves running (agents, services), which can block forever
|
||||
$Process = Start-Process -FilePath $GuestTools -ArgumentList '/install', '/quiet', '/norestart' -PassThru
|
||||
if ($Process.WaitForExit(900000)) {
|
||||
Write-Output "VirtIO guest tools installer exited with code $($Process.ExitCode) (0 = success, 3010 = reboot required)"
|
||||
} else {
|
||||
Write-Warning "VirtIO guest tools installer still running after 15 minutes, continuing."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,7 +183,10 @@ if (Get-Service -Name 'Mesh Agent' -ErrorAction SilentlyContinue) {
|
|||
$MeshInstaller = Join-Path $WorkDir 'meshagent.exe'
|
||||
if (Save-Download $MeshAgentUrl $MeshInstaller) {
|
||||
# -fullinstall installs and starts the service without showing the install dialog
|
||||
Start-Process -FilePath $MeshInstaller -ArgumentList '-fullinstall' -WindowStyle Hidden -Wait
|
||||
$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."
|
||||
|
|
@ -206,10 +216,17 @@ if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyCon
|
|||
}
|
||||
|
||||
# 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
|
||||
# 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) {
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $WingetInstaller -Force
|
||||
$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 driver, guest tools and OpenSSH installation.
|
||||
|
|
|
|||
|
|
@ -141,6 +141,25 @@
|
|||
======================================================================= -->
|
||||
<settings pass="specialize">
|
||||
|
||||
<!-- Runs as SYSTEM before anyone logs on. Lets admins elevate without a
|
||||
UAC prompt (oobe-*.ps1 sets the same value later), so nothing at
|
||||
first logon can stall on an elevation prompt nobody is there to
|
||||
click: the VirtIO installer, or the RunAs launch in step 4. -->
|
||||
<component name="Microsoft-Windows-Deployment"
|
||||
processorArchitecture="amd64"
|
||||
publicKeyToken="31bf3856ad364e35"
|
||||
language="neutral"
|
||||
versionScope="nonSxS"
|
||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||
<RunSynchronous>
|
||||
<RunSynchronousCommand wcm:action="add">
|
||||
<Order>1</Order>
|
||||
<Description>Elevate admins without a UAC prompt</Description>
|
||||
<Path>reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 0 /f</Path>
|
||||
</RunSynchronousCommand>
|
||||
</RunSynchronous>
|
||||
</component>
|
||||
|
||||
<component name="Microsoft-Windows-Shell-Setup"
|
||||
processorArchitecture="amd64"
|
||||
publicKeyToken="31bf3856ad364e35"
|
||||
|
|
@ -281,25 +300,29 @@
|
|||
tools (NetKVM is needed before anything can be downloaded). On
|
||||
physical PCs no drive has the installer and this does nothing. Scans every drive for
|
||||
the installer rather than assuming a letter, since the VirtIO
|
||||
disc moves around post-install. -->
|
||||
disc moves around post-install. Waits at most 15 minutes, so a stuck
|
||||
installer can never block the next step. -->
|
||||
<SynchronousCommand wcm:action="add">
|
||||
<Order>3</Order>
|
||||
<Description>Install VirtIO guest tools</Description>
|
||||
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-Volume | Where-Object DriveLetter | ForEach-Object { $exe = $_.DriveLetter + ':\virtio-win-guest-tools.exe'; if (Test-Path $exe) { Start-Process $exe -ArgumentList '/install','/quiet','/norestart' -Wait } }"</CommandLine>
|
||||
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-Volume | Where-Object DriveLetter | ForEach-Object { $exe = $_.DriveLetter + ':\virtio-win-guest-tools.exe'; if (Test-Path $exe) { $p = Start-Process $exe -ArgumentList '/install','/quiet','/norestart' -PassThru; [void]$p.WaitForExit(900000) } }"</CommandLine>
|
||||
<RequiresUserInput>false</RequiresUserInput>
|
||||
</SynchronousCommand>
|
||||
|
||||
<!-- Provisioning: pulls oobe-desktop.ps1 from the repo so the PC always
|
||||
gets the current version, retrying for ~2.5 minutes while the
|
||||
network comes up. If that fails, falls back to the copy of
|
||||
oobe-desktop.ps1 baked into the root of the ISO. The script installs
|
||||
oobe-desktop.ps1 baked into the root of the ISO. The script is saved to
|
||||
C:\ProgramData\OOBE and started elevated in its own window,
|
||||
so this step finishes as soon as it has launched it. The script installs
|
||||
the MeshCentral agent, applies the power/RDP/SSH settings and
|
||||
restarts the PC itself, so this must stay the LAST command.
|
||||
Step log: C:\ProgramData\OOBE\firstlogon.log (download / fallback / launch)
|
||||
Log: C:\ProgramData\OOBE\oobe-desktop.log -->
|
||||
<SynchronousCommand wcm:action="add">
|
||||
<Order>4</Order>
|
||||
<Description>Run oobe-desktop.ps1 (url.isworking.fyi, else ISO copy)</Description>
|
||||
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $s = $null; for ($i = 1; $i -le 10; $i++) { try { $s = Invoke-RestMethod 'https://url.isworking.fyi/oobe-desktop'; break } catch { Start-Sleep -Seconds 15 } }; if (-not $s) { $f = Get-PSDrive -PSProvider FileSystem | ForEach-Object { Join-Path $_.Root 'oobe-desktop.ps1' } | Where-Object { Test-Path $_ } | Select-Object -First 1; if ($f) { $s = Get-Content -Raw $f } }; if ($s) { Invoke-Expression $s } else { Write-Warning 'oobe-desktop.ps1 not found online or on any drive'; Start-Sleep -Seconds 60 }"</CommandLine>
|
||||
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "$d = Join-Path $env:ProgramData 'OOBE'; New-Item -ItemType Directory -Force $d | Out-Null; Start-Transcript (Join-Path $d 'firstlogon.log') -Append; [Net.ServicePointManager]::SecurityProtocol = 'Tls12'; $s = $null; for ($i = 1; $i -le 10; $i++) { try { $s = irm 'https://url.isworking.fyi/oobe-desktop' -UseBasicParsing; 'Downloaded'; break } catch { 'Attempt ' + $i + ' failed: ' + $_; sleep 15 } }; if (-not $s) { $f = Get-PSDrive -PSProvider FileSystem | % { Join-Path $_.Root 'oobe-desktop.ps1' } | ? { Test-Path $_ } | select -First 1; if ($f) { 'Using ' + $f; $s = Get-Content -Raw $f } }; if ($s) { $p = Join-Path $d 'oobe-desktop.ps1'; Set-Content $p $s; Start-Process powershell -Verb RunAs -ArgumentList ('-NoProfile -ExecutionPolicy Bypass -File ' + $p); 'Started ' + $p } else { Write-Warning 'oobe-desktop.ps1 not found online or on any drive'; sleep 60 }"</CommandLine>
|
||||
<RequiresUserInput>false</RequiresUserInput>
|
||||
</SynchronousCommand>
|
||||
|
||||
|
|
|
|||
|
|
@ -188,6 +188,25 @@
|
|||
======================================================================= -->
|
||||
<settings pass="specialize">
|
||||
|
||||
<!-- Runs as SYSTEM before anyone logs on. Lets admins elevate without a
|
||||
UAC prompt (oobe-*.ps1 sets the same value later), so nothing at
|
||||
first logon can stall on an elevation prompt nobody is there to
|
||||
click: the VirtIO installer, or the RunAs launch in step 4. -->
|
||||
<component name="Microsoft-Windows-Deployment"
|
||||
processorArchitecture="amd64"
|
||||
publicKeyToken="31bf3856ad364e35"
|
||||
language="neutral"
|
||||
versionScope="nonSxS"
|
||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||
<RunSynchronous>
|
||||
<RunSynchronousCommand wcm:action="add">
|
||||
<Order>1</Order>
|
||||
<Description>Elevate admins without a UAC prompt</Description>
|
||||
<Path>reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 0 /f</Path>
|
||||
</RunSynchronousCommand>
|
||||
</RunSynchronous>
|
||||
</component>
|
||||
|
||||
<component name="Microsoft-Windows-Shell-Setup"
|
||||
processorArchitecture="amd64"
|
||||
publicKeyToken="31bf3856ad364e35"
|
||||
|
|
@ -328,25 +347,29 @@
|
|||
<!-- NetKVM has to be in place before anything can be downloaded, so
|
||||
the guest tools go on from the ISO first. Scans every drive for
|
||||
the installer rather than assuming a letter, since the VirtIO
|
||||
disc moves around post-install. -->
|
||||
disc moves around post-install. Waits at most 15 minutes, so a stuck
|
||||
installer can never block the next step. -->
|
||||
<SynchronousCommand wcm:action="add">
|
||||
<Order>3</Order>
|
||||
<Description>Install VirtIO guest tools</Description>
|
||||
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-Volume | Where-Object DriveLetter | ForEach-Object { $exe = $_.DriveLetter + ':\virtio-win-guest-tools.exe'; if (Test-Path $exe) { Start-Process $exe -ArgumentList '/install','/quiet','/norestart' -Wait } }"</CommandLine>
|
||||
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-Volume | Where-Object DriveLetter | ForEach-Object { $exe = $_.DriveLetter + ':\virtio-win-guest-tools.exe'; if (Test-Path $exe) { $p = Start-Process $exe -ArgumentList '/install','/quiet','/norestart' -PassThru; [void]$p.WaitForExit(900000) } }"</CommandLine>
|
||||
<RequiresUserInput>false</RequiresUserInput>
|
||||
</SynchronousCommand>
|
||||
|
||||
<!-- Provisioning: pulls oobe-vms.ps1 from the repo so the VM always
|
||||
gets the current version, retrying for ~2.5 minutes while the
|
||||
network comes up. If that fails, falls back to the copy of
|
||||
oobe-vms.ps1 baked into the root of the ISO. The script installs
|
||||
oobe-vms.ps1 baked into the root of the ISO. The script is saved to
|
||||
C:\ProgramData\OOBE and started elevated in its own window,
|
||||
so this step finishes as soon as it has launched it. The script installs
|
||||
the MeshCentral agent, applies the power/RDP/SSH settings and
|
||||
restarts the VM itself, so this must stay the LAST command.
|
||||
Step log: C:\ProgramData\OOBE\firstlogon.log (download / fallback / launch)
|
||||
Log: C:\ProgramData\OOBE\oobe-vms.log -->
|
||||
<SynchronousCommand wcm:action="add">
|
||||
<Order>4</Order>
|
||||
<Description>Run oobe-vms.ps1 (url.isworking.fyi, else ISO copy)</Description>
|
||||
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $s = $null; for ($i = 1; $i -le 10; $i++) { try { $s = Invoke-RestMethod 'https://url.isworking.fyi/oobe-vms'; break } catch { Start-Sleep -Seconds 15 } }; if (-not $s) { $f = Get-PSDrive -PSProvider FileSystem | ForEach-Object { Join-Path $_.Root 'oobe-vms.ps1' } | Where-Object { Test-Path $_ } | Select-Object -First 1; if ($f) { $s = Get-Content -Raw $f } }; if ($s) { Invoke-Expression $s } else { Write-Warning 'oobe-vms.ps1 not found online or on any drive'; Start-Sleep -Seconds 60 }"</CommandLine>
|
||||
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command "$d = Join-Path $env:ProgramData 'OOBE'; New-Item -ItemType Directory -Force $d | Out-Null; Start-Transcript (Join-Path $d 'firstlogon.log') -Append; [Net.ServicePointManager]::SecurityProtocol = 'Tls12'; $s = $null; for ($i = 1; $i -le 10; $i++) { try { $s = irm 'https://url.isworking.fyi/oobe-vms' -UseBasicParsing; 'Downloaded'; break } catch { 'Attempt ' + $i + ' failed: ' + $_; sleep 15 } }; if (-not $s) { $f = Get-PSDrive -PSProvider FileSystem | % { Join-Path $_.Root 'oobe-vms.ps1' } | ? { Test-Path $_ } | select -First 1; if ($f) { 'Using ' + $f; $s = Get-Content -Raw $f } }; if ($s) { $p = Join-Path $d 'oobe-vms.ps1'; Set-Content $p $s; Start-Process powershell -Verb RunAs -ArgumentList ('-NoProfile -ExecutionPolicy Bypass -File ' + $p); 'Started ' + $p } else { Write-Warning 'oobe-vms.ps1 not found online or on any drive'; sleep 60 }"</CommandLine>
|
||||
<RequiresUserInput>false</RequiresUserInput>
|
||||
</SynchronousCommand>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue