feat(collector): answer-file generator (real account, no sm-bootstrap, embedded preconfig)
This commit is contained in:
105
windows/collector/New-SmAnswerFile.ps1
Normal file
105
windows/collector/New-SmAnswerFile.ps1
Normal file
@@ -0,0 +1,105 @@
|
||||
#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 a
|
||||
# specialize-pass command writes to C:\ProgramData\SilverMetal\preconfig.json.
|
||||
|
||||
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
|
||||
|
||||
$writePre = "powershell -NoProfile -ExecutionPolicy Bypass -Command "" New-Item -ItemType Directory -Force 'C:\ProgramData\SilverMetal' | Out-Null; [IO.File]::WriteAllBytes('C:\ProgramData\SilverMetal\preconfig.json', [Convert]::FromBase64String('$preB64')) """
|
||||
|
||||
@"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||
<settings pass="windowsPE">
|
||||
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||
<SetupUILanguage><UILanguage>$UiLanguage</UILanguage></SetupUILanguage>
|
||||
<InputLocale>$InputLocale</InputLocale><SystemLocale>$SystemLocale</SystemLocale>
|
||||
<UILanguage>$UiLanguage</UILanguage><UserLocale>$UserLocale</UserLocale>
|
||||
</component>
|
||||
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||
<DiskConfiguration>
|
||||
<WillShowUI>OnError</WillShowUI>
|
||||
<Disk wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||
<DiskID>0</DiskID><WillWipeDisk>true</WillWipeDisk>
|
||||
<CreatePartitions>
|
||||
<CreatePartition wcm:action="add"><Order>1</Order><Type>EFI</Type><Size>300</Size></CreatePartition>
|
||||
<CreatePartition wcm:action="add"><Order>2</Order><Type>MSR</Type><Size>16</Size></CreatePartition>
|
||||
<CreatePartition wcm:action="add"><Order>3</Order><Type>Primary</Type><Extend>true</Extend></CreatePartition>
|
||||
</CreatePartitions>
|
||||
<ModifyPartitions>
|
||||
<ModifyPartition wcm:action="add"><Order>1</Order><PartitionID>1</PartitionID><Label>System</Label><Format>FAT32</Format></ModifyPartition>
|
||||
<ModifyPartition wcm:action="add"><Order>2</Order><PartitionID>2</PartitionID></ModifyPartition>
|
||||
<ModifyPartition wcm:action="add"><Order>3</Order><PartitionID>3</PartitionID><Label>Windows</Label><Format>NTFS</Format><Letter>C</Letter></ModifyPartition>
|
||||
</ModifyPartitions>
|
||||
</Disk>
|
||||
</DiskConfiguration>
|
||||
<ImageInstall><OSImage>
|
||||
<InstallTo><DiskID>0</DiskID><PartitionID>3</PartitionID></InstallTo>
|
||||
<InstallFrom><MetaData wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"><Key>/IMAGE/INDEX</Key><Value>1</Value></MetaData></InstallFrom>
|
||||
</OSImage></ImageInstall>
|
||||
<UserData><AcceptEula>true</AcceptEula></UserData>
|
||||
</component>
|
||||
</settings>
|
||||
<settings pass="specialize">
|
||||
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||
<RunSynchronous>
|
||||
<RunSynchronousCommand wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||
<Order>1</Order>
|
||||
<Path>$(EscContent $writePre)</Path>
|
||||
<Description>Write SilverMetal preconfig</Description>
|
||||
</RunSynchronousCommand>
|
||||
</RunSynchronous>
|
||||
</component>
|
||||
</settings>
|
||||
<settings pass="oobeSystem">
|
||||
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||
<InputLocale>$InputLocale</InputLocale><SystemLocale>$SystemLocale</SystemLocale>
|
||||
<UILanguage>$UiLanguage</UILanguage><UILanguageFallback>$UiLanguage</UILanguageFallback><UserLocale>$UserLocale</UserLocale>
|
||||
</component>
|
||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||
<OOBE><HideEULAPage>true</HideEULAPage><HideOEMRegistrationScreen>true</HideOEMRegistrationScreen><HideOnlineAccountScreens>true</HideOnlineAccountScreens><HideLocalAccountScreen>true</HideLocalAccountScreen><HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE><ProtectYourPC>3</ProtectYourPC></OOBE>
|
||||
<UserAccounts><LocalAccounts>
|
||||
<LocalAccount wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||
<Name>$un</Name><Group>Administrators</Group><DisplayName>$dn</DisplayName>
|
||||
<Password><Value>$pw</Value><PlainText>true</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>
|
||||
<SynchronousCommand wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||
<Order>1</Order>
|
||||
<CommandLine>cmd /c powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath 'C:\Program Files\SilverOS\Welcome\SilverOS.Welcome.App.exe' -Verb RunAs"</CommandLine>
|
||||
<Description>Launch SilverMetal toolbox (run-once)</Description>
|
||||
</SynchronousCommand>
|
||||
</FirstLogonCommands>
|
||||
<RegisteredOwner>SilverMetal</RegisteredOwner><RegisteredOrganization>SilverLABS</RegisteredOrganization>
|
||||
</component>
|
||||
</settings>
|
||||
</unattend>
|
||||
"@
|
||||
}
|
||||
@@ -30,3 +30,40 @@ Describe 'Test-SmComputerName' {
|
||||
It 'rejects > 15 chars' { (Test-SmComputerName ('A'*16)).Ok | Should -BeFalse }
|
||||
It 'rejects illegal chars' { (Test-SmComputerName 'bad name').Ok | Should -BeFalse }
|
||||
}
|
||||
|
||||
Describe 'New-SmAnswerFile' {
|
||||
BeforeAll {
|
||||
. (Join-Path $PSScriptRoot '..\collector\New-SmAnswerFile.ps1')
|
||||
$cfg = @{
|
||||
DisplayName = 'Jamie'; Username = 'jamie'; Password = 'Sup3rPass!'
|
||||
ComputerName = 'SILVER-01'
|
||||
InputLocale = '0809:00000809'; SystemLocale = 'en-GB'; UiLanguage = 'en-US'; UserLocale = 'en-GB'
|
||||
Flavour = 'developer'; BitLockerEnable = $true; BitLockerPin = '246810'
|
||||
}
|
||||
$script:xml = New-SmAnswerFile @cfg
|
||||
}
|
||||
It 'is valid XML' { { [xml]$script:xml } | Should -Not -Throw }
|
||||
It 'creates the real account in Administrators' {
|
||||
$script:xml | Should -Match '<Name>jamie</Name>'
|
||||
$script:xml | Should -Match '<Group>Administrators</Group>'
|
||||
}
|
||||
It 'does NOT contain sm-bootstrap' { $script:xml | Should -Not -Match 'sm-bootstrap' }
|
||||
It 'sets AutoLogon once as the user' {
|
||||
$script:xml | Should -Match '<LogonCount>1</LogonCount>'
|
||||
$script:xml | Should -Match '<Username>jamie</Username>'
|
||||
}
|
||||
It 'sets the computer name' { $script:xml | Should -Match '<ComputerName>SILVER-01</ComputerName>' }
|
||||
It 'keeps WillWipeDisk for disk 0' { $script:xml | Should -Match '<WillWipeDisk>true</WillWipeDisk>' }
|
||||
It 'embeds a base64 preconfig write in specialize' {
|
||||
$script:xml | Should -Match 'preconfig\.json'
|
||||
$script:xml | Should -Match 'FromBase64String'
|
||||
}
|
||||
It 'embedded preconfig round-trips with the flavour and pin' {
|
||||
$m = [regex]::Match($script:xml, "FromBase64String\('([^']+)'\)")
|
||||
$m.Success | Should -BeTrue
|
||||
$json = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($m.Groups[1].Value)) | ConvertFrom-Json
|
||||
$json.flavour | Should -Be 'developer'
|
||||
$json.bitlocker.pin | Should -Be '246810'
|
||||
}
|
||||
It 'launches the toolbox in FirstLogonCommands' { $script:xml | Should -Match 'SilverOS\.Welcome\.App\.exe' }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user