| 1 | param([switch]$Prepare) |
| 2 | $ErrorActionPreference = 'Stop' |
| 3 | $ProgressPreference = 'SilentlyContinue' |
| 4 | $root = 'C:\Snowglobe' |
| 5 | $status = Join-Path $root 'setup' |
| 6 | $winlogon = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' |
| 7 | |
| 8 | function Set-SetupStatus([string]$stage) { |
| 9 | $path = Join-Path $status $stage |
| 10 | if (!(Test-Path $path)) { |
| 11 | [IO.File]::Open($path, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::ReadWrite).Dispose() |
| 12 | } |
| 13 | } |
| 14 | |
| 15 | try { |
| 16 | if ($Prepare) { |
| 17 | Remove-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run' -Name SnowglobeBootstrap -ErrorAction SilentlyContinue |
| 18 | Unregister-ScheduledTask -TaskName SnowglobeFinalize -Confirm:$false -ErrorAction SilentlyContinue |
| 19 | & "$root\install-guest.ps1" -Binary "$root\snowglobe-guest.exe" -User sandbox |
| 20 | (Get-LocalUser -Name sandbox).SID.Value | Set-Content "$root\template.sid" -Encoding ASCII |
| 21 | Get-ScheduledTask -TaskName 'SnowglobeGuest-*' | Disable-ScheduledTask | Out-Null |
| 22 | Get-Process snowglobe-guest -ErrorAction SilentlyContinue | Stop-Process -Force |
| 23 | $action = New-ScheduledTaskAction -Execute "$env:WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe" -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\Snowglobe\windows-provision.ps1' |
| 24 | $trigger = New-ScheduledTaskTrigger -AtStartup |
| 25 | $principal = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount -RunLevel Highest |
| 26 | $settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 5) |
| 27 | Register-ScheduledTask -TaskName SnowglobeProvision -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null |
| 28 | $password = [Guid]::NewGuid().ToString('N') + [Guid]::NewGuid().ToString('N') |
| 29 | Set-LocalUser sandbox -Password (ConvertTo-SecureString $password -AsPlainText -Force) -PasswordNeverExpires $true |
| 30 | Set-ItemProperty $winlogon AutoAdminLogon '1' |
| 31 | Set-ItemProperty $winlogon DefaultUserName 'sandbox' |
| 32 | Set-ItemProperty $winlogon DefaultDomainName $env:COMPUTERNAME |
| 33 | Set-ItemProperty $winlogon DefaultPassword $password |
| 34 | Remove-ItemProperty $winlogon AutoLogonCount -ErrorAction SilentlyContinue |
| 35 | powercfg.exe /hibernate off |
| 36 | Remove-Item "$root\identity.ini", "$root\provisioned" -Force -ErrorAction SilentlyContinue |
| 37 | if (Test-Path $status) { Remove-Item $status -Recurse -Force } |
| 38 | return |
| 39 | } |
| 40 | |
| 41 | New-Item $status -ItemType Directory -Force | Out-Null |
| 42 | if (Test-Path "$status\ready") { return } |
| 43 | if (Test-Path "$status\failed") { Remove-Item "$status\failed" -Force } |
| 44 | Set-SetupStatus personalizing |
| 45 | $seed = $null |
| 46 | $deadline = (Get-Date).AddMinutes(1) |
| 47 | while (!$seed) { |
| 48 | $seed = 68..90 | ForEach-Object { '{0}:\SNOWGLOB.INI' -f [char]$_ } | Where-Object { Test-Path $_ } | Select-Object -First 1 |
| 49 | if ($seed) { break } |
| 50 | if ((Get-Date) -gt $deadline) { throw 'The VM login was not attached.' } |
| 51 | Start-Sleep -Milliseconds 250 |
| 52 | } |
| 53 | $values = @{} |
| 54 | foreach ($line in Get-Content $seed) { |
| 55 | $pair = $line.Split('=', 2) |
| 56 | if ($pair.Count -eq 2) { $values[$pair[0]] = $pair[1] } |
| 57 | } |
| 58 | if (Test-Path "$status\restarting") { |
| 59 | if ($env:COMPUTERNAME -ine $values['HOSTNAME']) { throw 'The VM name was not applied.' } |
| 60 | $scheduler = New-Object -ComObject Schedule.Service |
| 61 | $scheduler.Connect() |
| 62 | $tasks = $scheduler.GetFolder('\').GetTasks(1) | Where-Object { $_.Name -like 'SnowglobeGuest-*' } |
| 63 | foreach ($task in $tasks) { $task.Enabled = $true } |
| 64 | $deadline = (Get-Date).AddMinutes(2) |
| 65 | while (!(Get-Process explorer -ErrorAction SilentlyContinue)) { |
| 66 | if ((Get-Date) -gt $deadline) { throw 'The desktop did not start.' } |
| 67 | Start-Sleep -Milliseconds 250 |
| 68 | } |
| 69 | foreach ($task in $tasks) { $task.Run($null) | Out-Null } |
| 70 | while (!(Get-Process explorer -ErrorAction SilentlyContinue) -or !(Get-Process snowglobe-guest -ErrorAction SilentlyContinue)) { |
| 71 | if ((Get-Date) -gt $deadline) { throw 'The desktop did not start.' } |
| 72 | Start-Sleep -Milliseconds 250 |
| 73 | } |
| 74 | Set-SetupStatus ready |
| 75 | return |
| 76 | } |
| 77 | $username = $values['USERNAME'] |
| 78 | $password = $values['PASSWORD'] |
| 79 | $hostname = $values['HOSTNAME'] |
| 80 | if ($username -notmatch '^[A-Za-z0-9][A-Za-z0-9._-]{0,19}$') { throw 'The VM account name is unsupported.' } |
| 81 | if ($password.Length -lt 12 -or $password.Length -gt 128 -or $password -match '[\r\n]') { throw 'The VM password is malformed.' } |
| 82 | if ($hostname -notmatch '^sgvm-[a-f0-9]{8}$') { throw 'The VM name is malformed.' } |
| 83 | $sid = Get-Content "$root\template.sid" |
| 84 | $user = Get-LocalUser | Where-Object { $_.SID.Value -eq $sid } |
| 85 | if (!$user) { throw 'The template account is missing.' } |
| 86 | if (Get-LocalUser | Where-Object { $_.Name -ieq $username -and $_.SID.Value -ne $sid }) { throw 'The VM account name is already in use.' } |
| 87 | $user | Set-LocalUser -Password (ConvertTo-SecureString $password -AsPlainText -Force) -PasswordNeverExpires $true |
| 88 | if ($user.Name -ine $username) { $user | Rename-LocalUser -NewName $username } |
| 89 | Rename-Computer -NewName $hostname -Force |
| 90 | Set-ItemProperty $winlogon AutoAdminLogon '1' |
| 91 | Set-ItemProperty $winlogon DefaultUserName $username |
| 92 | Set-ItemProperty $winlogon DefaultDomainName $hostname |
| 93 | Set-ItemProperty $winlogon DefaultPassword $password |
| 94 | Remove-ItemProperty $winlogon AutoLogonCount -ErrorAction SilentlyContinue |
| 95 | Set-SetupStatus restarting |
| 96 | Restart-Computer -Force |
| 97 | } catch { |
| 98 | if ($Prepare) { throw } |
| 99 | Set-SetupStatus failed |
| 100 | Add-Content "$root\setup.log" $_.Exception.Message |
| 101 | exit 1 |
| 102 | } |