#Requires -Version 5.1 # Pure generator: collected values -> Windows Setup answer-file XML string. # No WinForms dependency (unit-testable). Mirrors the legacy autounattend.xml but with # ONE real local-admin account (no sm-bootstrap) and an embedded preconfig.json that the # oobeSystem FirstLogonCommands write (in short base64 chunks) to # C:\ProgramData\SilverMetal\preconfig.json. The base64 is carried in chunked echo # commands rather than a single specialize RunSynchronousCommand/Path, because that Path # is capped at ~259 chars and a full base64 blob overflows it -> "answer file is invalid". function New-SmAnswerFile { param( [string]$DisplayName, [string]$Username, [string]$Password, [string]$ComputerName, [string]$InputLocale = '0809:00000809', [string]$SystemLocale = 'en-GB', [string]$UiLanguage = 'en-US', [string]$UserLocale = 'en-GB', [string]$Flavour, [bool]$BitLockerEnable = $false, [string]$BitLockerPin = '' ) $pre = [ordered]@{ schemaVersion = 1 flavour = $Flavour bitlocker = [ordered]@{ enable = [bool]$BitLockerEnable; pin = $BitLockerPin } apps = [ordered]@{ useFlavourDefaults = $true } } $preJson = ($pre | ConvertTo-Json -Depth 6 -Compress) $preB64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($preJson)) function Esc([string]$s) { [Security.SecurityElement]::Escape($s) } # Escape ONLY the characters XML element content requires (& < >). Unlike # SecurityElement::Escape this leaves single/double quotes literal, so the # embedded command keeps a working FromBase64String('...') literal. function EscContent([string]$s) { $s.Replace('&','&').Replace('<','<').Replace('>','>') } $dn = Esc $DisplayName; $un = Esc $Username; $pw = Esc $Password; $cn = Esc $ComputerName # Build the oobeSystem FirstLogonCommands. The preconfig base64 is split into short # (<=150 char) chunks, each appended to a temp file by its own `echo` command, then # the file is whitespace-stripped + base64-decoded into preconfig.json. This keeps # every single command line well under the unattend length limits. $preDir = 'C:\ProgramData\SilverMetal' $preB64File = "$preDir\pre.b64" $preFile = "$preDir\preconfig.json" # Split base64 into chunks of at most 150 chars (base64 alphabet has no XML/cmd # metachars, so chunks are safe in `echo` and in XML once `>` is escaped). $chunkSize = 150 $chunks = for ($i = 0; $i -lt $preB64.Length; $i += $chunkSize) { $preB64.Substring($i, [Math]::Min($chunkSize, $preB64.Length - $i)) } $cmds = New-Object System.Collections.Generic.List[string] # 1. Create the target dir. $cmds.Add("cmd /c md ""$preDir"" 2>nul") # 2..N. Append each base64 chunk to the temp file. foreach ($c in $chunks) { $cmds.Add("cmd /c >>""$preB64File"" echo $c") } # N+1. Strip whitespace (chunks are newline-separated in the file) and decode. $cmds.Add("powershell -nop -c ""[IO.File]::WriteAllBytes('$preFile',[Convert]::FromBase64String(((gc '$preB64File' -raw) -replace '\s','')))""") # N+2. Clean up the temp file. $cmds.Add("cmd /c del ""$preB64File""") # N+3 (LAST). Launch the SilverMetal toolbox (run-once). $cmds.Add("cmd /c powershell -NoProfile -ExecutionPolicy Bypass -Command ""Start-Process -FilePath 'C:\Program Files\SilverOS\Welcome\SilverOS.Welcome.App.exe' -Verb RunAs""") $firstLogonSb = New-Object System.Text.StringBuilder $order = 0 foreach ($cmd in $cmds) { $order++ [void]$firstLogonSb.AppendLine(" ") [void]$firstLogonSb.AppendLine(" $order") [void]$firstLogonSb.AppendLine(" $(EscContent $cmd)") [void]$firstLogonSb.AppendLine(" ") } $firstLogonCommands = $firstLogonSb.ToString().TrimEnd() @" $UiLanguage $InputLocale$SystemLocale $UiLanguage$UserLocale OnError 0true 1EFI300 2MSR16 3Primarytrue 11FAT32 22 33NTFSC 03 /IMAGE/INDEX1 true $InputLocale$SystemLocale $UiLanguage$UiLanguage$UserLocale truetruetruetruetrue3 $unAdministrators$dn $pwtrue</PlainText></Password> </LocalAccount> </LocalAccounts></UserAccounts> <AutoLogon><Enabled>true</Enabled><LogonCount>1</LogonCount><Username>$un</Username><Password><Value>$pw</Value><PlainText>true</PlainText></Password></AutoLogon> <ComputerName>$cn</ComputerName> <FirstLogonCommands> $firstLogonCommands </FirstLogonCommands> <RegisteredOwner>SilverMetal</RegisteredOwner><RegisteredOrganization>SilverLABS</RegisteredOrganization> </component> </settings> </unattend> "@ }